From 02f8d0b3813f8df2a2dca89a89d22ffc4b3e2c04 Mon Sep 17 00:00:00 2001 From: release-fix-bot Date: Sat, 18 Jul 2026 19:50:28 +0000 Subject: [PATCH 1/2] fix(release): pr-based release flow so version bumps reach main The Release workflow ran 'release-it --ci' which bumps the version, commits, tags, publishes to npm, and creates the GitHub release, but its 'git push' was silently rejected by main's branch-protection rules (PR-only pushes, 12 required status checks, CodeQL scan). release-it swallowed the error and tagged the step green, leaving an orphan tag and orphan GitHub release: run 29652144213 published v0.0.5 to npm and tagged v0.0.5 on GitHub while main stayed at 0.0.4. Replace release.yml with two workflows that respect branch protection: - release-pr.yml: bumps the version with the remote-touching steps disabled (--no-git.push, --no-npm.publish, --no-github.release), pushes the release commit to release/vX.Y.Z, and opens a PR with auto-merge + squash. workflow exits without waiting. - release-publish.yml: triggered by merged release PRs. retags the squash-merged commit (releasing the stale local tag from release-it and force-updating the remote tag), publishes to npm via OIDC trusted publishing, and creates the GitHub release. Also adds an 'increment' workflow_dispatch input on release-pr.yml so the maintainer can pass --increment=minor to skip past the prematurely-published v0.0.5 on the first release after this lands. Orphan v0.0.5 tag and the v0.0.5 GitHub release were cleaned up during the change; v0.0.5 on npm stays as-is. --- .github/workflows/release-pr.yml | 103 ++++++++++++++++++++++++++ .github/workflows/release-publish.yml | 73 ++++++++++++++++++ .github/workflows/release.yml | 45 ----------- 3 files changed, 176 insertions(+), 45 deletions(-) create mode 100644 .github/workflows/release-pr.yml create mode 100644 .github/workflows/release-publish.yml delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml new file mode 100644 index 0000000..6e28fc3 --- /dev/null +++ b/.github/workflows/release-pr.yml @@ -0,0 +1,103 @@ +name: release-pr + +# Phase 1 of the release flow. Bumps the version, pushes the release +# commit to a release/vX.Y.Z branch, and opens a PR to main with +# auto-merge + squash. Once the maintainer approves (required by +# branch protection) and all status checks pass, the PR merges and +# release-publish.yml takes over to push the tag, publish to npm via +# OIDC, and create the GitHub Release. +on: + workflow_dispatch: + inputs: + increment: + description: "Version increment (used by release-it)" + required: false + type: choice + # The workflow defaults to patch. Choose "minor" (or higher) + # the first time you release after v0.0.5 has been removed + # from the orphan-tag cleanup: v0.0.5 was prematurely + # published to npm by the broken workflow, so the next + # publish must skip past it. + default: "patch" + options: + - patch + - minor + - major + +concurrency: + group: release + cancel-in-progress: false + +permissions: + contents: write + pull-requests: write + +jobs: + release-pr: + runs-on: ubuntu-latest + environment: production + permissions: + contents: write + issues: write + pull-requests: write + id-token: write + steps: + - uses: actions/checkout@v6 + with: + # release-it needs the full history + tags to compute the bump + # and write the CHANGELOG. + fetch-depth: 0 + - uses: actions/setup-node@v6 + with: + node-version: lts/* + package-manager-cache: false + - run: npm ci || npm install + # GitHub Actions runners ship without a configured git + # identity, so configure one from the actor that dispatched the + # workflow (per release-it CI docs). Needed for the local commit + # release-it creates before we push anything. + - name: Configure git identity + run: | + git config --global user.name "${GITHUB_ACTOR}" + git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com" + - name: Bump version + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Run release-it with everything that touches the remote + # disabled. It still bumps the version in package.json, + # writes CHANGELOG.md, creates a local commit, and tags the + # commit locally. Defer git push, npm publish, and the + # GitHub release to release-publish.yml so the commit can + # reach main via a PR (branch protection requires it). + npx release-it --ci \ + --increment "${{ inputs.increment }}" \ + --no-git.push \ + --no-npm.publish \ + --no-github.release + - name: Open release PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION=$(node -p "require('./package.json').version") + BRANCH="release/v${VERSION}" + # Push the release commit + local tag onto a release branch. + # The tag follows the same commit that the PR will contain, + # and release-publish.yml will retag onto the squash commit + # after merge. + git checkout -b "${BRANCH}" + git push origin "refs/tags/v${VERSION}" "${BRANCH}" + # Open the PR with a conventional title so commitlint + the + # pr-title check pass on the release PR. + gh pr create \ + --base main \ + --head "${BRANCH}" \ + --title "chore(release): v${VERSION}" \ + --body "Automated release PR for v${VERSION}. + + Merging this PR will trigger the release-publish workflow, which will push the git tag, publish to npm via OIDC trusted publishing, and create the GitHub Release." \ + --label release + # Auto-merge with squash waits for the 1 approving review (the + # dispatching actor can self-approve) and the required status + # checks (CI, commitlint, CodeQL) before merging. + gh pr merge --auto --squash "${BRANCH}" diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml new file mode 100644 index 0000000..f829dde --- /dev/null +++ b/.github/workflows/release-publish.yml @@ -0,0 +1,73 @@ +name: release-publish + +# Phase 2 of the release flow. Triggered after a release PR is +# squash-merged into main. Retags the merged commit, publishes the +# package to npm via OIDC trusted publishing, and creates the GitHub +# Release. +on: + pull_request: + types: [closed] + branches: [main] + +concurrency: + group: release-publish + cancel-in-progress: false + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + release-publish: + # Only run when the merged PR is one of our release PRs. + if: | + github.event.pull_request.merged == true && + startsWith(github.event.pull_request.head.ref, 'release/v') + runs-on: ubuntu-latest + environment: production + permissions: + contents: write + issues: write + pull-requests: write + id-token: write + steps: + - uses: actions/checkout@v6 + with: + # Need tags to retarget onto the merge commit and to be able + # to push the tag onto HEAD (the squash-merged commit). + fetch-depth: 0 + - uses: actions/setup-node@v6 + with: + node-version: lts/* + package-manager-cache: false + - run: npm ci || npm install + # GitHub Actions runners ship without a configured git identity. + - name: Configure git identity + run: | + git config --global user.name "${GITHUB_ACTOR}" + git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com" + - name: Recreate tag on the squash-merged commit and push + run: | + VERSION=$(node -p "require('./package.json').version") + TAG="v${VERSION}" + # Drop the stale local tag (release-it put it on the + # pre-merge commit) and put the tag on HEAD (the + # squash-merged commit). Force-update the remote tag if a + # prior broken run already pushed something to it. + git tag -d "${TAG}" 2>/dev/null || true + git tag "${TAG}" HEAD + git push origin ":refs/tags/${TAG}" 2>/dev/null || true + git push origin "${TAG}" + - name: Publish to npm + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: npm publish --provenance --access public + - name: Create GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION=$(node -p "require('./package.json').version") + gh release create "v${VERSION}" \ + --title "v${VERSION}" \ + --generate-notes diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 81cd04f..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: release - -# Releases are automated by release-it. The version bump, CHANGELOG.md, -# commit, tag, GitHub release, and npm publish (via OIDC trusted publishing) -# are all driven by `npm run release:ci`. Trigger manually from the -# Actions tab once the next batch of feature/fix PRs is on main. -on: - workflow_dispatch: - -concurrency: - group: release - cancel-in-progress: false - -jobs: - release: - runs-on: ubuntu-latest - environment: production - permissions: - contents: write - issues: write - pull-requests: write - id-token: write - steps: - - uses: actions/checkout@v7 - with: - # release-it needs the full history + tags to compute the bump - # and write the CHANGELOG. - fetch-depth: 0 - - uses: actions/setup-node@v6 - with: - node-version: lts/* - package-manager-cache: false - - run: npm ci || npm install - # release-it commits the version bump + CHANGELOG update and pushes - # the tag. GitHub Actions runners ship without a configured git - # identity, so configure one from the actor that dispatched the - # workflow (per release-it CI docs). - - name: Configure git identity - run: | - git config --global user.name "${GITHUB_ACTOR}" - git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com" - - name: Release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: npm run release:ci From 3d7f6bb874488912f800662ee42fec747cd594a4 Mon Sep 17 00:00:00 2001 From: release-fix-bot Date: Sat, 18 Jul 2026 20:48:13 +0000 Subject: [PATCH 2/2] fix(release): bump actions/checkout and actions/setup-node to v7 Both actions released v7.0.0 since the previous commit: - actions/checkout v7.0.0 (2026-06-18) - actions/setup-node v7.0.0 (2026-07-14) setup-node v7 includes a fix to stop exporting a dummy NODE_AUTH_TOKEN, which is relevant to the publish step in release-publish.yml. v7 is already used by other workflows in this repo (commitlint, docs, security) so there is no version-drift risk to introduce. --- .github/workflows/release-pr.yml | 4 ++-- .github/workflows/release-publish.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 6e28fc3..4478710 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -42,12 +42,12 @@ jobs: pull-requests: write id-token: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: # release-it needs the full history + tags to compute the bump # and write the CHANGELOG. fetch-depth: 0 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version: lts/* package-manager-cache: false diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index f829dde..f564910 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -32,12 +32,12 @@ jobs: pull-requests: write id-token: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: # Need tags to retarget onto the merge commit and to be able # to push the tag onto HEAD (the squash-merged commit). fetch-depth: 0 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version: lts/* package-manager-cache: false