Compile to Binary
Distribute your Gridland CLI as a standalone executable with Bun
Bun can compile your Gridland CLI app into a single standalone executable — no runtime required for your users.
Quick start
Install dependencies
bun add @gridland/bunCreate your CLI entrypoint
// @ts-nocheck
import { createCliRenderer, createRoot } from "@gridland/bun"
function App() {
return (
<box border borderStyle="rounded" padding={1}>
<text bold fg="#a3be8c">Hello from my CLI!</text>
</box>
)
}
async function main() {
const renderer = await createCliRenderer({ exitOnCtrlC: true })
createRoot(renderer).render(<App />)
}
main()Compile it
bun build --compile ./src/cli.tsx --outfile mycliThis bundles your code, all dependencies, and the Bun runtime into a single binary.
Wrap your startup code in an async function instead of using top-level await to ensure compatibility with all bundler configurations.
Production builds
For distribution, add --minify to reduce size and --sourcemap to preserve readable stack traces:
bun build --compile --minify --sourcemap ./src/cli.tsx --outfile mycli| Flag | What it does |
|---|---|
--minify | Reduces transpiled output size |
--sourcemap | Embeds a zstd-compressed sourcemap so errors point to original source |
Bun's --bytecode flag is not compatible with Gridland CLIs. The opentui engine uses top-level await internally, which bytecode compilation does not support.
Cross-compilation
Bun supports cross-compiling to other platforms with the --target flag. However, @gridland/bun includes platform-specific native bindings that are resolved at build time, so you must build on the target platform (or in a CI runner matching the target OS/arch).
For example, build on a macOS ARM64 machine for macOS ARM64, or in a Linux x64 CI job for Linux x64:
# On the target machine (or matching CI runner):
bun build --compile --minify --sourcemap ./src/cli.tsx --outfile mycliSupported Bun targets
If your CLI does not depend on @gridland/bun's native bindings (e.g. a pure-JS tool using only @gridland/utils utilities), cross-compilation works with --target:
| Target | OS | Arch | Notes |
|---|---|---|---|
bun-darwin-arm64 | macOS | ARM64 | Apple Silicon |
bun-darwin-x64 | macOS | x64 | Intel Macs |
bun-linux-x64 | Linux | x64 | glibc |
bun-linux-arm64 | Linux | ARM64 | glibc |
bun-linux-x64-musl | Linux | x64 | Alpine/musl |
bun-linux-arm64-musl | Linux | ARM64 | Alpine/musl |
bun-windows-x64 | Windows | x64 | |
bun-windows-arm64 | Windows | ARM64 |
Append -baseline to x64 targets for CPUs before 2013 (no AVX2). If users see "Illegal instruction" errors, they need the baseline build.
Embedding assets
Embed files directly into the binary using the { type: "file" } import attribute:
import configPath from "./default-config.json" with { type: "file" }
import { file } from "bun"
const config = await file(configPath).json()Embed entire directories by passing glob patterns:
bun build --compile ./src/cli.tsx ./assets/**/*.json --outfile mycliList all embedded files at runtime:
import { embeddedFiles } from "bun"
for (const blob of embeddedFiles) {
console.log(`${blob.name} - ${blob.size} bytes`)
}Build-time constants
Inject version numbers or other constants with --define:
bun build --compile \
--define BUILD_VERSION='"1.0.0"' \
--define BUILD_TIME='"2025-01-15T10:30:00Z"' \
./src/cli.tsx --outfile myclideclare const BUILD_VERSION: string
declare const BUILD_TIME: string
console.log(`v${BUILD_VERSION} (built ${BUILD_TIME})`)macOS code signing
If distributing outside the App Store, sign your binary to avoid Gatekeeper warnings:
codesign --deep --force --sign "Your Identity" ./mycliFor full JIT support (recommended), create an entitlements file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
</dict>
</plist>codesign --deep --force --sign "Your Identity" --entitlements entitlements.plist ./mycliCI build script example
Since Gridland CLIs need platform-specific native bindings, use a matrix strategy in CI to build on each target platform:
jobs:
build:
strategy:
matrix:
include:
- os: macos-latest
name: darwin-arm64
- os: macos-13
name: darwin-x64
- os: ubuntu-latest
name: linux-x64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: bun build --compile --minify --sourcemap ./src/cli.tsx --outfile dist/mycli-${{ matrix.name }}
- uses: actions/upload-artifact@v4
with:
name: mycli-${{ matrix.name }}
path: dist/mycli-${{ matrix.name }}