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/xinstalls one subdirectory of a monorepo — but pnpm does not rewriteworkspace:*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, oneKernelclass in the process.- Node refuses to type-strip
.tsinsidenode_modules, so consumers need real.js+.d.ts:tsc -bemitsdist/per package (tsconfig.package.json→ per-packagetsconfig.build.json). - Development stays build-free: package
exportslistsms-source→src/index.tsfirst.tsconfig.json(customConditions),vitest.config.ts(resolve.conditions) and the app scripts (--conditions=sms-source) enable it; consumers never see it (default condition →dist/). mainnever carries build output (dist/and*.tsbuildinfoare gitignored). Release commits do; they are reachable only through their tag, so norelease/*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
mainwith a clean workspace layout. CD refuses a tag whose commit is not onmain(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 check → pnpm clean && pnpm build
→ pnpm 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/onmain. - Publish a package that imports a sibling driver; consumers must be able to install one driver alone.