npm: distribute binary via optional platform packages#261
Open
l-hellmann wants to merge 5 commits into
Open
Conversation
5 tasks
Replace the postinstall download with per-platform npm packages selected
via optionalDependencies + os/cpu fields. Bun and other managers block
postinstall scripts, which left the binary undownloaded ("You have not
installed zcli-..."). Delivering the binary through a normal optional
dependency works under Bun (it honors os/cpu) and avoids the
runtime-download regressions: EACCES on global installs, broken offline
and Docker installs, and stale binaries on upgrade.
The main package becomes a thin launcher that resolves and execs the
matching platform binary. A build script generates the platform packages
from the raw release binaries, and the release pipeline publishes them
before the main package so the optional deps resolve on install.
After publishing, install @zerops/zcli at the released version on linux, macOS (arm64 and amd64) and Windows via npm, plus Bun on linux/macOS, and run `zcli version`. Catches a broken release - missing platform package, os/cpu mismatch, Bun incompatibility, lost exec bit - immediately rather than via user reports.
Run the npm publish + smoke-test jobs on prereleased events too, but tag pre-releases as `next` instead of `latest`. `npm i @zerops/zcli` keeps installing the latest stable; pre-releases are reachable via `@zerops/zcli@next` for testing without affecting existing users.
main now stamps an install channel into the binary (raw=manual vs npm) so `zcli upgrade` knows whether to self-update or defer to the package manager. The npm packages must therefore ship the channel=npm binaries, which goreleaser only emits inside the *-npm.tar.gz archives. Download and extract those instead of the raw (channel=manual) release assets.
3169148 to
12cd63f
Compare
npm publishes aren't transactional, so guarantee the consumer-facing invariant instead: publish @zerops/zcli only after verifying all five platform packages exist at the target version, so its optionalDependencies can never resolve to a missing package. Platform publishes now skip versions already on the registry, making a re-run after a partial failure complete without bumping the version.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What Type of Change is this?
Description (required)
Reworks npm distribution from a
postinstall-time download to per-platform packages selected viaoptionalDependencies+os/cpufields (the pattern used by esbuild/swc/biome, described in Sentry's "Publishing binaries on npm").Why: Bun (and other managers / hardened setups) block
postinstallscripts by default, so the binary was never downloaded andzclifailed withYou have not installed zcli-.... Simply moving the download to first-run (see #260) fixes Bun but regresses the existing install base:EACCESon global installs run by a non-root user, no longer self-contained Docker/CI images, network required at first run, and stale binaries on upgrade. Delivering the binary through a normal optional dependency sidesteps all of that — Bun honorsos/cpu, and the binary arrives at install time exactly like before, with the package manager guaranteeing the version matches.What changed:
tools/npm) is now a thin launcher:bin/zcli.jsresolves the matching platform binary viarequire.resolve(lib/resolve.js+lib/platforms.js) and execs it, forwarding args and exit code. Removed theaxios/tar/rimrafdownload machinery and thepostinstall/preuninstallscripts.scripts/build-platform-packages.mjsgenerates one publishable package per platform (os/cpu, version, binary inbin/) from the release binaries.optionalDependenciesresolve.zcli upgradedefer to npm instead of self-updating), which goreleaser only emits inside the*-npm.tar.gzarchives. The publish job downloads and extracts those rather than the rawchannel=manualrelease assets. goreleaser itself is unchanged by this PR.smoke-testCI job: installs the just-published@zerops/zcliacross linux / macOS (arm64 + amd64) / Windows via npm, plus Bun on linux/macOS, and runszcli version. A broken release (missing platform package,os/cpumismatch, Bun incompatibility, lost exec bit) fails CI immediately instead of surfacing in user reports.prereleasedevents, publishing under thenextdist-tag instead oflatest.npm i @zerops/zclikeeps getting the latest stable; a pre-release is installable via@zerops/zcli@next(andbun add @zerops/zcli@next) for testing without affecting existing users. Promote a tested pre-release to stable with a full GitHub release (ornpm dist-tag add @zerops/zcli@<version> latest).Notes / open questions:
postinstallfallback (the Sentry post keeps one for managers run with--no-optional/--ignore-optional). Left out to stay clean and Bun-safe; we surface a clear error instead. Easy to add if wanted.npm publishpreserving the executable bit on the binary (same assumption esbuild makes; build script setschmod 755). Verified: the packed tarball keeps-rwxr-xr-x.smoke-testjob is a post-publish detector, not a pre-publish gate — the package is already live when it runs, so a failure alerts rather than prevents. A Verdaccio-based pre-publish gate is possible if we want one.reportjob still fires only on full releases, not pre-releases.Tested locally end-to-end with a fake binary set: build script output (
os/cpu/version, Windows.exe), resolver, launcher arg/exit-code forwarding, and the missing-package error path.goreleaser checkpasses, andnpm publish --dry-run/npm packconfirm the file list and exec bit.Related issues & labels (optional)