ci: release with semantic-release from conventional commits - #35
Conversation
|
Happy with this one as well. Will have to figure out how to make the NPM changes, I need to remember how to get access to the chartjs-ci npm account so I can setup the publishing |
Releases are cut by hand today: release-drafter collects labels into a draft, someone publishes it, and `publish-npm.yml` runs `npm publish` with a token. That path has two problems. The version is chosen by whoever publishes, and the npm side of it no longer works: `NPM_AUTH_TOKEN` here predates the classic token revocation of 2025-12-09, so the next release would fail at the publish step. semantic-release decides the version from what the commits say, writes the notes, tags, publishes, and comments on the issues and pull requests a release closes. `package.json` carries `0.0.0-development`; the published version is set during the release. Preset: `conventionalcommits`, not the default `angular`. Measured with this repo's own `.releaserc.json`: `feat!: ...` analyzes to `major` under conventionalcommits and to **null** under angular -- the `!` marker is silently ignored there, and a breaking change would ship as a patch, or as nothing at all. Under conventionalcommits: `fix` and `perf` patch, `feat` minor, `!` or a `BREAKING CHANGE:` footer major, everything else no release. The preset is pinned to ^9 on purpose. Version 10 requires conventional-changelog-writer >= 9, while `@semantic-release/release-notes-generator@14` depends on writer ^8; with ^10 installed, `analyzeCommits` succeeds and `generateNotes` throws "Missing helper", so a release would fail after the version had been decided. Both plugins were run against this config to confirm the pinned combination renders notes. Because this repository allows only squash merges, and the squash commit takes the pull request title, the title is the release trigger. `pr-title.yml` therefore checks the title on every pull request -- that is the enforcement point, not a local commit hook. The squash body keeps the individual commit messages, so a `BREAKING CHANGE:` footer written in any of them still counts. npm publishing moves to trusted publishing (OIDC): no token in the repository, `id-token: write` on the release job instead. This requires a trusted publisher to be configured on npm for the `release.yml` workflow of this repository -- see the pull request description. release-drafter is removed: semantic-release generates the same notes from the same commits, and two sources of release notes would disagree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
898f871 to
4b50e98
Compare
|
Rebased on Order does not matter. I implied it did. semantic-release analyzes every commit since the last tag, not only the newest, so #34 having merged first changes nothing. Over the real range The release job had a stale step. It ran The rebase also surfaced something about footers that is worth writing down. In the notes generated above, BREAKING CHANGES carries an extra line, Still the one thing I cannot do from here: the npm trusted publisher for this package — GitHub Actions, 🤖 Generated with Claude Code |
@etimberg — third proposal, independent of #33 and #34. This one has a part only a maintainer can do, so it is up front.
npm publishing here is already broken, and the fix is a setting on npm
The publishing rules changed after this package's last release in 2022:
NPM_AUTH_TOKENsecret this repository publishes with predates that by three years, so unless it has been rotated since, the currentpublish-npm.ymlwould fail atnpm publish— whether or not this PR is merged.The way out is trusted publishing: npm mints a short-lived credential for one workflow run over OIDC, and the repository holds no npm secret at all.
@semantic-release/npm@13supports it — it tries an OIDC token exchange first and only falls back to a token if that fails.What to configure on npmjs.com (maintainer action)
On the chartjs-test-utils package page → Settings → Trusted publisher:
chartjschartjs-test-utilsrelease.ymlThat is the
releasejob added in this PR (.github/workflows/release.yml), which runs on pushes tomasterwithid-token: write. npm requires npm CLI ≥ 11.5.1 and Node ≥ 22.14.0 for this; the job runs Node 24, which ships a new enough npm, and printsnpm --versionso a regression is visible in the log.Two things worth knowing:
release.ymlis ever renamed or the job moves into another workflow file, publishing stops until the trusted publisher is reconfigured — so add the new configuration before renaming, not after.NPM_AUTH_TOKENsecret deleted. OIDC keeps working; only token auth is disabled.If you would rather keep token publishing, the same workflow works with
NPM_TOKENset to a granular token — but that is the 90-day treadmill described above.Why semantic-release
Today the version is chosen by whoever publishes, and the notes are assembled by release-drafter from labels. semantic-release derives both from what the commits already say: version, notes, tag, npm publish, GitHub release, and comments on the issues and PRs a release closes.
package.jsoncarries0.0.0-development; the published version is written during the release. The existingv0.5.0tag is the baseline, so the next release continues from there.Two configuration choices, both measured rather than assumed
Preset
conventionalcommits, not the defaultangular. RunninganalyzeCommitswith this repo's own.releaserc.json:The same
feat!: ...message under the angular preset analyzes tonull— no release at all. The!marker is silently ignored there, which is a bad way to find out that a breaking change shipped as nothing.The preset is pinned to
^9. Version 10 requiresconventional-changelog-writer >= 9, while@semantic-release/release-notes-generator@14depends onwriter@^8. With^10installed,analyzeCommitssucceeds andgenerateNotesthrowsMissing helper: ...— the version would be decided and the release would then fail while writing the notes. Both plugins were run against this configuration to confirm the pinned pair actually renders notes; the rendered output is a normal## [1.0.0] ... ### ⚠ BREAKING CHANGES / ### Features / ### Bug Fixeschangelog.Squash merges make the PR title the trigger
This repository allows only squash merges, with the squash commit taking the pull request title (
COMMIT_OR_PR_TITLE) and the individual commit messages as the body (COMMIT_MESSAGES). So the pull request title is the release trigger, and a local commit hook would enforce the convention in the one place it does not matter.pr-title.ymltherefore checks the title on every pull request. The squash body still carries the individual commit messages, so aBREAKING CHANGE:footer written in any commit of the PR is honoured — verified with a squash-shaped message (title + concatenated bodies + footer), which analyzes tomajor.Verified (re-measured after rebasing onto
masterwith #33 and #34 in)Running the analyzer and the notes generator over the real commit range
v0.5.0..master, with this branch's own.releaserc.json:So the first release this produces is
1.0.0, with notes listing the Vitestchange under ⚠ BREAKING CHANGES and Features — no version chosen by hand,
and nothing lost by #34 having merged first.
semantic-release --dry-runloads the config and every plugin, and theGitHub plugin verifies. The npm plugin fails locally with
EINVALIDNPMTOKEN,as it should: OIDC exists only inside a GitHub Actions run, and there is no
token here. That is exactly what the trusted publisher configuration above
removes.
npm teston this branch: Biome clean over 23 files, typecheck clean,7 node specs, 24 browser specs plus the 2 deliberate skips in Chromium and
Firefox.
One thing worth knowing about squash merges and footers
In the generated notes above, the BREAKING CHANGES section carries one extra
line:
* chore: adopt biome for the Vitest sources. That is not a bug here —it is how footers parse. The squash commit of #34 concatenated two commit
messages, the
BREAKING CHANGE:footer came from the first one, andeverything after a footer token belongs to that footer until the next token, so
the following commit's subject was absorbed into it.
Practical rule for multi-commit pull requests: put the
BREAKING CHANGE:footer in the last commit of the branch. Same for issue references — a
#123written anywhere in a commit body shows up in the notes as a closedreference, which is why the notes above say
closes #33.Also in this PR
same notes from the same commits, and two sources of release notes disagree
the first time someone forgets a label.
publish-npm.ymlis removed;release.ymlreplaces it.npm test, asci.ymldoes, and no longer runs
npm run build— feat!: replace Karma and Jasmine with Vitest (v1.0.0) #34 removed the rollup bundle.Relationship to the other proposals
#33 and #34 are both merged, and this rebases cleanly on top of them. The order
turned out not to matter: semantic-release analyzes every commit since the last
tag, not just the newest one, so #34's
feat!title still produces the1.0.0it describes — measured above rather than assumed.
🤖 Generated with Claude Code