diff --git a/.github/workflows/dev-release.yml b/.github/workflows/dev-release.yml index 50de0621..55e23f25 100644 --- a/.github/workflows/dev-release.yml +++ b/.github/workflows/dev-release.yml @@ -26,8 +26,9 @@ jobs: with: fetch-depth: 0 ref: dev - # Always use the built-in workflow token; PAT rotation/breakage must not block dev releases. - token: ${{ github.token }} + # PAT_TOKEN required: dev-release pushes version bump commits to dev, + # and protected branches reject github.token writes once status checks are required. + token: ${{ secrets.PAT_TOKEN }} - name: Setup Bun uses: oven-sh/setup-bun@v2 @@ -58,10 +59,10 @@ jobs: id: release env: HUSKY: 0 - # Use built-in GITHUB_TOKEN for release + issue operations. - # Checkout credentials are resolved above with PAT fallback. - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # PAT_TOKEN bypasses branch protection so the workflow can push + # the dev release commit and tag back to the protected dev branch. + GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} + GH_TOKEN: ${{ secrets.PAT_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | diff --git a/tests/unit/scripts/github/dev-release-workflow.test.ts b/tests/unit/scripts/github/dev-release-workflow.test.ts new file mode 100644 index 00000000..5f103afb --- /dev/null +++ b/tests/unit/scripts/github/dev-release-workflow.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, test } from 'bun:test'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +function resolvePath(relativePath: string) { + return path.resolve(import.meta.dir, relativePath); +} + +describe('dev release workflow', () => { + test('uses PAT_TOKEN for protected dev branch release pushes', () => { + const workflowPath = resolvePath('../../../../.github/workflows/dev-release.yml'); + + expect(fs.existsSync(workflowPath)).toBe(true); + + const workflow = fs.readFileSync(workflowPath, 'utf8'); + const checkoutSection = workflow.slice( + workflow.indexOf('- name: Checkout'), + workflow.indexOf('- name: Setup Bun') + ); + const releaseSection = workflow.slice( + workflow.indexOf('- name: Release'), + workflow.indexOf('- name: Notify Discord') + ); + + expect(workflow).toContain('name: Dev Release'); + expect(workflow).toContain('branches: [dev]'); + expect(checkoutSection).toContain("token: ${{ secrets.PAT_TOKEN }}"); + expect(checkoutSection).not.toContain('token: ${{ github.token }}'); + expect(releaseSection).toContain('GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}'); + expect(releaseSection).toContain('GH_TOKEN: ${{ secrets.PAT_TOKEN }}'); + expect(releaseSection).not.toContain('GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}'); + expect(releaseSection).not.toContain('GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}'); + }); +});