mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-17 21:03:01 +00:00
139 lines
3.6 KiB
TypeScript
139 lines
3.6 KiB
TypeScript
/**
|
|
* Photon image processing wrapper.
|
|
*
|
|
* This module provides a unified interface to @silvia-odwyer/photon-node that works in:
|
|
* 1. Node.js (development, npm run build)
|
|
* 2. Bun compiled binaries (standalone distribution)
|
|
*
|
|
* 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:
|
|
* 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<typeof import("@silvia-odwyer/photon-node") | null> | 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<ReadFileSync>) => {
|
|
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.
|
|
*/
|
|
export async function loadPhoton(): Promise<typeof import("@silvia-odwyer/photon-node") | null> {
|
|
if (photonModule) {
|
|
return photonModule;
|
|
}
|
|
|
|
if (loadPromise) {
|
|
return loadPromise;
|
|
}
|
|
|
|
loadPromise = (async () => {
|
|
const restoreReadFileSync = patchPhotonWasmRead();
|
|
try {
|
|
photonModule = await import("@silvia-odwyer/photon-node");
|
|
return photonModule;
|
|
} catch {
|
|
photonModule = null;
|
|
return photonModule;
|
|
} finally {
|
|
restoreReadFileSync();
|
|
}
|
|
})();
|
|
|
|
return loadPromise;
|
|
}
|