|
13 | 13 | description: Git tag to publish (e.g. v1.4.1) |
14 | 14 | required: true |
15 | 15 | type: string |
| 16 | + dry_run: |
| 17 | + description: Build all distributables but do not touch the GitHub release |
| 18 | + required: false |
| 19 | + default: false |
| 20 | + type: boolean |
| 21 | + executor_run_id: |
| 22 | + description: >- |
| 23 | + Publish Executor run id for this tag. Set by the automatic trigger; |
| 24 | + the promote gate waits for exactly this run. Leave empty for manual |
| 25 | + dispatches. |
| 26 | + required: false |
| 27 | + default: "" |
| 28 | + type: string |
16 | 29 |
|
17 | 30 | permissions: |
18 | 31 | contents: read |
@@ -44,22 +57,22 @@ jobs: |
44 | 57 | # smoke: run the compiled-sidecar smoke test on legs whose target |
45 | 58 | # matches the runner (the mac x64 leg cross-compiles on an arm64 |
46 | 59 | # runner, so its binary can't be executed natively there). |
47 | | - - os: macos-latest |
| 60 | + - os: blacksmith-6vcpu-macos-latest |
48 | 61 | arch: arm64 |
49 | 62 | platform: mac |
50 | 63 | bun-target: bun-darwin-arm64 |
51 | 64 | smoke: true |
52 | | - - os: macos-latest |
| 65 | + - os: blacksmith-6vcpu-macos-latest |
53 | 66 | arch: x64 |
54 | 67 | platform: mac |
55 | 68 | bun-target: bun-darwin-x64 |
56 | 69 | smoke: false |
57 | | - - os: ubuntu-latest |
| 70 | + - os: blacksmith-4vcpu-ubuntu-2404 |
58 | 71 | arch: x64 |
59 | 72 | platform: linux |
60 | 73 | bun-target: bun-linux-x64 |
61 | 74 | smoke: true |
62 | | - - os: windows-latest |
| 75 | + - os: blacksmith-8vcpu-windows-2025 |
63 | 76 | arch: x64 |
64 | 77 | platform: win |
65 | 78 | bun-target: bun-windows-x64 |
@@ -103,6 +116,11 @@ jobs: |
103 | 116 | with: |
104 | 117 | node-version: 24 |
105 | 118 |
|
| 119 | + # No package/electron caches here on purpose: on Blacksmith's NVMe |
| 120 | + # runners a cold `bun install` (8-55s) is as fast as a cache |
| 121 | + # restore + warm install, and bun.lock changes on every release |
| 122 | + # (Version Packages), so each publish would pay the cache-save tail |
| 123 | + # (~45s on the mac critical path) for nothing. |
106 | 124 | - name: Install dependencies |
107 | 125 | run: bun install --frozen-lockfile |
108 | 126 |
|
@@ -215,9 +233,14 @@ jobs: |
215 | 233 |
|
216 | 234 | release: |
217 | 235 | needs: build |
| 236 | + # dry_run builds and uploads workflow artifacts but never touches the |
| 237 | + # GitHub release — used to rehearse workflow changes against a real tag. |
| 238 | + if: ${{ !inputs.dry_run }} |
218 | 239 | runs-on: blacksmith-4vcpu-ubuntu-2404 |
219 | 240 | permissions: |
220 | 241 | contents: write |
| 242 | + # Read Publish Executor run state for the promote gate below. |
| 243 | + actions: read |
221 | 244 |
|
222 | 245 | steps: |
223 | 246 | - name: Checkout validation script |
@@ -255,14 +278,61 @@ jobs: |
255 | 278 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
256 | 279 | run: | |
257 | 280 | set -euo pipefail |
258 | | - while IFS= read -r file; do |
259 | | - echo "Uploading: $file" |
260 | | - gh release upload "$RELEASE_TAG" "$file" --repo "$GITHUB_REPOSITORY" --clobber |
261 | | - done < <(find artifacts -type f \ |
| 281 | + # Parallel uploads; xargs exits non-zero if any single upload fails, |
| 282 | + # which keeps the promote step below from running on a partial set. |
| 283 | + find artifacts -type f \ |
262 | 284 | \( -name "*.dmg" -o -name "*.zip" -o -name "*.exe" \ |
263 | 285 | -o -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \ |
264 | 286 | -o -name "*.blockmap" \ |
265 | | - -o -name "latest*.yml" \)) |
| 287 | + -o -name "latest*.yml" \) -print0 \ |
| 288 | + | xargs -0 -n1 -P8 -I{} sh -c \ |
| 289 | + 'echo "Uploading: $1"; exec gh release upload "$RELEASE_TAG" "$1" --repo "$GITHUB_REPOSITORY" --clobber' _ {} |
| 290 | +
|
| 291 | + # A published release must imply the npm packages for this tag exist. |
| 292 | + # Publish Executor triggers this workflow before its npm publishes (to |
| 293 | + # overlap them with the desktop build) and hands over its own run id, |
| 294 | + # so wait for exactly that run to conclude successfully before going |
| 295 | + # public. A run-name search would fail open whenever the run fell |
| 296 | + # outside the listing window, so only an explicitly empty run id (a |
| 297 | + # manual dispatch) skips the gate — the operator owns the invariant |
| 298 | + # then. |
| 299 | + - name: Wait for executor package publish |
| 300 | + env: |
| 301 | + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 302 | + EXECUTOR_RUN_ID: ${{ inputs.executor_run_id }} |
| 303 | + run: | |
| 304 | + set -euo pipefail |
| 305 | + if [ -z "$EXECUTOR_RUN_ID" ]; then |
| 306 | + echo "No executor_run_id given (manual dispatch); skipping the publish gate." |
| 307 | + exit 0 |
| 308 | + fi |
| 309 | + deadline=$((SECONDS + 1200)) |
| 310 | + while :; do |
| 311 | + run=$(gh run view "$EXECUTOR_RUN_ID" --repo "$GITHUB_REPOSITORY" \ |
| 312 | + --json status,conclusion,url,displayTitle) |
| 313 | + title=$(echo "$run" | jq -r .displayTitle) |
| 314 | + if [ "$title" != "publish executor $RELEASE_TAG" ]; then |
| 315 | + echo "Run $EXECUTOR_RUN_ID is '$title', not 'publish executor $RELEASE_TAG' — refusing to promote." |
| 316 | + exit 1 |
| 317 | + fi |
| 318 | + status=$(echo "$run" | jq -r .status) |
| 319 | + if [ "$status" = "completed" ]; then |
| 320 | + conclusion=$(echo "$run" | jq -r .conclusion) |
| 321 | + if [ "$conclusion" = "success" ]; then |
| 322 | + echo "Publish Executor succeeded." |
| 323 | + break |
| 324 | + fi |
| 325 | + echo "Publish Executor concluded '$conclusion' — leaving the release in draft." |
| 326 | + echo "$run" | jq -r .url |
| 327 | + exit 1 |
| 328 | + fi |
| 329 | + if [ "$SECONDS" -ge "$deadline" ]; then |
| 330 | + echo "Timed out waiting for Publish Executor — leaving the release in draft." |
| 331 | + exit 1 |
| 332 | + fi |
| 333 | + echo "Publish Executor status: $status; waiting..." |
| 334 | + sleep 15 |
| 335 | + done |
266 | 336 |
|
267 | 337 | # Flip draft → published only after every desktop asset is uploaded — |
268 | 338 | # this is the atomic point where the new tag becomes "latest". |
|
0 commit comments