Changesets and releases in the Nextly monorepo
SkillDev toolsUse when adding a changeset to a PR, deciding whether a PR needs one, cutting or debugging a release, or when the Version PR / npm publish flow looks wrong.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Changesets and releases in the Nextly monorepo skill
What this skill tells your AI
The instructions your AI receives, as published by nextlyhq/nextly in .claude/skills/release-and-changesets/SKILL.md and read by ahel’s review.
The changeset rules (non-negotiable)
- ONE changeset per PR, not per commit.
- It must list ALL published packages (they are a
fixedlockstep group in.changeset/config.json; the repo is in pre-releasealphamode via.changeset/pre.json). - Always
patchwhile in alpha. - The description is the USER-facing impact, one or two lines, not an implementation note.
- No changeset for PRs that touch only tests, CI/workflows, repo docs, or other non-published files. When in doubt: does the change alter what a user installs from npm? No -> no changeset.
Create one with pnpm changeset (select all packages, patch) or write the
file by hand under .changeset/ following an existing one.
How releasing actually works (CI-only)
- PRs with changesets merge to
main. - The Changesets bot maintains a Version PR ("chore: version packages (alpha)") that accumulates pending changesets, bumps every package in lockstep, and updates changelogs.
- Merging the Version PR publishes: the release workflow builds and
publishes all packages to npm via trusted publishing (OIDC) in the
protected environment. There are no local publishes; never run
changeset publishornpm publishyourself. - Tags (
vX.Y.Z-alpha.N) and a consolidated GitHub Release are created by the workflow.
Known gotchas (learned the hard way)
- The release workflow pins an exact npm version on purpose (a floating
npm@latestonce broke every release via an engines bump). Do not "simplify" it back to latest. - Old branches can restore already-published changeset files on merge; if
the Version PR suddenly lists ancient entries, check for resurrected
.changeset/*.mdfiles and delete them in a cleanup PR. - The npm
latestdist-tag for the unscoped packages is managed manually after publishes; a publish alone does not move it. Only move it once a release is verified complete: promoting a partial train points a barenpm installat a version whose siblings never shipped. - If the Version PR looks wrong, fix the inputs (changeset files on main); never edit the Version PR's generated diff by hand.
A publish is not atomic
changeset publish pushes packages one at a time. When one fails, the rest
are already live and cannot be unpublished, so a release can end up split
across versions. Two guards exist:
pnpm release:preflightruns first insiderelease:publish. It refuses to start when a package has incomplete publish metadata, when the versions are not in lockstep, or when a package name has never been published (see bootstrapping below). It costs one registry lookup per package and saves a ten-minute build that could only end in a half-release.pnpm release:verifyruns after publishing and compares the registry to the workspace. The consolidated tag and GitHub Release are created only when it passes, so git and npm cannot disagree about what shipped.
Run either by hand at any time; both are read-only.
Recovering a partial release
Publishing is resumable: changeset publish skips versions the registry
already has, so a re-run only attempts the missing packages.
pnpm release:verifyto list exactly which packages are missing and why.- Fix the cause per package (metadata, npm access, trusted publisher).
- Land a commit on
mainthat adds no new changeset. In prerelease mode the.changeset/*.mdfiles stay on disk after versioning, but the ones already recorded inpre.json.changesetsdo not count as pending, so the action takes the publish path and retries the current version. Adding a changeset instead opens a Version PR and moves the train to the next version. - Do not bump the version to "get a clean run": that abandons the partial version permanently, leaving a hole where some packages exist at a version and their siblings never do.
Finalization (the consolidated tag and GitHub Release) is gated on the registry being complete, not on whether a particular run published something, so a recovery run still creates the tag for a release whose packages were published earlier.
Bootstrapping a brand-new package
A trusted publisher is configured per package on npmjs.com, and it can only be
attached to a package that exists. A new package therefore cannot publish
through the normal OIDC flow on its first release, which surfaces as a 404 … could not be found or you do not have permission to access it.
For each new package name, in order:
-
Confirm the manifest carries
license,repository.directory,engines.node, andpublishConfig.access: "public". A scoped package withoutaccess: "public"is published as restricted and fails. -
Claim the name with a placeholder that contains no code:
npm login node scripts/release/bootstrap-package.mjs @nextlyhq/<name> # shows the plan node scripts/release/bootstrap-package.mjs @nextlyhq/<name> --publish # claims 0.0.0The helper refuses to run against a name that already exists, and publishes only a
package.jsonand a README. Real versions are always published by CI. (nextlyitself was claimed this way with a0.0.1stub.) -
Add the package's Trusted Publisher entry at
https://www.npmjs.com/package/<name>/access: repositorynextlyhq/nextly, workflowrelease.yml, environmentProduction. The environment name must match the release workflow'senvironment:exactly, or publishing fails with the same 404. -
Re-run the release so the package rejoins the train.
There is no CI-only path for step 2, and no override for it: OIDC cannot perform
a package's first publish, and giving CI a long-lived npm token just to claim a
name would replace short-lived trusted publishing with a standing credential.
The helper refuses to publish when CI is set for that reason. Preflight blocks
the whole release until the name exists, which is deliberate: the alternative is
publishing the rest of the train and stranding this package.
Adding a package to the fixed[] group without completing the steps above is
what makes the next release fail.
Signals
- GitHub stars
- 57
- Forks
- 6
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
release-and-changesets- Source
- github.com/nextlyhq/nextly