@@ -25,7 +25,9 @@ run-name: "${{ github.event_name == 'workflow_dispatch' && format('Console CLI {
2525# - The release service runs in environment release (dev and production), which holds no secrets: the service admits
2626# it by OIDC. npm runs in environment npm (production only, no secrets), which every package's trusted publishing
2727# names, so no dev run can mint a token npm accepts.
28- # .github/ is the security boundary for releases: changes to it need review by someone who may release.
28+ # .github/ is the security boundary for releases. Changes to it need a code owner's review, which repository admins
29+ # may skip when merging a pull request. Required Checks and the merge methods apply to everyone, and only admins and
30+ # maintainers may merge into production.
2931
3032on :
3133 push :
@@ -119,10 +121,14 @@ jobs:
119121 - uses : actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
120122 with :
121123 node-version-file : .node-version
124+ package-manager-cache : false
125+ # No step here restores a cache: dev is the default branch, so a production run could restore one a dev run
126+ # wrote, and the build would run what it holds.
122127 - &setup-bun
123128 uses : oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
124129 with :
125130 bun-version : " 1.4.3"
131+ no-cache : true
126132 - run : bun install --frozen-lockfile
127133 # Stamped into the checkout only, never committed.
128134 - &stamp
@@ -274,8 +280,9 @@ jobs:
274280 name : signed-macos
275281 path : ${{ runner.temp }}/signed
276282
277- # Authenticode through Azure Artifact Signing for the two Windows binaries, signed in by OIDC (the federated
278- # credential names repo:anomalyco/opencode-console-cli:environment:signing). A check run repacks them unsigned.
283+ # Authenticode through Azure Artifact Signing for the two Windows binaries, signed in by OIDC. This repository uses
284+ # immutable OIDC subjects, so the federated credential names
285+ # repo:anomalyco@66570915/opencode-console-cli@1452060248:environment:signing. A check run repacks them unsigned.
279286 sign-windows :
280287 name : Sign the Windows binaries
281288 needs : [plan, build-binaries]
@@ -352,6 +359,8 @@ jobs:
352359 timestamp-digest : SHA256
353360 description : OpenCode Console CLI
354361 description-url : https://opencode.ai/console
362+ # Its PowerShell module and build tools would otherwise come from a cache any branch's run can write.
363+ cache-dependencies : false
355364 exclude-environment-credential : true
356365 exclude-workload-identity-credential : true
357366 exclude-managed-identity-credential : true
@@ -424,16 +433,42 @@ jobs:
424433
425434 # ---- Publish: only .github/release/ is checked out; artifacts are data --------------------------------------------
426435
436+ # Stable only, before anything leaves GitHub: claims refs/tags/v<version> at this run's commit (reserve-tag.sh), so a
437+ # tag someone made at another commit stops the release before it publishes rather than after. A rerun at the same
438+ # commit finds its own tag.
439+ reserve-tag :
440+ name : Reserve the release tag
441+ needs : [plan, build, build-binaries, sign-macos, sign-windows, sign-linux]
442+ if : >-
443+ ${{ !cancelled() && needs.plan.outputs.mode == 'stable' && needs.build.result == 'success'
444+ && needs.build-binaries.result == 'success' && needs.sign-macos.result == 'success'
445+ && needs.sign-windows.result == 'success' && needs.sign-linux.result == 'success' }}
446+ runs-on : ubuntu-24.04
447+ timeout-minutes : 5
448+ environment : release
449+ permissions :
450+ contents : write # creates the tag's ref
451+ steps :
452+ - *checkout-release
453+ - name : Reserve
454+ run : bash .github/release/reserve-tag.sh
455+ env :
456+ GH_TOKEN : ${{ github.token }}
457+ GH_REPO : ${{ github.repository }}
458+ CONSOLE_VERSION : ${{ needs.plan.outputs.version }}
459+ SHA : ${{ github.sha }}
460+
427461 # The job's OIDC token is the only credential; the service checks it against its registry entry for opencode-console
428462 # (this repository and workflow, environment release, dev for channel dev and production for stable and beta).
429463 publish-release-service :
430464 name : Publish to the release service
431- needs : [plan, build, build-binaries, sign-macos, sign-windows, sign-linux]
432- # Dev skips signing; every other run needs all three signed.
465+ needs : [plan, build, build-binaries, sign-macos, sign-windows, sign-linux, reserve-tag ]
466+ # Dev skips signing; every other run needs all three signed, and stable its tag reserved .
433467 if : >-
434468 ${{ !cancelled() && needs.build.result == 'success' && needs.build-binaries.result == 'success'
435469 && (needs.plan.outputs.mode == 'dev' || (needs.sign-macos.result == 'success'
436- && needs.sign-windows.result == 'success' && needs.sign-linux.result == 'success')) }}
470+ && needs.sign-windows.result == 'success' && needs.sign-linux.result == 'success'))
471+ && (needs.plan.outputs.mode != 'stable' || needs.reserve-tag.result == 'success') }}
437472 runs-on : ubuntu-24.04
438473 timeout-minutes : 20
439474 environment : ${{ needs.plan.outputs.dry_run != 'true' && 'release' || '' }}
@@ -485,6 +520,7 @@ jobs:
485520 with :
486521 node-version : " 24"
487522 check-latest : true
523+ package-manager-cache : false
488524 - *download-all
489525 - name : Publish
490526 run : |
@@ -518,7 +554,8 @@ jobs:
518554 CONSOLE_VERSION : ${{ needs.plan.outputs.version }}
519555 CONSOLE_CHANNEL : ${{ needs.plan.outputs.channel }}
520556 run : |
521- curl -fsSL --retry 5 --retry-all-errors https://anoma.ly/release/client.sh -o "$RUNNER_TEMP/client.sh"
557+ . .github/release/lib.sh
558+ release_client "$RUNNER_TEMP/client.sh"
522559 bash "$RUNNER_TEMP/client.sh" distribution --app opencode-console --artifact cli --channel "$CONSOLE_CHANNEL" \
523560 --entry "$(jq -nc --arg version "$CONSOLE_VERSION" '{kind: "npm", version: $version}')"
524561 - name : Tag and GitHub release
@@ -528,12 +565,19 @@ jobs:
528565 GH_REPO : ${{ github.repository }}
529566 CONSOLE_VERSION : ${{ needs.plan.outputs.version }}
530567 SHA : ${{ github.sha }}
568+ # The tag reserve-tag made at this commit; --verify-tag refuses to make one. A rerun after the release was made
569+ # uploads its files again in place.
531570 run : |
532571 . .github/release/lib.sh
533- files=()
572+ files=("$RUNNER_TEMP/in/build/opencode-console-$CONSOLE_VERSION.tgz" )
534573 for target in $TARGETS; do
535574 files+=("$(target_file "$target" "$RUNNER_TEMP/in/binaries" "$RUNNER_TEMP/in/signed-macos" "$RUNNER_TEMP/in/signed-windows" "$RUNNER_TEMP/in/signed-linux")")
536575 done
537- gh release create "v$CONSOLE_VERSION" --target "$SHA" --title "OpenCode Console CLI $CONSOLE_VERSION" \
538- --notes "@opencode/console $CONSOLE_VERSION: https://anoma.ly/release/opencode-console/cli/stable.json" \
539- "$RUNNER_TEMP/in/build/opencode-console-$CONSOLE_VERSION.tgz" "${files[@]}"
576+ SHA=$SHA bash .github/release/reserve-tag.sh
577+ if gh release view "v$CONSOLE_VERSION" > /dev/null 2>&1; then
578+ gh release upload "v$CONSOLE_VERSION" --clobber "${files[@]}"
579+ else
580+ gh release create "v$CONSOLE_VERSION" --verify-tag --title "OpenCode Console CLI $CONSOLE_VERSION" \
581+ --notes "@opencode/console $CONSOLE_VERSION: https://anoma.ly/release/opencode-console/cli/stable.json" \
582+ "${files[@]}"
583+ fi
0 commit comments