From 5aa068982825797c827ef19e511650ea2af8f196 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Fri, 16 Jan 2026 20:56:18 +0100 Subject: [PATCH] fix(coding-agent): fix standalone binary WASM loading on Linux, fixes #784 - Import photon-node from ESM entry point (photon_rs_bg.js) instead of CJS entry, allowing Bun to embed WASM in compiled binaries - Add photon.d.ts for TypeScript support of ESM entry - Add scripts/build-binaries.sh for local binary builds - Simplify GitHub workflow to use the build script - Add binaries/ to gitignore --- .github/workflows/build-binaries.yml | 82 +-------- .gitignore | 1 + .../coding-agent/src/utils/image-convert.ts | 6 +- .../coding-agent/src/utils/image-resize.ts | 10 +- packages/coding-agent/src/utils/photon.d.ts | 5 + packages/coding-agent/tsconfig.build.json | 2 +- scripts/build-binaries.sh | 158 ++++++++++++++++++ 7 files changed, 177 insertions(+), 87 deletions(-) create mode 100644 packages/coding-agent/src/utils/photon.d.ts create mode 100755 scripts/build-binaries.sh diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 65c425f2..fcff2104 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -36,86 +36,8 @@ jobs: node-version: '22' registry-url: 'https://registry.npmjs.org' - - name: Install dependencies - run: npm ci - - - name: Install cross-platform native bindings - run: | - # npm ci only installs optional deps for the current platform (linux-x64) - # We need all platform bindings for bun cross-compilation - # Use --force to bypass platform checks (os/cpu restrictions in package.json) - - # Clipboard bindings for all target platforms - npm install --no-save --force \ - @mariozechner/clipboard-darwin-arm64@0.3.0 \ - @mariozechner/clipboard-darwin-x64@0.3.0 \ - @mariozechner/clipboard-linux-x64-gnu@0.3.0 \ - @mariozechner/clipboard-linux-arm64-gnu@0.3.0 \ - @mariozechner/clipboard-win32-x64-msvc@0.3.0 - - # Sharp bindings for all target platforms - npm install --no-save --force \ - @img/sharp-darwin-arm64@0.34.5 \ - @img/sharp-darwin-x64@0.34.5 \ - @img/sharp-linux-x64@0.34.5 \ - @img/sharp-linux-arm64@0.34.5 \ - @img/sharp-win32-x64@0.34.5 \ - @img/sharp-libvips-darwin-arm64@1.2.4 \ - @img/sharp-libvips-darwin-x64@1.2.4 \ - @img/sharp-libvips-linux-x64@1.2.4 \ - @img/sharp-libvips-linux-arm64@1.2.4 - - - name: Build all packages - run: npm run build - - - name: Build binaries for all platforms - run: | - cd packages/coding-agent - - # Create output directories for each platform - mkdir -p binaries/{darwin-arm64,darwin-x64,linux-x64,linux-arm64,windows-x64} - - # Build for each platform - binary is always named 'pi' (or 'pi.exe' for Windows) - echo "Building for darwin-arm64..." - bun build --compile --target=bun-darwin-arm64 ./dist/cli.js --outfile binaries/darwin-arm64/pi - - echo "Building for darwin-x64..." - bun build --compile --target=bun-darwin-x64 ./dist/cli.js --outfile binaries/darwin-x64/pi - - echo "Building for linux-x64..." - bun build --compile --target=bun-linux-x64 ./dist/cli.js --outfile binaries/linux-x64/pi - - echo "Building for linux-arm64..." - bun build --compile --target=bun-linux-arm64 ./dist/cli.js --outfile binaries/linux-arm64/pi - - echo "Building for windows-x64..." - bun build --compile --target=bun-windows-x64 ./dist/cli.js --outfile binaries/windows-x64/pi.exe - - - name: Create release archives - run: | - cd packages/coding-agent - - # Copy shared files to each platform directory - for platform in darwin-arm64 darwin-x64 linux-x64 linux-arm64 windows-x64; do - cp package.json binaries/$platform/ - cp README.md binaries/$platform/ - cp CHANGELOG.md binaries/$platform/ - mkdir -p binaries/$platform/theme - cp dist/modes/interactive/theme/*.json binaries/$platform/theme/ - cp -r examples binaries/$platform/ - done - - # Create archives - cd binaries - - # Unix platforms (tar.gz) - use wrapper directory for mise compatibility - # mise auto-detects single-directory archives and strips one component - for platform in darwin-arm64 darwin-x64 linux-x64 linux-arm64; do - mv $platform pi && tar -czf pi-$platform.tar.gz pi && mv pi $platform - done - - # Windows (zip) - cd windows-x64 && zip -r ../pi-windows-x64.zip . && cd .. + - name: Build binaries + run: ./scripts/build-binaries.sh - name: Extract changelog for this version id: changelog diff --git a/.gitignore b/.gitignore index c0a2bf89..81fad8f7 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ syntax.jsonl out.jsonl pi-*.html out.html +packages/coding-agent/binaries/ diff --git a/packages/coding-agent/src/utils/image-convert.ts b/packages/coding-agent/src/utils/image-convert.ts index 590dcaf5..e6adfcae 100644 --- a/packages/coding-agent/src/utils/image-convert.ts +++ b/packages/coding-agent/src/utils/image-convert.ts @@ -1,4 +1,6 @@ -import photon from "@silvia-odwyer/photon-node"; +// Use ESM entry point so Bun can embed the WASM in compiled binaries +// (the CJS entry uses fs.readFileSync which breaks in standalone binaries) +import { PhotonImage } from "@silvia-odwyer/photon-node/photon_rs_bg.js"; /** * Convert image to PNG format for terminal display. @@ -14,7 +16,7 @@ export async function convertToPng( } try { - const image = photon.PhotonImage.new_from_byteslice(new Uint8Array(Buffer.from(base64Data, "base64"))); + const image = PhotonImage.new_from_byteslice(new Uint8Array(Buffer.from(base64Data, "base64"))); try { const pngBuffer = image.get_bytes(); return { diff --git a/packages/coding-agent/src/utils/image-resize.ts b/packages/coding-agent/src/utils/image-resize.ts index 7679dcc2..58f4d6f7 100644 --- a/packages/coding-agent/src/utils/image-resize.ts +++ b/packages/coding-agent/src/utils/image-resize.ts @@ -1,5 +1,7 @@ import type { ImageContent } from "@mariozechner/pi-ai"; -import photon from "@silvia-odwyer/photon-node"; +// Use ESM entry point so Bun can embed the WASM in compiled binaries +// (the CJS entry uses fs.readFileSync which breaks in standalone binaries) +import { PhotonImage, resize, SamplingFilter } from "@silvia-odwyer/photon-node/photon_rs_bg.js"; export interface ImageResizeOptions { maxWidth?: number; // Default: 2000 @@ -53,9 +55,9 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption const opts = { ...DEFAULT_OPTIONS, ...options }; const inputBuffer = Buffer.from(img.data, "base64"); - let image: ReturnType | undefined; + let image: ReturnType | undefined; try { - image = photon.PhotonImage.new_from_byteslice(new Uint8Array(inputBuffer)); + image = PhotonImage.new_from_byteslice(new Uint8Array(inputBuffer)); const originalWidth = image.get_width(); const originalHeight = image.get_height(); @@ -94,7 +96,7 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption height: number, jpegQuality: number, ): { buffer: Uint8Array; mimeType: string } { - const resized = photon.resize(image!, width, height, photon.SamplingFilter.Lanczos3); + const resized = resize(image!, width, height, SamplingFilter.Lanczos3); try { const pngBuffer = resized.get_bytes(); diff --git a/packages/coding-agent/src/utils/photon.d.ts b/packages/coding-agent/src/utils/photon.d.ts new file mode 100644 index 00000000..c43c637f --- /dev/null +++ b/packages/coding-agent/src/utils/photon.d.ts @@ -0,0 +1,5 @@ +// Type declarations for the ESM entry point of @silvia-odwyer/photon-node +// The ESM entry exports the same API but uses WASM imports that bundlers can embed +declare module "@silvia-odwyer/photon-node/photon_rs_bg.js" { + export * from "@silvia-odwyer/photon-node"; +} diff --git a/packages/coding-agent/tsconfig.build.json b/packages/coding-agent/tsconfig.build.json index 695dd9ad..db3f6d97 100644 --- a/packages/coding-agent/tsconfig.build.json +++ b/packages/coding-agent/tsconfig.build.json @@ -5,5 +5,5 @@ "rootDir": "./src" }, "include": ["src/**/*.ts"], - "exclude": ["node_modules", "dist", "**/*.d.ts", "src/**/*.d.ts"] + "exclude": ["node_modules", "dist"] } diff --git a/scripts/build-binaries.sh b/scripts/build-binaries.sh new file mode 100755 index 00000000..09e382f6 --- /dev/null +++ b/scripts/build-binaries.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# +# Build pi binaries for all platforms locally. +# Mirrors .github/workflows/build-binaries.yml +# +# Usage: +# ./scripts/build-binaries.sh [--skip-deps] [--platform ] +# +# Options: +# --skip-deps Skip installing cross-platform dependencies +# --platform Build only for specified platform (darwin-arm64, darwin-x64, linux-x64, linux-arm64, windows-x64) +# +# 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 + +set -euo pipefail + +cd "$(dirname "$0")/.." + +SKIP_DEPS=false +PLATFORM="" + +while [[ $# -gt 0 ]]; do + case $1 in + --skip-deps) + SKIP_DEPS=true + shift + ;; + --platform) + PLATFORM="$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) + ;; + *) + echo "Invalid platform: $PLATFORM" + echo "Valid platforms: darwin-arm64, darwin-x64, linux-x64, linux-arm64, windows-x64" + exit 1 + ;; + esac +fi + +echo "==> Installing dependencies..." +npm ci + +if [[ "$SKIP_DEPS" == "false" ]]; then + echo "==> Installing cross-platform native bindings..." + # npm ci only installs optional deps for the current platform + # We need 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 --no-save --force \ + @mariozechner/clipboard-darwin-arm64@0.3.0 \ + @mariozechner/clipboard-darwin-x64@0.3.0 \ + @mariozechner/clipboard-linux-x64-gnu@0.3.0 \ + @mariozechner/clipboard-linux-arm64-gnu@0.3.0 \ + @mariozechner/clipboard-win32-x64-msvc@0.3.0 \ + @img/sharp-darwin-arm64@0.34.5 \ + @img/sharp-darwin-x64@0.34.5 \ + @img/sharp-linux-x64@0.34.5 \ + @img/sharp-linux-arm64@0.34.5 \ + @img/sharp-win32-x64@0.34.5 \ + @img/sharp-libvips-darwin-arm64@1.2.4 \ + @img/sharp-libvips-darwin-x64@1.2.4 \ + @img/sharp-libvips-linux-x64@1.2.4 \ + @img/sharp-libvips-linux-arm64@1.2.4 +else + echo "==> Skipping cross-platform native bindings (--skip-deps)" +fi + +echo "==> Building all packages..." +npm run build + +echo "==> Building binaries..." +cd packages/coding-agent + +# Clean previous builds +rm -rf binaries +mkdir -p binaries/{darwin-arm64,darwin-x64,linux-x64,linux-arm64,windows-x64} + +# Determine which platforms to build +if [[ -n "$PLATFORM" ]]; then + PLATFORMS=("$PLATFORM") +else + PLATFORMS=(darwin-arm64 darwin-x64 linux-x64 linux-arm64 windows-x64) +fi + +for platform in "${PLATFORMS[@]}"; do + echo "Building for $platform..." + if [[ "$platform" == "windows-x64" ]]; then + bun build --compile --target=bun-$platform ./dist/cli.js --outfile binaries/$platform/pi.exe + else + bun build --compile --target=bun-$platform ./dist/cli.js --outfile binaries/$platform/pi + fi +done + +echo "==> Creating release archives..." + +# Copy shared files to each platform directory +for platform in "${PLATFORMS[@]}"; do + cp package.json binaries/$platform/ + cp README.md binaries/$platform/ + cp CHANGELOG.md binaries/$platform/ + mkdir -p binaries/$platform/theme + cp dist/modes/interactive/theme/*.json binaries/$platform/theme/ + cp -r examples binaries/$platform/ +done + +# Create archives +cd binaries + +for platform in "${PLATFORMS[@]}"; do + if [[ "$platform" == "windows-x64" ]]; 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-x64" ]]; 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 packages/coding-agent/binaries/" +ls -lh *.tar.gz *.zip 2>/dev/null || true +echo "" +echo "Extracted directories for testing:" +for platform in "${PLATFORMS[@]}"; do + echo " binaries/$platform/pi" +done