Skip to content
Draft
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
23 changes: 23 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,29 @@ jobs:
- name: Unit tests
run: npm test

commitlint:
name: Lint commit messages
runs-on: ubuntu-latest
# A push to master lands the same commits a PR already carries via
# rebase-merge, so linting the PR's range is what gates master.
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v5
with:
# Needs the base commit and everything since it, not just HEAD.
fetch-depth: 0

- uses: actions/setup-node@v5
with:
node-version: 24
cache: npm

- name: Install dependencies
run: npm ci

- name: Lint commits since the PR's base
run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose

e2e:
name: End-to-end tests (Playwright)
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ npm run build # build all packages (rolldown) and the docs site (vite SS
npm test # run each package's tests (vitest)
npm run ci-test # CI test run
npm run lint # oxlint across the repo
npm run lint:commits # commitlint over local commit messages
npm run format # oxfmt across the repo
npm run dev / npm start # run the docs site's Vite dev server
npm run typecheck # tsc over packages/ and scripts/ (e2e and infra typecheck separately)
Expand Down
15 changes: 15 additions & 0 deletions commitlint.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Gates the commit `type` that .releaserc.json's commit-analyzer needs to
// tell releasable work from everything else - see docs/release-strategy.md.
// subject-case, header-max-length, body-max-line-length and
// footer-max-line-length are disabled: they flag this project's normal
// prose (a proper noun leading a subject, a long line quoting
// CODING-STYLE.md or a path), not the type prefix this enforces.
module.exports = {
extends: ["@commitlint/config-conventional"],
rules: {
"header-max-length": [0],
"body-max-line-length": [0],
"footer-max-line-length": [0],
"subject-case": [0],
},
};
21 changes: 16 additions & 5 deletions docs/release-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,22 @@ always finishes.

`commit-analyzer` matches release rules against a parsed `type`, so a commit whose subject has no
`type:` prefix is unreleasable no matter what it changed. `master` carries a couple of dozen of
these and several are real work — `Add notAtom for catch-all/unmatched routes` (a `feat`),
`Emit dist/index.d.cts for jarl-atoms/jarl-react, fix require condition types` (a `fix`),
`Make jotai a peerDependency of jarl-atoms and jarl-react` (a breaking `fix`). They reached npm
only because a later typed `feat` swept them into its release, and none of them appear in the
changelog. Enforcing the format at commit or PR time (commitlint) is a separate change.
these predating enforcement below, and several are real work — `Add notAtom for catch-all/unmatched
routes` (a `feat`), `Emit dist/index.d.cts for jarl-atoms/jarl-react, fix require condition types`
(a `fix`), `Make jotai a peerDependency of jarl-atoms and jarl-react` (a breaking `fix`). They
reached npm only because a later typed `feat` swept them into its release, and none of them appear
in the changelog.

### Enforcing the type at PR time

The `commitlint` job in `.github/workflows/ci.yml` runs [commitlint](https://commitlint.js.org/)
(`commitlint.config.cjs`) over every commit a PR adds on top of its base, using
`@commitlint/config-conventional`'s `type-enum`/`type-case`/`type-empty` rules — the same type set
`commit-analyzer`'s `conventionalcommits` preset recognises (the table above). An untyped commit
(`Emit dist/index.d.cts for...`) or one with a miscased type (`Style: tighten...`) fails the job, so
neither can reach `master`. The config disables `config-conventional`'s subject-case and line-length
rules, which are unrelated to the type prefix and would otherwise flag this project's normal style —
see the config file's own comment.

## Changelog content

Expand Down
Loading
Loading