diff --git a/.github/workflows/changesets.yml b/.github/workflows/changesets.yml index 97229a08..1294cfe2 100644 --- a/.github/workflows/changesets.yml +++ b/.github/workflows/changesets.yml @@ -12,6 +12,8 @@ concurrency: permissions: contents: write pull-requests: write + # actions: write dispatches release.yml once the Version Packages PR is merged. + actions: write jobs: version: @@ -26,10 +28,34 @@ jobs: node-version: 24.13.0 cache: pnpm - run: pnpm install --frozen-lockfile - # PRs opened with github.token do not trigger pull_request CI; release.yml packaging smoke remains the publish gate. - uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # v1.9.0 + id: changesets with: version: pnpm changeset version createGithubReleases: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # The Version Packages PR only restates what the source changesets already + # said, so it is merged unattended. It cannot gate itself: a PR opened with + # GITHUB_TOKEN never triggers pull_request CI, so release.yml runs the full + # ci.yml gate before anything reaches npm. + - name: Merge the Version Packages PR and start the release + if: ${{ steps.changesets.outputs.pullRequestNumber != '' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PULL_REQUEST: ${{ steps.changesets.outputs.pullRequestNumber }} + run: | + set -euo pipefail + gh pr merge "$PULL_REQUEST" \ + --repo "$GITHUB_REPOSITORY" \ + --squash \ + --delete-branch + + # Merging with GITHUB_TOKEN raises no push event, so release.yml would + # never see this commit. workflow_dispatch is the documented exception + # to that rule and is what keeps the chain alive. This is also why the + # merge cannot loop back into this workflow. + gh workflow run release.yml \ + --repo "$GITHUB_REPOSITORY" \ + --ref main diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b224fd8b..1595a972 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,9 @@ on: push: branches: - main + # release.yml calls this as the pre-publish gate so the release runs the exact + # same checks as a pull request, with no second copy of the step list to drift. + workflow_call: permissions: contents: read diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 29bad59a..08f009b7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,10 @@ on: push: branches: - main + # changesets.yml merges the Version Packages PR with GITHUB_TOKEN, which raises + # no push event. workflow_dispatch is the documented exception to that rule, so + # it is how the auto-merged release reaches this workflow. + workflow_dispatch: # Serialize releases: a newer push replaces any pending (not-yet-started) run in # this group. An intermediate version whose run is superseded before it starts is @@ -67,9 +71,19 @@ jobs: cat view-error.log >&2 exit 1 - publish: + # Nothing reaches npm without the full pull-request gate. The Version Packages + # PR cannot run it itself: PRs opened with GITHUB_TOKEN never trigger workflows, + # so this is where a broken release is actually caught. + verify: needs: guard if: ${{ needs.guard.outputs.publish == 'true' }} + permissions: + contents: read + uses: ./.github/workflows/ci.yml + + publish: + needs: [guard, verify] + if: ${{ needs.guard.outputs.publish == 'true' }} runs-on: ubuntu-latest # id-token: write is required for npm OIDC trusted publishing; no npm secret # or NODE_AUTH_TOKEN is used. contents stays read-only here. @@ -96,7 +110,7 @@ jobs: working-directory: apps/cli/dist tag: - needs: [guard, publish] + needs: [guard, verify, publish] if: ${{ needs.guard.outputs.publish == 'true' }} runs-on: ubuntu-latest # contents: write is scoped to this job only, purely to push the release tag. @@ -127,7 +141,7 @@ jobs: git push origin "$tag" release: - needs: [guard, publish, tag] + needs: [guard, verify, publish, tag] if: ${{ needs.guard.outputs.publish == 'true' }} runs-on: ubuntu-latest # contents: write is scoped to this job only, purely to create the GitHub Release.