06. npm and JSR publish flow
This note describes the end-to-end publish flow for Mochi-emitted library packages. It is informative; the normative reference is MEP-72 §Specification §5 plus the MEP-52 Phase 18 workflow emit at transpiler3/typescript/build/workflow.go.
1. The npm path
When mochi pkg publish --to=npm runs (in CI, against a tag-triggered workflow), the sequence is:
- Build:
mochi build --target=npm-library -o dist/npmproduces adist/npm/directory with:package.json(npm manifest with theexportsmap,dependencies,devDependencies,peerDependenciesharvested frommochi.toml)dist/index.js(the compiled JS entry; produced bytscagainst the Mochi-emitted.tssource)dist/index.d.ts(TypeScript declarations; produced bytsc --declaration --emitDeclarationOnly)src/(the Mochi-emitted TypeScript source, kept for JSR consumption and for downstream debugging)README.md,LICENSE(harvested from the Mochi package root or frommochi.toml[ts.publish])
- Validate:
tsc --noEmitagainstdist/npm/dist/index.d.tsproduces zero diagnostics. - Dry-run:
npm pack --dry-run(indist/npm/) prints the tarball contents and verifies the manifest is well-formed. - Publish via CI: the MEP-52 Phase 18 emitted workflow at
.github/workflows/release.ymlrunsnpm publish --provenance --access=public:- The
--provenanceflag triggers the Sigstore signing path. The CI workflow'sid-token: writepermission letsnpm publishacquire a GitHub OIDC token. npm publishsubmits the OIDC token to npm's Trusted-Publishing endpoint. The npm side mints a short-lived publish credential, signs the tarball via Sigstore Fulcio, and records the attestation in the registry.- The
--access=publicflag is required for scoped packages (@scope/pkg); defaults torestrictedotherwise.
- The
- Verify: post-publish,
npm view <pkg>@<version> dist.tarballreturns the URL;npm audit signaturesagainst a synthetic downstream consumer verifies the Sigstore attestation against the public Sigstore root of trust.
2. The JSR path
When mochi pkg publish --to=jsr runs:
- Build:
mochi build --target=jsr-library -o dist/jsrproduces adist/jsr/directory with:jsr.json(JSR manifest withname = "@scope/pkg",version,exports = "./src/index.ts",publish.include = ["src/**/*.ts", "README.md", "LICENSE"])src/(the Mochi-emitted TypeScript source; JSR transpiles server-side, so nodist/tree is published)README.md,LICENSE
- Validate:
deno check src/index.tsagainst the Mochi-emitted source produces zero diagnostics. - Dry-run:
deno publish --dry-run --allow-dirtyvalidates the manifest and walks the include list. - Publish via CI: the same workflow's
deno publish --token-source=github-actionsstep:- Submits the OIDC token to JSR's Trusted-Publishing endpoint. JSR mints a short-lived publish credential, signs the manifest via the JSR signing infrastructure, posts the attestation to the JSR registry.
- Verify: post-publish,
https://jsr.io/@scope/pkgreturns the published page; the Sigstore attestation is recorded in the JSR registry's metadata.
3. Dual publish from one workflow run
The MEP-52 Phase 18 workflow runs both flows in the same release job:
jobs:
release:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: denoland/setup-deno@v2
- name: Build npm library
run: mochi build --target=npm-library -o dist/npm
- name: Publish to npm
if: startsWith(github.ref, 'refs/tags/')
working-directory: dist/npm
run: npm publish --provenance --access=public
- name: Build JSR library
run: mochi build --target=jsr-library -o dist/jsr
- name: Publish to JSR
if: startsWith(github.ref, 'refs/tags/')
working-directory: dist/jsr
run: deno publish --token-source=github-actions
Both publishes run in the same job, with the same OIDC token, on the same tag. The two registries' attestation chains are independent (npm Sigstore is npm's Fulcio cert; JSR Sigstore is JSR's). A downstream consumer verifying both attestations gets defence-in-depth against either registry's compromise.
4. Per-registry metadata requirements
npm requires:
name(must be unique in the npm namespace; scoped names like@mochilang/fooallow re-use of unscoped names)version(semver)descriptionlicense(SPDX orSEE LICENSE IN <file>)repository(RECOMMENDED for provenance attestation)- At least one of
bin,main,exports
JSR requires:
name(must be@<scope>/<pkg>)version(semver)exports(string path to entry or table of conditions)license(SPDX)- The
publish.includeallowlist
MEP-72 harvests all of the above from mochi.toml [ts.publish] plus [package]. Missing required fields produce a build-time error.
5. Publish-side gate
The MEP-52 Phase 18 test set includes TestPhase18ProvenanceDryRun (already shipping); MEP-72 adds:
TestPhase72NpmLibraryPack:npm pack --dry-runsucceeds against the emitteddist/npm/directory.TestPhase72JsrLibraryDryRun:deno publish --dry-run --allow-dirtysucceeds againstdist/jsr/.TestPhase72DualPublishYAML: the emitted workflow has bothnpm publishanddeno publishsteps with the right flags.TestPhase72LibraryMetadataHarvest: the emittedpackage.jsonandjsr.jsoncarry the metadata harvested frommochi.toml[ts.publish].TestPhase72NoLongLivedTokens: the emitted workflow has zero references toNPM_TOKENorJSR_TOKEN.
These gates run against the verdaccio-mock + JSR-mock harness from MEP-52 Phase 18.
6. Cross-references
- MEP-72 §Specification §5 — the normative CLI surface.
- 07-sigstore-npm-jsr-trusted-publishing — the Sigstore detail.
- MEP-52 Phase 18 implementation tracking — the workflow emit.