diff --git a/.github/workflows/draft.yml b/.github/workflows/draft.yml index 505f20c..1d6ead5 100644 --- a/.github/workflows/draft.yml +++ b/.github/workflows/draft.yml @@ -37,15 +37,19 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PublishBetaPackages: - uses: ./.github/workflows/publish.yml + runs-on: ubuntu-latest + timeout-minutes: 5 if: github.event_name == 'push' permissions: - contents: write - with: - tag_name: ${{ needs.PublishBetaRelease.outputs.tag_name }} - target_commitish: ${{ github.sha }} - secrets: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + actions: write + steps: + - name: Dispatch publish workflow + run: |- + gh workflow run publish.yml --repo ${{ github.repository }} \ + -f tag_name="${{ needs.PublishBetaRelease.outputs.tag_name }}" \ + -f target_commitish="${{ github.sha }}" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} needs: - PublishBetaRelease UpdateReleaseDraft: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c948fe9..0856bf6 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,7 +8,7 @@ name: Publish Release release: types: - published - workflow_call: + workflow_dispatch: inputs: tag_name: required: true @@ -16,15 +16,13 @@ name: Publish Release target_commitish: required: true type: string - secrets: - NPM_TOKEN: - required: true jobs: PublishPackages: runs-on: ubuntu-latest timeout-minutes: 20 permissions: contents: write + id-token: write steps: - name: Checkout uses: actions/checkout@v4 @@ -33,11 +31,11 @@ jobs: - name: Install Node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: Install pnpm uses: pnpm/action-setup@v4 with: - version: 9 + version: 10 - name: Install Dependencies run: pnpm install --no-frozen-lockfile - name: Run Build @@ -50,10 +48,6 @@ jobs: (cd packages/lib && npm version --no-git-tag-version ${{ inputs.tag_name || github.event.release.tag_name }}) (cd packages/cli && npm version --no-git-tag-version ${{ inputs.tag_name || github.event.release.tag_name }}) (cd packages/actions && npm version --no-git-tag-version ${{ inputs.tag_name || github.event.release.tag_name }}) - - name: Setup npm auth - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Publish packages run: |- TAG_NAME="${{ inputs.tag_name || github.event.release.tag_name }}" @@ -67,8 +61,6 @@ jobs: echo "Publishing with latest tag" pnpm -r publish --access public --no-git-checks fi - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} CommitVersionBump: runs-on: ubuntu-latest timeout-minutes: 20 diff --git a/workflows/draft.wac.ts b/workflows/draft.wac.ts index 0ac844d..db9a64d 100644 --- a/workflows/draft.wac.ts +++ b/workflows/draft.wac.ts @@ -1,9 +1,9 @@ import { Workflow, NormalJob, - ReusableWorkflowCallJob, Step, expressions as ex, + dedentString, } from '../packages/lib/src/index.js' const betaReleaseStep = new Step({ @@ -34,20 +34,33 @@ const betaReleaseJob = new NormalJob('PublishBetaRelease', { }, }).addStep(betaReleaseStep) -const publishBetaJob = new ReusableWorkflowCallJob('PublishBetaPackages', { - uses: './.github/workflows/publish.yml', +// Dispatches publish.yml instead of calling it as a reusable workflow: npm +// trusted publishing (OIDC) validates the top-level workflow's filename +// against the package's single trusted publisher, which is publish.yml. +// GITHUB_TOKEN-triggered events don't start workflows, but workflow_dispatch +// is exempt from that rule. +const publishBetaJob = new NormalJob('PublishBetaPackages', { + 'runs-on': 'ubuntu-latest', + 'timeout-minutes': 5, if: "github.event_name == 'push'", permissions: { - contents: 'write', - }, - with: { - tag_name: ex.expn(`needs.${betaReleaseJob.name}.outputs.tag_name`), - target_commitish: ex.expn('github.sha'), - }, - secrets: { - NPM_TOKEN: ex.secret('NPM_TOKEN'), + actions: 'write', }, -}).needs([betaReleaseJob]) +}) + .addStep( + new Step({ + name: 'Dispatch publish workflow', + run: dedentString(` + gh workflow run publish.yml --repo ${ex.expn('github.repository')} \\ + -f tag_name="${ex.expn(`needs.${betaReleaseJob.name}.outputs.tag_name`)}" \\ + -f target_commitish="${ex.expn('github.sha')}" + `), + env: { + GH_TOKEN: ex.secret('GITHUB_TOKEN'), + }, + }), + ) + .needs([betaReleaseJob]) const draftStep = new Step({ name: 'Draft next release', diff --git a/workflows/publish.wac.ts b/workflows/publish.wac.ts index 9a8d096..bc8d2d5 100644 --- a/workflows/publish.wac.ts +++ b/workflows/publish.wac.ts @@ -22,13 +22,15 @@ const checkout = new Step({ const installNode = new Step({ name: 'Install Node', uses: 'actions/setup-node@v4', - with: { 'node-version': 20 }, + // npm trusted publishing (OIDC) requires Node >= 22.14 + with: { 'node-version': 24 }, }) const installPnpm = new Step({ name: 'Install pnpm', uses: 'pnpm/action-setup@v4', - with: { version: 9 }, + // pnpm 10 supports OIDC trusted publishing; reads the same v9 lockfile + with: { version: 10 }, }) const installDependencies = new Step({ @@ -54,14 +56,10 @@ const bumpVersions = new Step({ `), }) -const setupNpmAuth = new Step({ - name: 'Setup npm auth', - run: 'echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc', - env: { - NPM_TOKEN: ex.secret('NPM_TOKEN'), - }, -}) - +// Auth is handled by npm trusted publishing (OIDC): the job's id-token +// permission lets pnpm mint a short-lived token from the runner's OIDC +// identity. The trusted publisher configured on npmjs.com must reference +// this workflow file (publish.yml). const publishPackages = new Step({ name: 'Publish packages', run: dedentString(` @@ -77,9 +75,6 @@ const publishPackages = new Step({ pnpm -r publish --access public --no-git-checks fi `), - env: { - NPM_TOKEN: ex.secret('NPM_TOKEN'), - }, }) const publishJob = new NormalJob('PublishPackages', { @@ -87,6 +82,7 @@ const publishJob = new NormalJob('PublishPackages', { 'timeout-minutes': 20, permissions: { contents: 'write', + 'id-token': 'write', }, }).addSteps([ checkout, @@ -95,7 +91,6 @@ const publishJob = new NormalJob('PublishPackages', { installDependencies, build, bumpVersions, - setupNpmAuth, publishPackages, ]) @@ -139,14 +134,15 @@ export const publishWorkflow = new Workflow('publish', { release: { types: ['published'], }, - workflow_call: { + // Dispatched (not workflow_call) by draft.yml for beta releases: npm + // trusted publishing validates the top-level workflow's filename, and a + // package can only have one trusted publisher — so every publish must + // run with publish.yml as the top-level workflow. + workflow_dispatch: { inputs: { tag_name: { required: true, type: 'string' }, target_commitish: { required: true, type: 'string' }, }, - secrets: { - NPM_TOKEN: { required: true }, - }, }, }, }).addJobs([publishJob, commitVersionBumpJob])