One tag ships everything: a GitHub Release with the XPIs, and the npm package
@odience-network/thunderbird-cli-enhanced
with the tb, tb-bridge and tb-mcp commands.
The repo root package.json is the npm package. Its files list ships cli/src/, bridge/bridge.js, mcp/src/, their package.json files (the bins read their version from them), server.json, the companion skill, README, LICENSE and CHANGELOG. npm run test:pack pins that list and fails on tests, extension sources, XPIs, node_modules, docs or key files.
The cli/, bridge/ and mcp/ workspaces are private: true and keep upstream's names (thunderbird-cli, thunderbird-cli-bridge, thunderbird-cli-mcp). Never publish them: upstream owns those names on npm.
-
Bump the version in all of these (
npm run test:packfails if they disagree):package.json,cli/package.json,bridge/package.json,mcp/package.json, and bothversionfields inserver.json. Then runnpm installsopackage-lock.jsonfollows. -
Move the
[Unreleased]section ofCHANGELOG.mdunder the new version. -
Make sure a signed
*-tb.xpifor the current add-on version is indist/releases/(see the sign-xpi workflow); the release job fails without one. It attaches the highest-versioned signed XPI it finds (sort -V), which can be older than the manifest while ATN review is pending — v1.3.0 shipped signed 2.1.0 alongside the unsigned 2.4.0. -
Run
npm run test:allandnpm run test:pack-smokelocally, open a PR, merge. -
Tag the merge commit and push the tag:
git tag v1.2.0 && git push origin v1.2.0
The release workflow then:
- checks the tag matches
package.json(v1.2.0↔1.2.0), - runs the tests, the tarball content check and the install smoke test,
- builds the unsigned XPI and creates the GitHub Release,
- runs the
npm-publishjob (GitHub environmentnpm) withnpm publish --provenance --access public. Versions with a pre-release suffix (1.2.0-rc.1) go to thenextdist-tag; everything else goes tolatest. If the version is already on npm, the job does nothing, so re-running a workflow is safe.
Verify:
npm view @odience-network/thunderbird-cli-enhanced version dist-tags
npm i -g @odience-network/thunderbird-cli-enhanced && tb --version && tb-bridge --version && tb-mcp --versionThe package page on npmjs shows a Provenance badge linking back to the workflow run.
Publishing uses trusted publishing (OIDC): npm trusts the npm-publish job directly, so no npm token is stored anywhere. Set up since 1.2.1; 1.2.0 was the only release published with a token.
- On npmjs, open the package settings for
@odience-network/thunderbird-cli-enhanced→ Trusted publishing → GitHub Actions, with organizationodience-network, repositorythunderbird-cli-enhanced, workflowrelease.ymland environmentnpm. All four must match, or the publish fails with a 404/403. - In GitHub → Settings → Environments, create
npm. Add required reviewers if a human should approve each publish.
The job needs id-token: write and npm >= 11.5.1 (the workflow upgrades npm). It sets no NODE_AUTH_TOKEN; the publish log shows the provenance statement being signed and published to the transparency log.
Never create an npm token for CI, commit one, put one in an .npmrc in the repo, or echo one in a workflow step. If trusted publishing ever has to be bypassed, use a short-lived granular token from a local shell, not a repository secret.
npm versions are immutable: a published version number can never be reused, even after unpublishing. Pick the least destructive option:
| Situation | Action |
|---|---|
| Bad version, a fix is coming | Publish the fix as a new patch version. Then warn on the bad one: npm deprecate @odience-network/thunderbird-cli-enhanced@1.2.0 "Broken X, use 1.2.1" |
latest must point back at a good version now |
npm dist-tag add @odience-network/thunderbird-cli-enhanced@1.1.9 latest (and deprecate the bad one) |
| Pre-release went to the wrong tag | npm dist-tag add …@1.2.0-rc.1 next and move latest back as above |
| Secret or private data was published | npm unpublish @odience-network/thunderbird-cli-enhanced@1.2.0, allowed within 72 hours of publishing if nothing depends on it; after that, contact npm support. Rotate the leaked secret first: unpublishing doesn't un-leak it. |
Deprecating, moving dist-tags and unpublishing need an npm login with publish rights on the package (an org owner), from a local shell, not CI.
The GitHub Release can be edited or deleted separately; the git tag stays in the history.