Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"repo": "cameri/nostream"
}
],
"commit": true,
"commit": ["../scripts/changeset-commit-message.cjs", { "skipCI": false }],
"privatePackages": {
"tag": true
},
Expand Down
4 changes: 4 additions & 0 deletions .changeset/release-ci-triggering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,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.
6 changes: 6 additions & 0 deletions .github/workflows/changesets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@ jobs:
contents: write
pull-requests: write
steps:
# The default GITHUB_TOKEN must not be the credential behind the version
# branch push: GitHub starts no workflow runs for events a GITHUB_TOKEN
# creates, so the version PR would never run its own required checks.
# RELEASE_PAT is the environment secret this job already hands to the
# action for its API calls; the push needs the same credential.
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
token: ${{ secrets.RELEASE_PAT }}

- uses: pnpm/action-setup@v4

Expand Down
37 changes: 37 additions & 0 deletions scripts/changeset-commit-message.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Conventional-commit messages for Changesets.
*
* Changesets' stock messages are `RELEASING: Releasing N package(s)` with a
* trailing `[skip ci]`. Both halves break the version PR this repo's release
* flow depends on:
*
* - `Lint commits` (commitlint, `@commitlint/config-conventional`) rejects
* `RELEASING:` as an unknown type, so a required check fails on a message no
* human wrote. It only ever looked green because a `workflow_dispatch` run
* carries no pull request context and returns a vacuous pass.
* - `[skip ci]` makes GitHub skip both push and pull_request runs for that
* commit, so the version PR's other five required checks never report either
* and the pull request cannot satisfy the branch's required-status-checks
* ruleset. Replacing the head commit with a `[skip ci]`-free one is the only
* thing that made those checks run.
*
* `changeset add` keeps the stock `docs(changeset): <summary>` subject. The
* version commit becomes `chore: release <package>@<version>`. `skipCI` is
* still honoured, so the documented option keeps meaning what it says.
*/

const skipCIMarker = (options, phase) =>
options?.skipCI === true || options?.skipCI === phase ? '\n\n[skip ci]\n' : ''

module.exports = {
getAddMessage: (changeset, options) => `docs(changeset): ${changeset.summary}${skipCIMarker(options, 'add')}`,

getVersionMessage: (releasePlan, options) => {
const released = releasePlan.releases.filter((release) => release.type !== 'none')
const subject = released.length
? `release ${released.map((release) => `${release.name}@${release.newVersion}`).join(', ')}`
: 'version packages'

return `chore: ${subject}${skipCIMarker(options, 'version')}`
},
}
70 changes: 70 additions & 0 deletions test/unit/ci/changesets-commit-message.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { readFileSync } from 'fs'
import { createRequire } from 'module'
import { join } from 'path'

import chai from 'chai'

const { expect } = chai

type CommitModule = {
getAddMessage: (changeset: { summary: string }, options: unknown) => string
getVersionMessage: (releasePlan: { releases: unknown[] }, options: unknown) => string
}

describe('changesets commit message', () => {
const config = JSON.parse(readFileSync(join(process.cwd(), '.changeset', 'config.json'), 'utf-8')) as {
commit: unknown
}

// createRequire works whether mocha loads this spec as CommonJS or as an ES
// module, which `require` itself does not.
const loadFromRepo = createRequire(join(process.cwd(), 'index.js'))

// Changesets resolves the specifier in the `commit` tuple as if it were
// imported from `<repo>/.changeset/x.mjs`, so that is the base used here too.
const commitModule = (): CommitModule => {
expect(config.commit, '`commit` in .changeset/config.json must be a [module, options] tuple').to.be.an('array')
const specifier = (config.commit as unknown[])[0]
expect(specifier, 'that tuple must name a module').to.be.a('string')

return loadFromRepo(join(process.cwd(), '.changeset', specifier as string)) as CommitModule
}

// The options Changesets actually passes to the module, so a change to them
// (say `skipCI: true`) cannot quietly put `[skip ci]` back on the version
// commit without failing these assertions.
const configuredOptions = (config.commit as unknown[])[1] ?? null

const releasePlan = {
releases: [
{ name: 'nostream', type: 'minor', oldVersion: '3.0.0', newVersion: '3.1.0' },
{ name: 'nostream-extra', type: 'none', oldVersion: '1.0.0', newVersion: '1.0.0' },
],
}

// The stock message is `RELEASING: Releasing N package(s)`, which commitlint
// rejects for type-case and type-enum.
it('writes a conventional subject', () => {
expect(commitModule().getVersionMessage(releasePlan, configuredOptions)).to.equal('chore: release nostream@3.1.0')
})

// `[skip ci]` makes GitHub skip both push and pull_request runs for the
// commit, which is why the version PR's required checks never reported.
it('writes no [skip ci]', () => {
expect(commitModule().getVersionMessage(releasePlan, configuredOptions)).not.to.include('skip ci')
})

it('names only the packages that are actually released', () => {
expect(commitModule().getVersionMessage(releasePlan, configuredOptions)).not.to.include('nostream-extra')
})

it('still honours skipCI when it is asked for', () => {
expect(commitModule().getVersionMessage(releasePlan, { skipCI: true })).to.include('[skip ci]')
})

it('keeps changeset add conventional', () => {
expect(commitModule().getAddMessage({ summary: 'a summary' }, { skipCI: false })).to.equal(
'docs(changeset): a summary',
)
})
})
Loading