Nexlem Release Process
This document is the single source of truth for cutting a Nexlem release. It covers the
changesets workflow, the prepublishOnly quality gate, and the GitHub release-notes process.
Deprecation policy: The tier-based deprecation contract for consumers lives in
docs/STABILITY.md, not here. Consult that document before demoting or removing any classified interface.
Overview
The release flow has three steps:
1. changeset add # record what changed and bump type
↓
2. changeset version # apply version bump → CHANGELOG.md + package.json
↓
3. changeset publish # bun publish → npm registry
All three steps use bunx @changesets/cli — see Known Pitfalls for the
correct binary name.
1. Adding a Changeset
Before merging a PR that contains a user-visible change, add a changeset describing what changed and whether it is a patch, minor, or major bump:
bunx @changesets/cli addThe CLI will prompt for:
- Bump type:
patch(bug fix),minor(new feature, backward-compatible), ormajor(breaking change) - Summary: a one-line description that will appear in
CHANGELOG.md
This creates a file under .changeset/<random-hash>.md. Commit that file alongside the PR.
When to add a changeset:
- Bug fixes visible to consumers →
patch - New
nexlem-sdk queryhandler or new/nexlemskill →minor - Breaking change to a stable-tier interface →
major(and follow the deprecation policy indocs/STABILITY.md) - Internal-only changes (tests, CI, docs) → no changeset required
2. Cutting a Release
When ready to release, apply all pending changesets to produce the version bump and CHANGELOG entry:
bunx @changesets/cli versionThis command:
- Reads all
.changeset/*.mdfiles - Determines the highest bump type (patch / minor / major)
- Updates
package.jsonversionfield - Appends a new version block to
CHANGELOG.md - Deletes the consumed
.changeset/*.mdfiles
After running changeset version:
- Review
CHANGELOG.md— edit the generated entry for clarity if needed - Review
package.json— confirm the version bump is correct - Commit both files:
git commit -m "chore: release vX.Y.Z" - Tag the release:
git tag vX.Y.Z - Push the commit and tag:
git push && git push --tags
3. Publishing
After tagging, publish to the npm registry:
bunx @changesets/cli publishThis runs bun publish for each non-private package in the repository.
Critical — private: true packages are skipped entirely:
If package.json contains "private": true, changeset publish will print
"No unpublished projects to publish" and exit without publishing anything. This is the
expected behavior; it is not an error.
The private: false flip and the actual npm publish to the registry are deferred to v2.3,
alongside the license decision. The Phase 48 release rehearsal exercised bun publish --dry-run
via a temporary, immediately-reverted flip to private: false, but the repository remains
private: true. Do not flip private: false permanently until the v2.3 publish is ready.
4. The prepublishOnly Gate
The prepublishOnly script in package.json runs automatically when bun publish packs the
package inline. It is a hard gate — if any step fails, the publish is aborted.
The gate checks (in order):
| Step | Command | What it catches |
|---|---|---|
| TypeScript | bunx tsc --noEmit | Type errors across the entire codebase |
| Lint | bunx biome check src/ | Style and lint violations in src/ |
| Tests | bun vitest run | Any failing unit or integration test |
| Clean tree (unstaged) | git diff --exit-code | Uncommitted changes to tracked files |
| Clean tree (staged) | git diff --cached --exit-code | Staged but not committed changes |
| Bin permissions | chmod +x bin/* | Ensures binaries are executable in the tarball |
Do not bypass the gate. Running bun publish --ignore-scripts bypasses prepublishOnly
and ships an unvalidated package. That flag is not documented here as a valid workflow step
because it is not one.
bun publish --dry-run exits 1 on Bun v1.3.14: When running bun publish --dry-run with
no npm auth token configured, all six prepublishOnly gate steps run and pass, but Bun then
contacts the npm registry to verify credentials and exits 1 with error: missing authentication.
This is expected behavior in a dry-run rehearsal context. In the real publish flow, the auth
token is set in the environment and the command exits 0 after successful publish.
Running the gate manually (without publishing):
bun run prepublishOnlyOr run individual steps:
bunx tsc --noEmit
bunx biome check src/
bun vitest run
git diff --exit-code && git diff --cached --exit-code
chmod +x bin/*5. GitHub Release Notes
GitHub release notes are distinct from CHANGELOG.md:
| CHANGELOG.md | GitHub Release | |
|---|---|---|
| Audience | Developers, scripts | End users |
| Detail level | Detailed, per-commit bullets | High-level, top 3-5 changes |
| Location | Committed to repo | GitHub Releases UI (not committed) |
| Format | Keep-a-Changelog | Free-form markdown |
How to create a GitHub release:
- Go to the repository's Releases page on GitHub
- Click "Draft a new release"
- Select the tag you pushed (
vX.Y.Z) - Set the title to
vX.Y.Z - Write the release notes (see format below)
- Attach the tarball if distributing pre-publish
Alternatively, use the GitHub CLI:
gh release create vX.Y.Z --title "vX.Y.Z" --notes "$(cat release-notes.md)"Release notes format:
## What's New in vX.Y.Z
[One-sentence headline summarizing the most important change]
### Highlights
- **[Feature name]:** [One-sentence description]
- **[Fix name]:** [One-sentence description]
- **[Improvement name]:** [One-sentence description]
### Install
[Install instructions for this version]
Full changelog: [CHANGELOG.md#XYZ](CHANGELOG.md#XYZ)The drafted release notes for the first public release live in
release-notes-v2.2.0.md at the repo root. That file is
passed to gh release create --notes-file release-notes-v2.2.0.md when cutting the GitHub
release.
Known Pitfalls
Always use bunx @changesets/cli, not bunx changeset
There is no npm package named changeset. The correct scoped package name is
@changesets/cli.
# WRONG — fails with "could not determine executable to run for package changeset"
bunx changeset add
bunx changeset version
# CORRECT
bunx @changesets/cli add
bunx @changesets/cli version
bunx @changesets/cli publishchangeset publish skips private: true packages
If package.json has "private": true, changeset publish exits without publishing.
This is expected behavior. Flip private: false before attempting to publish to the registry.
bun publish /path/to/tarball skips prepublishOnly
Running bun publish with a pre-built tarball path (bun publish ./package.tgz) does NOT
fire lifecycle scripts including prepublishOnly. Always use bun publish (no argument) or
bunx @changesets/cli publish to ensure the gate runs.
Related Documents
docs/STABILITY.md— stability contract and deprecation policy for consumersCHANGELOG.md— per-release record of changes.changeset/config.json— changesets configuration