4444 - tigrcorn-static
4545 - tigrcorn-transports
4646 - wt-peer-probes
47+ semver :
48+ description : Version action used by the prepare-release job.
49+ type : choice
50+ required : true
51+ default : patch
52+ options :
53+ - patch
54+ - minor
55+ - finalize
4756 dry_run :
4857 description : Run npm publish in dry-run mode.
4958 type : boolean
5059 required : false
5160 default : false
5261
62+ permissions :
63+ contents : write
64+ id-token : write
65+
66+ concurrency :
67+ group : publish-all-packages-${{ github.ref }}
68+ cancel-in-progress : false
69+
5370jobs :
71+ release-gates :
72+ name : release-gates
73+ uses : ./.github/workflows/_reusable-ci.yml
74+
75+ prepare-release :
76+ name : prepare-release
77+ needs : release-gates
78+ if : ${{ needs.release-gates.result == 'success' }}
79+ runs-on : ubuntu-latest
80+ outputs :
81+ commit_sha : ${{ steps.commit.outputs.commit_sha }}
82+ version : ${{ steps.commit.outputs.version }}
83+ npm_version : ${{ steps.commit.outputs.npm_version }}
84+ release_notes : ${{ steps.commit.outputs.release_notes }}
85+ prerelease : ${{ steps.commit.outputs.prerelease }}
86+ python_release_tag : ${{ steps.commit.outputs.python_release_tag }}
87+ npm_release_tag : ${{ steps.commit.outputs.npm_release_tag }}
88+ steps :
89+ - name : Validate target selection
90+ if : ${{ github.event_name == 'workflow_dispatch' && !inputs.gh_release && !inputs.pypi_publish && !inputs.npmjs_publish }}
91+ run : |
92+ echo "Select at least one publish target: gh_release, pypi_publish, or npmjs_publish." >&2
93+ exit 1
94+
95+ - name : Check out repository
96+ uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
97+ with :
98+ fetch-depth : 0
99+ token : ${{ secrets.GITHUB_TOKEN }}
100+
101+ - name : Set up Python
102+ uses : actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38
103+ with :
104+ python-version : " 3.12"
105+
106+ - name : Install release tooling
107+ run : python -m pip install --upgrade pip uv
108+
109+ - name : Compute and apply version plan
110+ shell : bash
111+ env :
112+ SEMVER : ${{ inputs.semver || 'finalize' }}
113+ PACKAGE_SELECTION : ${{ inputs.package_selection || 'all' }}
114+ run : |
115+ set -euo pipefail
116+ if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
117+ python tools/release/release_automation.py bump \
118+ --semver "${SEMVER}" \
119+ --packages "${PACKAGE_SELECTION}" \
120+ --summary release-plan.json \
121+ --write
122+ else
123+ python tools/release/release_automation.py current --summary release-plan.json
124+ fi
125+
126+ - name : Validate release targets are new
127+ if : ${{ github.event_name == 'workflow_dispatch' }}
128+ shell : bash
129+ env :
130+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
131+ run : |
132+ set -euo pipefail
133+ targets=()
134+ if [[ "${{ inputs.gh_release }}" == "true" ]]; then
135+ targets+=(--github)
136+ fi
137+ if [[ "${{ inputs.pypi_publish }}" == "true" ]]; then
138+ targets+=(--pypi)
139+ fi
140+ if [[ "${{ inputs.npmjs_publish }}" == "true" ]]; then
141+ targets+=(--npmjs)
142+ fi
143+ python tools/release/release_automation.py validate-release-targets \
144+ --summary release-plan.json \
145+ "${targets[@]}"
146+
147+ - name : Refresh Python lockfile
148+ if : ${{ github.event_name == 'workflow_dispatch' }}
149+ shell : bash
150+ run : |
151+ set -euo pipefail
152+ if python - <<'PY'
153+ import json
154+ plan = json.load(open("release-plan.json", encoding="utf-8"))
155+ raise SystemExit(0 if plan.get("python") else 1)
156+ PY
157+ then
158+ uv lock
159+ fi
160+
161+ - name : Commit version updates
162+ id : commit
163+ shell : bash
164+ run : |
165+ set -euo pipefail
166+ git config user.name "github-actions[bot]"
167+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
168+
169+ planned_changes="$(python - <<'PY'
170+ import json
171+ with open("release-plan.json", encoding="utf-8") as handle:
172+ plan = json.load(handle)
173+ print(len(plan.get("changed_files", [])))
174+ PY
175+ )"
176+ echo "Release plan changed files: ${planned_changes}"
177+ git status --short
178+
179+ commit_created=false
180+ if [[ "${{ github.event_name }}" == "workflow_dispatch" ]] && ! git diff --quiet; then
181+ git add pyproject.toml uv.lock pkgs packages docs/release-notes
182+ if ! git diff --cached --quiet; then
183+ git commit -m "release: prepare ${{ inputs.semver }} package versions"
184+ commit_created=true
185+ git push origin HEAD:${{ github.ref_name }}
186+ fi
187+ fi
188+
189+ commit_sha="$(git rev-parse HEAD)"
190+ if [[ "${planned_changes}" -gt 0 && "${commit_created}" != "true" && "${{ github.event_name }}" == "workflow_dispatch" ]]; then
191+ echo "Release plan expected version changes, but no semver bump commit was created." >&2
192+ exit 1
193+ fi
194+
195+ if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
196+ git fetch origin "${{ github.ref_name }}"
197+ remote_sha="$(git rev-parse "origin/${{ github.ref_name }}")"
198+ if [[ "${remote_sha}" != "${commit_sha}" ]]; then
199+ echo "Prepared release commit was not pushed to origin/${{ github.ref_name }}." >&2
200+ echo "prepared=${commit_sha}" >&2
201+ echo "remote=${remote_sha}" >&2
202+ exit 1
203+ fi
204+ fi
205+
206+ python - <<'PY' >> "$GITHUB_OUTPUT"
207+ import json
208+ from pathlib import Path
209+
210+ plan = json.loads(Path("release-plan.json").read_text(encoding="utf-8"))
211+ python_releases = plan.get("python", [])
212+ npm_releases = plan.get("npm", [])
213+ root = next((row for row in python_releases if row["name"] == "tigrcorn"), python_releases[0] if python_releases else None)
214+ npm = next((row for row in npm_releases if row["name"] == "@tigrcorn/wt-peer-probes"), npm_releases[0] if npm_releases else None)
215+ version = root["version"] if root else ""
216+ npm_version = npm["version"] if npm else ""
217+ if root and root["name"] == "tigrcorn":
218+ release_notes = f"docs/release-notes/RELEASE_NOTES_{version}.md"
219+ else:
220+ release_notes = "docs/release-notes/RELEASE_NOTES_0.3.15.md"
221+ print(f"version={version}")
222+ print(f"npm_version={npm_version}")
223+ print(f"release_notes={release_notes}")
224+ print(f"prerelease={str(bool(plan.get('prerelease'))).lower()}")
225+ print(f"python_release_tag={root['tag'] if root else ''}")
226+ print(f"npm_release_tag={npm['tag'] if npm else ''}")
227+ PY
228+ echo "commit_sha=${commit_sha}" >> "$GITHUB_OUTPUT"
229+
230+ - name : Upload release plan
231+ uses : actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
232+ with :
233+ name : release-plan
234+ path : release-plan.json
235+
54236 python-dist :
55237 name : python-dist
238+ needs : prepare-release
56239 if : ${{ github.event_name != 'workflow_dispatch' || ((inputs.pypi_publish || inputs.gh_release) && inputs.package_selection != 'probes' && inputs.package_selection != 'wt-peer-probes') }}
57240 runs-on : ubuntu-latest
58241 environment : staging
65248 steps :
66249 - name : Check out repository
67250 uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
251+ with :
252+ ref : ${{ needs.prepare-release.outputs.commit_sha }}
68253 - name : Set up Python
69254 uses : actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38
70255 with :
@@ -239,6 +424,7 @@ jobs:
239424
240425 wt-peer-probes-ci :
241426 name : wt-peer-probes-ci
427+ needs : prepare-release
242428 if : ${{ github.event_name != 'workflow_dispatch' || ((inputs.npmjs_publish || inputs.gh_release) && (inputs.package_selection == 'all' || inputs.package_selection == 'probes' || inputs.package_selection == 'wt-peer-probes')) }}
243429 runs-on : ubuntu-latest
244430 environment : ci
@@ -247,6 +433,8 @@ jobs:
247433 steps :
248434 - name : Check out repository
249435 uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
436+ with :
437+ ref : ${{ needs.prepare-release.outputs.commit_sha }}
250438 - name : Set up Node
251439 uses : actions/setup-node@v4
252440 with :
@@ -273,6 +461,7 @@ jobs:
273461
274462 wt-peer-probes-browser-tests :
275463 name : wt-peer-probes-browser-tests
464+ needs : prepare-release
276465 if : ${{ github.event_name != 'workflow_dispatch' || ((inputs.npmjs_publish || inputs.gh_release) && (inputs.package_selection == 'all' || inputs.package_selection == 'probes' || inputs.package_selection == 'wt-peer-probes')) }}
277466 runs-on : ubuntu-latest
278467 environment : ci
@@ -281,6 +470,8 @@ jobs:
281470 steps :
282471 - name : Check out repository
283472 uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
473+ with :
474+ ref : ${{ needs.prepare-release.outputs.commit_sha }}
284475 - name : Set up Node
285476 uses : actions/setup-node@v4
286477 with :
@@ -307,17 +498,47 @@ jobs:
307498 path : packages/wt-peer-probes/playwright-report/
308499 if-no-files-found : ignore
309500
501+ create-github-releases :
502+ name : create-github-releases
503+ needs : prepare-release
504+ if : ${{ github.event_name == 'workflow_dispatch' && inputs.gh_release }}
505+ runs-on : ubuntu-latest
506+ permissions :
507+ contents : write
508+ env :
509+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
510+ steps :
511+ - name : Check out prepared release commit
512+ uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
513+ with :
514+ fetch-depth : 0
515+ ref : ${{ needs.prepare-release.outputs.commit_sha }}
516+ token : ${{ secrets.GITHUB_TOKEN }}
517+ - name : Download release plan
518+ uses : actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
519+ with :
520+ name : release-plan
521+ path : .
522+ - name : Configure Git tag identity
523+ run : |
524+ git config user.name "github-actions[bot]"
525+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
526+ - name : Create tags and GitHub releases
527+ run : python tools/release/release_automation.py create-github-releases --summary release-plan.json
528+
310529 publish-wt-peer-probes-github-release :
311530 name : wt-peer-probes-github-release
312- needs : [wt-peer-probes-ci, wt-peer-probes-browser-tests]
313- if : ${{ github. event_name != 'workflow_dispatch' || (inputs.gh_release && (inputs.package_selection == 'all' || inputs.package_selection == 'probes' || inputs.package_selection == 'wt-peer-probes')) }}
531+ needs : [prepare-release, create-github-releases, wt-peer-probes-ci, wt-peer-probes-browser-tests]
532+ if : ${{ always() && (needs.create- github-releases.result == 'success' || needs.create-github-releases.result == 'skipped') && (github. event_name != 'workflow_dispatch' || (inputs.gh_release && (inputs.package_selection == 'all' || inputs.package_selection == 'probes' || inputs.package_selection == 'wt-peer-probes') )) }}
314533 runs-on : ubuntu-latest
315534 environment : github-release
316535 permissions :
317536 contents : write
318537 steps :
319538 - name : Check out repository
320539 uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
540+ with :
541+ ref : ${{ needs.prepare-release.outputs.commit_sha }}
321542 - name : Download WebTransport peer probe tarball
322543 uses : actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
323544 with :
@@ -326,17 +547,17 @@ jobs:
326547 - name : Create or update GitHub release with probe package
327548 uses : softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe
328549 with :
329- tag_name : ${{ github.ref_name }}
330- name : ${{ github.ref_name }}
331- body_path : docs/release-notes/RELEASE_NOTES_0.3.15.md
550+ tag_name : ${{ github.event_name == 'workflow_dispatch' && needs.prepare-release.outputs.npm_release_tag || github. ref_name }}
551+ name : ${{ github.event_name == 'workflow_dispatch' && needs.prepare-release.outputs.npm_release_tag || github. ref_name }}
552+ body_path : ${{ needs.prepare-release.outputs.release_notes }}
332553 files : .artifacts/wt-peer-probes/*.tgz
333554 draft : ${{ github.event_name == 'workflow_dispatch' && inputs.package_selection == 'all' }}
334- prerelease : ${{ github.event_name == 'release' && github.event.release.prerelease }}
555+ prerelease : ${{ github.event_name == 'workflow_dispatch' && needs.prepare-release.outputs.prerelease == 'true' || github.event_name == ' release' && github.event.release.prerelease }}
335556 make_latest : legacy
336557
337558 publish-wt-peer-probes-npmjs :
338559 name : wt-peer-probes-npmjs-publish
339- needs : [wt-peer-probes-ci, wt-peer-probes-browser-tests]
560+ needs : [prepare-release, wt-peer-probes-ci, wt-peer-probes-browser-tests]
340561 if : ${{ github.event_name != 'workflow_dispatch' || (inputs.npmjs_publish && (inputs.package_selection == 'all' || inputs.package_selection == 'probes' || inputs.package_selection == 'wt-peer-probes')) }}
341562 runs-on : ubuntu-latest
342563 environment : npm
@@ -346,6 +567,8 @@ jobs:
346567 steps :
347568 - name : Check out repository
348569 uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
570+ with :
571+ ref : ${{ needs.prepare-release.outputs.commit_sha }}
349572 - name : Set up Node
350573 uses : actions/setup-node@v4
351574 with :
@@ -377,8 +600,8 @@ jobs:
377600
378601 publish-python-github-release :
379602 name : python-github-release
380- needs : [python-dist, publish-wt-peer-probes-github-release]
381- if : ${{ always() && needs.python-dist.result == 'success' && (needs.publish-wt-peer-probes-github-release.result == 'success' || needs.publish-wt-peer-probes-github-release.result == 'skipped') && (github.event_name != 'workflow_dispatch' || (inputs.gh_release && inputs.package_selection != 'probes' && inputs.package_selection != 'wt-peer-probes')) }}
603+ needs : [prepare-release, create-github-releases, python-dist, publish-wt-peer-probes-github-release]
604+ if : ${{ always() && needs.python-dist.result == 'success' && (needs.create-github-releases.result == 'success' || needs.create-github-releases.result == 'skipped') && (needs. publish-wt-peer-probes-github-release.result == 'success' || needs.publish-wt-peer-probes-github-release.result == 'skipped') && (github.event_name != 'workflow_dispatch' || (inputs.gh_release && inputs.package_selection != 'probes' && inputs.package_selection != 'wt-peer-probes')) }}
382605 runs-on : ubuntu-latest
383606 permissions :
384607 contents : write
@@ -396,8 +619,11 @@ jobs:
396619 - name : Attach Python package and release evidence assets
397620 uses : softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe
398621 with :
622+ tag_name : ${{ github.event_name == 'workflow_dispatch' && needs.prepare-release.outputs.python_release_tag || github.ref_name }}
623+ name : ${{ github.event_name == 'workflow_dispatch' && needs.prepare-release.outputs.python_release_tag || github.ref_name }}
624+ body_path : ${{ needs.prepare-release.outputs.release_notes }}
399625 files : |
400626 dist/*
401627 .artifacts/release-assets/*
402628 draft : false
403- prerelease : ${{ github.event_name == 'release' && github.event.release.prerelease }}
629+ prerelease : ${{ github.event_name == 'workflow_dispatch' && needs.prepare-release.outputs.prerelease == 'true' || github.event_name == ' release' && github.event.release.prerelease }}
0 commit comments