88concurrency : ${{ github.workflow }}-${{ github.ref }}
99
1010jobs :
11+ # Publishing to npm is irreversible — never let a commit that fails CI or
12+ # Nix Build ship. Those workflows run in parallel on the same push, so this
13+ # job blocks the release until both conclude successfully for this SHA.
14+ wait-for-checks :
15+ name : Wait for CI and Nix Build
16+ runs-on : ubuntu-latest
17+ if : github.repository_owner == 'Pythoughts-labs'
18+ timeout-minutes : 45
19+ permissions :
20+ actions : read
21+ contents : read
22+ steps :
23+ - name : Wait for required workflows on this commit
24+ env :
25+ GH_TOKEN : ${{ github.token }}
26+ SHA : ${{ github.sha }}
27+ REPO : ${{ github.repository }}
28+ run : |
29+ for workflow in "CI" "Nix Build"; do
30+ echo "Waiting for workflow: $workflow"
31+ while true; do
32+ # Transient API failures must not kill the gate (step shell is -e).
33+ pair=$(gh api "repos/$REPO/actions/runs?head_sha=$SHA&per_page=50" \
34+ --jq "[.workflow_runs[] | select(.name==\"$workflow\")][0] | \"\(.status)/\(.conclusion)\"" \
35+ 2>/dev/null) || pair="api-error/null"
36+ case "$pair" in
37+ completed/success)
38+ echo "$workflow: success"; break ;;
39+ completed/*)
40+ echo "::error::$workflow concluded '${pair#completed/}' for $SHA — refusing to release."
41+ exit 1 ;;
42+ *)
43+ echo "$workflow: $pair — waiting..."; sleep 30 ;;
44+ esac
45+ done
46+ done
47+
1148 release :
1249 name : Release
50+ needs : wait-for-checks
1351 runs-on : ubuntu-latest
52+ timeout-minutes : 30
1453 if : github.repository_owner == 'Pythoughts-labs'
1554 outputs :
1655 packages_published : ${{ steps.changesets.outputs.published }}
77116 # registry dependency), so no deploy webhook is fired here — this job only
78117 # verifies the published release is internally consistent.
79118 verify-cdn-release :
119+ timeout-minutes : 15
80120 name : Verify release consistency
81121 needs : release
82122 if : needs.release.outputs.packages_published == 'true'
96136 run : node scripts/release/verify-release-consistency.mjs
97137
98138 update-brew-tap :
139+ timeout-minutes : 15
99140 name : Update Homebrew tap
100141 needs : release
101142 if : needs.release.outputs.packages_published == 'true'
@@ -146,6 +187,7 @@ jobs:
146187 APPLE_NOTARIZATION_ISSUER_ID : ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
147188
148189 publish-native-assets :
190+ timeout-minutes : 15
149191 name : Publish native release assets
150192 needs :
151193 - release
@@ -166,6 +208,17 @@ jobs:
166208 path : dist-native-release
167209 merge-multiple : true
168210
211+ - name : Assert all native targets are present
212+ run : |
213+ missing=0
214+ for target in darwin-arm64 darwin-x64 linux-arm64 linux-x64 win32-arm64 win32-x64; do
215+ if ! ls dist-native-release/pythinker-code-"$target".zip >/dev/null 2>&1; then
216+ echo "::error::Missing native bundle for $target — refusing to publish a partial release."
217+ missing=1
218+ fi
219+ done
220+ exit $missing
221+
169222 - name : Produce manifest.json
170223 env :
171224 RELEASE_TAG : ${{ needs.release.outputs.pythinker_release_tag }}
0 commit comments