feat: ship per-arch standalone binaries on each release#73
Open
ja-818 wants to merge 1 commit intostripe:mainfrom
Open
feat: ship per-arch standalone binaries on each release#73ja-818 wants to merge 1 commit intostripe:mainfrom
ja-818 wants to merge 1 commit intostripe:mainfrom
Conversation
Adds a release-binaries workflow that produces single-file standalone
link-cli binaries for darwin-arm64, darwin-x64, linux-x64, and
windows-x64 every time a GitHub Release is published, plus a
manifest.json with sha256 checksums and download URLs.
Motivation: agent platforms and desktop apps that want to bundle
link-cli currently have to build their own binaries from the npm
package, which means owning a CI pipeline that compiles ink + viem +
update-notifier into a single executable. Shipping binaries upstream
lets downstream consumers pin against a manifest URL the same way they
pin any other vendored CLI (codex, claude-code, etc).
How it works:
- scripts/build-binary.ts runs bun build --compile against the existing
packages/cli/dist/cli.js entrypoint. A small plugin stubs two
optional deps that ink and update-notifier reference but that are
not needed at runtime in a bundled context (react-devtools-core only
loads when DEV=true; update-notifier is already external in tsup).
- scripts/generate-manifest.ts emits dist-bin/manifest.json with
version, generated_at, and per-target { file, sha256, url }.
- .github/workflows/release-binaries.yml triggers on
release.published (changesets/action publishes a GH release on every
npm release), builds all four targets in parallel inside a single
Ubuntu runner via Bun cross-compile, and attaches the binaries +
manifest to the release.
Verified locally: each binary runs --help, --version, and exercises
the viem-dependent mpp decode path successfully on darwin-arm64.
Sizes (minified): darwin-arm64 60 MB, darwin-x64 64 MB, linux-x64
101 MB, windows-x64 111 MB. Linux/Windows are heavier because Bun
ships more runtime polyfills for non-host targets.
17e5b3f to
23fdf0a
Compare
Author
|
Force-pushed with the correct git author email so the commit links to my GitHub account. Going to sign the CLA now. |
| done | ||
|
|
||
| - name: Generate manifest | ||
| run: bun run scripts/generate-manifest.ts "${{ steps.tag.outputs.version }}" |
Contributor
There was a problem hiding this comment.
doesn't this script need two params?
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.
Summary
Adds a release workflow that attaches single-file standalone
link-clibinaries to every GitHub Release, plus amanifest.jsonwith sha256 checksums and per-target download URLs. Targets:darwin-arm64,darwin-x64,linux-x64,windows-x64.Motivation
We're shipping Houston (a desktop platform for non-technical founders to run AI agents) with Link as a first-class payments surface. Houston bundles three CLIs today (
codex,composio,claude-code); for each we download a signed binary from upstream, sha256-verify, sign + notarize alongside our.app, and stage it intoContents/Resources/bin/. That works well because each upstream ships per-arch standalone binaries.link-clionly ships on npm. Bundling a non-Node binary today means owning abun build --compile(orpkg) pipeline per consumer, which means every consumer hitting the same packaging edges (thereact-devtools-coreresolution issue under ink,update-notifierexternal handling, viem's WASM + dynamic imports). Solving it once upstream is a much smaller surface than every agent platform doing it independently.Tracking issue with full context: #72.
What this PR does
scripts/build-binary.ts: invokesBun.buildagainst the existingpackages/cli/dist/cli.jsentrypoint, with a tiny in-process plugin that stubs two optional deps (react-devtools-coreis dynamically loaded by ink only whenDEV=true;update-notifieris already external intsup.config.ts). Output:dist-bin/link-cli-<target>[.exe].scripts/generate-manifest.ts: walksdist-bin/, sha256s every binary, emitsmanifest.jsonwithversion,generated_at, and per-target{ file, sha256, url }..github/workflows/release-binaries.yml: triggers onrelease.published(the eventchangesets/actionfires on every npm publish). On a single Ubuntu runner, builds all four targets via Bun cross-compile, generates the manifest, and uploads everything to the existing release viagh release upload.workflow_dispatchis provided for manual rebuilds against an existing tag.README.md: short "Standalone binaries" section under Installation pointing at the latest release + the manifest URL pattern..gitignore: addsdist-bin/.Verification
Run locally on macOS arm64 (Bun 1.3.10, Node 22, pnpm 10.32):
Result on
darwin-arm64host:pnpm turbo run build.dist-bin/link-cli-darwin-arm64 --helpand--versionwork.dist-bin/link-cli-darwin-arm64 mpp decode --challenge 'Payment id="ch_001", realm="merchant.example", method="stripe", intent="charge", request="..."'runs the viem-backed challenge decoder successfully (validation reaches the per-field "amount: missing" check, which is the expected behavior for an incomplete test challenge).CI gauntlet on this branch passes locally:
pnpm biome check .— clean (96 files)pnpm turbo run typecheck— passpnpm turbo run test— 100/100 passpnpm turbo run build— passNotes
1.3.10in the workflow to keep release outputs reproducible. Happy to change that tolatestor whatever your team prefers.// @ts-expect-erroron one line because thecompileoption is supported byBun.buildat runtime but not yet in the public TypeScript types.release.yml— the existing changesets-driven flow is unchanged. This new workflow is fully additive and runs after the release event the existing flow already produces.Future work (not in this PR)
mpp decodeso the static bundle is smaller for non-MPP users (mentioned in the tracking issue).🤖 Co-authored with Claude Code.