Skip to content

Commit 98997f8

Browse files
ci(release): make the Changesets version PR pass its own required checks (#791)
1 parent 42d74b4 commit 98997f8

5 files changed

Lines changed: 118 additions & 1 deletion

File tree

‎.changeset/config.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"repo": "cameri/nostream"
77
}
88
],
9-
"commit": true,
9+
"commit": ["../scripts/changeset-commit-message.cjs", { "skipCI": false }],
1010
"privatePackages": {
1111
"tag": true
1212
},
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
---
3+
4+
Make the Changesets version PR pass its own required checks: give its commit message a conventional subject and drop the `[skip ci]` that suppressed every workflow run for it.

‎.github/workflows/changesets.yml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,16 @@ jobs:
1616
contents: write
1717
pull-requests: write
1818
steps:
19+
# The default GITHUB_TOKEN must not be the credential behind the version
20+
# branch push: GitHub starts no workflow runs for events a GITHUB_TOKEN
21+
# creates, so the version PR would never run its own required checks.
22+
# RELEASE_PAT is the environment secret this job already hands to the
23+
# action for its API calls; the push needs the same credential.
1924
- name: Checkout
2025
uses: actions/checkout@v3
2126
with:
2227
fetch-depth: 0
28+
token: ${{ secrets.RELEASE_PAT }}
2329

2430
- uses: pnpm/action-setup@v4
2531

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Conventional-commit messages for Changesets.
3+
*
4+
* Changesets' stock messages are `RELEASING: Releasing N package(s)` with a
5+
* trailing `[skip ci]`. Both halves break the version PR this repo's release
6+
* flow depends on:
7+
*
8+
* - `Lint commits` (commitlint, `@commitlint/config-conventional`) rejects
9+
* `RELEASING:` as an unknown type, so a required check fails on a message no
10+
* human wrote. It only ever looked green because a `workflow_dispatch` run
11+
* carries no pull request context and returns a vacuous pass.
12+
* - `[skip ci]` makes GitHub skip both push and pull_request runs for that
13+
* commit, so the version PR's other five required checks never report either
14+
* and the pull request cannot satisfy the branch's required-status-checks
15+
* ruleset. Replacing the head commit with a `[skip ci]`-free one is the only
16+
* thing that made those checks run.
17+
*
18+
* `changeset add` keeps the stock `docs(changeset): <summary>` subject. The
19+
* version commit becomes `chore: release <package>@<version>`. `skipCI` is
20+
* still honoured, so the documented option keeps meaning what it says.
21+
*/
22+
23+
const skipCIMarker = (options, phase) =>
24+
options?.skipCI === true || options?.skipCI === phase ? '\n\n[skip ci]\n' : ''
25+
26+
module.exports = {
27+
getAddMessage: (changeset, options) => `docs(changeset): ${changeset.summary}${skipCIMarker(options, 'add')}`,
28+
29+
getVersionMessage: (releasePlan, options) => {
30+
const released = releasePlan.releases.filter((release) => release.type !== 'none')
31+
const subject = released.length
32+
? `release ${released.map((release) => `${release.name}@${release.newVersion}`).join(', ')}`
33+
: 'version packages'
34+
35+
return `chore: ${subject}${skipCIMarker(options, 'version')}`
36+
},
37+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { readFileSync } from 'fs'
2+
import { createRequire } from 'module'
3+
import { join } from 'path'
4+
5+
import chai from 'chai'
6+
7+
const { expect } = chai
8+
9+
type CommitModule = {
10+
getAddMessage: (changeset: { summary: string }, options: unknown) => string
11+
getVersionMessage: (releasePlan: { releases: unknown[] }, options: unknown) => string
12+
}
13+
14+
describe('changesets commit message', () => {
15+
const config = JSON.parse(readFileSync(join(process.cwd(), '.changeset', 'config.json'), 'utf-8')) as {
16+
commit: unknown
17+
}
18+
19+
// createRequire works whether mocha loads this spec as CommonJS or as an ES
20+
// module, which `require` itself does not.
21+
const loadFromRepo = createRequire(join(process.cwd(), 'index.js'))
22+
23+
// Changesets resolves the specifier in the `commit` tuple as if it were
24+
// imported from `<repo>/.changeset/x.mjs`, so that is the base used here too.
25+
const commitModule = (): CommitModule => {
26+
expect(config.commit, '`commit` in .changeset/config.json must be a [module, options] tuple').to.be.an('array')
27+
const specifier = (config.commit as unknown[])[0]
28+
expect(specifier, 'that tuple must name a module').to.be.a('string')
29+
30+
return loadFromRepo(join(process.cwd(), '.changeset', specifier as string)) as CommitModule
31+
}
32+
33+
// The options Changesets actually passes to the module, so a change to them
34+
// (say `skipCI: true`) cannot quietly put `[skip ci]` back on the version
35+
// commit without failing these assertions.
36+
const configuredOptions = (config.commit as unknown[])[1] ?? null
37+
38+
const releasePlan = {
39+
releases: [
40+
{ name: 'nostream', type: 'minor', oldVersion: '3.0.0', newVersion: '3.1.0' },
41+
{ name: 'nostream-extra', type: 'none', oldVersion: '1.0.0', newVersion: '1.0.0' },
42+
],
43+
}
44+
45+
// The stock message is `RELEASING: Releasing N package(s)`, which commitlint
46+
// rejects for type-case and type-enum.
47+
it('writes a conventional subject', () => {
48+
expect(commitModule().getVersionMessage(releasePlan, configuredOptions)).to.equal('chore: release nostream@3.1.0')
49+
})
50+
51+
// `[skip ci]` makes GitHub skip both push and pull_request runs for the
52+
// commit, which is why the version PR's required checks never reported.
53+
it('writes no [skip ci]', () => {
54+
expect(commitModule().getVersionMessage(releasePlan, configuredOptions)).not.to.include('skip ci')
55+
})
56+
57+
it('names only the packages that are actually released', () => {
58+
expect(commitModule().getVersionMessage(releasePlan, configuredOptions)).not.to.include('nostream-extra')
59+
})
60+
61+
it('still honours skipCI when it is asked for', () => {
62+
expect(commitModule().getVersionMessage(releasePlan, { skipCI: true })).to.include('[skip ci]')
63+
})
64+
65+
it('keeps changeset add conventional', () => {
66+
expect(commitModule().getAddMessage({ summary: 'a summary' }, { skipCI: false })).to.equal(
67+
'docs(changeset): a summary',
68+
)
69+
})
70+
})

0 commit comments

Comments
 (0)