diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index 84856f8..428f194 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -6,8 +6,3 @@ paths: .github/workflows/release.yml: ignore: - 'property "workflow_(repository|sha)" is not defined in object type' - -# GitHub-hosted labels newer than actionlint 1.7.12, used by examples/aegiss. -self-hosted-runner: - labels: - - ubuntu-26.04 diff --git a/README.md b/README.md index f8d9539..b99454f 100644 --- a/README.md +++ b/README.md @@ -388,9 +388,6 @@ never make it the last thing in the description (see [Caveats](#caveats)): ## Checklist ``` -[`examples/aegiss/`](examples/aegiss) shows a complete migration of a repository that had -its own copies of these files. - ## Versioning and upgrading release-tools follows [the release process](#the-release-process) itself and uses [Semantic Versioning](https://semver.org) diff --git a/examples/aegiss/.github/workflows/backport.yml b/examples/aegiss/.github/workflows/backport.yml deleted file mode 100644 index 33ad8ec..0000000 --- a/examples/aegiss/.github/workflows/backport.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Backports: see https://github.com/Algorithmiq/release-tools#readme -name: Backport - -on: - pull_request_target: - types: [closed, labeled] - -permissions: - contents: read - -jobs: - backport: - uses: Algorithmiq/release-tools/.github/workflows/backport.yml@v1.0.0 - with: - client-id: ${{ vars.RELEASE_BOT_CLIENT_ID }} - secrets: - private-key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }} diff --git a/examples/aegiss/.github/workflows/deploy.yml b/examples/aegiss/.github/workflows/deploy.yml deleted file mode 100644 index 4985e74..0000000 --- a/examples/aegiss/.github/workflows/deploy.yml +++ /dev/null @@ -1,140 +0,0 @@ -name: Publish AEGISS to PyPI - -# Runs on the v* tags that release.yml pushes, as the release guide prescribes: build, -# draft the GitHub release with every file attached, publish to PyPI, then publish the -# GitHub release, which makes it immutable. Never on `release: published`. The release -# steps come from Algorithmiq/release-tools, at the version release.yml pins. -on: - workflow_dispatch: - push: - tags: - - "v*" - pull_request: - paths: - - .github/workflows/deploy.yml - - pyproject.toml - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - # a publish run must never be cut short half-way through - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -permissions: - contents: read - -env: - FORCE_COLOR: 3 - COLUMNS: 120 - -jobs: - build: - name: Build sdist and wheel - runs-on: ubuntu-26.04 - - steps: - - uses: actions/checkout@v7.0.1 - with: - fetch-depth: 0 # hatch-vcs reads the tags - - - name: Set up the build environment - uses: ./.github/actions/setup - - - name: Build distributions - run: just dist - - - name: Check the version matches the release tag - if: startsWith(github.ref, 'refs/tags/v') - run: | - expected="${GITHUB_REF_NAME#v}" - wheels=(dist/*.whl) - actual="$(basename "${wheels[0]}" | cut -d- -f2)" - if [[ "$actual" != "$expected" ]]; then - echo "::error::built version is '$actual', but the release tag is '$expected'" - exit 1 - fi - - - uses: actions/upload-artifact@v7.0.2 - with: - name: dist - path: dist/* - - # A separate artifact: the PyPI upload takes everything in dist/ - - name: Generate the SBOMs - run: just sbom - - - uses: actions/upload-artifact@v7.0.2 - with: - name: sbom - path: sbom/* - - draft_release: - # Immutable releases: files can only be attached while the release is a draft. - name: Create the draft GitHub release with the distributions and SBOMs - needs: build - if: startsWith(github.ref, 'refs/tags/v') - runs-on: ubuntu-26.04 - permissions: - contents: write - - steps: - - uses: actions/checkout@v7.0.1 - with: - fetch-depth: 0 # git-cliff needs the full history and the tags - - - uses: actions/download-artifact@v8.0.2 - with: - name: dist - path: dist - - - uses: actions/download-artifact@v8.0.2 - with: - name: sbom - path: sbom - - - name: Release notes from the changelog - id: notes - uses: Algorithmiq/release-tools/actions/release-notes@v1.0.0 - - - name: Create the draft release - uses: Algorithmiq/release-tools/actions/draft-release@v1.0.0 - with: - notes-file: ${{ steps.notes.outputs.path }} - files: | - dist/* - sbom/* - - upload_pypi: - name: Upload to PyPI - needs: draft_release - runs-on: ubuntu-26.04 - environment: pypi - permissions: - id-token: write - attestations: write - contents: read - - steps: - - uses: actions/download-artifact@v8.0.2 - with: - name: dist - path: dist - - - name: Generate artifact attestations - uses: actions/attest-build-provenance@v4.2.2 - with: - subject-path: "dist/*" - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@v1.14.3 - - publish_release: - # Publishing locks the tag and the files and creates GitHub's release attestation. - name: Publish the GitHub release - needs: upload_pypi - runs-on: ubuntu-26.04 - permissions: - contents: write - - steps: - - name: Publish the draft release - uses: Algorithmiq/release-tools/actions/publish-release@v1.0.0 diff --git a/examples/aegiss/.github/workflows/pr-checks.yml b/examples/aegiss/.github/workflows/pr-checks.yml deleted file mode 100644 index d31290a..0000000 --- a/examples/aegiss/.github/workflows/pr-checks.yml +++ /dev/null @@ -1,15 +0,0 @@ -# PR title and migration notes: see https://github.com/Algorithmiq/release-tools#readme -name: PR checks - -on: - pull_request_target: - types: [opened, edited, reopened, synchronize] - -permissions: - pull-requests: read - -jobs: - # Reports the checks "PR checks / PR title" and "PR checks / Migration notes". - pr-checks: - name: PR checks - uses: Algorithmiq/release-tools/.github/workflows/pr-checks.yml@v1.0.0 diff --git a/examples/aegiss/.github/workflows/release.yml b/examples/aegiss/.github/workflows/release.yml deleted file mode 100644 index 1e5d74e..0000000 --- a/examples/aegiss/.github/workflows/release.yml +++ /dev/null @@ -1,28 +0,0 @@ -# Releases: see https://github.com/Algorithmiq/release-tools#readme -name: Release - -on: - workflow_dispatch: - inputs: - bump: - description: "Kind of release to start" - type: choice - options: [minor, major] - default: minor - push: - branches: ["release/**"] - tags: ["v*"] - -permissions: - contents: read - -jobs: - release: - uses: Algorithmiq/release-tools/.github/workflows/release.yml@v1.0.0 - with: - client-id: ${{ vars.RELEASE_BOT_CLIENT_ID }} - bump: ${{ inputs.bump }} - # The version comes from the git tag (hatch-vcs). - version-files: "" - secrets: - private-key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }} diff --git a/examples/aegiss/README.md b/examples/aegiss/README.md deleted file mode 100644 index 8438ffe..0000000 --- a/examples/aegiss/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# Migrating AEGISS to release-tools - -AEGISS (branch `chore-release-workflows`) carries its own copies of the release -automation. These are the changes that make it use release-tools `v1.0.0` instead, with -the same behaviour. Apply them once release-tools `v1.0.0` exists; until then the stubs -point at a tag that doesn't. - -## Files - -Replace, with the files in this directory: - -- `.github/workflows/release.yml` → [`.github/workflows/release.yml`](.github/workflows/release.yml) - (stub; `version-files: ""` because the version comes from hatch-vcs) -- `.github/workflows/backport.yml` → [`.github/workflows/backport.yml`](.github/workflows/backport.yml) (stub) -- `.github/workflows/pr-checks.yml` → [`.github/workflows/pr-checks.yml`](.github/workflows/pr-checks.yml) (stub) -- `.github/workflows/deploy.yml` → [`.github/workflows/deploy.yml`](.github/workflows/deploy.yml) - (full file; [`deploy.yml.diff`](deploy.yml.diff) shows the change: the draft and - publish steps use the release-tools actions, and the draft job no longer needs the - build environment) - -Delete: - -- `.github/scripts/release.py` (and the then empty `.github/scripts/`) -- `cliff.toml` - -Patch (each applies with `git apply` from the root of AEGISS): - -- [`justfile.diff`](justfile.diff): `just changelog` uses the central `cliff.toml` at the - tag `release.yml` pins; `just release-notes` goes, the `release-notes` action replaces it. - This uses `--config-url`, which needs release-tools to be **public**. If it is internal - or private, use the `changelog` recipe of - [`../stubs/justfile-internal`](../stubs/justfile-internal) instead. -- [`docs.diff`](docs.diff): `README.md` (the "Releases" section: the departures that - release-tools now covers go, a paragraph points to release-tools), `AGENTS.md`, and - `pyproject.toml` (no more ruff exclusion for `.github/scripts`, no `cliff.toml` in the - sdist exclusions). - -Unchanged: `.github/PULL_REQUEST_TEMPLATE.md` (its Migration section already sits above -the Checklist), `CHANGELOG.md`, `docs.yml`, `prek.toml` (the stubs pass -`check-github-workflows`, the exact-pin hook and `check-workflow-commands`, which they -have no `run:` steps for), and `.github/dependabot.yml` (its `actions` group already covers -`Algorithmiq/release-tools`, so one PR moves all six references). - -## Settings - -- **Required checks.** The PR checks now report as `PR checks / PR title` and - `PR checks / Migration notes`. `pull_request_target` runs the workflow file of `main`, - so the migration PR itself still reports the old names `PR title` and - `Migration notes`; every PR after it reports the new ones. Replace the contexts in the - `main` and `release branches` rulesets right after merging the migration PR, or - require neither pair for that moment. (A ruleset that lists both pairs would block - every PR, since only one pair ever reports.) -- Install the release bot on `release-tools` and add `release-tools` to the - `--repos` lists of `RELEASE_BOT_CLIENT_ID` and `RELEASE_BOT_PRIVATE_KEY` (see the - [organisation setup](../../README.md#one-time-organisation-setup)); AEGISS itself - already has them. -- If release-tools is internal or private: its Actions **Access** setting must allow the - organisation's repositories. diff --git a/examples/aegiss/deploy.yml.diff b/examples/aegiss/deploy.yml.diff deleted file mode 100644 index 807d618..0000000 --- a/examples/aegiss/deploy.yml.diff +++ /dev/null @@ -1,58 +0,0 @@ ---- a/.github/workflows/deploy.yml -+++ b/.github/workflows/deploy.yml -@@ -2,7 +2,8 @@ - - # Runs on the v* tags that release.yml pushes, as the release guide prescribes: build, - # draft the GitHub release with every file attached, publish to PyPI, then publish the --# GitHub release, which makes it immutable. Never on `release: published`. -+# GitHub release, which makes it immutable. Never on `release: published`. The release -+# steps come from Algorithmiq/release-tools, at the version release.yml pins. - on: - workflow_dispatch: - push: -@@ -80,9 +81,6 @@ - with: - fetch-depth: 0 # git-cliff needs the full history and the tags - -- - name: Set up the build environment -- uses: ./.github/actions/setup -- - - uses: actions/download-artifact@v8.0.2 - with: - name: dist -@@ -94,17 +92,16 @@ - path: sbom - - - name: Release notes from the changelog -- run: just release-notes "$GITHUB_REF_NAME" > notes.md -+ id: notes -+ uses: Algorithmiq/release-tools/actions/release-notes@v1.0.0 - - - name: Create the draft release -- env: -- GH_TOKEN: ${{ github.token }} -- GH_REPO: ${{ github.repository }} -- TAG: ${{ github.ref_name }} -- run: | -- flags=(--draft --verify-tag --title "$TAG" --notes-file notes.md) -- if [[ "$TAG" == *rc* ]]; then flags+=(--prerelease); fi -- gh release create "$TAG" dist/* sbom/* "${flags[@]}" -+ uses: Algorithmiq/release-tools/actions/draft-release@v1.0.0 -+ with: -+ notes-file: ${{ steps.notes.outputs.path }} -+ files: | -+ dist/* -+ sbom/* - - upload_pypi: - name: Upload to PyPI -@@ -140,8 +137,4 @@ - - steps: - - name: Publish the draft release -- env: -- GH_TOKEN: ${{ github.token }} -- GH_REPO: ${{ github.repository }} -- TAG: ${{ github.ref_name }} -- run: gh release edit "$TAG" --draft=false -+ uses: Algorithmiq/release-tools/actions/publish-release@v1.0.0 diff --git a/examples/aegiss/docs.diff b/examples/aegiss/docs.diff deleted file mode 100644 index 62640a5..0000000 --- a/examples/aegiss/docs.diff +++ /dev/null @@ -1,71 +0,0 @@ ---- a/README.md -+++ b/README.md -@@ -130,19 +130,21 @@ - and publishes to PyPI. Only the newest release is supported. The - [contributing guide](docs/contributing.md#releases) describes the process. - -+The release automation is shared with the other Algorithmiq repositories through -+[Algorithmiq/release-tools](https://github.com/Algorithmiq/release-tools): `release.yml`, -+`backport.yml` and `pr-checks.yml` are short stubs calling its reusable workflows, -+`deploy.yml` uses its release actions, and the changelog comes from its `cliff.toml`, all -+pinned to the same release-tools tag, which Dependabot updates. -+ - Where this repository departs from the guide: - --- PR titles carry a [gitmoji](https://gitmoji.dev) after the type, and `build` and -- `style` are allowed types besides the guide's. `cliff.toml` strips the gitmoji from the -- changelog and leaves `build` and `style` changes out of it. --- The version comes from the git tag (hatch-vcs, so `release.yml` has no -- `VERSION_FILES`) with the `release-branch-semver` scheme, so that untagged commits on -+- PR titles carry a [gitmoji](https://gitmoji.dev) after the type; the shared `cliff.toml` -+ strips it from the changelog. -+- The version comes from the git tag (hatch-vcs, so `release.yml` passes no -+ `version-files`) with the `release-branch-semver` scheme, so that untagged commits on - `main` carry the next minor version instead of the last release candidate. - - The documentation is versioned: one version per minor release, `latest` for the newest - one and `dev` for `main`, deployed by `docs.yml` with mike. --- To pass this repository's hooks, the actions in the shared workflows are pinned to exact -- tags, `.github/scripts/release.py` is excluded from ruff, and the publish workflow runs -- git-cliff through `just release-notes`. - - --- - ---- a/AGENTS.md -+++ b/AGENTS.md -@@ -40,10 +40,10 @@ - (checked by `pr-checks.yml`). Deprecate before removing: `DeprecationWarning` for at least - one minor release (policy in `docs/contributing.md`). - - Releases follow Algorithmiq's branching and release guide; the repo's departures from it are -- listed in `README.md`. `CHANGELOG.md` is generated by git-cliff (`cliff.toml`), never edited -- by hand; `release.yml` tags releases, `deploy.yml` publishes them. `.github/scripts/release.py` -- and the `release`, `backport` and `pr-checks` workflows are shared across Algorithmiq -- repositories: keep them in sync with the guide rather than adapting them. -+ listed in `README.md`. `CHANGELOG.md` is generated by git-cliff with the `cliff.toml` of -+ Algorithmiq/release-tools, never edited by hand; `release.yml` tags releases, `deploy.yml` -+ publishes them. The `release`, `backport` and `pr-checks` workflows are stubs calling -+ Algorithmiq/release-tools, pinned by tag: change the shared logic there, not here. - - Agent commits carry `Assisted-by: :` and no `Co-authored-by`. - - Follow `.github/PULL_REQUEST_TEMPLATE.md`; prefix agent-authored descriptions, issues and - comments with `:robot: _AI text below_ :robot:`. ---- a/pyproject.toml -+++ b/pyproject.toml -@@ -99,7 +99,7 @@ - [tool.hatch.build.targets.sdist] - exclude = [ - ".claude", ".devcontainer", ".envrc", ".github", ".lychee.toml", ".vscode", "AGENTS.md", -- "CLAUDE.md", "cliff.toml", "codecov.yml", "cspell.json", "flake.lock", "flake.nix", "justfile", "local", -+ "CLAUDE.md", "codecov.yml", "cspell.json", "flake.lock", "flake.nix", "justfile", "local", - "nix", "prek.toml", "scripts", "sonar-project.properties", "tools", - ] - -@@ -160,9 +160,7 @@ - line-length = 88 - # `docs/tutorials` holds the tutorial notebooks; `scripts/` holds local, unversioned - # developer scratch scripts. Both are unlinted — but `docs/scripts/` is checked. --# `.github/scripts/` is the release helper shared by every Algorithmiq repository; it --# stays a copy of the release guide's version rather than following our lint rules. --exclude = ["docs/tutorials", "scripts", ".github/scripts"] -+exclude = ["docs/tutorials", "scripts"] - - [tool.ruff.lint] - select = ["ALL"] diff --git a/examples/aegiss/justfile.diff b/examples/aegiss/justfile.diff deleted file mode 100644 index f5887a1..0000000 --- a/examples/aegiss/justfile.diff +++ /dev/null @@ -1,29 +0,0 @@ ---- a/justfile -+++ b/justfile -@@ -89,20 +89,15 @@ - uv run --no-sync pytest -o log_cli=false --cov --cov-branch --cov-report=xml:coverage.xml \ - --cov-report=term-missing - --# Preview the changelog entry of the next release, from the PR titles merged since the last one. -+# Preview the changelog entry of the next release, from the PR titles merged since the last one, -+# with the central cliff.toml of Algorithmiq/release-tools at the version release.yml pins. - changelog: -- uvx --from {{ git_cliff }} git-cliff --unreleased -- --# Release notes for the checked-out tag TAG: its changelog entry, or the changes so far for a release candidate. --release-notes TAG: - #!/usr/bin/env bash - set -euo pipefail -- # cliff.toml only counts final tags as versions, so a candidate's changes are still unreleased -- if [[ "$1" == *rc* ]]; then -- uvx --from {{ git_cliff }} git-cliff --unreleased --tag "$1" --strip header -- else -- uvx --from {{ git_cliff }} git-cliff --current --strip header -- fi -+ ref=$(sed -nE 's|.*Algorithmiq/release-tools/.github/workflows/release.yml@([^[:space:]]+).*|\1|p' \ -+ .github/workflows/release.yml) -+ uvx --from {{ git_cliff }} git-cliff --unreleased \ -+ --config-url "https://raw.githubusercontent.com/Algorithmiq/release-tools/$ref/cliff.toml" - - # Build the sdist and wheel into dist/. - dist: diff --git a/tests/test_examples.py b/tests/test_examples.py index f3c85ad..fbe9e48 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -15,8 +15,8 @@ """Tests that the stubs in examples/, README.md and the dogfooding workflows agree. The consumer stubs in examples/stubs are the source: README.md shows them verbatim, -.github/workflows/self-*.yml are the same files calling the local reusable workflows, -and examples/aegiss uses them unchanged apart from comments. +and .github/workflows/self-*.yml are the same files calling the local reusable +workflows. """ from __future__ import annotations @@ -33,7 +33,6 @@ STUBS = ROOT / "examples" / "stubs" WORKFLOWS = ROOT / ".github" / "workflows" -AEGISS = ROOT / "examples" / "aegiss" REUSABLE = ("release", "backport", "pr-checks") RELEASE_TOOLS_REF = re.compile(r"Algorithmiq/release-tools/([\w./-]+)@([\w.-]+)") GIT_CLIFF_PIN = re.compile(r"git-cliff==([\d.]+)") @@ -65,16 +64,6 @@ def test_dogfooding_stub_is_the_consumer_stub(name: str) -> None: assert (WORKFLOWS / f"self-{name}.yml").read_text(encoding="utf-8") == local -@pytest.mark.parametrize("name", REUSABLE) -def test_aegiss_stub_is_the_consumer_stub(name: str) -> None: - def code(path: Path) -> list[str]: - lines = path.read_text(encoding="utf-8").splitlines() - return [line for line in lines if not line.lstrip().startswith("#")] - - stub = STUBS / f"{name}.yml" - assert code(AEGISS / ".github" / "workflows" / f"{name}.yml") == code(stub) - - @pytest.mark.parametrize( "name", [