impl-npm-publish-distribution

Impl: npm distribution via per-platform optionalDependencies (Shape A)

Metadata

Statusopen ‖ paused
Created2026-07-25T20:57:03.480428743+00:00
Started2026-07-26T07:15:47.316250667+00:00
Completed2026-07-26T07:16:15.731838049+00:00
Tagsrelease, npm, distribution, ci
Tokens0 in / 0 out

Description

Objective

Add npm as a third distribution channel onto the SAME attested GitHub-Release binaries (not a parallel build). The recommended shape is the industry-standard per-platform optionalDependencies driver pattern (Shape A): one small JS driver package + N platform subpackages, each holding one prebuilt binary. This reaches the no-Rust-toolchain majority (Node is ubiquitous) without weakening any property of the existing channels.

Context: docs/studies/wg-npm-distribution-design.md §5 (Shape A decision + the canonical driver shim), §6.5 (embed the manifest for offline SHA256), §8.3 (the CI npm-publish job), §10.1 (the minimum viable launch). docs/studies/roadmap-rate-limit-and-distribution.md §1/§4(T7). Depends on T6 for file-safety (both edit .github/workflows/release.yml); also means T7 repackages SIGNED binaries.

What to build

  1. New npm-publish job in .github/workflows/release.yml (needs: [plan, assemble], runs only when publish == 'true', i.e. on a real v* tag not a dry run), on ubuntu-22.04 (it only repackages + publishes, no compiling):
    • gh release download <tag> each archive (or reuse the assemble job uploaded artifacts).
    • Verify each archive Sigstore attestation: gh attestation verify <archive> --repo graphwork/wg.
    • Verify each archive SHA256 against release-manifest.json.
    • Extract wg/nex from each archive into a platform package dir with the os/cpu/libc package.json, LICENSE, and a copy of release-manifest.json.
    • For the driver package: set version + all five optionalDependencies versions to the release version; copy the bin/wg.js + bin/nex.js shims; embed release-manifest.json.
    • npm publish --provenance --access public for each of the 6 packages (5 platforms + driver). Use npm dist-tag for prerelease channels (rc/beta).
    • Anti-drift gate: assert the platform package embedded release-manifest.json is byte-equal to the released one (mirrors embed-worksgood-pi-check).
  2. The 6 packages (Shape A, study §5.1):
    • @wg/cli (driver): bin/wg.js + bin/nex.js shims (the canonical ~30-line form from study §5.4 — resolve platform package, spawn binary, clear --omit=optional error + cargo-install fallback); optionalDependencies map of the 5 platform packages; embedded release-manifest.json.
    • @wg/cli-linux-x64 (os: linux, cpu: x64, libc: glibc), @wg/cli-linux-arm64, @wg/cli-darwin-x64, @wg/cli-darwin-arm64, @wg/cli-win32-x64 (each contains wg + nex, .exe on win32).
  3. Driver shim runtime version-skew guard (study §10.3 open question — recommendation YES): assert require("@wg/cli-<os>-<cpu>/package.json").version === require("@wg/cli/package.json").version before spawn, error loudly (pi-plugin-compat-style handshake) if a monorepo hoist mismatched them.
  4. Document the glibc floor (study §4.4 — ubuntu-22.04 builds pin glibc >= 2.35) and the 4 unsupported arches in the driver README; the shim prints a helpful error if the host is unsupported.
  5. npm scope decision (study §10.3 open question): @wg vs @graphwork must be RESOLVED before the first publish (baked into package names, hard to change post-fact). If @wg 403s on publish, fail loud and surface @graphwork/cli as the alternative — do NOT silently rename.

Versioning (study §8.1)

npm package version = git tag (minus v) = Cargo version for stable releases. All 6 packages publish at the SAME version. Prereleases (v0.2.0-rc.1) map to npm prerelease versions under a rc dist-tag. The WG_*_COMPAT_VERSION consts NEVER appear in the npm version — they are runtime handshakes inside the binary, checked at runtime, not a delivery-channel concern.

File scope

  • .github/workflows/release.yml (new npm-publish job)
  • new npm/ package scaffolding: @wg/cli/bin/wg.js, @wg/cli/bin/nex.js, @wg/cli/package.json, @wg/cli/README.md, and the 5 platform package.json templates + a publish script.

Implement directly. The job + scaffolding are one cohesive release-engineering unit.

Validation

  • The npm-publish job runs only on a real v* tag (publish == 'true'), downstream of plan + assemble, and is skipped on dry-run-*/release-test-*.
  • Each downloaded archive is verified (gh attestation verify + SHA256 vs release-manifest.json) before repackaging; a tampered/mismatched archive FAILS the job.
  • The driver shim (bin/wg.js) resolves the correct platform package for the host (process.platform + process.arch), spawns the binary with stdio: inherit, and exits with the binary exit code.
  • The shim prints a CLEAR error under --omit=optional (platform package missing) and surfaces the cargo install --locked worksgood fallback.
  • The runtime version-skew guard errors loudly if the installed platform package version != the driver version.
  • The 5 platform packages carry the correct os/cpu/libc keys so npm skips wrong platforms automatically.
  • release-manifest.json is embedded in the driver and the anti-drift gate asserts it is byte-equal to the released one.
  • npm publish --provenance --access public is invoked for all 6 packages; prereleases go under a rc/beta dist-tag (NOT latest).
  • The driver README documents the glibc 2.35 floor, the 5 supported (os,cpu) tuples, and the 4 unsupported arches.
  • The npm scope decision is recorded (resolve @wg vs @graphwork before first publish; fail loud + surface the alternative on a 403).
  • A dry-run/rehearsal of the publish job (against a test tag or npm publish --dry-run) shows all 6 packages staged correctly.
  • No model pin; routes through the active zai profile.

Depends on

Required by

Log