From 84ddb20c904f6697d23370fb58899cb1a5569e4e Mon Sep 17 00:00:00 2001 From: Teakowa <27560638+Teakowa@users.noreply.github.com> Date: Tue, 18 Aug 2026 17:31:29 +0800 Subject: [PATCH] fix(release): replace release-plz with release-please Fixes #169 --- .github/workflows/release-please.yml | 88 ++++++++++++++++ .github/workflows/release-plz.yml | 148 --------------------------- .github/workflows/release.yml | 5 +- .release-please-manifest.json | 3 + CHANGELOG.md | 3 + dist/README.md | 2 +- docs/release.md | 29 +++--- release-please-config.json | 13 +++ release-plz.toml | 40 -------- scripts/sync-release-version.py | 68 ++++++++++++ version.txt | 1 + 11 files changed, 195 insertions(+), 205 deletions(-) create mode 100644 .github/workflows/release-please.yml delete mode 100644 .github/workflows/release-plz.yml create mode 100644 .release-please-manifest.json create mode 100644 CHANGELOG.md create mode 100644 release-please-config.json delete mode 100644 release-plz.toml create mode 100644 scripts/sync-release-version.py create mode 100644 version.txt diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..efd3288 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,88 @@ +name: Release-please + +on: + push: + branches: [main] + +env: + CARGO_TERM_COLOR: always + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + release: + name: Maintain Wright release + runs-on: ubuntu-latest + concurrency: + group: release-please-${{ github.ref }} + cancel-in-progress: false + outputs: + release_created: ${{ steps.release.outputs.release_created }} + release_tag: ${{ steps.release.outputs.tag_name }} + release_commit: ${{ steps.release.outputs.sha }} + steps: + - name: Check out repository + uses: actions/checkout@v7 + with: + fetch-depth: 0 + persist-credentials: true + token: ${{ github.token }} + + - name: Run release-please + id: release + uses: googleapis/release-please-action@v4 + with: + token: ${{ github.token }} + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + + - name: Synchronize checked-in release metadata + env: + GH_TOKEN: ${{ github.token }} + PR: ${{ steps.release.outputs.pr }} + run: | + set -euo pipefail + if [[ -z "${PR}" ]]; then + echo 'No release-please PR requires metadata synchronization.' + exit 0 + fi + pr_number="$(jq -r '.number // empty' <<<"${PR}")" + if [[ -z "${pr_number}" ]]; then + echo 'release-please returned a PR without a number.' >&2 + exit 1 + fi + + gh pr checkout "${pr_number}" + version="$(tr -d '[:space:]' < version.txt)" + python3 scripts/sync-release-version.py --version "${version}" + python3 scripts/update-dist-manifests.py --version "${version}" + python3 scripts/verify-dist.py + + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add Cargo.toml Cargo.lock dist + git diff --cached --check + if git diff --cached --quiet; then + echo "release metadata already synchronized for ${version}" + exit 0 + fi + git commit -m 'chore(release): synchronize Wright product metadata' + git push + + release-artifacts: + name: Build and publish release artifacts + needs: release + if: needs.release.outputs.release_created == 'true' + uses: ./.github/workflows/release.yml + with: + ref: ${{ needs.release.outputs.release_commit }} + tag: ${{ needs.release.outputs.release_tag }} + commit: ${{ needs.release.outputs.release_commit }} + secrets: inherit + permissions: + contents: write + id-token: write + packages: write diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml deleted file mode 100644 index 65ce3b3..0000000 --- a/.github/workflows/release-plz.yml +++ /dev/null @@ -1,148 +0,0 @@ -name: Release-plz - -on: - push: - branches: [main] - pull_request: - types: [closed] - -env: - CARGO_TERM_COLOR: always - -jobs: - release-pr: - name: Maintain release PR - if: github.event_name == 'push' - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - concurrency: - group: release-plz-pr-${{ github.ref }} - cancel-in-progress: false - steps: - - name: Checkout repository - uses: actions/checkout@v7 - with: - fetch-depth: 0 - persist-credentials: true - token: ${{ github.token }} - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - - - name: Run release-plz release-pr - id: release-plz - uses: >- - MarcoIeni/release-plz-action@476794ede164c5137bfc3a1dc6ed3675275690f9 - with: - command: release-pr - version: "0.3.160" - env: - # Use the per-run Actions token; this is not secrets.GH_TOKEN. - GITHUB_TOKEN: ${{ github.token }} - - - name: Synchronize checked-in release metadata - env: - GH_TOKEN: ${{ github.token }} - PR: ${{ steps.release-plz.outputs.pr }} - run: | - set -euo pipefail - pr_number="$(jq -r '.number // empty' <<<"${PR}")" - if [[ -z "${pr_number}" ]]; then - echo 'release-plz did not create or update a release PR.' - exit 0 - fi - - gh pr checkout "${pr_number}" - version="$(cargo metadata --locked --no-deps --format-version 1 | \ - python3 -c 'import json, sys; print(next(p["version"] for p in json.load(sys.stdin)["packages"] if p["name"] == "wright-cli"))')" - python3 scripts/update-dist-manifests.py --version "${version}" - python3 scripts/verify-dist.py - - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git add Cargo.toml Cargo.lock dist - git diff --cached --check - if git diff --cached --quiet; then - echo "release metadata already synchronized for ${version}" - exit 0 - fi - git commit -m "chore(release): synchronize distribution metadata" - git push - - release: - name: Publish staged release - if: >- - github.event_name == 'pull_request' && - github.event.pull_request.merged == true && - github.event.pull_request.base.ref == 'main' && - startsWith(github.event.pull_request.head.ref, 'release-plz-') && - github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest - environment: release - permissions: - contents: write - pull-requests: read - concurrency: - group: release-plz-release-${{ github.event.pull_request.base.ref }} - cancel-in-progress: false - outputs: - releases_created: ${{ steps.release-plz.outputs.releases_created }} - release_tag: ${{ steps.release-identity.outputs.tag }} - steps: - - name: Checkout merged release - uses: actions/checkout@v7 - with: - ref: ${{ github.event.pull_request.merge_commit_sha }} - fetch-depth: 0 - persist-credentials: false - token: ${{ github.token }} - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - - - name: Run release-plz release - id: release-plz - uses: >- - MarcoIeni/release-plz-action@476794ede164c5137bfc3a1dc6ed3675275690f - with: - command: release - version: "0.3.160" - env: - # Use the per-run Actions token; this is not secrets.GH_TOKEN. - GITHUB_TOKEN: ${{ github.token }} - - - name: Export release identity - id: release-identity - if: steps.release-plz.outputs.releases_created == 'true' - env: - RELEASES: ${{ steps.release-plz.outputs.releases }} - run: | - set -euo pipefail - tag="$(jq -r '.[] | select(.package_name == "wright-cli") | .tag' \ - <<<"${RELEASES}")" - [[ -n "${tag}" ]] || { - echo 'release-plz did not return the wright-cli release tag.' >&2 - exit 1 - } - echo "tag=${tag}" >>"${GITHUB_OUTPUT}" - - release-artifacts: - name: Build and publish release artifacts - needs: release - if: needs.release.outputs.releases_created == 'true' - uses: ./.github/workflows/release.yml - with: - ref: ${{ github.event.pull_request.merge_commit_sha }} - tag: ${{ needs.release.outputs.release_tag }} - commit: ${{ github.event.pull_request.merge_commit_sha }} - secrets: inherit - permissions: - contents: write - id-token: write - packages: write diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cac25a3..aa57f5c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,7 @@ on: required: true type: string tag: - description: GitHub Release tag created by release-plz + description: GitHub Release tag created by release-please required: true type: string commit: @@ -222,7 +222,7 @@ jobs: (cd release && sha256sum wright-*.tar.gz wright-*.zip > SHA256SUMS) ls -l release/ - - name: Wait for the release-plz draft Release + - name: Wait for the release-please draft Release env: GH_TOKEN: ${{ github.token }} run: | @@ -519,6 +519,7 @@ jobs: - publish-npm - publish-github-packages runs-on: ubuntu-latest + environment: release steps: - name: Publish the completed draft Release env: diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..69535c6 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.2.4" +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..006f1ca --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# Changelog + +All notable changes to Wright will be documented in this file. diff --git a/dist/README.md b/dist/README.md index 6ca4a64..095e296 100644 --- a/dist/README.md +++ b/dist/README.md @@ -38,7 +38,7 @@ install script stops covering the declared target matrix. ## Publication process -The release-plz workflow creates one draft GitHub Release and calls the +The release-please workflow creates one draft GitHub Release and calls the reusable release workflow (`release.yml`) with its tag and merge commit. The workflow keeps the Release draft until native and package-manager distribution stages complete: diff --git a/docs/release.md b/docs/release.md index 8add259..d50ad99 100644 --- a/docs/release.md +++ b/docs/release.md @@ -14,7 +14,7 @@ public distribution commit, build timestamp, and the runtime-dependency claim (`"requires": { "node": false, "overpy": false }`). -This is the local staging path and the validation suite behind the release-plz +This is the local staging path and the validation suite behind the release-please workflow; the reusable GitHub workflow publishes the per-platform archives, and this script verifies and packages the host platform. @@ -46,18 +46,19 @@ envelope carries `wright.version` + `wright.contract`. The release archive's ## Public distribution contract -A merge to `main` drives `.github/workflows/release-plz.yml`. The release +A merge to `main` drives `.github/workflows/release-please.yml`. The release workflow is the single product release path: -1. `release-plz release-pr` maintains a Release PR for the single product - package `wright-cli`. `release-plz.toml` uses `git_only = true`, so no - workspace crate is published to crates.io. +1. `release-please-action` maintains one root Release PR for the Wright + product. `release-please-config.json` uses the `simple` release type, with + `version.txt` and `CHANGELOG.md` as its product-level version and changelog + files. No workspace crate is published to crates.io. 2. The Release PR updates the shared workspace version, `Cargo.lock`, and the checked-in `dist/` metadata. All workspace crate changes are included in the product changelog decision. -3. Merging that Release PR runs `release-plz release`, which creates exactly - one `vX.Y.Z` tag and a draft GitHub Release. The job passes the release-plz - tag and merge commit to the reusable `release.yml` workflow. +3. Merging that Release PR runs release-please, which creates exactly one + `vX.Y.Z` tag and a draft GitHub Release. The job passes the release-please + tag and release commit to the reusable `release.yml` workflow. 4. The reusable workflow verifies the tag/revision and version identity, runs `scripts/release.sh` and `scripts/verify-dist.py`, builds and smoke-tests the native matrix, attaches archives/checksums/manifests/npm tarballs to the @@ -72,12 +73,12 @@ Release/tag available for a retry; it does not create a new product version. The Release PR is the release decision point. Maintainers do not enter a version, edit version files, create a tag, or dispatch a second workflow for the normal case. Review and merge the automatically maintained Release PR; -release-plz derives the next version from the shared workspace history and -creates the one product tag/release. +release-please derives the next version from Conventional Commits and creates +the one product tag/release. The `release.yml` workflow is reusable and is intentionally not triggered by a tag or Release event. The default Actions token cannot start a new workflow -from a tag push; passing release-plz outputs through a job dependency keeps +from a tag push; passing release-please outputs through a job dependency keeps the release in one run. ### Target matrix and artifact naming @@ -137,11 +138,11 @@ before attaching them to the draft Release. ### Repository configuration Enable Actions to create and approve pull requests, and grant the default -repository `GITHUB_TOKEN` `contents: write` and `pull-requests: write` for the -release-plz workflow. The reusable distribution workflow also needs +repository `GITHUB_TOKEN` `contents: write`, `issues: write`, and +`pull-requests: write` for the release-please workflow. The reusable distribution workflow also needs `id-token: write` for npm provenance and `packages: write` for GitHub Packages. Create a protected `release` environment if publication approval is required; -the release job is the only job that uses it. +the final `publish-release` job is the only job that uses it. Configure these optional/required environment secrets: diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..ab77b5e --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "packages": { + ".": { + "release-type": "simple", + "version-file": "version.txt", + "changelog-path": "CHANGELOG.md", + "include-component-in-tag": false, + "draft": true, + "force-tag-creation": true + } + } +} diff --git a/release-plz.toml b/release-plz.toml deleted file mode 100644 index c1b8682..0000000 --- a/release-plz.toml +++ /dev/null @@ -1,40 +0,0 @@ -[workspace] -changelog_update = false -dependencies_update = false -git_only = true -git_release_enable = false -git_release_draft = true -git_release_latest = false -git_tag_enable = false -git_tag_name = "v{{ version }}" -pr_labels = ["release"] -release = false -release_always = false -semver_check = false - -[[package]] -name = "wright-cli" -changelog_include = [ - "wright-analyzer", - "wright-bench", - "wright-consumer", - "wright-core", - "wright-driver", - "wright-ir", - "wright-language", - "wright-lpp", - "wright-lsp", - "wright-opy", - "wright-ostw", - "wright-transform", - "wright-workshop", -] -git_release_enable = true -git_release_name = "v{{ version }}" -git_release_body = "{{ changelog }}" -git_release_draft = true -git_release_latest = false -git_tag_enable = true -git_tag_name = "v{{ version }}" -release = true -version_group = "wright-product" diff --git a/scripts/sync-release-version.py b/scripts/sync-release-version.py new file mode 100644 index 0000000..f9b6f6a --- /dev/null +++ b/scripts/sync-release-version.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""Synchronize Cargo's workspace version and lockfile for a product release.""" + +from __future__ import annotations + +import argparse +import json +import re +import subprocess +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parent.parent +VERSION_RE = re.compile(r"^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$") +WORKSPACE_PACKAGE_RE = re.compile( + r"(?ms)(^\[workspace\.package\]\s*.*?^version\s*=\s*)\"[^\"]+\"" +) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--version", required=True, help="Wright product version") + return parser.parse_args() + + +def update_workspace_manifest(version: str) -> None: + manifest = REPO_ROOT / "Cargo.toml" + contents = manifest.read_text() + updated, count = WORKSPACE_PACKAGE_RE.subn(r'\1"' + version + '"', contents, count=1) + if count != 1: + raise SystemExit("Cargo.toml does not contain one [workspace.package] version") + manifest.write_text(updated) + + +def update_lockfile() -> None: + subprocess.run(["cargo", "update", "--workspace"], cwd=REPO_ROOT, check=True) + + +def verify_workspace_version(version: str) -> None: + output = subprocess.check_output( + ["cargo", "metadata", "--locked", "--no-deps", "--format-version", "1"], + cwd=REPO_ROOT, + text=True, + ) + packages = json.loads(output)["packages"] + mismatches = [ + f'{package["name"]}={package["version"]}' + for package in packages + if package["version"] != version + ] + if mismatches: + raise SystemExit( + f"workspace package versions do not match {version}: {', '.join(mismatches)}" + ) + + +def main() -> None: + args = parse_args() + if not VERSION_RE.fullmatch(args.version): + raise SystemExit(f"invalid release version: {args.version}") + update_workspace_manifest(args.version) + update_lockfile() + verify_workspace_version(args.version) + print(f"synchronized Cargo workspace to {args.version}") + + +if __name__ == "__main__": + main() diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..abd4105 --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +0.2.4