Skip to content

Release prep

Release prep #32

Workflow file for this run

name: Release prep
# Manual entry point: assemble changelog fragments, bump the version, build,
# and open a `release vX.Y.Z` PR. Merging that PR runs the Release workflow
# (publish + tag + GitHub Release). No NPM_TOKEN / NODE_AUTH_TOKEN: npm auth
# in the Release workflow is pure Trusted Publishing (OIDC; the npm-side
# Trusted Publisher is configured — bootstrap token mode removed 2026-08-14).
# The PR create/edit step uses the PAT secret because the omdsh-dev
# org disables GITHUB_TOKEN's createPullRequest capability; everything else
# uses only the built-in GITHUB_TOKEN.
#
# Trigger: Actions -> "Release prep" -> Run workflow. Pass an explicit target
# version (e.g. 0.1.0-alpha.2) or leave the input empty for an auto bump
# (`--patch`: X.Y.Z-pre.N -> N+1, stays in the prerelease line).
on:
workflow_dispatch:
inputs:
version:
description: "Target version (e.g. 0.1.0-alpha.2). Leave empty for auto pre-release bump."
required: false
type: string
permissions:
contents: write
pull-requests: write
# gh label create requires issues:write (labels API) — without it the
# ensure-label step silently fails (previously swallowed by `|| true`) and
# `gh pr create --label release` errors with "label 'release' not found".
issues: write
concurrency:
group: release-prep
cancel-in-progress: false
jobs:
prepare:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 11.21.0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "24.19.0"
registry-url: https://registry.npmjs.org
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
# Validate the input format BEFORE writing it to GITHUB_OUTPUT: a
# malformed (e.g. multiline) value must fail here instead of injecting
# extra output lines that downstream steps would read as the version.
- name: Reject already-released version
id: want
env:
INPUT_VERSION: ${{ inputs.version }}
run: |
V="$INPUT_VERSION"
if [ -z "$V" ]; then V="auto"; fi
if [ "$V" != "auto" ]; then
case "$V" in
*$'\n'*|*$'\r'*)
echo "::error::Invalid version \"$V\" (must be a single line)."
exit 1
;;
esac
if ! printf '%s' "$V" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'; then
echo "::error::Invalid version \"$V\". Expected X.Y.Z or X.Y.Z-pre.N (e.g. 0.1.0-alpha.2)."
exit 1
fi
if git rev-parse "v$V" >/dev/null 2>&1; then
echo "::error::Tag v$V already exists."
exit 1
fi
fi
echo "version=$V" >> "$GITHUB_OUTPUT"
- name: Assemble fragments + bump version
run: |
V="${{ steps.want.outputs.version }}"
if [ "$V" = "auto" ]; then
pnpm release:prepare -- --patch
else
pnpm release:prepare -- "$V"
fi
- name: Resolve resulting version
id: ver
run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
- name: Validate release version
run: pnpm release:validate -- "v${{ steps.ver.outputs.version }}"
- name: Build (release smoke)
run: pnpm build
- name: Extract changelog notes
run: |
awk -v v="${{ steps.ver.outputs.version }}" '
/^## \[/ { if (found) exit }
index($0, "## [" v "]") == 1 { found = 1 }
found { print }
' CHANGELOG.md > /tmp/notes.md
- name: Commit prepared release
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
BRANCH="release/v${{ steps.ver.outputs.version }}"
git checkout -B "$BRANCH"
# Explicit paths only: the release PR must contain exactly the
# version/changelog/fragment changes, never stray files.
git add package.json CHANGELOG.md .changes/
git commit -m "chore(release): prepare v${{ steps.ver.outputs.version }}"
git push --force-with-lease origin "$BRANCH"
- name: Ensure release label
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh label create release --color 0E8A16 --force
# State-aware PR handling. Query open PRs explicitly:
# open PR -> update title/body;
# none -> create a new PR (even when a closed PR exists for the
# same head branch — never reopen a closed release PR).
- name: Open or update release PR
env:
# The omdsh-dev org disables GITHUB_TOKEN's createPullRequest
# capability ("Allow GitHub Actions to create and approve pull
# requests" is off), so PR create/edit runs under the
# PAT secret (user identity) instead of the built-in token.
GH_TOKEN: ${{ secrets.PAT }}
VERSION: ${{ steps.ver.outputs.version }}
run: |
BRANCH="release/v${VERSION}"
TITLE="release v${VERSION}"
{
echo "Generated by the **Release prep** workflow."
echo
echo "Merging this PR publishes dsh-llm-fallbacks@v${VERSION} to npm (--provenance; Trusted Publishing OIDC, tokenless), tags v${VERSION}, and creates the GitHub Release."
echo
echo "## Changelog"
echo
cat /tmp/notes.md
} > /tmp/pr-body.md
OPEN_NUM="$(gh pr list --head "$BRANCH" --state open --json number -q '.[0].number' 2>/dev/null || true)"
if [ -n "$OPEN_NUM" ]; then
gh pr edit "$OPEN_NUM" --title "$TITLE" --body-file /tmp/pr-body.md
echo "Updated existing open release PR #${OPEN_NUM} for $BRANCH"
else
gh pr create --base main --head "$BRANCH" --title "$TITLE" \
--body-file /tmp/pr-body.md --label release
echo "Created release PR for $BRANCH"
fi