@@ -41,13 +41,151 @@ jobs:
4141 release_created : ${{ steps.release.outputs.release_created }}
4242 tag_name : ${{ steps.release.outputs.tag_name }}
4343 version : ${{ steps.release.outputs.version }}
44+ pr : ${{ steps.release.outputs.pr }}
4445 steps :
4546 - name : Run release-please
4647 id : release
4748 uses : googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
4849 with :
4950 token : ${{ secrets.GITHUB_TOKEN }}
5051
52+ # The release PR bumps the workspace crate versions in the Cargo.tomls but
53+ # release-please cannot update Cargo.lock, so without this job every release
54+ # merge ships a tag whose lockfile disagrees with its manifests (a --locked
55+ # build from the tag fails, and pr-checks builds --locked). Sync the lock on
56+ # the release branch so the tagged tree is internally consistent.
57+ #
58+ # Runs on every push-triggered release run, not only when release-please
59+ # reports the PR: the `pr` output is set only on runs that CREATE or UPDATE
60+ # the release PR, so gating on it would leave an already-open release PR
61+ # (opened before this job existed, or untouched by a non-releasable push)
62+ # permanently without the sync and without check runs. The resolve step
63+ # discovers any open release-please PR itself and no-ops when there is none.
64+ sync-release-lock :
65+ name : Sync Cargo.lock on the release PR
66+ needs : release-please
67+ if : ${{ !cancelled() && github.repository == 'Gitlawb/node' && github.event_name != 'workflow_dispatch' }}
68+ runs-on : ubuntu-latest
69+ permissions :
70+ contents : write
71+ pull-requests : read
72+ # actions: write lets the final step dispatch pr-checks on the release
73+ # branch. Note this grant is repo-wide dispatch rights (GitHub cannot
74+ # scope it to one workflow); acceptable here because reaching this job's
75+ # steps already requires write to main. Needed because every push to the
76+ # release branch (release-please's and the lock sync below) is made with
77+ # GITHUB_TOKEN, which triggers no workflow runs, so without an explicit
78+ # dispatch the release PR's head carries no check runs and the --locked
79+ # gate never validates it.
80+ actions : write
81+ steps :
82+ # Prefer the fresh `pr` output when this run just created/updated the
83+ # release PR; otherwise fall back to querying for an open release-please
84+ # branch (prefix match: the branch carries `--components--` suffixes).
85+ - name : Resolve the open release PR branch
86+ id : resolve
87+ env :
88+ GH_TOKEN : ${{ github.token }}
89+ OWNER : ${{ github.repository_owner }}
90+ BRANCH_FROM_OUTPUT : ${{ needs.release-please.outputs.pr && fromJSON(needs.release-please.outputs.pr).headBranchName || '' }}
91+ run : |
92+ branch="$BRANCH_FROM_OUTPUT"
93+ if [ -z "$branch" ]; then
94+ # The branch name alone is not a trust signal. `gh pr list` includes
95+ # fork PRs, and a fork's headRefName is bare (no owner prefix), so a
96+ # prefix match by itself lets any fork nominate the branch this job
97+ # checks out, commits to, and dispatches checks on. Require the head
98+ # repo to be THIS repository and the author to be the release-please
99+ # app, so neither a fork nor a same-repo human branch named
100+ # `release-please--*` can be selected. Then take the highest PR
101+ # number: `[0]` depended on gh's default ordering, which is not a
102+ # documented guarantee, and picking a stable one keeps a second
103+ # (component) release PR from making the choice flap between runs.
104+ branch=$(gh pr list --repo "$GITHUB_REPOSITORY" --state open \
105+ --json headRefName,headRepositoryOwner,number,author \
106+ --jq '[ .[]
107+ | select(.headRepositoryOwner.login == env.OWNER)
108+ | select(.author.login == "app/github-actions")
109+ | select(.headRefName | startswith("release-please--"))
110+ ] | max_by(.number).headRefName // empty')
111+ fi
112+ if [ -z "$branch" ]; then
113+ echo "no open release PR; nothing to sync"
114+ fi
115+ echo "branch=$branch" >> "$GITHUB_OUTPUT"
116+
117+ - name : Checkout release branch
118+ if : ${{ steps.resolve.outputs.branch != '' }}
119+ uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
120+ with :
121+ ref : ${{ steps.resolve.outputs.branch }}
122+
123+ # Resolve on a declared toolchain instead of whatever Rust the runner
124+ # image happens to ship, so the lock this job writes comes off the same
125+ # stable channel the other cargo jobs use. Not a claim of identical
126+ # resolution: `stable` still moves between runs, and the MSRV gate
127+ # checks this same lock on 1.91 (pr-checks.yml). It only removes the
128+ # runner image as an undeclared input.
129+ - name : Install Rust toolchain
130+ if : ${{ steps.resolve.outputs.branch != '' }}
131+ uses : dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # stable
132+ with :
133+ toolchain : stable
134+
135+ - name : Sync workspace crate versions into Cargo.lock
136+ if : ${{ steps.resolve.outputs.branch != '' }}
137+ run : cargo update --workspace
138+
139+ - name : Commit and push when the lock changed
140+ if : ${{ steps.resolve.outputs.branch != '' }}
141+ run : |
142+ if git diff --quiet Cargo.lock; then
143+ echo "Cargo.lock already in sync"
144+ exit 0
145+ fi
146+ git config user.name "github-actions[bot]"
147+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
148+ git add Cargo.lock
149+ git commit -m "chore: sync Cargo.lock with the release version bump"
150+ git push
151+
152+ # Every push to the release branch is made with GITHUB_TOKEN (release-
153+ # please's own pushes and the lock sync above), and events created by
154+ # GITHUB_TOKEN start no workflow runs. workflow_dispatch is the documented
155+ # exception GITHUB_TOKEN may trigger, so dispatch pr-checks at the release
156+ # branch to attach check runs to its current head. Unconditional (also on
157+ # the already-in-sync path) because the branch's ORIGINAL head has the
158+ # same no-runs problem. Loud on failure by design: a silent fallback
159+ # would recreate the checkless-head problem, and a job rerun is safe
160+ # (sync no-ops, dispatch retries). Loop-safe: pr-checks is read-only and
161+ # dispatches nothing. Runs after the push ack, so the dispatched ref tip
162+ # is the sync commit. Merge-queue note: this validates the branch tip;
163+ # the enforced merge queue's merge_group run still re-validates the true
164+ # merged result before landing.
165+ - name : Dispatch PR checks on the release branch
166+ if : ${{ steps.resolve.outputs.branch != '' }}
167+ env :
168+ GH_TOKEN : ${{ github.token }}
169+ BRANCH : ${{ steps.resolve.outputs.branch }}
170+ run : |
171+ # Every dispatch failure fails the job, including the 422 a release
172+ # branch cut before pr-checks gained its workflow_dispatch trigger
173+ # returns ("does not have a workflow_dispatch trigger", reported
174+ # against the dispatched ref's copy of the file). An earlier revision
175+ # tolerated exactly that 422 on the theory that release-please would
176+ # rewrite the branch and heal it, but a rewrite only happens when a
177+ # later releasable commit lands: an open release PR nobody touches is
178+ # never rewritten. Tolerating it therefore left the lock-sync commit
179+ # this job had just pushed sitting on the release head with no
180+ # --locked validation, which is the precise state this workflow exists
181+ # to prevent. The tolerance is also no longer needed: once this lands,
182+ # every release branch is cut from a main that already carries the
183+ # trigger, so the only branch that could 422 is one open at merge time
184+ # (there is none), and a job rerun after a manual rebase is safe (the
185+ # sync no-ops, the dispatch retries).
186+ gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/pr-checks.yml/dispatches" \
187+ -f "ref=$BRANCH"
188+
51189 # Each architecture builds NATIVELY (amd64 on ubuntu-latest, arm64 on
52190 # ubuntu-24.04-arm) and pushes by digest; docker-manifest below stitches the
53191 # digests into the tagged multi-arch image. No QEMU: emulating the arm64
@@ -308,7 +446,7 @@ jobs:
308446 set -euo pipefail
309447 args=""
310448 for b in $BINS; do args="$args -p $b"; done
311- cargo build --release --target ${{ matrix.target }} $args
449+ cargo build --release --locked -- target ${{ matrix.target }} $args
312450
313451 # Run each packaged binary's --version so a broken release artifact fails the
314452 # build instead of shipping. Every target builds on a matching-arch runner,
0 commit comments