Per-Platform npm Packages
@goondocks/myco ships as a small JavaScript shell that resolves a
platform-specific binary at install time. The binary itself lives in one of
five sibling packages on npm — only the matching one is installed on each
user’s machine.
@goondocks/myco ← core: bin/, dist/, scripts/, skills/, no binary
@goondocks/myco-darwin-arm64 ← binary only, ~30 MB packed
@goondocks/myco-darwin-x64 ← ditto
@goondocks/myco-linux-x64 ← ditto
@goondocks/myco-linux-arm64 ← ditto
@goondocks/myco-windows-x64 ← ditto
This is the same pattern esbuild, swc, rollup, and the Bun runtime use.
Product support is narrower than package availability in the current release: macOS is the primary supported platform, while Linux and Windows packages are published for early testing and remain experimental.
Why
Before this split, @goondocks/myco bundled all five cross-compiled Bun
binaries into a single ~200 MB tarball. Every user downloaded four
binaries they’d never run. After the split:
- Per-user install: ~200 MB → ~30–40 MB (5–6× smaller).
- The 210 MB CI tarball guardrail drops to 50 MB for core, 60 MB per platform package.
- A Bun-runtime bump no longer threatens the npm publish ceiling.
How resolution works
- Each platform package declares matching
osandcpufields in its publishedpackage.json. npm installs only the one whose constraints match the host; the others are skipped viaoptionalDependenciesplus the os/cpu filter. - The core package’s
postinstallrunspackages/myco/scripts/select-binary.mjs, which callsrequire.resolve('@goondocks/myco-<target>/bin/<bin>')to find the binary that npm installed. It writespackages/myco/vendor/resolved.jsonwith the absolutebinaryPath. - The bin shim
packages/myco/bin/myco.cjsreadsresolved.jsonandexecFileSyncs the binary with forwarded argv.
Why os/cpu are NOT in source
Workspace installs validate os/cpu on every linked workspace —
declaring darwin-x64 in source would break npm install for everyone
on darwin-arm64. The fields are injected just before npm pack by
scripts/inject-platform-metadata.mjs, which the release workflow
calls for each target. Source-tree package.jsons stay platform-neutral.
Release flow
The single tag myco/vX.Y.Z triggers the entire release. The CI workflow:
- Cross-compile the binary for each target into the matching
packages/myco-<target>/bin/. Each target uploads itsbin/as a GitHub Actions artifact. - Sync versions with
scripts/sync-package-versions.mjs --target myco --version <version>. This bumps everypackage.jsonin themycofamily — including the five platform package.json files — and rewritesoptionalDependenciesinpackages/myco/package.jsonso core pins each platform package to the same exact version. - Stage binaries back into their per-platform package directories.
- Inject
os/cpuinto each platform package.json (scripts/inject-platform-metadata.mjs <target>). - Pack each platform package and core (six tarballs total).
- Publish the five platform packages to npm first, then core. Platform-first ordering matters: if core were published first, fresh installs would fail to resolve the optionalDependencies until the platform packages caught up.
The release workflow lives in .github/workflows/publish.yml.
Dev / source-checkout behavior
For local development:
- All five platform packages are workspaces, symlinked into
node_modules/@goondocks/bynpm install. - The binary for the host target is written into
packages/myco-<host>/bin/mycobymake dev-link(which invokesscripts/build-single-target.mjs). - After the binary lands,
make dev-linkre-runsscripts/select-binary.mjssovendor/resolved.jsonis populated for callers that go throughbin/myco.cjs. (Bench dev typically reaches the binary via~/.local/bin/myco-dev, the direct symlink, but tools that go through the npm bin shim needresolved.json.) - Before the binary is built,
select-binary.mjsdetects the source checkout (presence ofpackages/myco/src/) and exits 0 with a warning rather than failing the postinstall — so a cleannpm cion the monorepo doesn’t break.
Adding a new target
To support an additional os/cpu:
- Create
packages/myco-<target>/with apackage.jsonmatching the existing platform packages (noos/cpuin source). - Add the package to
optionalDependenciesinpackages/myco/package.json. - Add the target to:
PLATFORM_METADATAinscripts/inject-platform-metadata.mjsMYCO_PLATFORM_OPTIONAL_DEPSand themycotarget’sfileslist inscripts/sync-package-versions.mjsMYCO_PLATFORM_OPTIONAL_DEPSconsumers in the publish workflow (cross-compile matrix, “Verify platform binaries” step, “Pack platform packages” step, publish step)VALIDinpackages/myco/scripts/build-single-target.mjs- the platform pattern in
packages/myco/src/service/spec-builder.ts#looksLikeDevBuildExecutable
- Provide a Bun cross-compile target plus libsqlite3 build for the architecture.