From 964f17b8d164d900edf4097f5f4a0f8a37929b10 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Sun, 18 Jan 2026 17:11:31 +0100 Subject: [PATCH] fix(coding-agent): load photon wasm in compiled binaries --- packages/coding-agent/package.json | 2 +- packages/coding-agent/src/utils/photon.ts | 100 +++++++++++++++++++++- scripts/build-binaries.sh | 1 + 3 files changed, 99 insertions(+), 4 deletions(-) diff --git a/packages/coding-agent/package.json b/packages/coding-agent/package.json index 085e1608..c969a3f0 100644 --- a/packages/coding-agent/package.json +++ b/packages/coding-agent/package.json @@ -33,7 +33,7 @@ "build": "tsgo -p tsconfig.build.json && shx chmod +x dist/cli.js && npm run copy-assets", "build:binary": "npm run build && bun build --compile ./dist/cli.js --outfile dist/pi && npm run copy-binary-assets", "copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/", - "copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/export-html/vendor && shx cp src/core/export-html/template.html dist/export-html/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp -r docs dist/ && shx cp -r examples dist/", + "copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/export-html/vendor && shx cp src/core/export-html/template.html dist/export-html/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp -r docs dist/ && shx cp -r examples dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/", "test": "vitest --run", "prepublishOnly": "npm run clean && npm run build" }, diff --git a/packages/coding-agent/src/utils/photon.ts b/packages/coding-agent/src/utils/photon.ts index d7a2f07f..6c320705 100644 --- a/packages/coding-agent/src/utils/photon.ts +++ b/packages/coding-agent/src/utils/photon.ts @@ -8,17 +8,107 @@ * The challenge: photon-node's CJS entry uses fs.readFileSync(__dirname + '/photon_rs_bg.wasm') * which bakes the build machine's absolute path into Bun compiled binaries. * - * Solution: Lazy-load photon via dynamic import and gracefully handle failures. - * Image processing functions have fallbacks that return original images when photon isn't available. + * Solution: + * 1. Patch fs.readFileSync to redirect missing photon_rs_bg.wasm reads + * 2. Copy photon_rs_bg.wasm next to the executable in build:binary */ +import type { PathOrFileDescriptor } from "fs"; +import { createRequire } from "module"; +import * as path from "path"; +import { fileURLToPath } from "url"; + +const require = createRequire(import.meta.url); +const fs = require("fs") as typeof import("fs"); + // Re-export types from the main package export type { PhotonImage as PhotonImageType } from "@silvia-odwyer/photon-node"; +type ReadFileSync = typeof fs.readFileSync; + +const WASM_FILENAME = "photon_rs_bg.wasm"; + // Lazy-loaded photon module let photonModule: typeof import("@silvia-odwyer/photon-node") | null = null; let loadPromise: Promise | null = null; +function pathOrNull(file: PathOrFileDescriptor): string | null { + if (typeof file === "string") { + return file; + } + if (file instanceof URL) { + return fileURLToPath(file); + } + return null; +} + +function getFallbackWasmPaths(): string[] { + const execDir = path.dirname(process.execPath); + return [ + path.join(execDir, WASM_FILENAME), + path.join(execDir, "photon", WASM_FILENAME), + path.join(process.cwd(), WASM_FILENAME), + ]; +} + +function patchPhotonWasmRead(): () => void { + const originalReadFileSync: ReadFileSync = fs.readFileSync.bind(fs); + const fallbackPaths = getFallbackWasmPaths(); + const mutableFs = fs as { readFileSync: ReadFileSync }; + + const patchedReadFileSync: ReadFileSync = ((...args: Parameters) => { + const [file, options] = args; + const resolvedPath = pathOrNull(file); + + if (resolvedPath?.endsWith(WASM_FILENAME)) { + try { + return originalReadFileSync(...args); + } catch (error) { + const err = error as NodeJS.ErrnoException; + if (err?.code && err.code !== "ENOENT") { + throw error; + } + + for (const fallbackPath of fallbackPaths) { + if (!fs.existsSync(fallbackPath)) { + continue; + } + if (options === undefined) { + return originalReadFileSync(fallbackPath); + } + return originalReadFileSync(fallbackPath, options); + } + + throw error; + } + } + + return originalReadFileSync(...args); + }) as ReadFileSync; + + try { + mutableFs.readFileSync = patchedReadFileSync; + } catch { + Object.defineProperty(fs, "readFileSync", { + value: patchedReadFileSync, + writable: true, + configurable: true, + }); + } + + return () => { + try { + mutableFs.readFileSync = originalReadFileSync; + } catch { + Object.defineProperty(fs, "readFileSync", { + value: originalReadFileSync, + writable: true, + configurable: true, + }); + } + }; +} + /** * Load the photon module asynchronously. * Returns cached module on subsequent calls. @@ -33,12 +123,16 @@ export async function loadPhoton(): Promise { + const restoreReadFileSync = patchPhotonWasmRead(); try { photonModule = await import("@silvia-odwyer/photon-node"); + return photonModule; } catch { photonModule = null; + return photonModule; + } finally { + restoreReadFileSync(); } - return photonModule; })(); return loadPromise; diff --git a/scripts/build-binaries.sh b/scripts/build-binaries.sh index 09e382f6..89da8133 100755 --- a/scripts/build-binaries.sh +++ b/scripts/build-binaries.sh @@ -116,6 +116,7 @@ for platform in "${PLATFORMS[@]}"; do cp package.json binaries/$platform/ cp README.md binaries/$platform/ cp CHANGELOG.md binaries/$platform/ + cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm binaries/$platform/ mkdir -p binaries/$platform/theme cp dist/modes/interactive/theme/*.json binaries/$platform/theme/ cp -r examples binaries/$platform/