# To read this file, run:
# perldoc ./CONTRIBUTING
=encoding utf8
=head1 CONTRIBUTING
How to contribute to C<GDPR-IAB-TCFv2>?
=head1 DESCRIPTION
Patches are welcome!
They must be built against the
C<L<devel|https://github.com/peczenyj/GDPR-IAB-TCFv2/tree/devel>> branch,
then submitted as pull requests at GitHub.
The documentation is written using the
L<POD|http://perldoc.perl.org/perlpod.html> format. Use the C<perldoc> tool
to render it in a terminal:
perldoc ./CONTRIBUTING
=head1 PROJECT STATUS
As of 2026-05-09 (the v0.400 release), C<GDPR-IAB-TCFv2> is in
B<maintenance mode>. Bug fixes, security fixes, CPAN-tester
regressions, and IAB-spec updates remain in scope. Larger feature work
-- roadmap Phases 7-9, the distribution items (DockerHub automation,
Debian package), and the sister-distribution ideas -- is now tracked
as B<help-wanted> issues on GitHub.
If you are looking for a place to contribute, the
L<help-wanted issues|https://github.com/peczenyj/GDPR-IAB-TCFv2/issues?q=is%3Aopen+label%3Ahelp-wanted>
are the curated entry point. F<TODO> at the repository root has
the full ecosystem and roadmap context, including suggested API
sketches and test scopes for each item.
=head1 PATCHING, STEP BY STEP
=over 4
=item 1. Get the source
git clone --origin upstream git://github.com/peczenyj/GDPR-IAB-TCFv2.git
cd GDPR-IAB-TCFv2
git checkout devel
=item 2. Install build dependencies
Not required for doc patches.
curl -L https://cpanmin.us | perl - --installdeps --with-develop .
=item 3. Make your fix/feature
git checkout -b <my-patch> devel
# edit files and add modification
# Run the testsuite
prove -v
git commit -s -m '<<some nice message>>'
=item 4. Setup a fork
=item 4.1. L<Fork the project on GitHub|https://github.com/peczenyj/GDPR-IAB-TCFv2/>
=item 4.2. Link your local repo to your fork (just once)
(You are using C<GDPR-IAB-TCFv2> isn't it?)
git remote add github <github-user>.github.com:<github-user>/GDPR-IAB-TCFv2.git
=item 5. Submit your work
=item 5.1 Push!
git push github <my-patch>
=item 5.2 Submit a pull request on GitHub
=item 6. Loop
Redo from step 3.
=back
=head1 RELEASING A NEW VERSION
This project follows the L<Git Flow|https://nvie.com/posts/a-successful-git-branching-model/>
branching model: B<feat/*> and B<fix/*> branches merge into B<devel>;
B<release/*> branches cut from B<devel> and merge into both B<main> and B<devel>;
B<hotfix/*> branches cut from B<main> and merge into both. Tags live on B<main>.
The recommended path uses the C<git-flow> CLI (C<git-flow-avh>) to drive the
ceremony. Plain C<git> works too — both variants are shown side-by-side at
each step so you can pick one.
The release pipeline itself is fully automated by
F<.github/workflows/release.yml>: pushing any C<v*> tag triggers a build, a
PAUSE upload, and a GitHub Release with the tarball attached. You only need
to drive the local prep and the merge/tag.
=head2 Prerequisites (one-time)
=over 4
=item 1. Install release tooling
# POD → Markdown converter (regenerates README.md)
cpanm Pod::Markdown
# or: apt install libpod-markdown-perl
# Conventional-Commits changelog generator
cargo install git-cliff
# or: brew install git-cliff
# git-flow CLI (optional but recommended)
apt install git-flow
# or: brew install git-flow-avh
=item 2. Initialize git-flow once (skip if you prefer plain git)
git flow init -d
When prompted, accept the project's existing layout:
=over 4
=item *
production branch = C<main>
=item *
next-release branch = C<devel>
=item *
feature prefix = C<feat/>
=item *
release prefix = C<release/>
=item *
hotfix prefix = C<hotfix/>
=back
The C<-d> flag uses defaults where they match; you'll only be prompted for
the values that differ.
=item 3. Configure CPAN credentials (repo admin, one-time)
In GitHub, go to B<Settings → Secrets and variables → Actions> and add:
=over 4
=item *
C<PAUSE_USER> — your PAUSE username
=item *
C<PAUSE_PASSWORD> — your PAUSE password
=back
If these secrets are missing, the GitHub Release step still runs but the
PAUSE upload step is skipped.
=back
=head2 Versioning convention
C<$VERSION> uses the 3-digit C<0.XYZ> form. Bump the last two digits
in steps of 10 for normal releases (e.g. C<0.350 → 0.360>) and by 1 for
pure bug-fix follow-ups (e.g. C<0.350 → 0.351>). The git tag prepends
C<v> (e.g. C<v0.360>); the C<$VERSION> string in the F<.pm> does not.
=head2 Bumping the version
Use the F<tools/bump-version> helper rather than hand-editing the
fifteen C<our $VERSION = "..."> declarations under F<lib/>. Pass the
new version as the only argument:
tools/bump-version 0.402
The script:
=over 4
=item *
Reads the current dist version from F<lib/GDPR/IAB/TCFv2.pm> and
refuses to run if the new version is not strictly greater (the same
sanity check PAUSE itself enforces against version regressions).
=item *
Rewrites C<our $VERSION = "..."> in every F<lib/**/*.pm> file in
place.
=item *
Refuses to run if any F<.pm> under F<lib/> is missing the
C<$VERSION> declaration entirely. That signals either a new module
that was added without seeding C<$VERSION>, or a drift caused by
hand-editing. Investigate and reconcile before bumping.
=back
The script does B<not> touch F<CHANGELOG.md>, run C<git cliff>,
amend git, or push. Those remain manual steps in the
L</Step-by-step release> flow below.
After running the bumper, run C<prove -lr xt> to confirm
F<xt/version.t> stays green.
=head2 Step-by-step release
=over 4
=item 1. Sync C<devel> and confirm what's queued
git checkout devel
git pull --ff-only
git log --oneline $(git describe --tags --abbrev=0)..HEAD
The log shows everything that will land in the new release. If something
that should be in this release is missing, merge its PR before continuing.
=item 2. Open a release branch
B<git-flow:>
git flow release start 0.360
B<vanilla git:>
git checkout -b release/0.360 devel
=item 3. Bump the version in both files
tools/bump-version 0.360 # rewrites $VERSION across lib/**.pm
$EDITOR CITATION.cff
# Edit:
# version: "0.360"
# date-released: "YYYY-MM-DD" # the actual tag/release date
See L</Bumping the version> above for what the helper does and the
sanity checks it enforces. F<Makefile.PL> reads C<$VERSION> from the
C<.pm> via C<VERSION_FROM>; F<CITATION.cff> is the only other file
that has to be hand-bumped, and both its C<version> and
C<date-released> fields must be updated together.
=item 4. Regenerate the changelog
git cliff --tag v0.360 -o CHANGELOG.md
C<cliff.toml> is preconfigured for Conventional Commits and groups by
C<feat:> / C<fix:> / C<docs:> / C<perf:> / C<refactor:> / C<Other>;
C<style:> / C<test:> / C<chore:> are skipped.
B<Sanity-check the output> — git-cliff slurps full commit message bodies,
so stray lines like C<# Conflicts:> from merge commits can leak into the
changelog. Strip them by hand if present.
=item 5. Regenerate the README
pod2markdown lib/GDPR/IAB/TCFv2.pm > README.md
Only the main module's POD ships in C<README.md>; submodule POD is
served via MetaCPAN.
=item 6. Build and verify locally
perl Makefile.PL
make
make test # runs t/
prove -lr xt # runs perlcritic + perltidy
make manifest # updates MANIFEST if test files were added
make dist # produces GDPR-IAB-TCFv2-0.360.tar.gz
Open the tarball and confirm only intended files ship (no F<.bak>,
no F<docs/>, no F<AGENTS.md>, no F<MYMETA.*>). C<MANIFEST.SKIP> already
excludes those, but a quick eyeball is cheap insurance.
=item 7. Commit the release prep
git add lib/GDPR/IAB/TCFv2.pm CITATION.cff CHANGELOG.md README.md
# Also `git add MANIFEST` if `make manifest` changed it
git commit -m "chore: prepare for v0.360 release"
One commit. B<Do not tag yet> — the tag is created in step 8.
=item 8. Finish the release
B<git-flow> (recommended — automates the merges and tag):
git flow release finish 0.360
# Prompts for the tag message; use something like:
# "Release v0.360 — <one-line theme>"
git push origin main
git push origin devel
git push origin v0.360 # ← triggers release.yml
C<git flow release finish> merges C<release/0.360> into B<main>, tags it
as C<v0.360>, merges back into B<devel> (so the version bump and changelog
land there too), and deletes the local C<release/0.360> branch.
I<You must push all three refs.> The release workflow only fires on the
tag push, but the tag must be reachable from C<main> for the workflow to
check out the right tree.
B<vanilla git> (more steps, gives reviewable PRs):
# 1. PR release/0.360 → devel
git push -u origin release/0.360
gh pr create --base devel \
--title "chore: prepare for v0.360 release" \
--body "Version bump, README regen, changelog refresh."
# Review and merge on GitHub.
# 2. PR devel → main
gh pr create --base main --head devel \
--title "Release v0.360"
# Review and merge on GitHub.
# 3. Tag main and push
git checkout main
git pull --ff-only
git tag -a v0.360 -m "Release v0.360 — <one-line summary>"
git push origin v0.360 # ← triggers release.yml
=item 9. Watch the release workflow
gh run watch -R peczenyj/GDPR-IAB-TCFv2
# or
gh run list -R peczenyj/GDPR-IAB-TCFv2 --workflow release.yml --limit 1
The workflow does:
=over 4
=item 1.
C<perl Makefile.PL && make manifest && make dist> — produces the tarball
=item 2.
C<cpan-upload -u $PAUSE_USER -p $PAUSE_PASSWORD ...> — uploads to PAUSE
(skipped silently if C<PAUSE_USER> is empty)
=item 3.
C<softprops/action-gh-release@v2> — creates the GitHub Release with the
tarball attached and auto-generated notes
=back
If the PAUSE step fails, the GitHub Release still gets created. You can
then re-upload manually via L<https://pause.perl.org/> without re-running
the workflow.
=item 10. Post-release sanity
# Confirm the GitHub Release
gh release view v0.360 -R peczenyj/GDPR-IAB-TCFv2
# Wait ~30 min for PAUSE → MetaCPAN propagation, then:
curl -s https://fastapi.metacpan.org/v1/release/GDPR-IAB-TCFv2 \
| jq .version
# Should report "0.360"
The docker image (C<peczenyj/gdpr-iab-tcfv2:0.360> and C<:latest>) is published
by F<.github/workflows/docker.yml> on the same C<v*> tag push.
=back
=head2 Pre-release review (and why we don't use draft GitHub Releases)
GitHub supports B<draft> releases that you can review before publishing.
For most projects this is a useful pre-publish review gate. For B<this>
project it is not, and the doc deliberately omits the pattern. Two reasons:
=over 4
=item 1.
B<PAUSE upload is irreversible.> Once F<release.yml> uploads
F<GDPR-IAB-TCFv2-0.360.tar.gz> to PAUSE, the version number is burned
forever — you can delete the file but can't re-upload a new tarball
under the same version. So a "review the GitHub Release before it goes
out" gate doesn't actually protect the thing that matters
(the CPAN-distributed artifact); it only protects the rendered
release-notes page.
=item 2.
B<The current F<release.yml> hard-codes C<draft: false>.> If you created
a draft release with C<gh release create --draft> and then pushed the
tag, the workflow's C<softprops/action-gh-release@v2> step would update
the release and flip C<draft> to false — closing the review window the
moment the workflow finishes (~3 minutes). Conversely, if you let
C<gh release create --target main --draft> create the tag via the REST
API instead of pushing it via C<git>, F<release.yml> would B<not> fire
at all (push events from API-created tags don't trigger workflows), and
your tarball would never reach PAUSE.
=back
The review gate that actually buys you something is the B<release-prep
PR> in the vanilla-git path of step 8: opening C<release/0.360 → devel>
and C<devel → main> as PRs lets a reviewer (or future-you) catch a typo
in the changelog, an off-by-one in C<$VERSION>, or a stale README
B<before> the merge that triggers the unstoppable downstream automation.
Use that path when you want pre-release review.
If you ever do want the draft-release pattern to work properly, it
needs a workflow tweak: add C<release: { types: [published] }> to
F<release.yml>'s C<on:> triggers, and remove C<draft: false> from the
softprops step. The doc does not include that variant today.
=head2 Hotfix releases
For an urgent fix that has to skip C<devel> and ship straight from C<main>:
B<git-flow:>
git flow hotfix start 0.351
# Make the fix, bump $VERSION, regenerate CHANGELOG and README, commit
git flow hotfix finish 0.351
git push origin main devel v0.351
B<vanilla git:>
git checkout -b hotfix/0.351 main
# Make the fix, bump $VERSION (both lib/GDPR/IAB/TCFv2.pm AND CITATION.cff),
# regenerate CHANGELOG and README
git add lib/GDPR/IAB/TCFv2.pm CITATION.cff CHANGELOG.md README.md
git commit -m "fix: <description> (v0.351)"
git push -u origin hotfix/0.351
# PR hotfix/0.351 → main
gh pr create --base main --title "Hotfix v0.351 — <description>"
# Merge.
# Merge main back into devel so the fix doesn't get lost
git checkout devel
git pull --ff-only
git merge --no-ff main -m "Merge hotfix v0.351 back into devel"
git push
# Tag main and push
git checkout main && git pull --ff-only
git tag -a v0.351 -m "Hotfix v0.351 — <description>"
git push origin v0.351
=cut