Skip to content

Commit 57c71cc

Browse files
authored
relayburn: install wrapper dispatches Rust burn binary via platform packages (#360)
* relayburn: install wrapper dispatches Rust burn binary via platform packages Mirror the napi-rs `@relayburn/sdk-<platform>` shape: the umbrella `relayburn` package declares per-platform `@relayburn/cli-<platform>` packages as `optionalDependencies`, and the `burn` shim in `packages/relayburn/bin/burn.js` resolves and execs the prebuilt Rust binary out of whichever one npm installed. Replaces the prior dispatch through `@relayburn/cli` (TS). Per-platform packages cover darwin-arm64, darwin-x64, linux-arm64-gnu, linux-x64-gnu; Windows is forward-compat in the dispatcher mapping but not yet shipped (#359). Adds `.github/workflows/cli-build.yml` matrix build (mirrors `napi-build.yml`); the `publish:` job is left as a stub until the cutover publish.yml integration follow-up. The `bin/` directory under each platform package is gitignored — populated by the matrix build. Refs #240. * relayburn: exclude from pnpm workspace so frozen-lockfile installs The umbrella `relayburn` package now lists per-platform Rust binary packages (`@relayburn/cli-darwin-arm64`, …) as `optionalDependencies`. Those don't exist on npm yet, so pnpm 10 silently drops them from the lockfile during install — and CI's `pnpm install --frozen-lockfile` then rejects the lockfile because the specifiers in `package.json` aren't represented. Mirror the `sdk-node` exclusion in `pnpm-workspace.yaml`: drop `packages/relayburn` from the workspace. Nothing in-repo consumes it (it's a thin install wrapper that spawns the Rust binary), so this is a no-op for everyone except the install-time path. * relayburn: address binary package review feedback
1 parent 1d25abb commit 57c71cc

17 files changed

Lines changed: 578 additions & 12 deletions

File tree

.github/workflows/cli-build.yml

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
name: cli build
2+
3+
# Build prebuilt `burn` binaries for every platform we publish to npm under
4+
# `@relayburn/cli-*`. PRs validate the matrix; tags / manual dispatch *would*
5+
# publish, but the publish step is stubbed off for now — the cutover publish
6+
# workflow integration lands as a follow-up to this PR.
7+
#
8+
# Mirrors `.github/workflows/napi-build.yml`: the napi-rs SDK uses the same
9+
# umbrella + per-platform-package shape, just with `.node` artifacts instead
10+
# of native executables.
11+
12+
on:
13+
pull_request:
14+
paths:
15+
- 'crates/relayburn-cli/**'
16+
- 'crates/relayburn-sdk/**'
17+
- 'Cargo.toml'
18+
- 'Cargo.lock'
19+
- 'rust-toolchain.toml'
20+
- 'packages/relayburn/**'
21+
- '.github/workflows/cli-build.yml'
22+
push:
23+
branches: [main]
24+
paths:
25+
- 'crates/relayburn-cli/**'
26+
- 'crates/relayburn-sdk/**'
27+
- 'Cargo.toml'
28+
- 'Cargo.lock'
29+
- 'rust-toolchain.toml'
30+
- 'packages/relayburn/**'
31+
- '.github/workflows/cli-build.yml'
32+
workflow_dispatch:
33+
inputs:
34+
publish:
35+
description: 'Publish prebuilt artifacts to npm under the `next` tag (stubbed; full wiring lands with the cutover publish workflow)'
36+
type: boolean
37+
default: false
38+
39+
permissions:
40+
contents: read
41+
42+
concurrency:
43+
group: cli-build-${{ github.ref }}
44+
cancel-in-progress: true
45+
46+
jobs:
47+
build:
48+
name: build ${{ matrix.short }}
49+
strategy:
50+
fail-fast: false
51+
matrix:
52+
include:
53+
- target: x86_64-apple-darwin
54+
os: macos-15-intel
55+
short: darwin-x64
56+
- target: aarch64-apple-darwin
57+
os: macos-14
58+
short: darwin-arm64
59+
- target: x86_64-unknown-linux-gnu
60+
os: ubuntu-latest
61+
short: linux-x64-gnu
62+
- target: aarch64-unknown-linux-gnu
63+
os: ubuntu-latest
64+
short: linux-arm64-gnu
65+
runs-on: ${{ matrix.os }}
66+
steps:
67+
- name: Checkout
68+
uses: actions/checkout@v6
69+
70+
- name: Setup Rust toolchain
71+
# rust-toolchain.toml at the repo root pins the channel + components.
72+
run: |
73+
rustup toolchain install
74+
rustup target add ${{ matrix.target }}
75+
76+
- name: Install aarch64-linux cross toolchain (linux-arm64 only)
77+
if: matrix.target == 'aarch64-unknown-linux-gnu'
78+
# `libsqlite3-sys` (a transitive dep via `rusqlite`) compiles bundled
79+
# SQLite C source via `cc-rs`, which probes for `aarch64-linux-gnu-gcc`.
80+
# Same setup as `napi-build.yml`'s aarch64-linux leg.
81+
run: |
82+
sudo apt-get update
83+
sudo apt-get install -y gcc-aarch64-linux-gnu
84+
85+
- name: Cache cargo registry + target
86+
uses: actions/cache@v4
87+
with:
88+
path: |
89+
~/.cargo/registry
90+
~/.cargo/git
91+
target
92+
key: cargo-cli-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock', '**/Cargo.toml', 'rust-toolchain.toml') }}
93+
restore-keys: |
94+
cargo-cli-${{ runner.os }}-${{ matrix.target }}-
95+
cargo-cli-${{ runner.os }}-
96+
97+
- name: Build burn binary for ${{ matrix.target }}
98+
# The binary name is `burn` (the `[[bin]]` rename in
99+
# `crates/relayburn-cli/Cargo.toml`); the crate is `relayburn-cli`.
100+
# CC_/linker env vars only take effect on the aarch64-linux leg
101+
# (cargo ignores per-target vars when host == target); native legs
102+
# are unaffected.
103+
env:
104+
CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc
105+
CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++
106+
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
107+
run: |
108+
cargo build --release --target ${{ matrix.target }} --bin burn -p relayburn-cli
109+
110+
- name: Strip binary
111+
# Stripping cuts ~half the size off a release `burn` (debug info,
112+
# symbol tables that aren't needed at runtime). On macOS use `-x` to
113+
# strip non-global symbols only — fully stripping a Mach-O binary
114+
# breaks codesigning. On linux, default `strip` is fine; when
115+
# cross-compiling for aarch64 we use the matching cross-strip.
116+
run: |
117+
BIN="target/${{ matrix.target }}/release/burn"
118+
case "${{ matrix.target }}" in
119+
*-apple-darwin)
120+
strip -x "$BIN"
121+
;;
122+
aarch64-unknown-linux-gnu)
123+
aarch64-linux-gnu-strip "$BIN"
124+
;;
125+
*-unknown-linux-gnu)
126+
strip "$BIN"
127+
;;
128+
esac
129+
ls -lh "$BIN"
130+
131+
- name: Stage binary into platform package
132+
# Mirror the layout the publish step will rely on:
133+
# `packages/relayburn/npm/<short>/bin/burn`. The directory is
134+
# gitignored at rest; the artifact upload below is what survives
135+
# the matrix run for the publish job to download.
136+
run: |
137+
mkdir -p packages/relayburn/npm/${{ matrix.short }}/bin
138+
cp target/${{ matrix.target }}/release/burn packages/relayburn/npm/${{ matrix.short }}/bin/burn
139+
chmod +x packages/relayburn/npm/${{ matrix.short }}/bin/burn
140+
141+
- name: Upload artifact
142+
uses: actions/upload-artifact@v4
143+
with:
144+
name: relayburn-cli-${{ matrix.short }}
145+
path: packages/relayburn/npm/${{ matrix.short }}/bin/burn
146+
if-no-files-found: error
147+
retention-days: 7
148+
149+
- name: Smoke test (`burn --help`)
150+
# Native legs only. The aarch64-linux leg cross-compiles on an x64
151+
# host so the runner's interpreter cannot execute the binary.
152+
if: matrix.target != 'aarch64-unknown-linux-gnu'
153+
run: |
154+
set -euo pipefail
155+
packages/relayburn/npm/${{ matrix.short }}/bin/burn --help
156+
packages/relayburn/npm/${{ matrix.short }}/bin/burn --version
157+
158+
smoke_dir="$(mktemp -d)"
159+
umbrella_dir="$(mktemp -d)"
160+
trap 'rm -rf "$smoke_dir" "$umbrella_dir"' EXIT
161+
npm install --prefix "$smoke_dir" --no-save --omit=optional \
162+
--ignore-scripts --no-audit --no-fund \
163+
./packages/relayburn/npm/${{ matrix.short }}
164+
"$smoke_dir/node_modules/.bin/burn" --help
165+
"$smoke_dir/node_modules/.bin/burn" --version
166+
167+
npm install --prefix "$umbrella_dir" --no-save --omit=optional \
168+
--ignore-scripts --no-audit --no-fund \
169+
./packages/relayburn
170+
NODE_PATH="$smoke_dir/node_modules" "$umbrella_dir/node_modules/.bin/burn" --help
171+
NODE_PATH="$smoke_dir/node_modules" "$umbrella_dir/node_modules/.bin/burn" --version
172+
173+
publish:
174+
# Stub. Real publish wiring lands as a follow-up that integrates with
175+
# `.github/workflows/publish.yml`: download the matrix artifacts, drop
176+
# each into the right `packages/relayburn/npm/<short>/bin/` directory,
177+
# and `npm publish` the umbrella + each per-platform package via OIDC
178+
# trusted publisher. Until then, calling `workflow_dispatch` with
179+
# `publish: true` exercises the artifact download path so we catch
180+
# wiring errors early without actually pushing to npm.
181+
needs: build
182+
if: github.event_name == 'workflow_dispatch' && github.event.inputs.publish == 'true'
183+
runs-on: ubuntu-latest
184+
permissions:
185+
contents: read
186+
id-token: write
187+
steps:
188+
- name: Checkout
189+
uses: actions/checkout@v6
190+
191+
- name: Download all artifacts
192+
uses: actions/download-artifact@v4
193+
with:
194+
path: packages/relayburn/npm-artifacts
195+
196+
- name: Inspect artifacts (publish stub)
197+
run: |
198+
echo 'Publish step is currently a stub. Full wiring lands in the cutover publish.yml integration follow-up.'
199+
ls -lahR packages/relayburn/npm-artifacts || true

.github/workflows/publish.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,42 @@ jobs:
221221
done
222222
echo "versions=${VERSIONS# }" >> "$GITHUB_OUTPUT"
223223
224+
# `relayburn` points at prebuilt platform packages with exact
225+
# optionalDependency versions. Keep those package manifests and
226+
# the generic CLI fallback in lockstep with the umbrella version
227+
# that npm version just computed.
228+
node --input-type=module <<'SYNCEOF'
229+
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
230+
import { join } from 'node:path';
231+
232+
const formatJson = (value) => `${JSON.stringify(value, null, 2)}\n`;
233+
const relayburnPath = 'packages/relayburn/package.json';
234+
const relayburn = JSON.parse(readFileSync(relayburnPath, 'utf8'));
235+
const version = relayburn.version;
236+
const platformRoot = 'packages/relayburn/npm';
237+
238+
const platformPackages = readdirSync(platformRoot, { withFileTypes: true })
239+
.filter((entry) => entry.isDirectory())
240+
.map((entry) => {
241+
const path = join(platformRoot, entry.name, 'package.json');
242+
if (!existsSync(path)) return null;
243+
const json = JSON.parse(readFileSync(path, 'utf8'));
244+
json.version = version;
245+
writeFileSync(path, formatJson(json));
246+
return json.name;
247+
})
248+
.filter(Boolean)
249+
.sort();
250+
251+
relayburn.optionalDependencies = {
252+
'@relayburn/cli': version,
253+
...Object.fromEntries(platformPackages.map((name) => [name, version])),
254+
};
255+
writeFileSync(relayburnPath, formatJson(relayburn));
256+
257+
console.log(`relayburn optional dependencies and ${platformPackages.length} platform package(s) synced to ${version}.`);
258+
SYNCEOF
259+
224260
# Lockstep the Rust workspace to the npm version. Any package's
225261
# post-bump version works since they're all equal after the bump.
226262
# The cli/sdk-node path-deps on relayburn-sdk pin MAJOR.MINOR
@@ -564,6 +600,7 @@ jobs:
564600
git config user.name "github-actions[bot]"
565601
git config user.email "github-actions[bot]@users.noreply.github.com"
566602
git add packages/*/package.json packages/*/CHANGELOG.md CHANGELOG.md \
603+
packages/relayburn/npm/*/package.json \
567604
Cargo.toml Cargo.lock \
568605
crates/relayburn-cli/Cargo.toml crates/relayburn-sdk-node/Cargo.toml
569606
if git diff --cached --quiet; then

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,10 @@ target/
1515
# publish time. They should never be committed.
1616
packages/sdk-node/src/*.node
1717
packages/sdk-node/*.node
18+
19+
# Prebuilt `burn` binaries for the Rust CLI — the cli-build matrix
20+
# produces these and the cutover publish workflow drops them into
21+
# `packages/relayburn/npm/<platform>/bin/` before `npm publish`. The
22+
# umbrella `relayburn` package resolves them at install time via
23+
# `optionalDependencies`. They should never be committed.
24+
packages/relayburn/npm/*/bin/

packages/relayburn/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All notable changes to `relayburn`.
44

55
## [Unreleased]
66

7+
### Changed
8+
9+
- `relayburn` now prefers the prebuilt Rust `burn` binary from per-platform `@relayburn/cli-<platform>` packages, with `@relayburn/cli` kept as a fallback for unsupported or missing native packages.
10+
711
## [1.6.2] - 2026-05-02
812

913
### Changed

packages/relayburn/README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,23 @@ Install the [`burn`](https://github.com/AgentWorkforce/burn) CLI globally:
66
npm i -g relayburn
77
```
88

9-
This is a thin wrapper that depends on [`@relayburn/cli`](https://www.npmjs.com/package/@relayburn/cli) and exposes the `burn` command. Both names produce the same binary; pick whichever you find easier to type.
9+
This package is a thin install wrapper. It declares the per-platform
10+
`@relayburn/cli-<platform>` packages as `optionalDependencies`; npm's
11+
`os` / `cpu` filters install only the one matching your machine, and the
12+
`burn` shim resolves and execs the prebuilt Rust binary it ships. If a
13+
native package is not available, the shim falls back to the generic
14+
TypeScript `@relayburn/cli` package.
1015

1116
```sh
1217
burn --help
1318
burn summary --since 7d
1419
burn hotspots --since 7d
1520
```
1621

17-
See the project [README](https://github.com/AgentWorkforce/burn#readme) for full documentation.
22+
Prebuilt platforms: `darwin-arm64`, `darwin-x64`, `linux-arm64-gnu`
23+
(glibc), `linux-x64-gnu` (glibc). Other hosts use the TypeScript CLI
24+
fallback. Windows native binary support is tracked in
25+
[#359](https://github.com/AgentWorkforce/burn/issues/359).
26+
27+
See the project [README](https://github.com/AgentWorkforce/burn#readme)
28+
for full documentation.

0 commit comments

Comments
 (0)