Repository navigation
ci(release): push the Version Packages PR as a GitHub App, so its CI runs #1092
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
auxesis
wants to merge
2
commits into
main
Choose a base branch
from
ci/version-packages-app-token
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| import { describe, expect, it } from 'vitest' | ||
| import { readWorkflow } from './lib/workflows.mjs' | ||
|
|
||
| /** | ||
| * The Version Packages PR must be pushed with a GitHub App's token, not with | ||
| * the workflow's own `GITHUB_TOKEN`. GitHub starts no workflow run for a push | ||
| * made with `GITHUB_TOKEN`, so a Version Packages PR pushed that way carries no | ||
| * CI at all, and #1020 merged untested with stale skill pins (#1044). | ||
| * | ||
| * The App's key is held in the `release` environment, which only `main` may | ||
| * deploy to, so a workflow on any other branch cannot mint a token. The token | ||
| * is narrowed to this repository and to the two permissions changesets needs, | ||
| * whatever the App itself is granted. | ||
| * | ||
| * Each property below fails open if it is removed: the release still works, | ||
| * and the only symptom is a later Version Packages PR with no checks, or a key | ||
| * reachable from every branch. | ||
| */ | ||
|
|
||
| const RELEASE_WORKFLOW = '.github/workflows/release.yml' | ||
| const TOKEN_ACTION = 'actions/create-github-app-token' | ||
| const CHANGESETS_ACTION = 'changesets/action' | ||
|
|
||
| const release = readWorkflow(RELEASE_WORKFLOW)?.jobs?.release | ||
| const steps = release?.steps ?? [] | ||
| const tokenIndex = steps.findIndex((step) => | ||
| String(step?.uses ?? '').startsWith(`${TOKEN_ACTION}@`), | ||
| ) | ||
| const changesetsIndex = steps.findIndex((step) => | ||
| String(step?.uses ?? '').startsWith(`${CHANGESETS_ACTION}@`), | ||
| ) | ||
| const tokenStep = steps[tokenIndex] | ||
| const changesetsStep = steps[changesetsIndex] | ||
|
|
||
| describe('release.yml pushes the Version Packages PR as a GitHub App', () => { | ||
| it('finds the release job, the token step and the changesets step', () => { | ||
| expect(release).toBeTruthy() | ||
| expect(tokenIndex).toBeGreaterThanOrEqual(0) | ||
| expect(changesetsIndex).toBeGreaterThanOrEqual(0) | ||
| }) | ||
|
|
||
| it('runs the release job in the release environment', () => { | ||
| expect(release.environment).toBe('release') | ||
| }) | ||
|
|
||
| it('pins the token action to a commit', () => { | ||
| expect(tokenStep.uses).toMatch(new RegExp(`^${TOKEN_ACTION}@[0-9a-f]{40}$`)) | ||
| }) | ||
|
|
||
| it('mints the token from the release environment secrets', () => { | ||
| expect(tokenStep.with?.['client-id']).toBe( | ||
| '${{ secrets.RELEASE_PLZ_APP_CLIENT_ID }}', | ||
| ) | ||
| expect(tokenStep.with?.['private-key']).toBe( | ||
| '${{ secrets.RELEASE_PLZ_APP_PRIVATE_KEY }}', | ||
| ) | ||
| }) | ||
|
|
||
| it('narrows the token to this repository and two permissions', () => { | ||
| expect(tokenStep.with?.repositories).toBe('stack') | ||
| const permissions = Object.entries(tokenStep.with ?? {}) | ||
| .filter(([key]) => key.startsWith('permission-')) | ||
| .sort(([a], [b]) => a.localeCompare(b)) | ||
| expect(permissions).toEqual([ | ||
| ['permission-contents', 'write'], | ||
| ['permission-pull-requests', 'write'], | ||
| ]) | ||
| }) | ||
|
|
||
| it('mints the token before changesets runs, and hands it to changesets', () => { | ||
| expect(tokenStep.id).toBeTruthy() | ||
| expect(tokenIndex).toBeLessThan(changesetsIndex) | ||
| expect(changesetsStep.env?.GITHUB_TOKEN).toBe( | ||
| `\${{ steps.${tokenStep.id}.outputs.token || secrets.GITHUB_TOKEN }}`, | ||
| ) | ||
| }) | ||
|
|
||
| it('publishes even when the mint fails, and says the PR will carry no CI', () => { | ||
| // The mint gates `changeset publish` in the same job. A rotated secret or | ||
| // a suspended App must cost the PR its CI, not the release its publish. | ||
| expect(tokenStep['continue-on-error']).toBe(true) | ||
| const warning = steps | ||
| .slice(tokenIndex + 1, changesetsIndex) | ||
| .find((step) => String(step?.run ?? '').includes('::warning')) | ||
| expect(warning, 'no step warns when the token is empty').toBeDefined() | ||
| expect(warning.if).toBe(`\${{ steps.${tokenStep.id}.outputs.token == '' }}`) | ||
| }) | ||
|
|
||
| it('keeps the commits signed by GitHub', () => { | ||
| expect(changesetsStep.with?.commitMode).toBe('github-api') | ||
| }) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This step makes npm publishing depend on the App. The same token step runs before
changeset publishin the same job. If the mint fails, the job stops and nothing publishes. A mint can fail if the secret is missing or rotated, if the App is uninstalled or suspended, or if thereleaseenvironment rejects the job. A bad CI convenience then blocks a release that is already merged. The PR body says this is intended, but a fallback is cheap. Setcontinue-on-error: trueon this step. Then setGITHUB_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}. Publishing keeps working, and only the PR CI is lost. Add a::warning::step when the output is empty so the failure is visible. If you want the hard stop, say so in a comment, and note that the test asserts the exactGITHUB_TOKENexpression and must change.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@freshtonic verified and taken: the mint sits before
changesets/action, which runschangeset publishin the same step, so a failed mint did stop the publish. Fixed in 90e5f8c —continue-on-error: trueon the mint,GITHUB_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}, and a::warningstep when the token is empty naming what was lost and what to check. The test asserts the fallback expression, thecontinue-on-error, and the warning step'sif.