History

fix desktop

Rhys Sullivan committed 6 months ago

Commit 8921e791 parent 15719265 files · +68 −1Browse files

Unified · Split

5 files
  1. .changeset/desktop-sidecar-quickjs-wasm.md +10 −0
  2. apps/desktop/package.json +3 −0
  3. apps/desktop/scripts/build-sidecar.ts +22 −1
  4. apps/desktop/src/sidecar/server.ts +30 −0
  5. bun.lock +3 −0
.changeset/desktop-sidecar-quickjs-wasm.md+10−0
@@ -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.
apps/desktop/package.json+3−0
@@ -28,6 +28,9 @@ · 27 unmodified lines
2828 "devDependencies": {
2929 "@executor-js/app": "workspace:*",
3030 "@executor-js/local": "workspace:*",
31+ "@executor-js/runtime-quickjs": "workspace:*",
32+ "@jitl/quickjs-wasmfile-release-sync": "catalog:",
33+ "quickjs-emscripten": "catalog:",
3134 "@executor-js/plugin-desktop-settings": "workspace:*",
3235 "@executor-js/plugin-file-secrets": "workspace:*",
3336 "@executor-js/plugin-google-discovery": "workspace:*",
apps/desktop/scripts/build-sidecar.ts+22−1
@@ -17,7 +17,8 @@ · 16 unmodified lines
1717 */
1818 import { mkdir, rm, cp, writeFile } from "node:fs/promises";
1919 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";
2122 import { $ } from "bun";
2223
2324 const ROOT = resolve(import.meta.dir, "..");
@@ -42,6 +43,23 @@ const targetIsWindows = BUN_TARGET.includes("windows") || process.platform === " · 18 unmodified lines
4243 const binaryName = targetIsWindows ? "executor-sidecar.exe" : "executor-sidecar";
4344 const sidecarBinary = resolve(SIDECAR_OUT_DIR, binaryName);
4445
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+
4563 const createEmbeddedMigrationsSource = async (): Promise<string> => {
4664 const migrationsDir = resolve(APPS_LOCAL, "drizzle");
4765 const files = (await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: migrationsDir })))
@@ -92,6 +110,9 @@ try { · 44 unmodified lines
92110 await writeFile(EMBEDDED_MIGRATIONS_PATH, EMBEDDED_MIGRATIONS_STUB);
93111 }
94112
113+console.log(`[build-sidecar] staging QuickJS WASM → ${SIDECAR_OUT_DIR}`);
114+await cp(resolveQuickJsWasmPath(), join(SIDECAR_OUT_DIR, "emscripten-module.wasm"));
115+
95116 console.log(`[build-sidecar] staging web UI → ${WEB_UI_OUT_DIR}`);
96117 await cp(APPS_LOCAL_DIST, WEB_UI_OUT_DIR, { recursive: true });
97118
apps/desktop/src/sidecar/server.ts+30−0
@@ -7,6 +7,36 @@ · 6 unmodified lines
77 * announces readiness with the resolved port on stdout so the Electron
88 * main process can attach a BrowserWindow to it.
99 */
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+
1040 import { startServer } from "@executor-js/local";
1141
1242 const requestedPort = parseInt(process.env.EXECUTOR_PORT ?? "0", 10);
bun.lock+3−0
@@ -136,11 +136,14 @@ · 135 unmodified lines
136136 "@executor-js/plugin-mcp": "workspace:*",
137137 "@executor-js/plugin-onepassword": "workspace:*",
138138 "@executor-js/plugin-openapi": "workspace:*",
139+ "@executor-js/runtime-quickjs": "workspace:*",
140+ "@jitl/quickjs-wasmfile-release-sync": "catalog:",
139141 "@types/node": "catalog:",
140142 "bun-types": "catalog:",
141143 "electron": "41.2.1",
142144 "electron-builder": "^26",
143145 "electron-vite": "^5",
146+ "quickjs-emscripten": "catalog:",
144147 "typescript": "catalog:",
145148 "vite": "catalog:",
146149 },