Skip to main content

canary

Stamps a canary version into package.json, ready to publish.

pnpm dist canary

Run by CI, as pnpm run canary — there is rarely a reason to run it by hand.

Stamped @acme/widget@1.7.0-0.canary.3

In a distribution the canary is of the release it is working towards, in npm's spelling — 26.9.1-0.canary.0 for 26.09.01-alpha.0 — and what it ships is recorded in the manifest it publishes, as for a release.

The version it picks

2.0.0-0.canary.3

The 0.canary.N is not decoration. A canary has to sort below every real version of what it is building towards, or somebody's range resolves to one, and that is harder than it looks:

ShapeBelow 2.0.0Below 2.0.0-beta.2
2.0.0-canary.3yesno — canary beats beta as a string
2.0.0-beta.2.canary.3yesno — it is after beta.2
2.0.0-0.canary.3yesyes

Semver ranks numeric identifiers below alphanumeric ones, so a numeric first identifier is the one slot beneath alpha, beta, rc and anything else anyone names. That puts a canary under the floor of every prerelease range, so no range resolves to it:

maxSatisfying([2.0.0-beta.2, 2.0.0-0.canary.0], '^2.0.0-beta.2')  ->  2.0.0-beta.2

npm install pkg@canary is unaffected, because a dist tag is a name rather than a range.

One shape for every target, prerelease or not, so there is no case where this is nearly right.

The count

N is this canary's position within its release cycle, read back from the registry. The prefix ignores the prerelease, so 2.0.0-alpha.0, 2.0.0-beta.1 and 2.0.0 are one continuous run of canaries rather than three that restart and collide at .0.

A CI run number would look similar and be wrong. It skips whenever a run fails or deduplicates, which turns published versions into a record of workflow bookkeeping rather than of releases.

Doing nothing, on purpose

Each canary records the commit it was built from in a gitSha field. When the canary tag already points at the commit being built, nothing is written:

@acme/widget@1.7.0-0.canary.3 is already the canary for 4d1b9d8.

That is what makes the nightly build a no-op on a quiet day, rather than a version bump with nothing in it.

In the workflow

It writes version and skip as step outputs, so the publish step can decide for itself:

- name: Stamp the canary version
  id: canary
  run: pnpm run canary

- name: Publish to npm
  if: steps.canary.outputs.skip != 'true'
  run: npm publish --tag canary --provenance

A canary is not a release and carries no promise. It exists so that "does main work for me?" can be answered without a checkout — see Releasing a module.