Skip to content

Commit f5b3517

Browse files
committed
ci: prepare release versions before publishing
1 parent edfd4ed commit f5b3517

3 files changed

Lines changed: 850 additions & 10 deletions

File tree

.github/workflows/publish-all-packages.yml

Lines changed: 236 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,198 @@ on:
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+
5370
jobs:
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
@@ -65,6 +248,8 @@ jobs:
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

Comments
 (0)