Testing and release
The checks
Section titled “The checks”Four, and each exists because something got past review without it.
npm run check:imports --prefix server # zero imports leave the module directorynpm run build --prefix client # build FIRST — two tests read the chunknpm run check:externals --prefix client # no shared dependency welded into the chunknpm test --prefix server && npm test --prefix clientcheck:imports enforces the zero-internal-imports
rule. It is the mechanical form of
the module boundary — without it, the boundary is a convention, and conventions lose.
check:externals is the one that catches a chunk shipping bare import 'react'
specifiers, or a second React welded in. Both build cleanly. Neither works in a browser.
Also worth running your OpenAPI fragment check if you publish one — the filename is fixed
at swagger-fragment.json in the bundle root, so a module cannot point core at some other
file.
What a release artifact is
Section titled “What a release artifact is”Not source. An operator never builds anything, and that constraint shapes everything here.
A release is the directory core’s loader expects to find at modules/<id>/, already
assembled — the prebuilt client chunk, any runtime dependency installed, the schema
fragment, the OpenAPI fragment — packed exactly as it will be unpacked.
Two artifacts ship:
<id>-<version>.tar.gz- an install manifest carrying that tarball’s URL and its
sha256
The admin install downloads the tarball, verifies the hash, and unpacks it. Nothing runs
npm on the way.
The version that ships is the tag
Section titled “The version that ships is the tag”The template derives the next version from conventional-commit subjects since the newest
v* tag:
| Commits since the last tag | Result |
|---|---|
feat!: or BREAKING CHANGE |
major |
feat: |
minor |
fix: / perf: |
patch |
| Nothing releasable | no release is cut |
| First ever run, no tag | releases what module.json declares |
Your committed module.json version is a floor and a starting point, not a record of the
last release. Name a version there above the newest tag and that version is what releases
— which is still the natural way to say “this one is a minor” when a coreApi bump forces
the question.
Pin the core you build against
Section titled “Pin the core you build against”Keep a ci/core-ref.json naming the exact core commit your module is written against, and
have CI assert your declared coreApi still holds against that core’s
MODULE_API_VERSION.
Moving that sha is the moment someone re-reads what changed. It is the same mechanism the Integration Kit uses, and the reason a contract bump upstream becomes a visible decision in your repository rather than a silent one.
Before you tag
Section titled “Before you tag”A short list, all of it learned rather than invented:
- The module boots on a real deployment, not just in tests. Failure is contained, so a broken module is easy to not notice — the site comes up and one section is missing.
schema.sqlis genuinely idempotent. It runs on every boot, not once.purge.sqlstill makes sense to someone who has forgotten your module, because that is who will run it.- Your
coreApirange covers the oldest core you actually test against, not just the newest one you have. - Every capability string you publish is one you intend to keep. Something outside your repository is branching on them.