Release prep #32
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |