Unified · Split
| @@ -0,0 +1,10 @@ | ||
| 1 | +--- | |
| 2 | +"@executor-js/desktop": patch | |
| 3 | +"executor": patch | |
| 4 | +--- | |
| 5 | + | |
| 6 | +Fix: desktop sidecar now stages the QuickJS WASM file next to the compiled | |
| 7 | +binary and preloads it at boot. Without this, MCP `execute` calls (and any | |
| 8 | +other code execution) hit `quickjs-emscripten`'s loader inside bunfs and | |
| 9 | +crashed with `ENOENT: emscripten-module.wasm`. Mirrors the standalone CLI's | |
| 10 | +preload pattern. | |
| @@ -28,6 +28,9 @@ · 27 unmodified lines | ||
| 28 | 28 | "devDependencies": { |
| 29 | 29 | "@executor-js/app": "workspace:*", |
| 30 | 30 | "@executor-js/local": "workspace:*", |
| 31 | + "@executor-js/runtime-quickjs": "workspace:*", | |
| 32 | + "@jitl/quickjs-wasmfile-release-sync": "catalog:", | |
| 33 | + "quickjs-emscripten": "catalog:", | |
| 31 | 34 | "@executor-js/plugin-desktop-settings": "workspace:*", |
| 32 | 35 | "@executor-js/plugin-file-secrets": "workspace:*", |
| 33 | 36 | "@executor-js/plugin-google-discovery": "workspace:*", |
| @@ -17,7 +17,8 @@ · 16 unmodified lines | ||
| 17 | 17 | */ |
| 18 | 18 | import { mkdir, rm, cp, writeFile } from "node:fs/promises"; |
| 19 | 19 | import { existsSync } from "node:fs"; |
| 20 | −import { join, resolve } from "node:path"; | |
| 20 | +import { createRequire } from "node:module"; | |
| 21 | +import { dirname, join, resolve } from "node:path"; | |
| 21 | 22 | import { $ } from "bun"; |
| 22 | 23 | |
| 23 | 24 | const ROOT = resolve(import.meta.dir, ".."); |
| @@ -42,6 +43,23 @@ const targetIsWindows = BUN_TARGET.includes("windows") || process.platform === " · 18 unmodified lines | ||
| 42 | 43 | const binaryName = targetIsWindows ? "executor-sidecar.exe" : "executor-sidecar"; |
| 43 | 44 | const sidecarBinary = resolve(SIDECAR_OUT_DIR, binaryName); |
| 44 | 45 | |
| 46 | +// QuickJS ships its WASM as a side asset; `bun build --compile` can't pull | |
| 47 | +// it into bunfs, so we stage it next to the binary and the sidecar entry | |
| 48 | +// preloads it via `setQuickJSModule` before any server import. | |
| 49 | +const resolveQuickJsWasmPath = (): string => { | |
| 50 | + const req = createRequire(join(REPO_ROOT, "packages/kernel/runtime-quickjs/package.json")); | |
| 51 | + const quickJsPkg = req.resolve("quickjs-emscripten/package.json"); | |
| 52 | + const wasmPath = resolve( | |
| 53 | + dirname(quickJsPkg), | |
| 54 | + "../@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.wasm", | |
| 55 | + ); | |
| 56 | + if (!existsSync(wasmPath)) { | |
| 57 | + // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: build-time fatal | |
| 58 | + throw new Error(`QuickJS WASM not found at ${wasmPath}`); | |
| 59 | + } | |
| 60 | + return wasmPath; | |
| 61 | +}; | |
| 62 | + | |
| 45 | 63 | const createEmbeddedMigrationsSource = async (): Promise<string> => { |
| 46 | 64 | const migrationsDir = resolve(APPS_LOCAL, "drizzle"); |
| 47 | 65 | const files = (await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: migrationsDir }))) |
| @@ -92,6 +110,9 @@ try { · 44 unmodified lines | ||
| 92 | 110 | await writeFile(EMBEDDED_MIGRATIONS_PATH, EMBEDDED_MIGRATIONS_STUB); |
| 93 | 111 | } |
| 94 | 112 | |
| 113 | +console.log(`[build-sidecar] staging QuickJS WASM → ${SIDECAR_OUT_DIR}`); | |
| 114 | +await cp(resolveQuickJsWasmPath(), join(SIDECAR_OUT_DIR, "emscripten-module.wasm")); | |
| 115 | + | |
| 95 | 116 | console.log(`[build-sidecar] staging web UI → ${WEB_UI_OUT_DIR}`); |
| 96 | 117 | await cp(APPS_LOCAL_DIST, WEB_UI_OUT_DIR, { recursive: true }); |
| 97 | 118 | |
| @@ -7,6 +7,36 @@ · 6 unmodified lines | ||
| 7 | 7 | * announces readiness with the resolved port on stdout so the Electron |
| 8 | 8 | * main process can attach a BrowserWindow to it. |
| 9 | 9 | */ |
| 10 | +import { dirname, join } from "node:path"; | |
| 11 | + | |
| 12 | +// Pre-load QuickJS WASM for compiled binaries. `bun build --compile` can't | |
| 13 | +// embed the side-asset WASM that `quickjs-emscripten` ships with, so | |
| 14 | +// build-sidecar.ts stages it next to this binary and we feed the bytes in | |
| 15 | +// via `setQuickJSModule` before any server import touches QuickJS. Mirrors | |
| 16 | +// the CLI's preload in apps/cli/src/main.ts. | |
| 17 | +const wasmOnDisk = join(dirname(process.execPath), "emscripten-module.wasm"); | |
| 18 | +if (typeof Bun !== "undefined" && (await Bun.file(wasmOnDisk).exists())) { | |
| 19 | + const { setQuickJSModule } = await import("@executor-js/runtime-quickjs"); | |
| 20 | + const { newQuickJSWASMModule } = await import("quickjs-emscripten"); | |
| 21 | + type QuickJSSyncVariant = import("quickjs-emscripten").QuickJSSyncVariant; | |
| 22 | + const wasmBinary = await Bun.file(wasmOnDisk).arrayBuffer(); | |
| 23 | + const importFFI: QuickJSSyncVariant["importFFI"] = () => | |
| 24 | + import("@jitl/quickjs-wasmfile-release-sync/ffi").then((m) => m.QuickJSFFI); | |
| 25 | + const importModuleLoader: QuickJSSyncVariant["importModuleLoader"] = async () => { | |
| 26 | + const { default: original } = await import( | |
| 27 | + "@jitl/quickjs-wasmfile-release-sync/emscripten-module" | |
| 28 | + ); | |
| 29 | + return (moduleArg = {}) => original({ ...moduleArg, wasmBinary }); | |
| 30 | + }; | |
| 31 | + const variant: QuickJSSyncVariant = { | |
| 32 | + type: "sync" as const, | |
| 33 | + importFFI, | |
| 34 | + importModuleLoader, | |
| 35 | + }; | |
| 36 | + const mod = await newQuickJSWASMModule(variant); | |
| 37 | + setQuickJSModule(mod); | |
| 38 | +} | |
| 39 | + | |
| 10 | 40 | import { startServer } from "@executor-js/local"; |
| 11 | 41 | |
| 12 | 42 | const requestedPort = parseInt(process.env.EXECUTOR_PORT ?? "0", 10); |
| @@ -136,11 +136,14 @@ · 135 unmodified lines | ||
| 136 | 136 | "@executor-js/plugin-mcp": "workspace:*", |
| 137 | 137 | "@executor-js/plugin-onepassword": "workspace:*", |
| 138 | 138 | "@executor-js/plugin-openapi": "workspace:*", |
| 139 | + "@executor-js/runtime-quickjs": "workspace:*", | |
| 140 | + "@jitl/quickjs-wasmfile-release-sync": "catalog:", | |
| 139 | 141 | "@types/node": "catalog:", |
| 140 | 142 | "bun-types": "catalog:", |
| 141 | 143 | "electron": "41.2.1", |
| 142 | 144 | "electron-builder": "^26", |
| 143 | 145 | "electron-vite": "^5", |
| 146 | + "quickjs-emscripten": "catalog:", | |
| 144 | 147 | "typescript": "catalog:", |
| 145 | 148 | "vite": "catalog:", |
| 146 | 149 | }, |