Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion .github/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,38 @@ Documentation edits usually do release, on purpose: `make skills` copies the doc
installed skill stays in step with the docs. A homepage or styling change touches only `website/`
and does not.

## One-time setup

A repository administrator does these once, in a browser:

1. **Release GitHub App.** Install the organization's delivery GitHub App on this repository,
with contents, pull requests, issues and actions write access. In Settings > Secrets and
variables > Actions, add the variable `RELEASE_APP_ID` (the App ID) and the secret
`RELEASE_APP_PRIVATE_KEY` (the full contents of a private key generated under the app's
Credentials). Release Please opens release pull requests and dispatches `publish.yml` with
this app's token, because events from the default `GITHUB_TOKEN` do not start workflows.
Without either setting, the Release Please run fails at its first step and names the missing
one.
2. **PyPI trusted publishers.** For both `sqlclean` and `sqlclean-cli`, add a GitHub publisher
with owner `sqlstack-io`, repository `sqlclean`, workflow `publish.yml` and environment
`pypi`. GitHub creates the `pypi` environment the first time `publish.yml` uses it.
3. **Cloudflare Pages.** The `sqlclean` Pages project builds `website/` from this repository.
Add `sqlclean.io` and `www.sqlclean.io` under its Custom domains.

Automation tokens, such as a coding agent's GitHub App token or `GITHUB_TOKEN`, usually cannot
read or write Actions variables, secrets or app installations, and a private key can only be
downloaded when it is generated. These steps need a person.

## Tooling versions

`uv.lock` is only reproducible with one uv release line: a newer uv rewrites the file's format,
and the release metadata check then rejects the release pull request. Workflows pin uv 0.12.9
and `[tool.uv] required-version` in `pyproject.toml` keeps local uv on 0.12, so update both
together. `make release-rehearsal` (the Release rehearsal job in Verify) applies Release Please's
version edits on a scratch worktree, refreshes the lockfiles the same way the release workflow
does, and runs the release metadata check, so a mismatch fails a pull request rather than a
release.

## Verify

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

A manual or release dispatch has no pull request diff, so it runs everything.
37 changes: 37 additions & 0 deletions .github/scripts/simulate_release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# Rehearse a release on a scratch worktree: apply Release Please's version edits, refresh the
# lockfiles the way release-please.yml does, then run the strict release metadata check.
# This catches lockfile drift, such as a different uv rewriting uv.lock, before a real release.
set -euo pipefail

repo="$(git rev-parse --show-toplevel)"
current="$(jq -r '."."' "${repo}/.release-please-manifest.json")"
IFS=. read -r major minor _ <<< "${current}"
next="${major}.$((minor + 1)).0"
scratch="$(mktemp -d)"
trap 'git -C "${repo}" worktree remove --force "${scratch}" >/dev/null 2>&1 || true' EXIT

git -C "${repo}" worktree add --detach --quiet "${scratch}" HEAD
cd "${scratch}"
# Release Please rewrites the version on every line marked x-release-please-version.
for file in pyproject.toml Cargo.toml packages/sqlclean-cli/pyproject.toml; do
sed -i "/x-release-please-version/s/${current//./\\.}/${next}/" "${file}"
done
# The Python release strategy also rewrites [project] version, which carries no marker.
sed -i "0,/^version = \"${current//./\\.}\"/s//version = \"${next}\"/" pyproject.toml
jq --arg next "${next}" '."." = $next' .release-please-manifest.json > manifest.tmp
mv manifest.tmp .release-please-manifest.json
# Release Please adds the new section under the title and keeps the earlier history.
{
printf '# Changelog\n\n## [%s](https://example.com/compare/v%s...v%s) (2000-01-01)\n\n' \
"${next}" "${current}" "${next}"
tail -n +3 CHANGELOG.md
} > changelog.tmp
mv changelog.tmp CHANGELOG.md
# The same refresh as release-please.yml; cargo metadata rewrites Cargo.lock without compiling.
uv lock --quiet
cargo metadata --format-version 1 > /dev/null
git -c commit.gpgsign=false -c user.name=release-rehearsal -c user.email=release-rehearsal@example.com \
commit --quiet --all --message "chore(main): release ${next}"
python3 "${repo}/.github/scripts/release_metadata_check.py" --repo "${scratch}" --base HEAD~1 --head HEAD
echo "Release rehearsal ${current} -> ${next} passed with $(uv --version)"
2 changes: 2 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ jobs:
with:
toolchain: 1.95.0
- uses: astral-sh/setup-uv@v6
with:
version: "0.12.9"
# The sqlclean package is pure Python, so one wheel serves every platform.
- name: Build Python package sdist and wheel
run: uv build --sdist --wheel --out-dir dist
Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ jobs:
release-please:
runs-on: ubuntu-latest
steps:
- name: Check release credentials are configured
env:
HAS_APP_ID: ${{ vars.RELEASE_APP_ID != '' }}
HAS_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY != '' }}
run: |
missing=()
[[ "${HAS_APP_ID}" == true ]] || missing+=("the Actions variable RELEASE_APP_ID")
[[ "${HAS_PRIVATE_KEY}" == true ]] || missing+=("the Actions secret RELEASE_APP_PRIVATE_KEY")
for item in "${missing[@]}"; do
echo "::error title=Release credentials missing::Add ${item} in Settings > Secrets and variables > Actions. See .github/RELEASING.md, One-time setup."
done
[[ ${#missing[@]} -eq 0 ]]

- name: Create delivery token
id: delivery-token
uses: actions/create-github-app-token@v2
Expand Down Expand Up @@ -128,6 +141,8 @@ jobs:

- uses: astral-sh/setup-uv@v6
if: steps.release.outputs.prs_created == 'true'
with:
version: "0.12.9"

- uses: dtolnay/rust-toolchain@master
if: steps.release.outputs.prs_created == 'true'
Expand Down
28 changes: 27 additions & 1 deletion .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
with:
version: "0.12.9"
- run: python3 -m scripts.check_docs_skill
- run: uvx ruff@0.17.3 format --check .
- run: uvx ruff@0.17.3 check .
Expand All @@ -59,6 +61,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
- uses: astral-sh/setup-uv@v6
with:
version: "0.12.9"
enable-cache: true
- run: uv python install 3.12
- run: uv sync --locked --all-groups
Expand All @@ -78,6 +81,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
- uses: astral-sh/setup-uv@v6
with:
version: "0.12.9"
enable-cache: true
- run: uv python install 3.12
- run: uv sync --locked --all-groups
Expand Down Expand Up @@ -108,6 +112,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
- uses: astral-sh/setup-uv@v6
with:
version: "0.12.9"
enable-cache: true
- run: uv python install 3.12
- run: uv sync --locked --all-groups
Expand All @@ -128,6 +133,8 @@ jobs:
toolchain: 1.95.0
- uses: Swatinem/rust-cache@v2
- uses: astral-sh/setup-uv@v6
with:
version: "0.12.9"
- run: uv python install 3.12
- run: uv sync --locked --all-groups
- run: uv run sqlclean --version
Expand All @@ -144,6 +151,8 @@ jobs:
toolchain: 1.95.0
- uses: Swatinem/rust-cache@v2
- uses: astral-sh/setup-uv@v6
with:
version: "0.12.9"
- run: uv python install 3.12
- run: uv build --sdist --out-dir dist
- run: uvx maturin sdist --out ../../dist
Expand All @@ -157,11 +166,27 @@ jobs:
"${GITHUB_WORKSPACE}/.source-venv/bin/sqlclean" skills --target agents
test -s .agents/skills/sqlclean/references/docs/CONTENTS.md

release-rehearsal:
name: Release rehearsal
needs: [changes]
if: needs.changes.outputs.full == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
with:
version: "0.12.9"
- uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.95.0
- run: bash .github/scripts/simulate_release.sh

verify:
# The single check to require: it passes when every suite the change selected passed.
name: Verify
if: always()
needs: [changes, light, skills, checks, minimum-rust, windows, native-package-smoke, source-build]
needs:
[changes, light, skills, checks, minimum-rust, windows, native-package-smoke, source-build, release-rehearsal]
runs-on: ubuntu-latest
steps:
- env:
Expand All @@ -172,6 +197,7 @@ jobs:
SUITES: >-
${{ needs.skills.result }} ${{ needs.minimum-rust.result }} ${{ needs.windows.result }}
${{ needs.native-package-smoke.result }} ${{ needs.source-build.result }}
${{ needs.release-rehearsal.result }}
run: |
if [[ "${CHANGES}" != success || "${LIGHT}" != success ]]; then
echo "Change classification or the light checks did not pass" >&2
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
SHELL := /bin/bash

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

check:
uv run ruff format .
Expand All @@ -20,6 +20,9 @@ check-ci:
check-docs-skill:
uv run python -m scripts.check_docs_skill

release-rehearsal:
bash .github/scripts/simulate_release.sh

skills:
uv run python -m scripts.generate_docs_skill

Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ dev = [
]

[tool.uv]
# Keep in step with the uv pinned in .github/workflows: other uv versions rewrite uv.lock
# differently, which the release metadata check rejects.
required-version = ">=0.12.9,<0.13"
cache-keys = [
{ file = "pyproject.toml" },
]
Expand Down
Loading