name: Release PR
on:
workflow_dispatch:
push:
branches: [master]
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
jobs:
release-pr:
name: release-plz PR
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
concurrency:
group: release-plz-pr-${{ github.ref }}
cancel-in-progress: false
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Install Linux build deps
run: |
sudo apt-get update
sudo apt-get install -y \
libudev-dev pkg-config gcc g++ clang libssl-dev libzstd-dev
- name: Mint GitHub App token
id: app-token
uses: ./.github/actions/github-app-token-from-1password
with:
op-service-account-token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
op-github-app-item: ${{ secrets.OP_GITHUB_APP_ITEM }}
- name: Run release-plz (release-pr)
id: release_pr
uses: release-plz/action@v0.5
with:
command: release-pr
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
CARGO_REGISTRY_TOKEN: ${{ steps.app-token.outputs.cargo-registry-token }}
- name: Guard against a silently stalled release
if: steps.release_pr.outputs.prs_created == 'false'
uses: actions/github-script@v8
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const { readFileSync } = require("node:fs");
const { owner, repo } = context.repo;
// An already-open release PR is the normal "updated, not created" path.
const openPulls = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: "open",
per_page: 100,
});
if (openPulls.some((pull) => pull.head.ref.startsWith("release-plz/"))) {
core.info("A release PR is already open — nothing to flag.");
return;
}
const tags = await github.paginate(github.rest.repos.listTags, {
owner,
repo,
per_page: 100,
});
const lastTag = tags
.map((tag) => tag.name)
.filter((tag) => /^v\d+\.\d+\.\d+/.test(tag))
.sort((left, right) => right.localeCompare(left, undefined, { numeric: true, sensitivity: "base" }))[0] ?? "";
// The release job runs in parallel and cuts the `v{version}` tag only
// after publishing, so that tag is usually absent from this job's
// checkout — never trust the local tag list to decide "did a release
// just happen". Read the manifest instead: merging a release PR bumps
// the workspace version, so a manifest version ahead of the last tag
// means the release already landed (the parallel job is tagging it),
// not a stall. Only when the manifest still sits at the last released
// tag can leftover release-worthy commits mean a swallowed release-pr.
const manifestVersion = readFileSync("Cargo.toml", "utf8")
.match(/\[workspace\.package\][\s\S]*?^version\s*=\s*"([^"]+)"/m)?.[1] ?? "";
if (lastTag && manifestVersion && `v${manifestVersion}` !== lastTag) {
core.info(`Workspace version ${manifestVersion} is ahead of ${lastTag} — a release PR already merged; the release job cuts the tag. Nothing to flag.`);
return;
}
const commits = lastTag
? (await github.rest.repos.compareCommitsWithBasehead({
owner,
repo,
basehead: `${lastTag}...${context.sha}`,
})).data.commits
: await github.paginate(github.rest.repos.listCommits, {
owner,
repo,
sha: context.sha,
per_page: 100,
});
const worthy = commits.filter(({ commit }) => /^(feat|fix|perf)(\(.+\))?!?:|^[a-z]+(\(.+\))?!:/.test(commit.message.split("\n")[0])).length;
if (worthy !== 0) {
core.setFailed(`release-plz opened no release PR, but ${worthy} release-worthy commit(s) exist since ${lastTag || "repo start"} and none is open — likely a swallowed release-plz failure (see the release-pr step).`);
return;
}
core.info(`No release PR and no release-worthy commits since ${lastTag || "repo start"}; nothing to release.`);
release:
name: release-plz release
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: read
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Install Linux build deps
run: |
sudo apt-get update
sudo apt-get install -y \
libudev-dev pkg-config gcc g++ clang libssl-dev libzstd-dev
- name: Mint GitHub App token
id: app-token
uses: ./.github/actions/github-app-token-from-1password
with:
op-service-account-token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
op-github-app-item: ${{ secrets.OP_GITHUB_APP_ITEM }}
- name: Run release-plz (release)
uses: release-plz/action@v0.5
with:
command: release
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
CARGO_REGISTRY_TOKEN: ${{ steps.app-token.outputs.cargo-registry-token }}