#!/usr/bin/env bash # # Build pi binaries for all platforms locally. # Mirrors .github/workflows/build-binaries.yml # # Usage: # ./scripts/build-binaries.sh [--skip-install] [--skip-deps] [--skip-build] [--platform ] [--out ] # # Options: # --skip-install Skip npm ci # --skip-deps Skip installing cross-platform dependencies # --skip-build Skip npm run build # --platform Build only for specified platform (darwin-arm64, darwin-x64, linux-x64, linux-arm64, windows-x64, windows-arm64) # --out Output directory (default: packages/coding-agent/binaries) # # Output: # packages/coding-agent/binaries/ # pi-darwin-arm64.tar.gz # pi-darwin-x64.tar.gz # pi-linux-x64.tar.gz # pi-linux-arm64.tar.gz # pi-windows-x64.zip # pi-windows-arm64.zip set -euo pipefail cd "$(dirname "$0")/.." SKIP_INSTALL=false SKIP_DEPS=false SKIP_BUILD=false PLATFORM="" OUTPUT_DIR="" while [[ $# -gt 0 ]]; do case $1 in --skip-install) SKIP_INSTALL=true shift ;; --skip-deps) SKIP_DEPS=true shift ;; --skip-build) SKIP_BUILD=true shift ;; --platform) PLATFORM="$2" shift 2 ;; --out) OUTPUT_DIR="$2" shift 2 ;; *) echo "Unknown option: $1" exit 1 ;; esac done # Validate platform if specified if [[ -n "$PLATFORM" ]]; then case "$PLATFORM" in darwin-arm64|darwin-x64|linux-x64|linux-arm64|windows-x64|windows-arm64) ;; *) echo "Invalid platform: $PLATFORM" echo "Valid platforms: darwin-arm64, darwin-x64, linux-x64, linux-arm64, windows-x64, windows-arm64" exit 1 ;; esac fi if [[ -z "$OUTPUT_DIR" ]]; then OUTPUT_DIR="packages/coding-agent/binaries" fi if [[ "$OUTPUT_DIR" != /* ]]; then OUTPUT_DIR="$(pwd)/$OUTPUT_DIR" fi if [[ "$SKIP_INSTALL" == "false" ]]; then echo "==> Installing dependencies..." npm ci --ignore-scripts else echo "==> Skipping npm ci (--skip-install)" fi if [[ "$SKIP_DEPS" == "false" ]]; then echo "==> Installing cross-platform native bindings..." CLIPBOARD_VERSION=$(node -p "require('./packages/coding-agent/package.json').optionalDependencies['@mariozechner/clipboard']") # npm ci only installs optional deps for the current platform # We need the base clipboard package and all platform bindings for bun cross-compilation # Use --force to bypass platform checks (os/cpu restrictions in package.json) # Install all in one command to avoid npm removing packages from previous installs npm install --include=optional --no-save --package-lock=false --force --ignore-scripts \ @mariozechner/clipboard@"$CLIPBOARD_VERSION" \ @mariozechner/clipboard-darwin-arm64@"$CLIPBOARD_VERSION" \ @mariozechner/clipboard-darwin-x64@"$CLIPBOARD_VERSION" \ @mariozechner/clipboard-linux-x64-gnu@"$CLIPBOARD_VERSION" \ @mariozechner/clipboard-linux-arm64-gnu@"$CLIPBOARD_VERSION" \ @mariozechner/clipboard-win32-x64-msvc@"$CLIPBOARD_VERSION" \ @mariozechner/clipboard-win32-arm64-msvc@"$CLIPBOARD_VERSION" else echo "==> Skipping cross-platform native bindings (--skip-deps)" fi if [[ "$SKIP_BUILD" == "false" ]]; then echo "==> Building all packages..." npm run build else echo "==> Skipping package build (--skip-build)" fi echo "==> Building binaries..." cd packages/coding-agent # Clean previous builds rm -rf "$OUTPUT_DIR" mkdir -p "$OUTPUT_DIR"/{darwin-arm64,darwin-x64,linux-x64,linux-arm64,windows-x64,windows-arm64} # Determine which platforms to build if [[ -n "$PLATFORM" ]]; then PLATFORMS=("$PLATFORM") else PLATFORMS=(darwin-arm64 darwin-x64 linux-x64 linux-arm64 windows-x64 windows-arm64) fi for platform in "${PLATFORMS[@]}"; do echo "Building for $platform..." # Bun compiled executables only embed worker scripts when they are passed as # explicit build entrypoints. The runtime can still use new URL(...), but the # worker must be present in the compiled executable. if [[ "$platform" == windows-* ]]; then bun build --compile --target=bun-$platform ./dist/bun/cli.js ./src/utils/image-resize-worker.ts --outfile "$OUTPUT_DIR/$platform/pi.exe" else bun build --compile --target=bun-$platform ./dist/bun/cli.js ./src/utils/image-resize-worker.ts --outfile "$OUTPUT_DIR/$platform/pi" fi done echo "==> Creating release archives..." # Copy shared files to each platform directory for platform in "${PLATFORMS[@]}"; do cp package.json "$OUTPUT_DIR/$platform/" cp README.md "$OUTPUT_DIR/$platform/" cp CHANGELOG.md "$OUTPUT_DIR/$platform/" cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm "$OUTPUT_DIR/$platform/" mkdir -p "$OUTPUT_DIR/$platform/theme" cp dist/modes/interactive/theme/*.json "$OUTPUT_DIR/$platform/theme/" mkdir -p "$OUTPUT_DIR/$platform/assets" cp dist/modes/interactive/assets/* "$OUTPUT_DIR/$platform/assets/" cp -r dist/core/export-html "$OUTPUT_DIR/$platform/" cp -r docs "$OUTPUT_DIR/$platform/" cp -r examples "$OUTPUT_DIR/$platform/" case "$platform" in darwin-arm64) clipboard_native_package="clipboard-darwin-arm64" ;; darwin-x64) clipboard_native_package="clipboard-darwin-x64" ;; linux-x64) clipboard_native_package="clipboard-linux-x64-gnu" ;; linux-arm64) clipboard_native_package="clipboard-linux-arm64-gnu" ;; windows-x64) clipboard_native_package="clipboard-win32-x64-msvc" ;; windows-arm64) clipboard_native_package="clipboard-win32-arm64-msvc" ;; esac mkdir -p "$OUTPUT_DIR/$platform/node_modules/@mariozechner" cp -r ../../node_modules/@mariozechner/clipboard "$OUTPUT_DIR/$platform/node_modules/@mariozechner/" cp -r ../../node_modules/@mariozechner/$clipboard_native_package "$OUTPUT_DIR/$platform/node_modules/@mariozechner/" # Copy terminal input native helpers next to compiled binaries. if [[ "$platform" == darwin-* ]]; then mkdir -p "$OUTPUT_DIR/$platform/native/darwin/prebuilds/$platform" cp ../tui/native/darwin/prebuilds/$platform/darwin-modifiers.node "$OUTPUT_DIR/$platform/native/darwin/prebuilds/$platform/" fi if [[ "$platform" == windows-* ]]; then if [[ "$platform" == "windows-arm64" ]]; then win32_arch_dir="win32-arm64" else win32_arch_dir="win32-x64" fi mkdir -p "$OUTPUT_DIR/$platform/native/win32/prebuilds/$win32_arch_dir" cp ../tui/native/win32/prebuilds/$win32_arch_dir/win32-console-mode.node "$OUTPUT_DIR/$platform/native/win32/prebuilds/$win32_arch_dir/" fi done # Create archives cd "$OUTPUT_DIR" for platform in "${PLATFORMS[@]}"; do if [[ "$platform" == windows-* ]]; then # Windows (zip) echo "Creating pi-$platform.zip..." (cd "$platform" && zip -r ../pi-$platform.zip .) else # Unix platforms (tar.gz) - use wrapper directory for mise compatibility echo "Creating pi-$platform.tar.gz..." mv "$platform" pi && tar -czf pi-$platform.tar.gz pi && mv pi "$platform" fi done # Extract archives for easy local testing echo "==> Extracting archives for testing..." for platform in "${PLATFORMS[@]}"; do rm -rf "$platform" if [[ "$platform" == windows-* ]]; then mkdir -p "$platform" && (cd "$platform" && unzip -q ../pi-$platform.zip) else tar -xzf pi-$platform.tar.gz && mv pi "$platform" fi done echo "" echo "==> Build complete!" echo "Archives available in $OUTPUT_DIR/" ls -lh *.tar.gz *.zip 2>/dev/null || true echo "" echo "Extracted directories for testing:" for platform in "${PLATFORMS[@]}"; do if [[ "$platform" == windows-* ]]; then echo " $OUTPUT_DIR/$platform/pi.exe" else echo " $OUTPUT_DIR/$platform/pi" fi done