gridland
Guides

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/bun

Create your CLI entrypoint

src/cli.tsx
// @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 mycli

This bundles your code, all dependencies, and the Bun runtime into a single binary.

Run it

./mycli

No Bun, Node, or npm install needed on the target machine.

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
FlagWhat it does
--minifyReduces transpiled output size
--sourcemapEmbeds 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 mycli

Supported 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:

TargetOSArchNotes
bun-darwin-arm64macOSARM64Apple Silicon
bun-darwin-x64macOSx64Intel Macs
bun-linux-x64Linuxx64glibc
bun-linux-arm64LinuxARM64glibc
bun-linux-x64-muslLinuxx64Alpine/musl
bun-linux-arm64-muslLinuxARM64Alpine/musl
bun-windows-x64Windowsx64
bun-windows-arm64WindowsARM64

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:

src/cli.tsx
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 mycli

List 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 mycli
src/cli.tsx
declare 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" ./mycli

For full JIT support (recommended), create an entitlements file:

entitlements.plist
<?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 ./mycli

CI build script example

Since Gridland CLIs need platform-specific native bindings, use a matrix strategy in CI to build on each target platform:

.github/workflows/build.yml
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 }}