Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Releasing

The baseline is private: no npm registry. Downstream repos install git tags; each tag points at a release commit (not on any branch) that carries built dist/ and package.jsons whose internal deps point back at the same tag.

Why this shape

  • pnpm add github:owner/repo#tag&path:/packages/x installs one subdirectory of a monorepo — but pnpm does not rewrite workspace:* ranges for git dependencies. So in the release commit every "@sms/kernel": "workspace:*" becomes "@sms/kernel": "github:owner/repo#v0.2.0&path:/packages/core/kernel". pnpm then resolves the whole @sms/* graph transitively, deduplicated, one Kernel class in the process.
  • Node refuses to type-strip .ts inside node_modules, so consumers need real .js + .d.ts: tsc -b emits dist/ per package (tsconfig.package.json → per-package tsconfig.build.json).
  • Development stays build-free: package exports list sms-sourcesrc/index.ts first. tsconfig.json (customConditions), vitest.config.ts (resolve.conditions) and the app scripts (--conditions=sms-source) enable it; consumers never see it (default condition → dist/).
  • main never carries build output (dist/ and *.tsbuildinfo are gitignored). Release commits do; they are reachable only through their tag, so no release/* branches exist.

Cutting a release: push a tag

pnpm check                          # no CI on main — the gate runs here first
git tag v0.2.0 && git push origin v0.2.0

That is the whole procedure. .github/workflows/cd.yml (the only workflow in the repo) runs pnpm check, builds, makes a detached release commit with rewritten package.jsons and committed dist/, moves the tag onto that commit, pushes it, and publishes a GitHub Release with the install line. The workflow pins Node 22.14 (newer 22.x changes .ts handling for the worker-thread executor).

Rules:

  • The tag must point at a commit on main with a clean workspace layout. CD refuses a tag whose commit is not on main (e.g. one that already points at a release commit).
  • Do not re-use a tag name after it has shipped; releases are lockstep (one version for every package).
  • Do not push tags from a local pnpm release --push. CD owns the tag: a locally-moved tag makes CD check out the release commit and fail on --frozen-lockfile. If that happens, the tag is still valid — create the GitHub Release by hand (gh release create vX.Y.Z).

Rehearsing locally

scripts/release.ts is what CD runs; you can run it without pushing to see what a release would contain:

pnpm release 0.2.0 --dry-run  # show what would be rewritten
pnpm release 0.2.0            # runs pnpm check, builds, makes a detached release commit, tags it locally
git tag -d v0.2.0                                   # discard the rehearsal
pnpm release 0.2.0 --repo other/fork   # for forks (default: parsed from `git remote get-url origin`)

What the script does: clean tree required → pnpm checkpnpm clean && pnpm buildpnpm check:pkg (publint + arethetypeswrong on the built packages, see docs/quality.md §6) → detach HEAD → for each non-private package: set version, rewrite workspace:* in dependencies/ peerDependencies to git+path specs, drop devDependencies and scripts, rewrite exports to dist only (every subpath kept — ./testkit, ./testing, ./node — just the sms-source condition dropped), git add -f dist → commit → annotated tag → switch back to your branch.

Verifying a release

mkdir /tmp/consumer && cd /tmp/consumer && pnpm init
pnpm add "github:owner/sms-baseline#v0.2.0&path:/packages/baseline" \
         "github:owner/sms-baseline#v0.2.0&path:/packages/drivers/store/sqlite" \
         "github:owner/sms-baseline#v0.2.0&path:/packages/drivers/executor/quickjs"
node -e "import('@sms/baseline').then(m => console.log(Object.keys(m).length, 'exports'))"

The same mechanics were validated with a git+file:// clone in this repo’s development (see the session notes in docs/research-positioning.md → “Status”).

Do not

  • Check out a release tag to develop on it (its package.jsons are not a valid workspace).
  • Commit dist/ on main.
  • Publish a package that imports a sibling driver; consumers must be able to install one driver alone.