How a version of this package reaches npm and GitHub. A release ends at a rendered GitHub Release, not at the npm approval.
-
The feature PR carries the README
## What's new in X.Y.Zentry for the version, and squash-merges so its subject ends with(#N). -
Land a
chore(release): X.Y.Zcommit onmainthat touchespackage.jsonalone. CI'stest/packaging.test.tson that commit proves the tarball ships only the files the package metadata section expects. -
Tag that commit and push the tag:
git tag vX.Y.Z && git push origin vX.Y.Zpublish.ymlstarts; its build job runs the release gates (tag matches version, tag on main), the install, the tests, and packs the tarball; its publish job waits on thenpm-publishenvironment. Push the tag before you create the Release, because--target <short sha>fails. -
Compose the body, read it, then create the Release:
scripts/release-notes.sh vX.Y.Z > notes.md gh release create vX.Y.Z --verify-tag --title vX.Y.Z --notes-file notes.mdWith more than one release in flight, create them in ascending version order.
-
Approve the
npm-publishenvironment for the tag once the run is green through the build gates, the tag-on-main check included, which proves the tagged commit sits onmain. With more than one release waiting, approve in ascending version order, so npmlateststays monotonic. -
Verify the published version and the rendered Release:
npm view @atomichub/atomicassets version gh release view vX.Y.Z
The publish job authenticates through npm trusted publishing (OIDC). It holds
no npm token and sets no registry URL on the setup step, so nothing writes an
.npmrc auth entry and npm 11.5.1 or later exchanges the job's OIDC identity
for a short-lived credential of its own. The npm-publish environment is the
gate on that identity: the build job runs immediately on the pushed tag, and
the publish job waits until a maintainer approves it.
publishConfig.provenance in package.json makes a default local npm publish
fail, because no OIDC identity is available outside CI to satisfy it. It is
data inside the manifest being published, not an access control. The durable
control is the npm-side package setting that requires trusted publishing,
which closes the classic-token path that no workflow change can reach.
The Release title is the tag name verbatim. The body is an optional one-sentence summary, then the sections that have items, then the commit list, then the compare link as the last line. Nothing follows the link, and a section with no items is left out.
<one-sentence summary, optional>
## Breaking changes
- <what changed, and what the reader does about it>. (#N)
## Upgrading
- <what the move from the previous stable release takes: migrations, configuration keys, image tags>.
## Features
- <what is new>. (#N)
## Bug fixes
- <what was wrong and is not now>. (#N)
## Security
- <the advisory or the dependency lift, named>. (#N)
## Deprecations
- <what is deprecated and what replaces it>. (#N)
## Other changes
- <a change a consumer notices that fits no section above>. (#N)
## Commits
- <short sha> <subject>
Full changelog: https://github.com/atomicassets/atomicassets-sdk/compare/<PREV>...<TAG>
The section order is breaking changes, upgrading, features, bug fixes, security, deprecations, other changes. The two optional ones:
## Upgradingsays what the move from the previous stable release takes: renamed exports, configuration to set, a step to run. It is written against that release rather than against the tag range, and a candidate body may confine it to what changed since the previous candidate that carries a Release.## Securitycarries the advisories and the dependency lifts, each named by its GHSA or CVE.
A release with neither leaves both out, which is the normal case here.
- Neutral and factual, the register of the Node.js or esbuild release notes.
- Sectioned. The heading says what kind of change it is, so the item does not repeat it.
- One to three plain sentences per item: what changed, and what the reader does about it when action is needed. Code identifiers in backticks.
- Every item ends with its PR reference
(#N), or with its short sha in backticks when the change had no PR. - No preface, no motivation essay, no clause chain explaining how the author got there. The why stays only where it changes what the reader does.
- Present tense for the new behavior, sentence-case headings, straight quotes, and no em-dash.
scripts/release-notes.sh vX.Y.Z reads the README ## What's new in X.Y.Z entry written in the feature PR, promotes its ### headings to ## , appends the commit list, and appends the compare link. Write that entry with H3 section headings (### Breaking changes, ### Features, and the rest) and the Release body needs no second draft.
The script needs bash, git, awk and sed. It reads the README at the tag, not from the working tree, so the body describes what the tag ships. A prerelease tag reads the entry for its base version, so vX.Y.Z-rc.1 reads ## What's new in X.Y.Z as that entry stands at the candidate tag. It exits non-zero and names what is missing when the tag does not exist, when the README at the tag has no entry for the version, or when no earlier tag exists.
package.json carries the fields the npm page and a consumer read, and a release does not change them by accident:
nameandversion.description: one sentence on what the package does and for whom.license, with theLICENSEfile shipped.homepage.repository: an object withtype: gitand thegit+httpsURL of this repository.bugs: an object with the issues URL.author: an object withnameandurl.keywords.engines.main,module,typesand theexportsmap.files: the build output and the notices that must ship.sideEffects.publishConfigwithaccess: publicandprovenance: true, so a publish outsidepublish.ymleither carries the same access and provenance or fails, instead of publishing without them.
The README opens with the package name, the npm version, CI and license badges, a short introduction on what the package is for, and an install line, because that README is the npm page. npm pack --dry-run lists what the tarball ships; the build output, its type declarations and source maps, the README, the license and the notices are expected, anything else is a files mistake. test/packaging.test.ts runs that check in CI, so the tarball is proven before the tag.
PREVfor a stable tag is the nearest earlier stablev*tag reachable fromTAG^, which is whatgit describe --tags --abbrev=0 --match 'v*' --exclude 'v*-*' vX.Y.Z^returns, so a stable release lists every commit since the last stable release. For a prerelease tagPREVis the nearest earlier tag of any kind, so each candidate lists what it adds to the tag before it. A stable tag whose only earlier tags are prereleases takes the nearest of them. Tags from older release lines count.## Commitslists the wholePREV..TAGrange, oldest first, including the release commit. Its line count equalsgit rev-list --count PREV..TAG.- A tag with no earlier tag has no
PREV. Its body is the summary and the sentenceInitial release., with no commit list and no compare link, and it is written by hand. - A prerelease tag (
vX.Y.Z-rc.1and the like) is created with--prerelease. - A Release created for a tag older than the current latest is created with
--latest=false, so the latest marker stays on the newest version.