diff --git a/scripts/release.sh b/scripts/release.sh index f528b2577..daa059789 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -7,12 +7,13 @@ # The flow, end to end: # 1. A release PR (branch chore/release-*, title "chore: release ...") # bumps the versions; the pre-commit hook writes changelog//.md. -# 2. `scripts/ci-merge.sh --release` runs the full local list plus the -# release gate on the PR head and squash-merges it when green. -# 3. `scripts/release.sh` on the merged main (this script): -# - finds the changelog files the head commit added (or takes paths), -# - refuses unless local CI is green on that commit (runs -# `scripts/ci.sh --release` unless a PASS marker already exists), +# 2. The PR merges on green GitHub CI, like any PR, after +# `node scripts/release-gate.mjs` passed on its head. +# 3. `scripts/release.sh` on the merged release commit (this script): +# - finds the changelog files the commit added (or takes paths), +# - refuses unless GitHub CI is green on that exact commit (the CI +# workflow run for the sha, all required checks passing) and the +# commit is on origin/main; main may have moved on since, # - pushes the tag publish-, which triggers release.yml to # publish exactly those packages to npm and GitHub Packages and # lockstep-publish the wrappers when cli moved, @@ -22,9 +23,10 @@ # (scripts/purge-cdn.sh). # Every step is idempotent, so re-running after a partial failure is safe. # -# scripts/release.sh # the head commit of origin/main +# scripts/release.sh # the checked-out release commit # scripts/release.sh changelog/core/0.7.65.md changelog/cli/0.10.70.md # scripts/release.sh --no-purge # skip the CDN purge +# scripts/release.sh --gate # also run scripts/release-gate.mjs here first # # Credentials stay where they are: gh's login (tag push, releases), the npm # registry is only read, CLOUDFLARE_API_TOKEN for the purge. Nothing here @@ -34,10 +36,11 @@ set -euo pipefail REPO="webjsdev/webjs" cd "$(git rev-parse --show-toplevel)" -purge=1 files=() +purge=1 gate=0 files=() for a in "$@"; do case "$a" in --no-purge) purge=0 ;; + --gate) gate=1 ;; -h|--help) sed -n '2,33p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; changelog/*.md) files+=("$a") ;; *) echo "release: unknown argument '$a'" >&2; exit 2 ;; @@ -45,10 +48,10 @@ for a in "$@"; do done git fetch -q origin main --tags -sha="$(git rev-parse origin/main)" -[ "$(git rev-parse HEAD)" = "$sha" ] || { - echo "release: check out origin/main first (HEAD is $(git rev-parse --short=12 HEAD), main is ${sha:0:12}); a detached worktree is fine:" >&2 - echo " git worktree add --detach ../webjs-release origin/main && cd ../webjs-release && npm ci" >&2 +sha="$(git rev-parse HEAD)" +git merge-base --is-ancestor "$sha" origin/main || { + echo "release: HEAD ${sha:0:12} is not on origin/main; check out the merged release commit (a detached worktree is fine):" >&2 + echo " git worktree add --detach ../webjs-release && cd ../webjs-release && npm ci" >&2 exit 2 } [ -z "$(git status --porcelain --untracked-files=no)" ] || { echo "release: the tree has uncommitted changes" >&2; exit 2; } @@ -59,12 +62,20 @@ fi [ ${#files[@]} -gt 0 ] || { echo "release: HEAD adds no changelog//.md; nothing to release"; exit 0; } echo "release: ${sha:0:12} releases:"; printf ' %s\n' "${files[@]}" -# 1. The gate, on the exact commit being published. -if [ -f "${TMPDIR:-/tmp}/webjs-ci-${sha:0:12}/PASS" ]; then - echo "release: local CI already green on ${sha:0:12}" -else - bash scripts/ci.sh --release +# 1. The gate, on the exact commit being published: GitHub CI green on this +# sha (the authoritative CI), and the release gate when asked for. +ci_run="$(gh run list --repo "$REPO" --workflow ci.yml --commit "$sha" --limit 1 --json databaseId,status,conclusion -q '.[0] | "\(.databaseId) \(.status) \(.conclusion)"' 2>/dev/null || true)" +read -r ci_id ci_status ci_conclusion <<<"${ci_run:-"" "" ""}" +if [ -z "$ci_id" ]; then + echo "release: no GitHub CI run for ${sha:0:12}; it runs on the PR and on the push to main. Dispatch one with: gh workflow run ci.yml --repo $REPO --ref main" >&2 + exit 1 +fi +if [ "$ci_status" != completed ] || [ "$ci_conclusion" != success ]; then + echo "release: GitHub CI run $ci_id for ${sha:0:12} is $ci_status/$ci_conclusion; wait for it or rerun its failed jobs: gh run rerun $ci_id --repo $REPO --failed" >&2 + exit 1 fi +echo "release: GitHub CI green on ${sha:0:12} (run $ci_id)" +if [ "$gate" = 1 ]; then node scripts/release-gate.mjs; fi # 2. The tag that makes release.yml publish. The workflow diffs HEAD~1..HEAD # at the tag, so it publishes exactly what this commit added.