From 11d0a409066f5f4426124c144f991ecf45cced26 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 18 Jun 2026 22:50:36 +0200 Subject: [PATCH 1/5] ci: add release-plz automation --- .github/workflows/release-plz.yml | 89 +++++++++++++++++++++++++++++++ .github/workflows/release.yml | 31 ----------- docs/RELEASE-AUTOMATION.md | 58 ++++++++++++++++++++ release-plz.toml | 16 ++++++ 4 files changed, 163 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/release-plz.yml create mode 100644 docs/RELEASE-AUTOMATION.md create mode 100644 release-plz.toml diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml new file mode 100644 index 00000000..1e6c0334 --- /dev/null +++ b/.github/workflows/release-plz.yml @@ -0,0 +1,89 @@ +name: Release-plz + +on: + push: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + +env: + CARGO_TERM_COLOR: always + +jobs: + release-plz-release: + name: Publish crate and create GitHub release + if: github.repository == 'ScriptedAlchemy/tracedecay' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: read + id-token: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.RELEASE_PLZ_TOKEN || github.token }} + + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: npm + cache-dependency-path: dashboard/package-lock.json + + - name: Build dashboard assets + working-directory: dashboard + run: | + npm ci + npm run build + + - uses: dtolnay/rust-toolchain@stable + + - name: Run release-plz release + uses: release-plz/action@v0.5 + with: + command: release + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN || github.token }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + + release-plz-pr: + name: Open or update release PR + if: github.repository == 'ScriptedAlchemy/tracedecay' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + concurrency: + group: release-plz-${{ github.ref }} + cancel-in-progress: false + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.RELEASE_PLZ_TOKEN || github.token }} + + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: npm + cache-dependency-path: dashboard/package-lock.json + + - name: Build dashboard assets + working-directory: dashboard + run: | + npm ci + npm run build + + - uses: dtolnay/rust-toolchain@stable + + - name: Run release-plz release-pr + uses: release-plz/action@v0.5 + with: + command: release-pr + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN || github.token }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2e6cd1e2..9ebf04b8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -243,37 +243,6 @@ jobs: git diff --cached --quiet || git commit -m "tracedecay ${VERSION}" git push - publish-crate: - name: Publish to crates.io - if: github.event_name == 'release' && !github.event.release.prerelease - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: "22" - cache: npm - cache-dependency-path: dashboard/package-lock.json - - # The crate package ships the prebuilt dashboard dist bundles (see - # `package.include` in Cargo.toml), so they must exist before publish. - - name: Build dashboard assets - working-directory: dashboard - run: | - npm ci - npm run build - - - uses: dtolnay/rust-toolchain@stable - - # NOTE: publishes the renamed `tracedecay` crate — a new crate name on - # crates.io; the legacy `tokensave` crate is not updated by this job. - - name: Publish - run: cargo publish - env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - update-scoop: name: Update Scoop bucket if: github.event_name == 'release' && !github.event.release.prerelease && !cancelled() diff --git a/docs/RELEASE-AUTOMATION.md b/docs/RELEASE-AUTOMATION.md new file mode 100644 index 00000000..4923f4a5 --- /dev/null +++ b/docs/RELEASE-AUTOMATION.md @@ -0,0 +1,58 @@ +# Release Automation + +TraceDecay uses two workflows for stable releases: + +1. `Release-plz` runs on pushes to `master`. + - Opens or updates a release PR. + - Bumps `Cargo.toml` and `Cargo.lock`. + - Updates `CHANGELOG.md`. + - Publishes the `tracedecay` crate to crates.io when the release PR is merged. + - Creates the `vX.Y.Z` tag and GitHub Release. +2. `Release` runs after a GitHub Release is published. + - Builds platform binaries. + - Uploads release assets. + - Updates the Homebrew tap, Scoop bucket, and `server.json`. + +`release.yml` intentionally does not run `cargo publish`; crates.io publishing belongs to `release-plz.yml`. + +## Required GitHub Setup + +Set repository Actions workflow permissions to allow write access: + +```bash +gh api \ + --method PUT \ + repos/ScriptedAlchemy/tracedecay/actions/permissions/workflow \ + -f default_workflow_permissions=write \ + -F can_approve_pull_request_reviews=true +``` + +Add these repository secrets: + +- `RELEASE_PLZ_TOKEN`: fine-grained PAT or GitHub App token with read/write `Contents` and `Pull requests` access. This token is important because releases created with the default `GITHUB_TOKEN` do not trigger the follow-up `release.yml` workflow. +- `CARGO_REGISTRY_TOKEN`: crates.io token with publish access for `tracedecay`. +- `TAP_GITHUB_TOKEN`: token that can push to `ScriptedAlchemy/homebrew-tap` and `ScriptedAlchemy/scoop-bucket`. +- `SIGNPATH_API_TOKEN`: only needed while Windows signing remains enabled. + +Repository variables required by `release.yml`: + +- `SIGNPATH_ORGANIZATION_ID` + +## Crates.io Setup + +The crate must exist on crates.io before fully automated publishing is reliable. If `tracedecay` has not had its first real publish yet, publish `0.0.2` once manually or make sure `CARGO_REGISTRY_TOKEN` has permission to publish the package. + +After that, release-plz detects unpublished changes from crates.io, opens a release PR, and publishes on merge. + +## Normal Release Flow + +1. Merge feature/fix PRs into `master`. +2. `Release-plz` opens or updates a release PR. +3. Review the generated version and changelog. +4. Merge the release PR. +5. `Release-plz` publishes the crate and creates the GitHub Release. +6. The GitHub Release triggers `release.yml`, which builds and uploads binaries and updates package-manager manifests. + +## Manual Recovery + +If release-plz publishes the crate but the binary artifact workflow does not run, check whether `RELEASE_PLZ_TOKEN` was configured. Then manually dispatch `Release` from the Actions tab against the release tag. diff --git a/release-plz.toml b/release-plz.toml new file mode 100644 index 00000000..e93f59ae --- /dev/null +++ b/release-plz.toml @@ -0,0 +1,16 @@ +[workspace] +repo_url = "https://github.com/ScriptedAlchemy/tracedecay" +release_always = false +git_release_enable = true +git_release_name = "v{{ version }}" +git_release_body = "{{ changelog }}" +git_release_type = "prod" +git_release_draft = false +git_release_latest = true +git_tag_enable = true +git_tag_name = "v{{ version }}" +pr_branch_prefix = "release-plz-" +pr_labels = ["release"] +publish = true +publish_timeout = "1h" +semver_check = true From a300297e10b7048313d148435865d8be664c3318 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 18 Jun 2026 13:56:37 -0700 Subject: [PATCH 2/5] ci: port upstream signing flow --- .github/workflows/release-plz.yml | 20 ++----- .github/workflows/release.yml | 95 ++++++++++++++++++++----------- docs/RELEASE-AUTOMATION.md | 2 + release-plz.toml | 5 +- 4 files changed, 71 insertions(+), 51 deletions(-) diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml index 1e6c0334..6da0e2e0 100644 --- a/.github/workflows/release-plz.yml +++ b/.github/workflows/release-plz.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - token: ${{ secrets.RELEASE_PLZ_TOKEN || github.token }} + persist-credentials: false - uses: actions/setup-node@v4 with: @@ -46,7 +46,7 @@ jobs: with: command: release env: - GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN || github.token }} + GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }} CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} release-plz-pr: @@ -64,19 +64,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - token: ${{ secrets.RELEASE_PLZ_TOKEN || github.token }} - - - uses: actions/setup-node@v4 - with: - node-version: "22" - cache: npm - cache-dependency-path: dashboard/package-lock.json - - - name: Build dashboard assets - working-directory: dashboard - run: | - npm ci - npm run build + persist-credentials: false - uses: dtolnay/rust-toolchain@stable @@ -85,5 +73,5 @@ jobs: with: command: release-pr env: - GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN || github.token }} + GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }} CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9ebf04b8..0c47a33b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -86,48 +86,33 @@ jobs: shell: bash run: gh release upload ${{ github.ref_name }} tracedecay-${{ github.ref_name }}-${{ matrix.name }}.${{ matrix.archive }} --clobber - # --- Windows: Authenticode signing via SignPath --- - # The unsigned .exe is uploaded as a workflow artifact (upload-artifact wraps the - # single file in a ZIP), submitted to SignPath, and the signed .exe is repackaged - # into the release zip. Signing only runs for real releases, not workflow_dispatch. + # --- Windows: Authenticode signing via SignPath (build half) --- + # Production signing uses a SignPath certificate that can require manual approval, + # so signing is split out into the sign-windows job. Here we only upload the + # unsigned .exe and record its artifact id, freeing the Windows runner instead of + # blocking it while the signing request waits. - name: Upload unsigned binary for signing if: matrix.archive == 'zip' && github.event_name == 'release' id: upload-unsigned - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: tracedecay-windows-unsigned path: target/${{ matrix.target }}/release/tracedecay.exe if-no-files-found: error - - name: Submit signing request to SignPath - if: matrix.archive == 'zip' && github.event_name == 'release' - uses: signpath/github-action-submit-signing-request@v2 - with: - api-token: ${{ secrets.SIGNPATH_API_TOKEN }} - organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} - # NOTE: SignPath is an external service — the project must also be - # renamed tokensave -> tracedecay in the SignPath dashboard, or - # signing will fail with an unknown project-slug error. - project-slug: tracedecay - # Test certificate while bootstrapping; switch to release-signing for production. - signing-policy-slug: test-signing - github-artifact-id: ${{ steps.upload-unsigned.outputs.artifact-id }} - wait-for-completion: true - output-artifact-directory: signed - - - name: Package signed binary (windows) + - name: Record unsigned artifact id if: matrix.archive == 'zip' && github.event_name == 'release' - shell: pwsh - run: | - Compress-Archive -Path signed/tracedecay.exe -DestinationPath tracedecay-${{ github.ref_name }}-${{ matrix.name }}.zip -Force + shell: bash + run: echo "${{ steps.upload-unsigned.outputs.artifact-id }}" > unsigned-artifact-id.txt - - name: Upload signed binary archive + - name: Upload artifact id for signing job if: matrix.archive == 'zip' && github.event_name == 'release' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - shell: bash - run: gh release upload ${{ github.ref_name }} tracedecay-${{ github.ref_name }}-${{ matrix.name }}.${{ matrix.archive }} --clobber + uses: actions/upload-artifact@v6 + with: + name: windows-unsigned-artifact-id + path: unsigned-artifact-id.txt + if-no-files-found: error # --- Homebrew bottle (only for platforms with bottle_tag) --- @@ -143,11 +128,54 @@ jobs: - name: Upload bottle artifact if: matrix.bottle_tag && github.event_name == 'release' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: bottle-${{ matrix.bottle_tag }} path: "tracedecay-*.bottle.tar.gz" + sign-windows: + name: Sign Windows binary + if: github.event_name == 'release' && !github.event.release.prerelease && !cancelled() + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + actions: read + steps: + - name: Download unsigned artifact id + uses: actions/download-artifact@v7 + with: + name: windows-unsigned-artifact-id + + - name: Read unsigned artifact id + id: meta + run: echo "artifact-id=$(cat unsigned-artifact-id.txt)" >> "$GITHUB_OUTPUT" + + - name: Submit signing request to SignPath + uses: signpath/github-action-submit-signing-request@v2 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + # NOTE: SignPath is an external service — the project must also be + # renamed tokensave -> tracedecay in the SignPath dashboard, or + # signing will fail with an unknown project-slug error. + project-slug: tracedecay + signing-policy-slug: release-signing + github-artifact-id: ${{ steps.meta.outputs.artifact-id }} + wait-for-completion: true + wait-for-completion-timeout-in-seconds: 3600 + output-artifact-directory: signed + + - name: Package signed binary + run: | + cd signed + zip "../tracedecay-${GITHUB_REF_NAME}-x86_64-windows.zip" tracedecay.exe + + - name: Upload signed binary archive + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh release upload "${GITHUB_REF_NAME}" "tracedecay-${GITHUB_REF_NAME}-x86_64-windows.zip" --clobber --repo "$GITHUB_REPOSITORY" + update-homebrew: name: Update Homebrew tap if: github.event_name == 'release' && !github.event.release.prerelease && !cancelled() @@ -163,7 +191,7 @@ jobs: echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Download bottle artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: path: bottles merge-multiple: true @@ -178,7 +206,6 @@ jobs: - name: Download source tarball run: | - VERSION="${{ steps.version.outputs.version }}" curl -sL "https://github.com/${{ github.repository }}/archive/refs/tags/${GITHUB_REF_NAME}.tar.gz" -o "source.tar.gz" - name: Compute SHA256 hashes @@ -246,7 +273,7 @@ jobs: update-scoop: name: Update Scoop bucket if: github.event_name == 'release' && !github.event.release.prerelease && !cancelled() - needs: build + needs: sign-windows runs-on: ubuntu-latest steps: - name: Get release info diff --git a/docs/RELEASE-AUTOMATION.md b/docs/RELEASE-AUTOMATION.md index 4923f4a5..480fe8c9 100644 --- a/docs/RELEASE-AUTOMATION.md +++ b/docs/RELEASE-AUTOMATION.md @@ -38,6 +38,8 @@ Repository variables required by `release.yml`: - `SIGNPATH_ORGANIZATION_ID` +SignPath must have a `tracedecay` project with a `release-signing` signing policy. The Windows build uploads the unsigned exe and a separate `sign-windows` job waits for manual SignPath approval, then uploads the signed zip to the GitHub Release. + ## Crates.io Setup The crate must exist on crates.io before fully automated publishing is reliable. If `tracedecay` has not had its first real publish yet, publish `0.0.2` once manually or make sure `CARGO_REGISTRY_TOKEN` has permission to publish the package. diff --git a/release-plz.toml b/release-plz.toml index e93f59ae..d307170f 100644 --- a/release-plz.toml +++ b/release-plz.toml @@ -1,4 +1,6 @@ [workspace] +allow_dirty = true +dependencies_update = false repo_url = "https://github.com/ScriptedAlchemy/tracedecay" release_always = false git_release_enable = true @@ -12,5 +14,6 @@ git_tag_name = "v{{ version }}" pr_branch_prefix = "release-plz-" pr_labels = ["release"] publish = true +publish_allow_dirty = true publish_timeout = "1h" -semver_check = true +semver_check = false From 731f8b409ec7025799b472362c65284814492053 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 18 Jun 2026 14:01:10 -0700 Subject: [PATCH 3/5] ci: create tap formula directory --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0c47a33b..3df88bb0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -234,6 +234,7 @@ jobs: X86_64_LINUX_SHA="${{ steps.hashes.outputs.x86_64_linux_sha }}" git clone "https://x-access-token:${TAP_GITHUB_TOKEN}@github.com/ScriptedAlchemy/homebrew-tap.git" tap + mkdir -p tap/Formula cat > tap/Formula/tracedecay.rb << EOF class Tracedecay < Formula From 76791da8984966c129a673ba165d0f13c260f919 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 18 Jun 2026 14:03:49 -0700 Subject: [PATCH 4/5] ci: drop signing requirement from release --- .github/workflows/release.yml | 76 +++++------------------------------ docs/RELEASE-AUTOMATION.md | 7 ---- 2 files changed, 9 insertions(+), 74 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3df88bb0..d23a6cf3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -86,33 +86,18 @@ jobs: shell: bash run: gh release upload ${{ github.ref_name }} tracedecay-${{ github.ref_name }}-${{ matrix.name }}.${{ matrix.archive }} --clobber - # --- Windows: Authenticode signing via SignPath (build half) --- - # Production signing uses a SignPath certificate that can require manual approval, - # so signing is split out into the sign-windows job. Here we only upload the - # unsigned .exe and record its artifact id, freeing the Windows runner instead of - # blocking it while the signing request waits. - - - name: Upload unsigned binary for signing + - name: Package binary (windows) if: matrix.archive == 'zip' && github.event_name == 'release' - id: upload-unsigned - uses: actions/upload-artifact@v6 - with: - name: tracedecay-windows-unsigned - path: target/${{ matrix.target }}/release/tracedecay.exe - if-no-files-found: error + shell: pwsh + run: | + Compress-Archive -Path target/${{ matrix.target }}/release/tracedecay.exe -DestinationPath tracedecay-${{ github.ref_name }}-${{ matrix.name }}.zip -Force - - name: Record unsigned artifact id + - name: Upload binary archive (windows) if: matrix.archive == 'zip' && github.event_name == 'release' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash - run: echo "${{ steps.upload-unsigned.outputs.artifact-id }}" > unsigned-artifact-id.txt - - - name: Upload artifact id for signing job - if: matrix.archive == 'zip' && github.event_name == 'release' - uses: actions/upload-artifact@v6 - with: - name: windows-unsigned-artifact-id - path: unsigned-artifact-id.txt - if-no-files-found: error + run: gh release upload ${{ github.ref_name }} tracedecay-${{ github.ref_name }}-${{ matrix.name }}.${{ matrix.archive }} --clobber # --- Homebrew bottle (only for platforms with bottle_tag) --- @@ -133,49 +118,6 @@ jobs: name: bottle-${{ matrix.bottle_tag }} path: "tracedecay-*.bottle.tar.gz" - sign-windows: - name: Sign Windows binary - if: github.event_name == 'release' && !github.event.release.prerelease && !cancelled() - needs: build - runs-on: ubuntu-latest - permissions: - contents: write - actions: read - steps: - - name: Download unsigned artifact id - uses: actions/download-artifact@v7 - with: - name: windows-unsigned-artifact-id - - - name: Read unsigned artifact id - id: meta - run: echo "artifact-id=$(cat unsigned-artifact-id.txt)" >> "$GITHUB_OUTPUT" - - - name: Submit signing request to SignPath - uses: signpath/github-action-submit-signing-request@v2 - with: - api-token: ${{ secrets.SIGNPATH_API_TOKEN }} - organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} - # NOTE: SignPath is an external service — the project must also be - # renamed tokensave -> tracedecay in the SignPath dashboard, or - # signing will fail with an unknown project-slug error. - project-slug: tracedecay - signing-policy-slug: release-signing - github-artifact-id: ${{ steps.meta.outputs.artifact-id }} - wait-for-completion: true - wait-for-completion-timeout-in-seconds: 3600 - output-artifact-directory: signed - - - name: Package signed binary - run: | - cd signed - zip "../tracedecay-${GITHUB_REF_NAME}-x86_64-windows.zip" tracedecay.exe - - - name: Upload signed binary archive - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh release upload "${GITHUB_REF_NAME}" "tracedecay-${GITHUB_REF_NAME}-x86_64-windows.zip" --clobber --repo "$GITHUB_REPOSITORY" - update-homebrew: name: Update Homebrew tap if: github.event_name == 'release' && !github.event.release.prerelease && !cancelled() @@ -274,7 +216,7 @@ jobs: update-scoop: name: Update Scoop bucket if: github.event_name == 'release' && !github.event.release.prerelease && !cancelled() - needs: sign-windows + needs: build runs-on: ubuntu-latest steps: - name: Get release info diff --git a/docs/RELEASE-AUTOMATION.md b/docs/RELEASE-AUTOMATION.md index 480fe8c9..5076467f 100644 --- a/docs/RELEASE-AUTOMATION.md +++ b/docs/RELEASE-AUTOMATION.md @@ -32,13 +32,6 @@ Add these repository secrets: - `RELEASE_PLZ_TOKEN`: fine-grained PAT or GitHub App token with read/write `Contents` and `Pull requests` access. This token is important because releases created with the default `GITHUB_TOKEN` do not trigger the follow-up `release.yml` workflow. - `CARGO_REGISTRY_TOKEN`: crates.io token with publish access for `tracedecay`. - `TAP_GITHUB_TOKEN`: token that can push to `ScriptedAlchemy/homebrew-tap` and `ScriptedAlchemy/scoop-bucket`. -- `SIGNPATH_API_TOKEN`: only needed while Windows signing remains enabled. - -Repository variables required by `release.yml`: - -- `SIGNPATH_ORGANIZATION_ID` - -SignPath must have a `tracedecay` project with a `release-signing` signing policy. The Windows build uploads the unsigned exe and a separate `sign-windows` job waits for manual SignPath approval, then uploads the signed zip to the GitHub Release. ## Crates.io Setup From 468ca1b9505f0ed1b891669b2aa50c50dc147fe1 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 18 Jun 2026 14:17:40 -0700 Subject: [PATCH 5/5] ci: harden release provenance --- .github/workflows/release-plz.yml | 1 + .github/workflows/release.yml | 43 +++++++++++++++++++++++++------ docs/RELEASE-AUTOMATION.md | 6 +++-- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml index 6da0e2e0..c8692a58 100644 --- a/.github/workflows/release-plz.yml +++ b/.github/workflows/release-plz.yml @@ -16,6 +16,7 @@ jobs: name: Publish crate and create GitHub release if: github.repository == 'ScriptedAlchemy/tracedecay' runs-on: ubuntu-latest + environment: crates-io permissions: contents: write pull-requests: read diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d23a6cf3..c378aa9a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,8 @@ on: permissions: contents: write + id-token: write + attestations: write env: CARGO_TERM_COLOR: always @@ -67,8 +69,15 @@ jobs: id: version shell: bash run: | - VERSION="${GITHUB_REF_NAME#v}" - echo "version=$VERSION" >> "$GITHUB_OUTPUT" + if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then + TAG="dry-run-${GITHUB_SHA::7}" + VERSION="${TAG#v}" + else + TAG="${GITHUB_REF_NAME}" + VERSION="${GITHUB_REF_NAME#v}" + fi + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" # --- Binary archive (all platforms) --- @@ -76,33 +85,45 @@ jobs: if: matrix.archive == 'tar.gz' run: | cd target/${{ matrix.target }}/release - tar czf ../../../tracedecay-${{ github.ref_name }}-${{ matrix.name }}.tar.gz tracedecay + tar czf ../../../tracedecay-${{ steps.version.outputs.tag }}-${{ matrix.name }}.tar.gz tracedecay cd ../../.. + - name: Generate archive attestation (unix) + if: matrix.archive == 'tar.gz' && github.event_name == 'release' + uses: actions/attest@v4 + with: + subject-path: tracedecay-${{ steps.version.outputs.tag }}-${{ matrix.name }}.${{ matrix.archive }} + - name: Upload binary archive (unix) if: matrix.archive == 'tar.gz' && github.event_name == 'release' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash - run: gh release upload ${{ github.ref_name }} tracedecay-${{ github.ref_name }}-${{ matrix.name }}.${{ matrix.archive }} --clobber + run: gh release upload ${{ github.ref_name }} tracedecay-${{ steps.version.outputs.tag }}-${{ matrix.name }}.${{ matrix.archive }} --clobber - name: Package binary (windows) - if: matrix.archive == 'zip' && github.event_name == 'release' + if: matrix.archive == 'zip' shell: pwsh run: | - Compress-Archive -Path target/${{ matrix.target }}/release/tracedecay.exe -DestinationPath tracedecay-${{ github.ref_name }}-${{ matrix.name }}.zip -Force + Compress-Archive -Path target/${{ matrix.target }}/release/tracedecay.exe -DestinationPath tracedecay-${{ steps.version.outputs.tag }}-${{ matrix.name }}.zip -Force + + - name: Generate archive attestation (windows) + if: matrix.archive == 'zip' && github.event_name == 'release' + uses: actions/attest@v4 + with: + subject-path: tracedecay-${{ steps.version.outputs.tag }}-${{ matrix.name }}.${{ matrix.archive }} - name: Upload binary archive (windows) if: matrix.archive == 'zip' && github.event_name == 'release' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash - run: gh release upload ${{ github.ref_name }} tracedecay-${{ github.ref_name }}-${{ matrix.name }}.${{ matrix.archive }} --clobber + run: gh release upload ${{ github.ref_name }} tracedecay-${{ steps.version.outputs.tag }}-${{ matrix.name }}.${{ matrix.archive }} --clobber # --- Homebrew bottle (only for platforms with bottle_tag) --- - name: Package Homebrew bottle - if: matrix.bottle_tag && github.event_name == 'release' + if: matrix.bottle_tag run: | VERSION="${{ steps.version.outputs.version }}" BOTTLE_TAG="${{ matrix.bottle_tag }}" @@ -111,6 +132,12 @@ jobs: chmod +x tracedecay/${VERSION}/bin/tracedecay tar czf "tracedecay-${VERSION}.${BOTTLE_TAG}.bottle.tar.gz" tracedecay/ + - name: Generate bottle attestation + if: matrix.bottle_tag && github.event_name == 'release' + uses: actions/attest@v4 + with: + subject-path: tracedecay-${{ steps.version.outputs.version }}.${{ matrix.bottle_tag }}.bottle.tar.gz + - name: Upload bottle artifact if: matrix.bottle_tag && github.event_name == 'release' uses: actions/upload-artifact@v6 diff --git a/docs/RELEASE-AUTOMATION.md b/docs/RELEASE-AUTOMATION.md index 5076467f..c45981cd 100644 --- a/docs/RELEASE-AUTOMATION.md +++ b/docs/RELEASE-AUTOMATION.md @@ -30,12 +30,14 @@ gh api \ Add these repository secrets: - `RELEASE_PLZ_TOKEN`: fine-grained PAT or GitHub App token with read/write `Contents` and `Pull requests` access. This token is important because releases created with the default `GITHUB_TOKEN` do not trigger the follow-up `release.yml` workflow. -- `CARGO_REGISTRY_TOKEN`: crates.io token with publish access for `tracedecay`. +- `CARGO_REGISTRY_TOKEN`: crates.io token with publish access for `tracedecay`. This is used as a bootstrap fallback until crates.io Trusted Publishing is configured after `release-plz.yml` lands on `master`. - `TAP_GITHUB_TOKEN`: token that can push to `ScriptedAlchemy/homebrew-tap` and `ScriptedAlchemy/scoop-bucket`. ## Crates.io Setup -The crate must exist on crates.io before fully automated publishing is reliable. If `tracedecay` has not had its first real publish yet, publish `0.0.2` once manually or make sure `CARGO_REGISTRY_TOKEN` has permission to publish the package. +The `tracedecay` crate should use crates.io Trusted Publishing once `release-plz.yml` exists on `master`. Configure the trusted publisher as GitHub Actions for `ScriptedAlchemy/tracedecay`, workflow `release-plz.yml`, environment `crates-io`. + +The first version of a crate must exist before trusted publishing can be configured. `tracedecay` already exists on crates.io, so after this PR is merged crates.io can be configured for OIDC publishing and `CARGO_REGISTRY_TOKEN` can be removed from `.github/workflows/release-plz.yml`. After that, release-plz detects unpublished changes from crates.io, opens a release PR, and publishes on merge.