diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml new file mode 100644 index 0000000..69803c6 --- /dev/null +++ b/.github/workflows/release-plz.yml @@ -0,0 +1,148 @@ +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.125" + env: + GITHUB_TOKEN: ${{ github.token }} + GH_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.125" + env: + GITHUB_TOKEN: ${{ github.token }} + GH_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-tag.yml b/.github/workflows/release-tag.yml deleted file mode 100644 index 2db50e7..0000000 --- a/.github/workflows/release-tag.yml +++ /dev/null @@ -1,97 +0,0 @@ -name: Release tag - -on: - workflow_dispatch: - inputs: - version: - description: "Release version, e.g. 0.2.0" - required: true - -permissions: - contents: write - actions: write - -concurrency: - group: release-tag - cancel-in-progress: false - -jobs: - create-tag: - name: Create and push release tag - runs-on: ubuntu-latest - steps: - - name: Check out repository - uses: actions/checkout@v7 - with: - fetch-depth: 0 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - - - name: Update version, commit, and push the tag - id: tag - env: - INPUT_VERSION: ${{ inputs.version }} - run: | - set -euo pipefail - git fetch origin main --tags - git pull --ff-only origin main - - VERSION="$INPUT_VERSION" - VERSION="${VERSION#v}" - if ! printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$'; then - echo "invalid version '$VERSION' (expected semver like 0.1.0)"; exit 1 - fi - - if git ls-remote --exit-code --tags origin "refs/tags/v$VERSION" >/dev/null 2>&1; then - echo "tag v$VERSION already exists on origin"; exit 1 - fi - - PACKAGE_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"))')" - if [[ "$VERSION" != "$PACKAGE_VERSION" ]]; then - python3 - "$VERSION" <<'PY' - from pathlib import Path - import re - import sys - - version = sys.argv[1] - path = Path("Cargo.toml") - text = path.read_text() - pattern = re.compile(r"(?ms)^(\[workspace\.package\]\n.*?^version = )\"[^\"]+\"") - updated, count = pattern.subn(lambda match: f'{match.group(1)}"{version}"', text, count=1) - if count != 1: - raise SystemExit("expected exactly one [workspace.package] version") - path.write_text(updated) - PY - - # A workspace package-version bump changes the version recorded for - # workspace packages in Cargo.lock. Update that manifest-derived - # lockfile state, then prove the result is clean in locked mode. - cargo metadata --no-deps --format-version 1 >/dev/null - cargo metadata --locked --no-deps --format-version 1 >/dev/null - fi - - 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 - git commit -m "chore(release): bump version to $VERSION" - git push origin HEAD:main - fi - - git tag "v$VERSION" - git push origin "v$VERSION" - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "pushed version commit and tag v$VERSION" - - - name: Trigger the release workflow - run: gh workflow run release.yml --ref main -f version="${{ steps.tag.outputs.version }}" - env: - GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e124397..5f9c77f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,14 +1,27 @@ name: Release on: - push: - tags: - - "v*" - workflow_dispatch: + workflow_call: inputs: - version: - description: "Release version, e.g. 0.1.0 (a matching v tag must already exist, e.g. via the Release tag workflow)" + ref: + description: Git revision containing the release packages required: true + type: string + tag: + description: GitHub Release tag created by release-plz + required: true + type: string + commit: + description: Release commit used for version identity + required: true + type: string + secrets: + GH_TOKEN: + description: Token with write access to wrightkit/homebrew-tap + required: true + NPM_TOKEN: + description: Optional npmjs.org publication token + required: false permissions: contents: write @@ -20,14 +33,27 @@ jobs: name: Release gates runs-on: ubuntu-latest env: - # The tag this run releases: the dispatch input (normalized with the v - # prefix) or, on a tag push, the tag name itself. - RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && (startsWith(inputs.version, 'v') && inputs.version || format('v{0}', inputs.version)) || github.ref_name }} + RELEASE_TAG: ${{ inputs.tag }} + RELEASE_COMMIT: ${{ inputs.commit }} steps: - name: Check out repository uses: actions/checkout@v7 with: ref: ${{ env.RELEASE_TAG }} + fetch-depth: 0 + + - name: Verify release revision and tag identity + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ inputs.tag }} + RELEASE_COMMIT: ${{ inputs.commit }} + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "${RELEASE_COMMIT}" + tag_commit="$(gh api \ + "repos/${GITHUB_REPOSITORY}/commits/${RELEASE_TAG}" \ + --jq .sha)" + test "${tag_commit}" = "${RELEASE_COMMIT}" - name: Install Rust toolchain uses: dtolnay/rust-toolchain@master @@ -50,6 +76,9 @@ jobs: - name: Run release gates (fmt, clippy, tests, v1 gates, scenarios, benchmarks, standalone proof) run: scripts/release.sh "${RELEASE_TAG#v}" + - name: Verify checked-in distribution metadata + run: python3 scripts/verify-dist.py + - name: Upload gate reports if: always() uses: actions/upload-artifact@v7 @@ -84,14 +113,15 @@ jobs: run: shell: bash env: - RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && (startsWith(inputs.version, 'v') && inputs.version || format('v{0}', inputs.version)) || github.ref_name }} + RELEASE_TAG: ${{ inputs.tag }} TARGET: ${{ matrix.target }} EXT: ${{ matrix.ext }} steps: - name: Check out repository uses: actions/checkout@v7 with: - ref: ${{ env.RELEASE_TAG }} + ref: ${{ inputs.ref }} + fetch-depth: 0 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@master @@ -162,12 +192,12 @@ jobs: dist/wright-*.zip dist/wright-*.zip.sha256 - publish: - name: Publish GitHub Release + upload-artifacts: + name: Upload native release artifacts needs: build runs-on: ubuntu-latest env: - RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && (startsWith(inputs.version, 'v') && inputs.version || format('v{0}', inputs.version)) || github.ref_name }} + RELEASE_TAG: ${{ inputs.tag }} steps: - name: Download release artifacts uses: actions/download-artifact@v7 @@ -192,30 +222,37 @@ jobs: (cd release && sha256sum wright-*.tar.gz wright-*.zip > SHA256SUMS) ls -l release/ - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ env.RELEASE_TAG }} - generate_release_notes: true - fail_on_unmatched_files: true - files: | - release/wright-*.tar.gz - release/wright-*.tar.gz.sha256 - release/wright-*.zip - release/wright-*.zip.sha256 - release/SHA256SUMS + - name: Wait for the release-plz draft Release + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + for attempt in $(seq 1 30); do + if gh release view "${RELEASE_TAG}" --json isDraft --jq '.isDraft' 2>/dev/null | grep -qx true; then + exit 0 + fi + sleep 2 + done + echo "draft GitHub Release ${RELEASE_TAG} was not found" >&2 + exit 1 + + - name: Attach native archives and checksums + env: + GH_TOKEN: ${{ github.token }} + run: gh release upload "${RELEASE_TAG}" release/* --clobber package-manifests: name: Generate package-manager manifests - needs: publish + needs: upload-artifacts runs-on: ubuntu-latest env: - RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && (startsWith(inputs.version, 'v') && inputs.version || format('v{0}', inputs.version)) || github.ref_name }} + RELEASE_TAG: ${{ inputs.tag }} steps: - name: Check out repository uses: actions/checkout@v7 with: - ref: ${{ env.RELEASE_TAG }} + ref: ${{ inputs.ref }} + fetch-depth: 0 - name: Download release artifacts uses: actions/download-artifact@v7 @@ -264,21 +301,17 @@ jobs: path: ${{ github.workspace }}/manifests/dist/homebrew/wright.rb if-no-files-found: error - - name: Attach manifests to the Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ env.RELEASE_TAG }} - files: | - attach/wright-*.homebrew.rb - attach/wright-*.scoop.json - attach/wright-*.winget.zip + - name: Attach package-manager manifests to the draft Release + env: + GH_TOKEN: ${{ github.token }} + run: gh release upload "${RELEASE_TAG}" attach/* --clobber publish-tap: name: Publish formula to homebrew-tap needs: package-manifests runs-on: ubuntu-latest env: - RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && (startsWith(inputs.version, 'v') && inputs.version || format('v{0}', inputs.version)) || github.ref_name }} + RELEASE_TAG: ${{ inputs.tag }} steps: - name: Download generated Homebrew formula uses: actions/download-artifact@v7 @@ -320,15 +353,16 @@ jobs: package-npm: name: Package npm distribution - needs: publish + needs: upload-artifacts runs-on: ubuntu-latest env: - RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && (startsWith(inputs.version, 'v') && inputs.version || format('v{0}', inputs.version)) || github.ref_name }} + RELEASE_TAG: ${{ inputs.tag }} steps: - name: Check out repository uses: actions/checkout@v7 with: - ref: ${{ env.RELEASE_TAG }} + ref: ${{ inputs.ref }} + fetch-depth: 0 - name: Set up Node.js uses: actions/setup-node@v7 @@ -373,12 +407,10 @@ jobs: ' echo "npm packages smoke test passed on $(uname -s) $(uname -m)" - - name: Attach npm tarballs to Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ env.RELEASE_TAG }} - files: | - npm-packages/*.tgz + - name: Attach npm tarballs to the draft Release + env: + GH_TOKEN: ${{ github.token }} + run: gh release upload "${RELEASE_TAG}" npm-packages/*.tgz --clobber - name: Upload npm tarballs for registry publishing uses: actions/upload-artifact@v7 @@ -412,9 +444,22 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | set -euo pipefail + publish_if_missing() { + local pkg="$1" + local metadata name version published + metadata="$(tar -xOf "${pkg}" package/package.json)" + name="$(node -e 'let s=""; process.stdin.on("data", c => s += c).on("end", () => process.stdout.write(JSON.parse(s).name))' <<<"${metadata}")" + version="$(node -e 'let s=""; process.stdin.on("data", c => s += c).on("end", () => process.stdout.write(JSON.parse(s).version))' <<<"${metadata}")" + published="$(npm view "${name}@${version}" version --registry=https://registry.npmjs.org 2>/dev/null || true)" + if [[ "${published}" == "${version}" ]]; then + echo "${name}@${version} is already published; skipping" + return + fi + echo "Publishing ${pkg} to npmjs.org..." + npm publish "${pkg}" --access public --provenance + } for pkg in "$GITHUB_WORKSPACE"/npm-packages/*.tgz; do - echo "Publishing $pkg to npmjs.org..." - npm publish "$pkg" --access public --provenance + publish_if_missing "${pkg}" done - name: Skip npmjs.org publication @@ -446,9 +491,36 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail - for pkg in "$GITHUB_WORKSPACE"/npm-packages/*.tgz; do - echo "Publishing $pkg to GitHub Packages..." - npm publish "$pkg" \ + publish_if_missing() { + local pkg="$1" + local metadata name version published + metadata="$(tar -xOf "${pkg}" package/package.json)" + name="$(node -e 'let s=""; process.stdin.on("data", c => s += c).on("end", () => process.stdout.write(JSON.parse(s).name))' <<<"${metadata}")" + version="$(node -e 'let s=""; process.stdin.on("data", c => s += c).on("end", () => process.stdout.write(JSON.parse(s).version))' <<<"${metadata}")" + published="$(npm view "${name}@${version}" version --registry=https://npm.pkg.github.com 2>/dev/null || true)" + if [[ "${published}" == "${version}" ]]; then + echo "${name}@${version} is already published; skipping" + return + fi + echo "Publishing ${pkg} to GitHub Packages..." + npm publish "${pkg}" \ --registry=https://npm.pkg.github.com \ --access public + } + for pkg in "$GITHUB_WORKSPACE"/npm-packages/*.tgz; do + publish_if_missing "${pkg}" done + + publish-release: + name: Publish completed GitHub Release + needs: + - publish-tap + - publish-npm + - publish-github-packages + runs-on: ubuntu-latest + steps: + - name: Publish the completed draft Release + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ inputs.tag }} + run: gh release edit "${RELEASE_TAG}" --draft=false --latest diff --git a/dist/README.md b/dist/README.md index 644915d..6ca4a64 100644 --- a/dist/README.md +++ b/dist/README.md @@ -38,19 +38,25 @@ install script stops covering the declared target matrix. ## Publication process -The release workflow (`release.yml`) executes two downstream distribution jobs -after release archives are published: +The release-plz 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: 1. `package-manifests` regenerates the Homebrew, WinGet, and Scoop manifests from - the published release checksums and attaches them to the Release as: + the native release checksums and attaches them to the draft Release as: - `wright-.homebrew.rb` - `wright-.winget.zip` (unzip into a winget-pkgs checkout) - `wright-.scoop.json` 2. `package-npm` packages the release binaries into platform-native npm packages via `scripts/package-npm.py`, runs smoke tests on the packaged artifacts, - attaches the `.tgz` tarballs to the Release, and publishes the same tarballs - to npmjs.org and GitHub Packages. + attaches the `.tgz` tarballs to the draft Release, and publishes the same + tarballs to npmjs.org and GitHub Packages. + +3. `publish-release` marks the draft Release public only after the Homebrew tap + and registry jobs succeed. Re-running an already completed registry stage + skips package versions that already exist. ### Homebrew diff --git a/docs/release.md b/docs/release.md index ee0394c..8add259 100644 --- a/docs/release.md +++ b/docs/release.md @@ -14,9 +14,9 @@ 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 public -tag-driven release workflow; the GitHub workflow publishes the per-platform -archives, and this script verifies and packages the host platform. +This is the local staging path and the validation suite behind the release-plz +workflow; the reusable GitHub workflow publishes the per-platform archives, +and this script verifies and packages the host platform. ## What the release script verifies before stamping @@ -37,7 +37,7 @@ Any gate failure aborts the release before the version is stamped. ## Version metadata The binaries report the workspace implementation version (one authoritative -`version = "0.1.0"` in `[workspace.package]`; every crate inherits it via +`version = ""` in `[workspace.package]`; every crate inherits it via `version.workspace = true`). `wright version` / `wright --version` prints the CLI banner, `wright-lsp --version` prints the LSP banner, and the LSP `initialize` response carries `serverInfo.version`. Every `wright-result/v1` @@ -46,42 +46,39 @@ envelope carries `wright.version` + `wright.contract`. The release archive's ## Public distribution contract -A `v*` tag push (e.g. `v0.1.0`) drives `.github/workflows/release.yml`: - -1. **release-gates** verifies the tag version equals the workspace - implementation version (drift guard), then runs the full `scripts/release.sh` - gate suite. -2. **build** compiles `wright` + `wright-lsp` for the target matrix and - packages each platform-appropriate archive. -3. **publish** verifies the complete artifact set and creates the GitHub - Release from the tag with archives and checksums attached. -4. **package-manifests** regenerates the package-manager metadata from the - published checksums and attaches it to the Release (see below). - -A failure in any gate or any required target aborts the workflow before -publication; there is no partial release. +A merge to `main` drives `.github/workflows/release-plz.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. +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. +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 + draft, publishes downstream registries and the Homebrew tap, and only then + marks the GitHub Release public. + +A failure in any gate or required downstream stage leaves the same draft +Release/tag available for a retry; it does not create a new product version. ### Creating a release -The tag is the release decision point. The **Release tag** workflow is the -single-input release entry point: enter the target version, and it validates -SemVer, updates `[workspace.package]` in the root `Cargo.toml` and the -generated version metadata, commits that version bump to `main`, pushes -`v`, and dispatches `release.yml`. It rejects an existing tag and -does not start the release until the committed workspace version matches the -tag. - -For a manual command-line release, update the workspace version and generated -metadata first, then use either: - -* **From the command line:** `git tag v0.1.0 && git push origin v0.1.0` — the - tag push triggers `release.yml` directly; or -* **From GitHub:** run the **Release tag** workflow - (Actions → Release tag → Run workflow) with only the target version, e.g. - `0.2.0`. +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. -Both paths run the same release gates, build the same target matrix, and -publish through the same `publish` job. +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 +the release in one run. ### Target matrix and artifact naming @@ -133,16 +130,33 @@ matching `.zip.sha256`, then extract with `tar -xf` or Explorer. Each build leg smoke-tests its **packaged archive** (not workspace binaries): it extracts the archive, runs `wright --version` and `wright-lsp --version`, asserts both report the tagged version, and compiles/checks the -`synthetic/basic-rule` and `scenarios/loops` fixtures. The publish job +`synthetic/basic-rule` and `scenarios/loops` fixtures. The upload job re-verifies that every declared target's archive and checksum are present -before the Release is created. +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 +`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. + +Configure these optional/required environment secrets: + +* `NPM_TOKEN` enables npmjs.org publication. If absent, npmjs.org is skipped. +* `GH_TOKEN` is a fine-grained token with write access to + `wrightkit/homebrew-tap`; it is required for automatic Homebrew tap updates. +* The workflow's built-in `GITHUB_TOKEN` publishes GitHub Packages and updates + the draft GitHub Release. ## Supported installation channels All channels consume the canonical GitHub Release archives above; none of them rebuild Wright. Metadata lives under [`dist/`](dist/README.md), generated by `scripts/update-dist-manifests.py`, and is regenerated by the -`package-manifests` job of this workflow from the published per-target +the release PR maintenance step and again by the `package-manifests` job from the published per-target checksums, then attached to the Release as `wright-.homebrew.rb`, `wright-.winget.zip`, and `wright-.scoop.json`. The `publish-tap` job then pushes the generated @@ -204,7 +218,9 @@ release pipeline does not assume them. Version drift is detectable: CI runs `scripts/verify-dist.py`, which regenerates the checked-in metadata for the current workspace version and fails on any mismatch, and the release workflow generates the attached manifests from the release's own -checksum files. +checksum files. Registry publication checks whether the exact package/version +already exists before publishing, so rerunning the downstream stage reuses the +same release identity. ### Still deferred diff --git a/release-plz.toml b/release-plz.toml new file mode 100644 index 0000000..c1b8682 --- /dev/null +++ b/release-plz.toml @@ -0,0 +1,40 @@ +[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"