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 | "devDependencies": { | 28 | "devDependencies": { |
| 29 | "@executor-js/app": "workspace:*", | 29 | "@executor-js/app": "workspace:*", |
| 30 | "@executor-js/local": "workspace:*", | 30 | "@executor-js/local": "workspace:*", |
| 31 | "@executor-js/runtime-quickjs": "workspace:*", | ||
| 32 | "@jitl/quickjs-wasmfile-release-sync": "catalog:", | ||
| 33 | "quickjs-emscripten": "catalog:", | ||
| 31 | "@executor-js/plugin-desktop-settings": "workspace:*", | 34 | "@executor-js/plugin-desktop-settings": "workspace:*", |
| 32 | "@executor-js/plugin-file-secrets": "workspace:*", | 35 | "@executor-js/plugin-file-secrets": "workspace:*", |
| 33 | "@executor-js/plugin-google-discovery": "workspace:*", | 36 | "@executor-js/plugin-google-discovery": "workspace:*", |
| @@ -17,7 +17,8 @@ · 16 unmodified lines | |||
| 17 | */ | 17 | */ |
| 18 | import { mkdir, rm, cp, writeFile } from "node:fs/promises"; | 18 | import { mkdir, rm, cp, writeFile } from "node:fs/promises"; |
| 19 | import { existsSync } from "node:fs"; | 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 | import { $ } from "bun"; | 22 | import { $ } from "bun"; |
| 22 | 23 | ||
| 23 | const ROOT = resolve(import.meta.dir, ".."); | 24 | const ROOT = resolve(import.meta.dir, ".."); |
| @@ -42,6 +43,23 @@ const targetIsWindows = BUN_TARGET.includes("windows") || process.platform === " · 18 unmodified lines | |||
| 42 | const binaryName = targetIsWindows ? "executor-sidecar.exe" : "executor-sidecar"; | 43 | const binaryName = targetIsWindows ? "executor-sidecar.exe" : "executor-sidecar"; |
| 43 | const sidecarBinary = resolve(SIDECAR_OUT_DIR, binaryName); | 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 | const createEmbeddedMigrationsSource = async (): Promise<string> => { | 63 | const createEmbeddedMigrationsSource = async (): Promise<string> => { |
| 46 | const migrationsDir = resolve(APPS_LOCAL, "drizzle"); | 64 | const migrationsDir = resolve(APPS_LOCAL, "drizzle"); |
| 47 | const files = (await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: migrationsDir }))) | 65 | const files = (await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: migrationsDir }))) |
| @@ -92,6 +110,9 @@ try { · 44 unmodified lines | |||
| 92 | await writeFile(EMBEDDED_MIGRATIONS_PATH, EMBEDDED_MIGRATIONS_STUB); | 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 | console.log(`[build-sidecar] staging web UI → ${WEB_UI_OUT_DIR}`); | 116 | console.log(`[build-sidecar] staging web UI → ${WEB_UI_OUT_DIR}`); |
| 96 | await cp(APPS_LOCAL_DIST, WEB_UI_OUT_DIR, { recursive: true }); | 117 | await cp(APPS_LOCAL_DIST, WEB_UI_OUT_DIR, { recursive: true }); |
| 97 | 118 | ||
| @@ -7,6 +7,36 @@ · 6 unmodified lines | |||
| 7 | * announces readiness with the resolved port on stdout so the Electron | 7 | * announces readiness with the resolved port on stdout so the Electron |
| 8 | * main process can attach a BrowserWindow to it. | 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 | import { startServer } from "@executor-js/local"; | 40 | import { startServer } from "@executor-js/local"; |
| 11 | 41 | ||
| 12 | const requestedPort = parseInt(process.env.EXECUTOR_PORT ?? "0", 10); | 42 | const requestedPort = parseInt(process.env.EXECUTOR_PORT ?? "0", 10); |
| @@ -136,11 +136,14 @@ · 135 unmodified lines | |||
| 136 | "@executor-js/plugin-mcp": "workspace:*", | 136 | "@executor-js/plugin-mcp": "workspace:*", |
| 137 | "@executor-js/plugin-onepassword": "workspace:*", | 137 | "@executor-js/plugin-onepassword": "workspace:*", |
| 138 | "@executor-js/plugin-openapi": "workspace:*", | 138 | "@executor-js/plugin-openapi": "workspace:*", |
| 139 | "@executor-js/runtime-quickjs": "workspace:*", | ||
| 140 | "@jitl/quickjs-wasmfile-release-sync": "catalog:", | ||
| 139 | "@types/node": "catalog:", | 141 | "@types/node": "catalog:", |
| 140 | "bun-types": "catalog:", | 142 | "bun-types": "catalog:", |
| 141 | "electron": "41.2.1", | 143 | "electron": "41.2.1", |
| 142 | "electron-builder": "^26", | 144 | "electron-builder": "^26", |
| 143 | "electron-vite": "^5", | 145 | "electron-vite": "^5", |
| 146 | "quickjs-emscripten": "catalog:", | ||
| 144 | "typescript": "catalog:", | 147 | "typescript": "catalog:", |
| 145 | "vite": "catalog:", | 148 | "vite": "catalog:", |
| 146 | }, | 149 | }, |