Skip to content

Commit b1ca2fe

Browse files
committed
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-<version> 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<version> 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.
1 parent a907bdf commit b1ca2fe

2 files changed

Lines changed: 157 additions & 16 deletions

File tree

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,97 @@
1+
# Tags and publishes an alpha release when its release PR is merged.
2+
#
3+
# Triggered when a `release/alpha-*` PR (opened by "Prepare Alpha Release") is
4+
# merged into `alpha`. It builds, pushes the `v<version>` tag, and publishes
5+
# @reearth/core to the npm `alpha` dist-tag.
6+
#
7+
# Publishing is secured via npm Trusted Publishing (OIDC): no NPM_TOKEN, no PAT,
8+
# no GitHub App. Auth is a short-lived OIDC handshake and every publish carries
9+
# a provenance attestation (verify with `npm audit signatures`).
10+
#
11+
# Tag + publish are combined into this single job on purpose: a tag pushed by
12+
# GITHUB_TOKEN does not trigger other workflows, so publishing here (rather than
13+
# in a separate `on: push: tags` workflow) is what lets us avoid a privileged
14+
# token entirely.
15+
#
16+
# npm Trusted Publisher (one-time, already configured on npmjs.com):
17+
# @reearth/core -> repo reearth/core, workflow release_alpha.yml, action
18+
# "npm publish". Keep this file named release_alpha.yml so that stays valid.
19+
#
20+
# beta/latest still publish via npm_release.yml + NPM_TOKEN and are unaffected.
21+
22+
name: Release Alpha
23+
124
on:
2-
push:
3-
branches: alpha
4-
paths-ignore:
5-
- "package.json"
6-
workflow_dispatch:
25+
pull_request:
26+
types: [closed]
27+
28+
permissions:
29+
contents: write # push the version tag
30+
id-token: write # OIDC token for npm Trusted Publishing + provenance
731

832
jobs:
9-
release:
10-
permissions:
11-
contents: write
12-
uses: reearth/core/.github/workflows/npm_release.yml@beta
13-
with:
14-
branch: alpha
15-
tag: alpha
16-
version-args: --preid alpha prerelease
17-
secrets:
18-
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
19-
PAT: ${{ secrets.PAT }}
33+
publish:
34+
name: Tag and publish alpha
35+
runs-on: ubuntu-latest
36+
# Gates the whole job (tag + publish) on a required reviewer approving the
37+
# npm-publish environment, so a merged PR can't auto-publish unreviewed.
38+
environment: npm-publish
39+
# Only for merged release PRs into alpha (not every closed PR).
40+
if: >-
41+
github.event.pull_request.merged == true &&
42+
github.event.pull_request.base.ref == 'alpha' &&
43+
startsWith(github.event.pull_request.head.ref, 'release/alpha-')
44+
steps:
45+
- uses: actions/checkout@v4
46+
with:
47+
ref: ${{ github.event.pull_request.merge_commit_sha }}
48+
- uses: actions/setup-node@v4
49+
with:
50+
# Trusted Publishing requires Node >=22.14 and npm >=11.5.1. Pin a
51+
# concrete >=22.14 version so the floor can't regress via runner
52+
# installer changes.
53+
node-version: 22.14.0
54+
registry-url: "https://registry.npmjs.org"
55+
- name: Activate npm via corepack
56+
# Node 22 ships npm 10.x; Trusted Publishing needs >=11.5.1. corepack
57+
# fetches a fresh npm binary, avoiding the broken self-upgrade path.
58+
run: |
59+
corepack enable npm
60+
corepack prepare npm@11.5.1 --activate
61+
npm --version
62+
- name: Run install
63+
run: yarn install --frozen-lockfile
64+
- name: Build
65+
run: yarn build
66+
- name: Verify tag matches package.json version
67+
id: version
68+
run: |
69+
VERSION=$(node -p "require('./package.json').version")
70+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
71+
echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"
72+
echo "Publishing @reearth/core@$VERSION"
73+
- name: Create and push tag
74+
run: |
75+
TAG="${{ steps.version.outputs.tag }}"
76+
HEAD_SHA=$(git rev-parse HEAD)
77+
# Commit the remote tag points to (peel annotated tags via ^{}); empty
78+
# if the tag does not exist remotely.
79+
REMOTE_SHA=$(git ls-remote --tags origin "refs/tags/${TAG}^{}" | awk '{print $1}')
80+
[ -z "$REMOTE_SHA" ] && REMOTE_SHA=$(git ls-remote --tags origin "refs/tags/${TAG}" | awk '{print $1}')
81+
if [ -n "$REMOTE_SHA" ]; then
82+
if [ "$REMOTE_SHA" = "$HEAD_SHA" ]; then
83+
echo "Tag ${TAG} already exists and matches HEAD; skipping tag push."
84+
else
85+
echo "::error::Tag ${TAG} already exists but points to ${REMOTE_SHA}, not the release commit ${HEAD_SHA}. Refusing to publish a mismatched build."
86+
exit 1
87+
fi
88+
else
89+
git config user.name "github-actions[bot]"
90+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
91+
git tag -a "${TAG}" -m "${TAG}"
92+
git push origin "${TAG}"
93+
fi
94+
- name: Publish to npm with provenance (OIDC)
95+
# No NPM_TOKEN: auth is the OIDC handshake configured as a Trusted
96+
# Publisher on npmjs.com. --access public because @reearth/core is scoped.
97+
run: npm publish --provenance --access public --tag alpha
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Prepares an alpha release.
2+
#
3+
# Dispatch this workflow on the `alpha` branch. It bumps the alpha prerelease
4+
# version, commits it to a `release/alpha-<version>` branch, and opens a PR
5+
# into `alpha`. Merging that PR triggers release_alpha.yml, which tags the
6+
# release and publishes to npm (dist-tag `alpha`) via OIDC.
7+
#
8+
# Uses only the built-in GITHUB_TOKEN: no PAT, no GitHub App. The version bump
9+
# never touches the protected `alpha` branch directly; it goes through the PR,
10+
# so no branch-protection bypass is required.
11+
12+
name: Prepare Alpha Release
13+
14+
on:
15+
workflow_dispatch:
16+
17+
permissions:
18+
contents: write # push the release/alpha-* branch
19+
pull-requests: write # open the release PR
20+
21+
jobs:
22+
prepare:
23+
name: Open alpha release PR
24+
runs-on: ubuntu-latest
25+
if: github.ref == 'refs/heads/alpha'
26+
steps:
27+
- uses: actions/checkout@v4
28+
- uses: actions/setup-node@v4
29+
with:
30+
node-version: 22.14.0
31+
- name: Set up git user
32+
run: |
33+
git config user.name "github-actions[bot]"
34+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
35+
- name: Run install
36+
run: yarn install --frozen-lockfile
37+
- name: Bump alpha prerelease version
38+
id: bump
39+
# --no-git-tag-version: we commit the bump onto the release branch
40+
# ourselves; the tag is created later by the publish workflow. This
41+
# still runs the preversion (yarn test run) and version (yarn build)
42+
# npm scripts, so tests and the build gate the bump.
43+
run: |
44+
npm version --preid alpha prerelease --no-git-tag-version
45+
VERSION=$(node -p "require('./package.json').version")
46+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
47+
- name: Create release branch and PR
48+
env:
49+
VERSION: ${{ steps.bump.outputs.version }}
50+
GH_TOKEN: ${{ github.token }}
51+
run: |
52+
BRANCH="release/alpha-${VERSION}"
53+
git checkout -b "$BRANCH"
54+
git add package.json
55+
git commit -m "chore: release ${VERSION}"
56+
git push origin "$BRANCH"
57+
gh pr create \
58+
--base alpha \
59+
--head "$BRANCH" \
60+
--title "chore: release ${VERSION}" \
61+
--body "Prepares alpha release \`${VERSION}\`.
62+
63+
Merging this PR tags \`v${VERSION}\` and publishes \`@reearth/core@${VERSION}\` to the npm \`alpha\` dist-tag via OIDC trusted publishing (with provenance)."

0 commit comments

Comments
 (0)