Skip to content

MILAB-7081: trusted publishing for node-matrix, node-matrix-pnpm, node-docker-simple-fast-pnpm, node-simple, java-gradle - #219

Merged
mike-ainsel merged 2 commits into
v4-betafrom
MILAB-7081_trusted-publishing-shared-workflows
Oct 20, 2026
Merged

mike-ainsel merged 2 commits into
v4-betafrom
MILAB-7081_trusted-publishing-shared-workflows

Conversation

@mike-ainsel

@mike-ainsel mike-ainsel commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Follow-up to #217: trusted publishing (OIDC) for the shared node workflows that #217 did not cover. npm no longer accepts our 2FA-bypass token for direct publish (EOTP since ~2026-10-16), so these callers currently cannot publish: runenv-r* (9 repos, node-matrix), runenv-python-3 and software-conda (node-matrix-pnpm), java-debug and makeblastdb (node-docker-simple-fast-pnpm), the MiXCR/MiTool/RepSeq.IO software packages (java-gradle), and the asset boilerplate (node-simple).

All of these already publish from GitHub-hosted runners with id-token: write, so no split job is needed.

Changes

  • node-matrix-pnpm, node-docker-simple-fast-pnpm: changesets/action + pnpm -r publish is replaced by actions/node/pack-unpublished + actions/node/publish-tarballs in the same job (the path proven in MILAB-7081: npm trusted publishing (OIDC) for node-simple-pnpm #217). node-docker-simple-fast-pnpm keeps changesets/action for the "chore: update versions" PR and packs/uploads only when its hasChangesets output is false.
  • node-matrix, node-simple, blocks/node/build-and-publish (used by java-gradle): they keep npm publish; a new actions/node/prepare-trusted-publishing step runs before it:
    • switches the job to Node 24 when its Node is older than 22.14, and installs npm 11.21.0 when its npm is older than 11.5.1 (npm documents Node 22.14+ and npm 11.5.1+ for trusted publishing);
    • for uploads to registry.npmjs.org from a public repository, adds a missing package.json repository naming the repository and fails on one naming another: npm auto-enables provenance there and the registry rejects it (E422) otherwise, the same failure MILAB-7081: npm trusted publishing (OIDC) for node-simple-pnpm #217 reproduced and fixed for the split path. Other registries (GitHub Packages) are left alone; publishConfig.registry wins over the job's registry-url.
      The token stays configured; npm tries OIDC first and uses it only if the exchange fails.
  • npm-split-publish: third entry lib/prepare (src/bin/prepare.ts), public-repository detection moved to src/github.ts, prepareRepository / ensureRepository in src/repository.ts, tests in test/prepare.test.ts.
  • pack-unpublished (all split-publish callers, incl. node-simple-pnpm): refuses a package registry.npmjs.org does not have yet, before any script runs. Without a token its first version needs a manual publish (npm accepts a trusted publisher only for an existing package); before, the upload failed midway and left a partial release.
  • README: manifest.jsonl documented as a public contract (pframes-rs writes it by hand).
  • scripts/npm-trust-setup.sh: npm prompts for 2FA only when stdin and stdout are a terminal (npm lib/utils/auth.js), so the captured npm trust list --json failed with EOTP and the script stopped. On EOTP the call is now re-run on /dev/tty, which shows the browser approval; ticking "skip 2FA for 5 minutes" lets the rest of the batch through.

Verified

  • vitest 111/111 (pack integration also on pnpm 9.12), bats 10/10, lib/ rebuilt from source.
  • Independent review: no blockers; its findings are addressed here (Node check, registry-aware prepare, new-package check, manifest docs).
  • actionlint: no new findings in the changed workflows (counts identical to v4-beta).
  • Not yet run end to end: the new step and actions resolve at @v4-beta, so the first real run is a caller release after merge. Each package also needs its trusted publisher (repository + build.yaml, "npm publish" allowed) within 2 days of that release.

Merge order

  1. This PR into v4-beta, then promote v4-beta → v4 (merge-beta.sh).
  2. Then milaboratory/github-ci-internal#110 (uses prepare-trusted-publishing, which only exists after this PR), then promote github-ci-internal.
  3. milaboratory/pframes-rs#301 and MILAB-7081: publish to npm with trusted publishing (OIDC) platforma#1913 do not depend on this PR (they use pack-unpublished/publish-tarballs@v4, already on v4).

Callers of these workflows can no longer publish with the token anyway, so after promotion each package needs its trusted publisher, added within 2 days before that repo's next release.

MILAB-7081

npm no longer accepts 2FA-bypass tokens for direct publish. These workflows
already publish from GitHub-hosted runners with id-token: write, so they
need no split:

- node-matrix-pnpm, node-docker-simple-fast-pnpm: pnpm -r publish via
  changesets/action is replaced by pack-unpublished + publish-tarballs in
  the same job. node-docker-simple-fast-pnpm keeps changesets/action for
  the version PR and publishes only when no changesets are pending.
- node-matrix, node-simple, java-gradle (blocks/node/build-and-publish):
  new actions/node/prepare-trusted-publishing before `npm publish`: npm
  11.5.1+ and, in public repositories, a package.json `repository` naming
  the repository (npm auto-enables provenance; the registry rejects it
  otherwise). The token stays as the fallback npm uses if OIDC fails.
- npm-trust-setup.sh: npm prompts for 2FA only on a terminal; on EOTP the
  list call is re-run on /dev/tty so the browser approval appears.
@notion-workspace

Copy link
Copy Markdown

core.info('Private repository: npm adds no provenance, package.json left as is.');
return;
}
const result = ensureRepository(pkgDir, root, sourceRepository);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 GitHub Packages releases can fail

prepare applies npmjs repository checks to every public GitHub repository, including uploads to GitHub Packages—the default registry in all three callers. If a package's repository points to an upstream or separate repository, ensureRepository now stops the release even though npmjs provenance rules do not apply.

Resolve the package's target registry first and call ensureRepository only for npmjs uploads, as packWorkspace already does.

Prompt To Fix With AI
This is a comment left during a code review.
Path: actions/node/npm-split-publish/src/bin/prepare.ts
Line: 22

Comment:
**GitHub Packages releases can fail**

`prepare` applies npmjs repository checks to every public GitHub repository, including uploads to GitHub Packages—the default registry in all three callers. If a package's `repository` points to an upstream or separate repository, `ensureRepository` now stops the release even though npmjs provenance rules do not apply.

Resolve the package's target registry first and call `ensureRepository` only for npmjs uploads, as `packWorkspace` already does.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code

Comment on lines +88 to +91
if [ "${rc}" -ne 0 ] && grep -q 'EOTP' "${err}" && authenticate "${pkg}"; then
# Approved in the browser; the 2FA skip window covers the retry.
rc=0
out="$(npm trust list "${pkg}" --json 2>"${err}")" || rc=$?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Approval retry lacks tests

The new interactive approval and successful retry have no test coverage. The existing EOTP test expects failure, and the npm stub always fails for that package. The suite therefore cannot catch a broken approval-and-retry path.

Add a terminal-backed test or an injectable authentication helper covering EOTP, successful approval, and a successful JSON retry. Assert that approval output stays out of the captured JSON and that the batch continues.

Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/npm-trust-setup.sh
Line: 88-91

Comment:
**Approval retry lacks tests**

The new interactive approval and successful retry have no test coverage. The existing `EOTP` test expects failure, and the npm stub always fails for that package. The suite therefore cannot catch a broken approval-and-retry path.

Add a terminal-backed test or an injectable authentication helper covering `EOTP`, successful approval, and a successful JSON retry. Assert that approval output stays out of the captured JSON and that the batch continues.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

- pack refuses packages registry.npmjs.org does not have yet, before any
  script runs: without a token their first version needs a manual publish
  (npm only accepts a trusted publisher for an existing package). Avoids a
  partial release that keeps failing at upload.
- prepare-trusted-publishing: switch to Node 24 when the job's Node is older
  than 22.14 (npm documents 22.14+ for trusted publishing); leave package.json
  alone for registries other than npmjs (publishConfig.registry wins), via a
  tested prepareRepository.
- README: manifest.jsonl documented as a public contract (pframes-rs writes
  it); new-package rule.
@mike-ainsel
mike-ainsel merged commit fcea556 into v4-beta Oct 20, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants