Open the version bump as a pull request
The bump job has never once worked. Its first step died in two seconds on
`set: Illegal option -o pipefail` -- it is the only job here that runs in a
container, and a container job is handed `sh -e {0}` rather than the bash the
runner gives its own jobs. Dash has no `pipefail`, and no `10#` either, so the
arithmetic on the line after would have gone the same way. It now asks for
bash, which node:22 carries.
That would have got the job as far as its last step, which pushed straight to
main. Nothing had ever exercised that, and it needs the token to be allowed
past whatever protects the branch -- hence the second token, VERSION_BUMP_TOKEN,
standing by for where it is not. unsupervised-scheduler has been bumping its
version on every release for a while by pushing a branch and opening a pull
request with the ordinary task token, so that is what this does now. The second
token is no longer needed, and neither is `[skip ci]`: main is never pushed, so
there is no build of the just-published image to suppress. The pull request
puts the changed package.json through a build before it lands.
scheduler builds the request body with jq because its CI image carries jq. This
one is node:22, where node is the thing that certainly is there.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XBT25ZDzm453A8XViSRqRB
This commit is contained in:
@@ -157,19 +157,26 @@ jobs:
|
|||||||
# it is a candidate for, so there is nothing yet to move past.
|
# it is a candidate for, so there is nothing yet to move past.
|
||||||
if: github.ref_type == 'tag' && !contains(github.ref_name, '-')
|
if: github.ref_type == 'tag' && !contains(github.ref_name, '-')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
# npm does the bump, so this one job wants node. A job in a container is
|
||||||
|
# given `sh -e {0}` as its shell rather than the bash the runner's own jobs
|
||||||
|
# get, and dash has neither `pipefail` nor the `10#` below — which failed
|
||||||
|
# the first line of the first step the one time this ran. node:22 is Debian
|
||||||
|
# and carries bash, so asking for it keeps these scripts the same as the
|
||||||
|
# ones in the job above.
|
||||||
container:
|
container:
|
||||||
image: node:22
|
image: node:22
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
steps:
|
steps:
|
||||||
# The tag names a commit in main's history, but the bump belongs on the
|
# The tag names a commit in main's history, but the bump belongs on the
|
||||||
# branch, so this checks out main rather than the tag.
|
# branch, so this checks out main rather than the tag. The full history
|
||||||
|
# because a shallow clone cannot reliably push a branch back.
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
# The task token can push only if the instance allows Actions to
|
fetch-depth: 0
|
||||||
# write to the repository. Where it does not, set VERSION_BUMP_TOKEN
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
# to a personal access token with write access and it is used
|
|
||||||
# instead.
|
|
||||||
token: ${{ secrets.VERSION_BUMP_TOKEN || secrets.GITEA_TOKEN }}
|
|
||||||
|
|
||||||
- name: Work out the next patch version
|
- name: Work out the next patch version
|
||||||
id: next
|
id: next
|
||||||
@@ -211,30 +218,59 @@ jobs:
|
|||||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||||
echo "package.json ${current} -> ${NEXT}"
|
echo "package.json ${current} -> ${NEXT}"
|
||||||
|
|
||||||
- name: Commit it to main
|
# The bump arrives as a pull request rather than as a commit straight to
|
||||||
|
# main. Pushing a branch asks nothing of the task token beyond ordinary
|
||||||
|
# write access, so nothing here depends on being allowed past whatever
|
||||||
|
# protects main; and the pull request puts the changed package.json
|
||||||
|
# through the build before it lands. Since main is never pushed, the
|
||||||
|
# `[skip ci]` that would otherwise be needed to stop this rebuilding the
|
||||||
|
# image just published is not.
|
||||||
|
- name: Open a pull request for it
|
||||||
if: steps.bump.outputs.changed == 'true'
|
if: steps.bump.outputs.changed == 'true'
|
||||||
env:
|
env:
|
||||||
NEXT: ${{ steps.next.outputs.next }}
|
NEXT: ${{ steps.next.outputs.next }}
|
||||||
|
RELEASED: ${{ github.ref_name }}
|
||||||
|
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
branch="release/bump-${NEXT}"
|
||||||
|
|
||||||
# A name that is not a person, and a reserved address that can never
|
# A name that is not a person, and a reserved address that can never
|
||||||
# resolve to one. Nothing here names the instance it runs on.
|
# resolve to one. Nothing here names the instance it runs on.
|
||||||
git config user.name 'Release bot'
|
git config user.name 'Release bot'
|
||||||
git config user.email '[email protected]'
|
git config user.email '[email protected]'
|
||||||
|
|
||||||
|
git checkout -b "${branch}"
|
||||||
git add package.json package-lock.json
|
git add package.json package-lock.json
|
||||||
# `[skip ci]` because this commit is a number and nothing else:
|
git commit -m "Set the working version to ${NEXT}"
|
||||||
# without it the push to main starts another build of the very image
|
git push origin "${branch}"
|
||||||
# that was just published.
|
|
||||||
git commit -m "Set the working version to ${NEXT} [skip ci]"
|
|
||||||
|
|
||||||
if ! git push origin HEAD:main; then
|
# node rather than jq to build the request body: jq is not in this
|
||||||
echo >&2
|
# image, and node is the one thing that certainly is.
|
||||||
echo "Could not push the version bump to main. Either the Actions" >&2
|
payload="$(BRANCH="${branch}" node -e 'process.stdout.write(JSON.stringify({
|
||||||
echo "token has no write access to this repository, or main is" >&2
|
head: process.env.BRANCH,
|
||||||
echo "protected against direct pushes. Set VERSION_BUMP_TOKEN to a" >&2
|
base: "main",
|
||||||
echo "token that may push to main, or allow that token past the" >&2
|
title: `Set the working version to ${process.env.NEXT}`,
|
||||||
echo "branch protection." >&2
|
body: `${process.env.RELEASED} has shipped, so the tree was left on a version that is published and immutable. This moves it on to ${process.env.NEXT}, which is deliberately not a version that exists.`,
|
||||||
exit 1
|
}))')"
|
||||||
fi
|
|
||||||
|
api="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
||||||
|
response="$(mktemp)"
|
||||||
|
code="$(curl -sS -o "${response}" -w '%{http_code}' \
|
||||||
|
-X POST "${api}/pulls" \
|
||||||
|
-H "Authorization: token ${TOKEN}" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d "${payload}")"
|
||||||
|
|
||||||
|
case "${code}" in
|
||||||
|
201) echo "Opened ${branch} against main." ;;
|
||||||
|
# A release re-run that got this far: the branch and its pull
|
||||||
|
# request are already there, which is the state we wanted anyway.
|
||||||
|
409) echo "A pull request for ${branch} is already open." ;;
|
||||||
|
*)
|
||||||
|
echo "Could not open the pull request (HTTP ${code}):" >&2
|
||||||
|
cat "${response}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|||||||
@@ -210,16 +210,22 @@ The registry comes from the `REGISTRY` repository variable, the image name from
|
|||||||
`IMAGE_NAME` or the repository name, and credentials from `REGISTRY_USER` and the
|
`IMAGE_NAME` or the repository name, and credentials from `REGISTRY_USER` and the
|
||||||
`REGISTRY_TOKEN` secret. Nothing about any particular deployment is committed here.
|
`REGISTRY_TOKEN` secret. Nothing about any particular deployment is committed here.
|
||||||
|
|
||||||
A release also moves `package.json` on to the next patch version, committed to main by
|
A release also moves `package.json` on to the next patch version, opened as a pull
|
||||||
the `bump` job — so the number in the tree is never one that has already shipped and
|
request by the `bump` job — so the number in the tree is never one that has already
|
||||||
been made immutable. It lives in `publish.yml` rather than a workflow of its own so it
|
shipped and been made immutable. It lives in `publish.yml` rather than a workflow of its
|
||||||
can say `needs: build`: a version that failed to publish has not been released, and
|
own so it can say `needs: build`: a version that failed to publish has not been
|
||||||
bumping past it would claim otherwise. Prereleases are skipped, being candidates for a
|
released, and bumping past it would claim otherwise. Prereleases are skipped, being
|
||||||
version that has not shipped. The bump goes through `npm version` rather than an edit in
|
candidates for a version that has not shipped. The bump goes through `npm version`
|
||||||
place, because the version is in the lockfile too, in more than one place, and the two
|
rather than an edit in place, because the version is in the lockfile too, in more than
|
||||||
have to agree. The commit carries `[skip ci]`, or pushing it would rebuild the image
|
one place, and the two have to agree.
|
||||||
that was just published. Pushing to main needs a token with write access —
|
|
||||||
`VERSION_BUMP_TOKEN` overrides the task token where that one cannot.
|
It arrives as a pull request rather than as a commit straight to main: pushing a branch
|
||||||
|
asks nothing of the task token beyond ordinary write access, so it does not matter what
|
||||||
|
protects main, and the changed `package.json` goes through a build before it lands.
|
||||||
|
Nothing pushes to main, so no `[skip ci]` is needed to stop the bump rebuilding the
|
||||||
|
image just published. The job is the only one that runs in a container (`node:22`, for
|
||||||
|
npm), and a job in a container is handed `sh`, not bash — hence the explicit
|
||||||
|
`shell: bash`, without which `set -o pipefail` fails the first line of the first step.
|
||||||
|
|
||||||
Two things any deployment has to get right, both learned the hard way:
|
Two things any deployment has to get right, both learned the hard way:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user