Skip to content

ci(release): publish binaries to the GitHub Packages npm registry - #19

Merged
jdh313 merged 1 commit into
mainfrom
npm-registry-publish
Aug 29, 2026
Merged

ci(release): publish binaries to the GitHub Packages npm registry#19
jdh313 merged 1 commit into
mainfrom
npm-registry-publish

Conversation

@jdh313

@jdh313 jdh313 commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Adds a second distribution channel alongside the release assets, so jdh-agents and ndr can consume agentforge as a dependency instead of hand-rolling fetch-and-verify code.

bunx @jdh313/agentforge check MARKETPLACE.yaml --out marketplaces

Shape

One wrapper package (@jdh313/agentforge) declaring three optionalDependencies@jdh313/agentforge-{darwin-arm64,linux-arm64,linux-x64} — each guarded by os/cpu so only the matching binary installs. This is the @biomejs/biome shape rather than esbuild's: no postinstall script, which hardened CI (install scripts disabled) and offline installs both break on.

The wrapper's bin is a small shim that computes @jdh313/agentforge-${process.platform}-${process.arch} and require.resolves it. No platform table — the release matrix already names every asset with Node's own spellings, so a table would be a second copy that can drift.

Why GitHub Packages and not npmjs

The release assets stay and remain the anonymous path. GitHub Packages' npm registry requires a token even for a public package, so this channel serves repos that already have one; anyone else still gets curl + SHA256SUMS.

Chosen over npmjs.org for reversibility. An npm publish is close to permanent — package@version can never be reused, and after 72 hours unpublishing requires no dependents, <300 weekly downloads, and a single maintainer. A GitHub package can be deleted outright. Given the only current consumers are two of my own repos, buying reversibility is the better trade. npmjs stays available as a later move, and the unscoped name agentforge is currently free there.

Ordering (load-bearing in two places)

  1. Platform packages publish before the wrapper. The wrapper pins them exactly, so a wrapper landing first is a version consumers can install and cannot run. Failing mid-run this way leaves inert orphans instead.
  2. publish-npm runs after publish. A tagged release must never exist without a downloadable binary; npm is the part allowed to fail late.

Auth

NODE_AUTH_TOKEN is the run's own GITHUB_TOKEN under a new packages: write permission. No long-lived npm credential to store or rotate.

Requires one manual step after merge: each consuming repo (jdh-agents, ndr) needs a one-time grant under the package's Manage Actions accessAdd Repository. Automatic access covers only the linked repo; that grant is what lets their GITHUB_TOKEN install.

Testing

Verified locally, not just dry-run:

  • DRY_RUN=1 AGENTFORGE_VERSION=v0.3.0 node scripts/publish-npm.mjs — all three packages, correct order, correct registry. Tarball 24.1 MB / 64.6 MB unpacked, in line with @biomejs/cli-darwin-arm64 at 57.6 MB.
  • Hand-assembled node_modules/@jdh313/ layout: shim resolves and executes, --version0.3.0, list-targets correct.
  • Platform package removed: shim exits 1 with the release-page fallback message.
  • setup-node pinned to the real v7.0.0 SHA, matching the workflow's other v7/v8 pins; registry-url and scope confirmed present in that version's action.yml.
  • 167 tests pass, biome and typecheck clean.

Risk surface

Additive. Touches no build, compile, or existing publish step — the release assets are produced and attached exactly as before. ci: type, so merging cuts no release; the first real publish happens on the next feat:/fix:.

Untested until then: the actual authenticated publish. A dry-run on a branch is not possible, since .releaserc.json pins branches: ["main"] and every downstream job gates on released == 'true'.

Rollback

Revert the commit. The publish-npm job disappears; assets are unaffected. Any already-published packages can be deleted from the repo's Packages tab.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LxNdWk6ZUzq6uWFtd94yWD

Add a second distribution channel alongside the release assets: one wrapper
package (`@jdh313/agentforge`) plus one package per platform, each guarded by
`os`/`cpu` so a package manager installs only the matching binary. This is the
@biomejs/biome shape — no postinstall script, which install-script-disabled CI
and offline installs both break on.

The assets stay the anonymous path. GitHub Packages' npm registry requires a
token even for a public package, so npm serves the repos that already have one
and the release binaries serve everyone else.

Ordering is load-bearing in two places. Platform packages publish before the
wrapper, whose optionalDependencies pin them exactly, so a mid-run failure
leaves inert orphans rather than an entry point that resolves to nothing. And
`publish-npm` runs after `publish`, so a tagged release never exists without a
downloadable binary.

Auth is the run's own GITHUB_TOKEN under `packages: write` — no long-lived npm
credential to store or rotate.

Verified locally: `--dry-run` publishes all three packages in order; a
hand-assembled node_modules resolves the shim and runs `--version` and
`list-targets`; removing the platform package exits 1 with the release-page
fallback.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LxNdWk6ZUzq6uWFtd94yWD
@jdh313
jdh313 merged commit cb660b9 into main Aug 29, 2026
1 check passed
@jdh313
jdh313 deleted the npm-registry-publish branch August 29, 2026 17:47
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 0.4.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant