Skip to content

Commit fc82267

Browse files
ampagentkvlonge
andcommitted
ci: rehearse releases in Verify and name missing release credentials
simulate_release.sh applies Release Please's version edits on a scratch worktree, refreshes the lockfiles like the release workflow and runs the release metadata check. required-version keeps local uv on the pinned line. Release Please now fails first with the missing variable or secret named. Amp-Thread-ID: https://ampcode.com/threads/T-01a18b8a-340f-717d-9ca7-6dcf564a065a Co-authored-by: Kevin Longe <kvlonge@gmail.com>
1 parent cc9e435 commit fc82267

6 files changed

Lines changed: 108 additions & 3 deletions

File tree

‎.github/RELEASING.md‎

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,38 @@ Documentation edits usually do release, on purpose: `make skills` copies the doc
2626
installed skill stays in step with the docs. A homepage or styling change touches only `website/`
2727
and does not.
2828

29+
## One-time setup
30+
31+
A repository administrator does these once, in a browser:
32+
33+
1. **Release GitHub App.** Install the organization's delivery GitHub App on this repository,
34+
with contents, pull requests, issues and actions write access. In Settings > Secrets and
35+
variables > Actions, add the variable `RELEASE_APP_ID` (the App ID) and the secret
36+
`RELEASE_APP_PRIVATE_KEY` (the full contents of a private key generated under the app's
37+
Credentials). Release Please opens release pull requests and dispatches `publish.yml` with
38+
this app's token, because events from the default `GITHUB_TOKEN` do not start workflows.
39+
Without either setting, the Release Please run fails at its first step and names the missing
40+
one.
41+
2. **PyPI trusted publishers.** For both `sqlclean` and `sqlclean-cli`, add a GitHub publisher
42+
with owner `sqlstack-io`, repository `sqlclean`, workflow `publish.yml` and environment
43+
`pypi`. GitHub creates the `pypi` environment the first time `publish.yml` uses it.
44+
3. **Cloudflare Pages.** The `sqlclean` Pages project builds `website/` from this repository.
45+
Add `sqlclean.io` and `www.sqlclean.io` under its Custom domains.
46+
47+
Automation tokens, such as a coding agent's GitHub App token or `GITHUB_TOKEN`, usually cannot
48+
read or write Actions variables, secrets or app installations, and a private key can only be
49+
downloaded when it is generated. These steps need a person.
50+
51+
## Tooling versions
52+
53+
`uv.lock` is only reproducible with one uv release line: a newer uv rewrites the file's format,
54+
and the release metadata check then rejects the release pull request. Workflows pin uv 0.12.9
55+
and `[tool.uv] required-version` in `pyproject.toml` keeps local uv on 0.12, so update both
56+
together. `make release-rehearsal` (the Release rehearsal job in Verify) applies Release Please's
57+
version edits on a scratch worktree, refreshes the lockfiles the same way the release workflow
58+
does, and runs the release metadata check, so a mismatch fails a pull request rather than a
59+
release.
60+
2961
## Verify
3062

3163
`verify.yml` classifies a pull request by the paths it changes and runs only the suites it needs.
@@ -35,6 +67,6 @@ The `Verify` job is the one check to require: it passes when every selected suit
3567
|--------|--------|
3668
| Only `website/`, `benchmarks/`, root `*.md`, `.github/demos/`, images, `website.yml` | docs skill check and ruff |
3769
| Also the bundled skill under `crates/sqlclean-cli/skill/` | plus a Linux build and the `sqlclean skills` tests |
38-
| Anything else, including workflows, Rust, Python, tests and lockfiles | everything: `make verify`, minimum Rust, Windows, three-platform package smoke tests and the source build |
70+
| Anything else, including workflows, Rust, Python, tests and lockfiles | everything: `make verify`, minimum Rust, Windows, three-platform package smoke tests, the source build and the release rehearsal |
3971

4072
A manual or release dispatch has no pull request diff, so it runs everything.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/usr/bin/env bash
2+
# Rehearse a release on a scratch worktree: apply Release Please's version edits, refresh the
3+
# lockfiles the way release-please.yml does, then run the strict release metadata check.
4+
# This catches lockfile drift, such as a different uv rewriting uv.lock, before a real release.
5+
set -euo pipefail
6+
7+
repo="$(git rev-parse --show-toplevel)"
8+
current="$(jq -r '."."' "${repo}/.release-please-manifest.json")"
9+
IFS=. read -r major minor _ <<< "${current}"
10+
next="${major}.$((minor + 1)).0"
11+
scratch="$(mktemp -d)"
12+
trap 'git -C "${repo}" worktree remove --force "${scratch}" >/dev/null 2>&1 || true' EXIT
13+
14+
git -C "${repo}" worktree add --detach --quiet "${scratch}" HEAD
15+
cd "${scratch}"
16+
# Release Please rewrites the version on every line marked x-release-please-version.
17+
for file in pyproject.toml Cargo.toml packages/sqlclean-cli/pyproject.toml; do
18+
sed -i "/x-release-please-version/s/${current//./\\.}/${next}/" "${file}"
19+
done
20+
# The Python release strategy also rewrites [project] version, which carries no marker.
21+
sed -i "0,/^version = \"${current//./\\.}\"/s//version = \"${next}\"/" pyproject.toml
22+
jq --arg next "${next}" '."." = $next' .release-please-manifest.json > manifest.tmp
23+
mv manifest.tmp .release-please-manifest.json
24+
# Release Please adds the new section under the title and keeps the earlier history.
25+
{
26+
printf '# Changelog\n\n## [%s](https://example.com/compare/v%s...v%s) (2000-01-01)\n\n' \
27+
"${next}" "${current}" "${next}"
28+
tail -n +3 CHANGELOG.md
29+
} > changelog.tmp
30+
mv changelog.tmp CHANGELOG.md
31+
# The same refresh as release-please.yml; cargo metadata rewrites Cargo.lock without compiling.
32+
uv lock --quiet
33+
cargo metadata --format-version 1 > /dev/null
34+
git -c commit.gpgsign=false -c user.name=release-rehearsal -c user.email=release-rehearsal@example.com \
35+
commit --quiet --all --message "chore(main): release ${next}"
36+
python3 "${repo}/.github/scripts/release_metadata_check.py" --repo "${scratch}" --base HEAD~1 --head HEAD
37+
echo "Release rehearsal ${current} -> ${next} passed with $(uv --version)"

‎.github/workflows/release-please.yml‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@ jobs:
2020
release-please:
2121
runs-on: ubuntu-latest
2222
steps:
23+
- name: Check release credentials are configured
24+
env:
25+
HAS_APP_ID: ${{ vars.RELEASE_APP_ID != '' }}
26+
HAS_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY != '' }}
27+
run: |
28+
missing=()
29+
[[ "${HAS_APP_ID}" == true ]] || missing+=("the Actions variable RELEASE_APP_ID")
30+
[[ "${HAS_PRIVATE_KEY}" == true ]] || missing+=("the Actions secret RELEASE_APP_PRIVATE_KEY")
31+
for item in "${missing[@]}"; do
32+
echo "::error title=Release credentials missing::Add ${item} in Settings > Secrets and variables > Actions. See .github/RELEASING.md, One-time setup."
33+
done
34+
[[ ${#missing[@]} -eq 0 ]]
35+
2336
- name: Create delivery token
2437
id: delivery-token
2538
uses: actions/create-github-app-token@v2

‎.github/workflows/verify.yml‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,27 @@ jobs:
166166
"${GITHUB_WORKSPACE}/.source-venv/bin/sqlclean" skills --target agents
167167
test -s .agents/skills/sqlclean/references/docs/CONTENTS.md
168168
169+
release-rehearsal:
170+
name: Release rehearsal
171+
needs: [changes]
172+
if: needs.changes.outputs.full == 'true'
173+
runs-on: ubuntu-latest
174+
steps:
175+
- uses: actions/checkout@v4
176+
- uses: astral-sh/setup-uv@v6
177+
with:
178+
version: "0.12.9"
179+
- uses: dtolnay/rust-toolchain@master
180+
with:
181+
toolchain: 1.95.0
182+
- run: bash .github/scripts/simulate_release.sh
183+
169184
verify:
170185
# The single check to require: it passes when every suite the change selected passed.
171186
name: Verify
172187
if: always()
173-
needs: [changes, light, skills, checks, minimum-rust, windows, native-package-smoke, source-build]
188+
needs:
189+
[changes, light, skills, checks, minimum-rust, windows, native-package-smoke, source-build, release-rehearsal]
174190
runs-on: ubuntu-latest
175191
steps:
176192
- env:
@@ -181,6 +197,7 @@ jobs:
181197
SUITES: >-
182198
${{ needs.skills.result }} ${{ needs.minimum-rust.result }} ${{ needs.windows.result }}
183199
${{ needs.native-package-smoke.result }} ${{ needs.source-build.result }}
200+
${{ needs.release-rehearsal.result }}
184201
run: |
185202
if [[ "${CHANGES}" != success || "${LIGHT}" != success ]]; then
186203
echo "Change classification or the light checks did not pass" >&2

‎Makefile‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
SHELL := /bin/bash
22

3-
.PHONY: check check-ci check-docs-skill check-rust develop-native skills test test-e2e test-integration test-rust test-unit verify
3+
.PHONY: check check-ci check-docs-skill check-rust develop-native release-rehearsal skills test test-e2e test-integration test-rust test-unit verify
44

55
check:
66
uv run ruff format .
@@ -20,6 +20,9 @@ check-ci:
2020
check-docs-skill:
2121
uv run python -m scripts.check_docs_skill
2222

23+
release-rehearsal:
24+
bash .github/scripts/simulate_release.sh
25+
2326
skills:
2427
uv run python -m scripts.generate_docs_skill
2528

‎pyproject.toml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ dev = [
5050
]
5151

5252
[tool.uv]
53+
# Keep in step with the uv pinned in .github/workflows: other uv versions rewrite uv.lock
54+
# differently, which the release metadata check rejects.
55+
required-version = ">=0.12.9,<0.13"
5356
cache-keys = [
5457
{ file = "pyproject.toml" },
5558
]

0 commit comments

Comments
 (0)