Skip to content

Testing and release

Four, and each exists because something got past review without it.

Terminal window
npm run check:imports --prefix server # zero imports leave the module directory
npm run build --prefix client # build FIRST — two tests read the chunk
npm run check:externals --prefix client # no shared dependency welded into the chunk
npm test --prefix server && npm test --prefix client

check: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.

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 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.

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.

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.sql is genuinely idempotent. It runs on every boot, not once.
  • purge.sql still makes sense to someone who has forgotten your module, because that is who will run it.
  • Your coreApi range 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.