From b1ca2fe007d0d4027f3af801624fe9f987bafccd Mon Sep 17 00:00:00 2001 From: Nimit Bhandari Date: Wed, 15 Jul 2026 15:18:14 +0530 Subject: [PATCH] ci: PR-based alpha release with OIDC trusted publishing Replace the direct-push + NPM_TOKEN alpha release with a resium-style, credential-free flow: - release_alpha_prepare.yml (new): workflow_dispatch bumps the alpha prerelease version, commits it to a release/alpha- branch, and opens a PR into alpha. GITHUB_TOKEN only. - release_alpha.yml (rewritten): on merge of a release/alpha-* PR, builds, pushes the v tag, and runs npm publish --provenance --tag alpha via OIDC trusted publishing. GITHUB_TOKEN (tag) + OIDC (publish). Gated on the npm-publish environment so publishing waits for a required-reviewer approval. Tag and publish are combined into one merge-triggered job so no privileged token is needed to cross-trigger publishing. No NPM_TOKEN, no PAT, no GitHub App, and no branch-protection bypass. The bump never pushes directly to the protected alpha branch. Requires the repo default branch to be alpha so the Prepare workflow is dispatchable. npm Trusted Publisher stays registered as workflow release_alpha.yml. beta/latest still use npm_release.yml + NPM_TOKEN. --- .github/workflows/release_alpha.yml | 110 +++++++++++++++++--- .github/workflows/release_alpha_prepare.yml | 63 +++++++++++ 2 files changed, 157 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/release_alpha_prepare.yml diff --git a/.github/workflows/release_alpha.yml b/.github/workflows/release_alpha.yml index 7baa5b7..1b42965 100644 --- a/.github/workflows/release_alpha.yml +++ b/.github/workflows/release_alpha.yml @@ -1,19 +1,97 @@ +# Tags and publishes an alpha release when its release PR is merged. +# +# Triggered when a `release/alpha-*` PR (opened by "Prepare Alpha Release") is +# merged into `alpha`. It builds, pushes the `v` tag, and publishes +# @reearth/core to the npm `alpha` dist-tag. +# +# Publishing is secured via npm Trusted Publishing (OIDC): no NPM_TOKEN, no PAT, +# no GitHub App. Auth is a short-lived OIDC handshake and every publish carries +# a provenance attestation (verify with `npm audit signatures`). +# +# Tag + publish are combined into this single job on purpose: a tag pushed by +# GITHUB_TOKEN does not trigger other workflows, so publishing here (rather than +# in a separate `on: push: tags` workflow) is what lets us avoid a privileged +# token entirely. +# +# npm Trusted Publisher (one-time, already configured on npmjs.com): +# @reearth/core -> repo reearth/core, workflow release_alpha.yml, action +# "npm publish". Keep this file named release_alpha.yml so that stays valid. +# +# beta/latest still publish via npm_release.yml + NPM_TOKEN and are unaffected. + +name: Release Alpha + on: - push: - branches: alpha - paths-ignore: - - "package.json" - workflow_dispatch: + pull_request: + types: [closed] + +permissions: + contents: write # push the version tag + id-token: write # OIDC token for npm Trusted Publishing + provenance jobs: - release: - permissions: - contents: write - uses: reearth/core/.github/workflows/npm_release.yml@beta - with: - branch: alpha - tag: alpha - version-args: --preid alpha prerelease - secrets: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - PAT: ${{ secrets.PAT }} + publish: + name: Tag and publish alpha + runs-on: ubuntu-latest + # Gates the whole job (tag + publish) on a required reviewer approving the + # npm-publish environment, so a merged PR can't auto-publish unreviewed. + environment: npm-publish + # Only for merged release PRs into alpha (not every closed PR). + if: >- + github.event.pull_request.merged == true && + github.event.pull_request.base.ref == 'alpha' && + startsWith(github.event.pull_request.head.ref, 'release/alpha-') + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.merge_commit_sha }} + - uses: actions/setup-node@v4 + with: + # Trusted Publishing requires Node >=22.14 and npm >=11.5.1. Pin a + # concrete >=22.14 version so the floor can't regress via runner + # installer changes. + node-version: 22.14.0 + registry-url: "https://registry.npmjs.org" + - name: Activate npm via corepack + # Node 22 ships npm 10.x; Trusted Publishing needs >=11.5.1. corepack + # fetches a fresh npm binary, avoiding the broken self-upgrade path. + run: | + corepack enable npm + corepack prepare npm@11.5.1 --activate + npm --version + - name: Run install + run: yarn install --frozen-lockfile + - name: Build + run: yarn build + - name: Verify tag matches package.json version + id: version + run: | + VERSION=$(node -p "require('./package.json').version") + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "tag=v$VERSION" >> "$GITHUB_OUTPUT" + echo "Publishing @reearth/core@$VERSION" + - name: Create and push tag + run: | + TAG="${{ steps.version.outputs.tag }}" + HEAD_SHA=$(git rev-parse HEAD) + # Commit the remote tag points to (peel annotated tags via ^{}); empty + # if the tag does not exist remotely. + REMOTE_SHA=$(git ls-remote --tags origin "refs/tags/${TAG}^{}" | awk '{print $1}') + [ -z "$REMOTE_SHA" ] && REMOTE_SHA=$(git ls-remote --tags origin "refs/tags/${TAG}" | awk '{print $1}') + if [ -n "$REMOTE_SHA" ]; then + if [ "$REMOTE_SHA" = "$HEAD_SHA" ]; then + echo "Tag ${TAG} already exists and matches HEAD; skipping tag push." + else + echo "::error::Tag ${TAG} already exists but points to ${REMOTE_SHA}, not the release commit ${HEAD_SHA}. Refusing to publish a mismatched build." + exit 1 + fi + else + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git tag -a "${TAG}" -m "${TAG}" + git push origin "${TAG}" + fi + - name: Publish to npm with provenance (OIDC) + # No NPM_TOKEN: auth is the OIDC handshake configured as a Trusted + # Publisher on npmjs.com. --access public because @reearth/core is scoped. + run: npm publish --provenance --access public --tag alpha diff --git a/.github/workflows/release_alpha_prepare.yml b/.github/workflows/release_alpha_prepare.yml new file mode 100644 index 0000000..a057b30 --- /dev/null +++ b/.github/workflows/release_alpha_prepare.yml @@ -0,0 +1,63 @@ +# Prepares an alpha release. +# +# Dispatch this workflow on the `alpha` branch. It bumps the alpha prerelease +# version, commits it to a `release/alpha-` branch, and opens a PR +# into `alpha`. Merging that PR triggers release_alpha.yml, which tags the +# release and publishes to npm (dist-tag `alpha`) via OIDC. +# +# Uses only the built-in GITHUB_TOKEN: no PAT, no GitHub App. The version bump +# never touches the protected `alpha` branch directly; it goes through the PR, +# so no branch-protection bypass is required. + +name: Prepare Alpha Release + +on: + workflow_dispatch: + +permissions: + contents: write # push the release/alpha-* branch + pull-requests: write # open the release PR + +jobs: + prepare: + name: Open alpha release PR + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/alpha' + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22.14.0 + - name: Set up git user + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + - name: Run install + run: yarn install --frozen-lockfile + - name: Bump alpha prerelease version + id: bump + # --no-git-tag-version: we commit the bump onto the release branch + # ourselves; the tag is created later by the publish workflow. This + # still runs the preversion (yarn test run) and version (yarn build) + # npm scripts, so tests and the build gate the bump. + run: | + npm version --preid alpha prerelease --no-git-tag-version + VERSION=$(node -p "require('./package.json').version") + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + - name: Create release branch and PR + env: + VERSION: ${{ steps.bump.outputs.version }} + GH_TOKEN: ${{ github.token }} + run: | + BRANCH="release/alpha-${VERSION}" + git checkout -b "$BRANCH" + git add package.json + git commit -m "chore: release ${VERSION}" + git push origin "$BRANCH" + gh pr create \ + --base alpha \ + --head "$BRANCH" \ + --title "chore: release ${VERSION}" \ + --body "Prepares alpha release \`${VERSION}\`. + + Merging this PR tags \`v${VERSION}\` and publishes \`@reearth/core@${VERSION}\` to the npm \`alpha\` dist-tag via OIDC trusted publishing (with provenance)."