@deploybaseline/core, each provider, the state stores, and @deploybaseline/cli are released when they change, not together.
runbook/RELEASE.md in the repository is the operator’s walkthrough: one-time setup, every step of a release, validating one before merge, and a table of failures with their fixes. This page summarizes it.Preparing a release
make release does the mechanical steps below for you and stops before committing:
TODO(release) line that the release plan refuses to publish until you replace it. The steps it performs, if you prefer to do them by hand:
1
Branch
Create a branch such as
release-stripe-0.14.2, or prepare-v0.15.0 for a CLI release.2
Bump each package you are releasing
Edit
version in that package’s package.json. Bump only the packages whose code changed, by semver: a patch for a fix, a minor for a feature. Before 1.0 a breaking change is a minor.3
Regenerate provider data
packages/cli/src/catalog.generated.json) records each provider’s version, and that is the copy the CLI bundles. The script fails when a provider’s version is outside the range its slot in packages/cli/src/registry.ts gives. A provider minor outside that range needs the range widened, and then a CLI release, before CLIs pick it up.4
Save state fixtures
packages/cli/src/__tests__/fixtures/state/<provider>/<version>.json for each provider version that has none yet. The CLI tests restore every fixture, and one written by a provider with a different stateVersion must be refused rather than half-restored. Commit the new files.5
Write the changelog entries
Each package keeps Remove the markers from that file’s previous entry. Only the latest entry in each file has them.
CHANGELOG.md in its own directory; the CLI’s is the root CHANGELOG.md. Add the entry at the top, wrapped in markers, with the version as its heading:6
Check the plan
7
Note bundle growth if needed
scripts/check-bundle.mjs fails CI when the CLI tarball exceeds packages/cli/bundle-budget.json, unless the CLI’s release entry contains a Bundle size: note. If the growth is intended, raise the budget and add that note.8
Validate packaging (optional)
Release workflow on your branch in dry-run mode: build, test, pack each package that would publish, check its tarball, and npm publish --dry-run. Nothing is published, tagged, or released. make release-verify runs the tarball check locally.9
Open the PR and merge to main
CI must pass as usual.
npx @deploybaseline/cli update moves pinned projects forward. See Provider versions.
What happens after merge
TheRelease workflow (.github/workflows/release.yml) runs on every push to main and on manual dispatch. It publishes nothing unless the repository variable RELEASE_ENABLED is "true". A dispatch that publishes must run on main; a dispatch with dry_run runs from any branch and only packs and verifies. One release runs at a time.
check-release
check-release
Runs
node scripts/release-packages.mjs plan. For each public workspace package it asks npm whether that package’s exact version exists, and lists tags on origin to find versions that reached npm without their GitHub release. It asks for the exact version rather than the latest dist-tag, so moving latest back during a rollback does not trigger a republish. The job fails when a package to release has no changelog entry for its version, or when a package name does not exist on npm at all: trusted publishing can only be configured for a package that already exists, so a new package needs a one-time bootstrap publish first.publish
publish
Runs in the protected
Release environment with id-token: write. It installs with --frozen-lockfile, builds, type-checks, and tests, then runs node scripts/release-packages.mjs publish. That packs each package with pnpm pack (which resolves workspace: ranges), checks the tarball (every entry point named in package.json is present, the README and notices are present, no workspace: range is left), and publishes that exact tarball with npm publish --provenance --access public using npm trusted publishing over OIDC, in dependency order: core, then the providers and stores, then the CLI. Transient npm failures, including Sigstore’s TLOG_CREATE_ENTRY_ERROR, are retried with a fresh provenance signature. Versions already on npm are skipped, so a rerun after a partial failure is safe.github-release
github-release
Creates one GitHub release per published package, with the text between that package’s release markers as the notes, at the commit that set the package’s version. The CLI is tagged
v<version>; every other package is tagged <name>@<version>, such as @deploybaseline/[email protected]. An existing release with that tag is left alone. Because npm goes first, a run that dies after publishing leaves only tags missing, and the next run (a rerun, a dispatch on main, or the next merge) creates them.New packages
scripts/release-packages.mjs discovers packages from the workspace, so a new provider is published without editing the workflow. pnpm new:provider creates its CHANGELOG.md. Its first version still needs a bootstrap publish and a trusted publisher configured on npm, as below. This is the last item in the provider definition of done.
Bootstrapping package names
npm configures trusted publishing per package, and only for a package that already exists, so CI cannot publish a name that has never been on npm.release-packages.mjs plan stops on such a name. A maintainer publishes a placeholder for each one by hand, once, and the real version then goes out from CI with provenance, a tag, and a GitHub release like any other.
1
Log in to npm
Use an account that can publish to the
@deploybaseline scope:2
Publish the placeholders
0.0.0-bootstrap.0 under the bootstrap dist-tag, holding only a package.json and a README. A prerelease of 0.0.0 matches no range a CLI or project asks for, so nothing installs it. Names already on npm are skipped, so a rerun after a failure is safe.3
Add a trusted publisher to each package
On npmjs.com, open each package’s settings and add a trusted publisher: GitHub Actions, repository
deploybaseline/cli, workflow release.yml, environment Release. node scripts/release-packages.mjs lists every package name.4
Release as usual
pnpm release-plan now lists each package’s real version instead of warning about missing names. Merging to main publishes them through the Release workflow.5
Deprecate the placeholders
After the real versions are on npm:
Compatibility checks
TheCompatibility workflow (.github/workflows/compatibility.yml) runs daily and on manual dispatch once the repository variable COMPAT_ENABLED is "true". Both of its checks install packages into an empty project and start every built-in service with --offline:
node scripts/compatibility.mjs hostruns the CLI frommainagainst the latest published provider in each default range. It also fails when a default range has no published version.node scripts/compatibility.mjs providersruns the providers frommainagainst the oldest supported CLI,oldestCliincompatibility.json. It skips while that isnull. Set it to the first published CLI that runs provider packages, and raise it when a CLI drops a protocol version.
node scripts/compatibility.mjs host --packed (make compatibility) on every pull request: the built CLI against the workspace’s packed providers, installed from their tarballs. Each provider’s saved-state fixtures in packages/cli/src/__tests__/fixtures/state/ are restored on every test run, so a provider change that can no longer read state saved by an earlier version, without raising stateVersion, fails CI.
Packaging details
scripts/prepare-package.mjsruns asprepackin every package and copies the rootLICENSE,NOTICE, andTHIRD_PARTY_NOTICES.mdin (plusREADME.mdfor the CLI). CI unpacks every tarball and checks these files match the root copies.- The CLI’s
filesfield excludes source maps from the tarball. node scripts/release-packages.mjsprints the release order,--jsonadds each package’s tag and changelog,node scripts/release-packages.mjs notes <name>prints a package’s current notes, andnode scripts/release-packages.mjs verify [name...]packs and checks tarballs. CI runsverifyon every pull request.scripts/release.mjs(prepare) andscripts/release-packages.mjs(publish) sharescripts/lib/releases.mjs, so they agree on the package list, publish order, changelog rules, and version arithmetic.