authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-04 10:29:05-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-05 16:15:34-08:00
logea5a100a744a73f0aaa62f2606296d8193935c59
tree5d731062d6664f3b635c6b02fd5141e3aa85ab2b
parent6841fa17d44e089b4cef03645efd6419a06f7611

Defer release commit+tag to finalize job

Move the version bump commit and tag creation out of the preflight job into a new finalize job that runs only after build, publish, and release all succeed. This prevents orphaned version commits and tags on main when downstream jobs fail. - Preflight: validate version, lint/test only — no commit/tag/push - Release: softprops/action-gh-release creates tag for workflow_dispatch - Finalize: bump package.json versions, refresh lockfile, commit to main Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

1 files changed, 72 insertions(+), 62 deletions(-)

.github/workflows/release.yml+72-62
......@@ -22,12 +22,10 @@ jobs:
2222 outputs:
2323 version: ${{ steps.release_meta.outputs.version }}
2424 tag: ${{ steps.release_meta.outputs.tag }}
25 ref: ${{ steps.release_ref.outputs.value }}
25 ref: ${{ github.sha }}
2626 steps:
2727 - name: Checkout
2828 uses: actions/checkout@v4
29 with:
30 fetch-depth: 0
3129
3230 - id: release_meta
3331 name: Resolve release version
......@@ -58,32 +56,6 @@ jobs:
5856 with:
5957 node-version-file: package.json
6058
61 - name: Update version strings
62 if: github.event_name == 'workflow_dispatch'
63 env:
64 RELEASE_VERSION: ${{ steps.release_meta.outputs.version }}
65 run: |
66 node --input-type=module -e '
67 import { readFileSync, writeFileSync } from "node:fs";
68
69 const files = [
70 "apps/server/package.json",
71 "apps/desktop/package.json",
72 "apps/web/package.json",
73 "packages/contracts/package.json",
74 ];
75
76 for (const file of files) {
77 const packageJson = JSON.parse(readFileSync(file, "utf8"));
78 packageJson.version = process.env.RELEASE_VERSION;
79 writeFileSync(file, `${JSON.stringify(packageJson, null, 2)}\n`);
80 }
81 '
82
83 - name: Refresh lockfile
84 if: github.event_name == 'workflow_dispatch'
85 run: bun install
86
8759 - name: Install dependencies
8860 run: bun install --frozen-lockfile
8961
......@@ -96,39 +68,6 @@ jobs:
9668 - name: Test
9769 run: bun run test
9870
99 - name: Commit and push release version + tag
100 if: github.event_name == 'workflow_dispatch'
101 shell: bash
102 env:
103 RELEASE_TAG: ${{ steps.release_meta.outputs.tag }}
104 run: |
105 if git ls-remote --exit-code --tags origin "$RELEASE_TAG" >/dev/null 2>&1; then
106 echo "Tag $RELEASE_TAG already exists on origin." >&2
107 exit 1
108 fi
109
110 git config user.name "github-actions[bot]"
111 git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
112
113 if ! git diff --quiet -- apps/server/package.json apps/desktop/package.json apps/web/package.json packages/contracts/package.json bun.lock; then
114 git add apps/server/package.json apps/desktop/package.json apps/web/package.json packages/contracts/package.json bun.lock
115 git commit -m "chore(release): prepare $RELEASE_TAG" -m "Co-authored-by: codex <codex@users.noreply.github.com>"
116 fi
117
118 git tag "$RELEASE_TAG"
119 git push origin "HEAD:${GITHUB_REF_NAME}"
120 git push origin "refs/tags/$RELEASE_TAG"
121
122 - id: release_ref
123 name: Resolve source ref
124 shell: bash
125 run: |
126 if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
127 echo "value=refs/tags/${{ steps.release_meta.outputs.tag }}" >> "$GITHUB_OUTPUT"
128 else
129 echo "value=${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
130 fi
131
13271 build:
13372 name: Build ${{ matrix.label }}
13473 needs: preflight
......@@ -320,6 +259,7 @@ jobs:
320259 tag_name: ${{ needs.preflight.outputs.tag }}
321260 name: T3 Code v${{ needs.preflight.outputs.version }}
322261 generate_release_notes: true
262 make_latest: true
323263 files: |
324264 release-assets/*.dmg
325265 release-assets/*.zip
......@@ -328,3 +268,73 @@ jobs:
328268 release-assets/*.blockmap
329269 release-assets/latest*.yml
330270 fail_on_unmatched_files: true
271
272 finalize:
273 name: Finalize release
274 needs: [preflight, release]
275 runs-on: ubuntu-24.04
276 steps:
277 - name: Checkout
278 uses: actions/checkout@v4
279 with:
280 ref: main
281 fetch-depth: 0
282
283 - name: Setup Bun
284 uses: oven-sh/setup-bun@v2
285 with:
286 bun-version-file: package.json
287
288 - name: Setup Node
289 uses: actions/setup-node@v4
290 with:
291 node-version-file: package.json
292
293 - name: Update version strings
294 env:
295 RELEASE_VERSION: ${{ needs.preflight.outputs.version }}
296 run: |
297 node --input-type=module -e '
298 import { readFileSync, writeFileSync } from "node:fs";
299
300 const files = [
301 "apps/server/package.json",
302 "apps/desktop/package.json",
303 "apps/web/package.json",
304 "packages/contracts/package.json",
305 ];
306
307 let changed = false;
308 for (const file of files) {
309 const packageJson = JSON.parse(readFileSync(file, "utf8"));
310 if (packageJson.version !== process.env.RELEASE_VERSION) {
311 packageJson.version = process.env.RELEASE_VERSION;
312 writeFileSync(file, `${JSON.stringify(packageJson, null, 2)}\n`);
313 changed = true;
314 }
315 }
316
317 if (!changed) {
318 console.log("All package.json versions already match release version.");
319 }
320 '
321
322 - name: Refresh lockfile
323 run: bun install
324
325 - name: Commit and push version bump
326 shell: bash
327 env:
328 RELEASE_TAG: ${{ needs.preflight.outputs.tag }}
329 run: |
330 if git diff --quiet -- apps/server/package.json apps/desktop/package.json apps/web/package.json packages/contracts/package.json bun.lock; then
331 echo "No version changes to commit."
332 exit 0
333 fi
334
335 git config user.name "github-actions[bot]"
336 git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
337
338 git add apps/server/package.json apps/desktop/package.json apps/web/package.json packages/contracts/package.json bun.lock
339 git commit -m "chore(release): prepare $RELEASE_TAG"
340 git push origin HEAD:main