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
78 changes: 78 additions & 0 deletions .github/workflows/release-plz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
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
environment: crates-io
permissions:
contents: write
pull-requests: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false

- 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 }}
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
persist-credentials: false

- uses: dtolnay/rust-toolchain@stable

- name: Run release-plz release-pr
uses: release-plz/action@v0.5
with:
command: release-pr
Comment on lines +72 to +75

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make dashboard-only edits open release PRs

When a change only touches tracked dashboard UI sources under dashboard/*/src/**, this release-pr job will not bump a version because release-plz opens PRs from changed packaged crate files; this crate’s whitelist ships generated dashboard/*/dist/** assets instead, and those files are gitignored/not committed. The release job later builds those assets before publishing, so dashboard-only fixes do affect the published crate but can be stranded with no release PR until some Rust/package file changes.

Useful? React with 👍 / 👎.

env:
GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
112 changes: 39 additions & 73 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ on:

permissions:
contents: write
id-token: write
attestations: write

env:
CARGO_TERM_COLOR: always
Expand Down Expand Up @@ -67,72 +69,61 @@ 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) ---

- name: Package binary (unix)
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

# --- 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.
run: gh release upload ${{ github.ref_name }} tracedecay-${{ steps.version.outputs.tag }}-${{ matrix.name }}.${{ matrix.archive }} --clobber

- name: Upload unsigned binary for signing
if: matrix.archive == 'zip' && github.event_name == 'release'
id: upload-unsigned
uses: actions/upload-artifact@v4
with:
name: tracedecay-windows-unsigned
path: target/${{ matrix.target }}/release/tracedecay.exe
if-no-files-found: error
- name: Package binary (windows)
if: matrix.archive == 'zip'
shell: pwsh
run: |
Compress-Archive -Path target/${{ matrix.target }}/release/tracedecay.exe -DestinationPath tracedecay-${{ steps.version.outputs.tag }}-${{ matrix.name }}.zip -Force

- name: Submit signing request to SignPath
- name: Generate archive attestation (windows)
if: matrix.archive == 'zip' && github.event_name == 'release'
uses: signpath/github-action-submit-signing-request@v2
uses: actions/attest@v4
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)
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
subject-path: tracedecay-${{ steps.version.outputs.tag }}-${{ matrix.name }}.${{ matrix.archive }}

- name: Upload signed binary 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 }}"
Expand All @@ -141,9 +132,15 @@ 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@v4
uses: actions/upload-artifact@v6
with:
name: bottle-${{ matrix.bottle_tag }}
path: "tracedecay-*.bottle.tar.gz"
Expand All @@ -163,7 +160,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
Expand All @@ -178,7 +175,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
Expand Down Expand Up @@ -207,6 +203,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
Expand Down Expand Up @@ -243,37 +240,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()
Expand Down
55 changes: 55 additions & 0 deletions docs/RELEASE-AUTOMATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# 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`. 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 `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.

## 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Don’t document dispatch as a recovery path

If the follow-up release event is missed, manually dispatching Release against the tag will not repair it: .github/workflows/release.yml treats workflow_dispatch as a dry run (dry-run-${GITHUB_SHA::7}) and all upload/update jobs are still gated on github.event_name == 'release', so this instruction only rebuilds local dry-run archives and leaves the GitHub release assets and package-manager manifests unchanged.

Useful? React with 👍 / 👎.

19 changes: 19 additions & 0 deletions release-plz.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[workspace]
allow_dirty = true
dependencies_update = false
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_allow_dirty = true
publish_timeout = "1h"
semver_check = false
Loading