fix(coding-agent): bundle mupdf into the compiled binary so PDF reads work
The vendored markit engine kept `mupdf` external, but a single-file `bun --compile` binary has no node_modules to resolve it from, so the standalone binary aborted at startup with `Cannot find package 'mupdf'` — the otherwise-lazy import is resolved eagerly at boot. Bundle mupdf and embed its WASM blob (scripts/embed-mupdf-wasm.ts, reset after the build); npm and source installs still load mupdf from node_modules. Import mupdf lazily inside the PDF converter so the bundled markit chunk's init stays synchronous: mupdf's top-level await otherwise made the chunk init async and bun's compiled bundler failed to await it through the barrel, exposing the converters before their module-level const tables initialized (undefined EXTENSIONS). Also keeps the ~10MB wasm off non-PDF document conversions.
This commit is contained in:
@@ -44,6 +44,7 @@ async function main(): Promise<void> {
|
||||
await runCommand(["bun", "--cwd=../stats", "scripts/generate-client-bundle.ts", "--generate"]);
|
||||
await runCommand(["bun", "scripts/generate-docs-index.ts", "--generate"]);
|
||||
await runCommand(["bun", "--cwd=../natives", "run", "embed:native"]);
|
||||
await runCommand(["bun", "scripts/embed-mupdf-wasm.ts", "--generate"]);
|
||||
try {
|
||||
const buildEnv = shouldAdhocSignDarwinBinary() ? { ...Bun.env, BUN_NO_CODESIGN_MACHO_BINARY: "1" } : Bun.env;
|
||||
await runCommand(
|
||||
@@ -61,8 +62,6 @@ async function main(): Promise<void> {
|
||||
"--define",
|
||||
`process.env.PI_TINY_TRANSFORMERS_VERSION=${JSON.stringify(transformersVersion)}`,
|
||||
"--external",
|
||||
"mupdf",
|
||||
"--external",
|
||||
"fastembed",
|
||||
"--external",
|
||||
"onnxruntime-node",
|
||||
@@ -97,6 +96,7 @@ async function main(): Promise<void> {
|
||||
await runCommand(["codesign", "--force", "--sign", "-", outputPath]);
|
||||
}
|
||||
} finally {
|
||||
await runCommand(["bun", "scripts/embed-mupdf-wasm.ts", "--reset"]);
|
||||
await runCommand(["bun", "--cwd=../natives", "run", "embed:native", "--reset"]);
|
||||
}
|
||||
} finally {
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
// Embeds mupdf's `mupdf-wasm.wasm` into the compiled single-file binary.
|
||||
//
|
||||
// mupdf loads its wasm by reading the `mupdf-wasm.wasm` sibling of its own
|
||||
// module via `new URL(..., import.meta.url)` + `readFileSync`. A `bun --compile`
|
||||
// binary has no node_modules, so that read fails (`ENOENT .../mupdf-wasm.wasm`),
|
||||
// and marking mupdf `--external` instead makes `bun --compile` eagerly fail to
|
||||
// resolve the package at startup (the static `import * as mupdf` lives in a lazy
|
||||
// chunk but is hoisted). So the binary build bundles mupdf and embeds the wasm
|
||||
// bytes here, handing them to the WASM module as `$libmupdf_wasm_Module.wasmBinary`
|
||||
// (see src/utils/markit.ts).
|
||||
//
|
||||
// `--generate` copies the wasm next to src/utils/mupdf-wasm-embed.ts and rewrites
|
||||
// that module to import it via `with { type: "file" }`; `--reset` restores the
|
||||
// checked-in placeholder and removes the copy. The npm `dist/cli.js` bundle never
|
||||
// runs this — it keeps mupdf external and loads the wasm from node_modules.
|
||||
|
||||
import * as fs from "node:fs/promises";
|
||||
import { createRequire } from "node:module";
|
||||
import * as path from "node:path";
|
||||
|
||||
const utilsDir = path.join(import.meta.dir, "..", "src", "utils");
|
||||
const helperPath = path.join(utilsDir, "mupdf-wasm-embed.ts");
|
||||
const wasmCopyPath = path.join(utilsDir, "mupdf-wasm.wasm");
|
||||
|
||||
const placeholder = `// AUTOGENERATED -- managed by scripts/embed-mupdf-wasm.ts. Do not edit by hand.
|
||||
//
|
||||
// Compiled single-file binaries cannot let mupdf resolve its \`mupdf-wasm.wasm\`
|
||||
// sibling from the read-only bunfs, so the binary build (scripts/build-binary.ts
|
||||
// and scripts/ci-release-build-binaries.ts) regenerates this module to embed the
|
||||
// wasm bytes via \`with { type: "file" }\` and copies the wasm next to it. Source
|
||||
// checkouts, \`bun test\`, and the npm \`dist/cli.js\` bundle keep mupdf external and
|
||||
// load the wasm from node_modules, so this placeholder returns undefined and the
|
||||
// build resets back to it afterward.
|
||||
export function loadEmbeddedMupdfWasm(): Uint8Array | undefined {
|
||||
\treturn undefined;
|
||||
}
|
||||
`;
|
||||
|
||||
const generated = `// AUTOGENERATED -- managed by scripts/embed-mupdf-wasm.ts. Do not edit or commit.
|
||||
import { readFileSync } from "node:fs";
|
||||
import wasmPath from "./mupdf-wasm.wasm" with { type: "file" };
|
||||
|
||||
export function loadEmbeddedMupdfWasm(): Uint8Array | undefined {
|
||||
\treturn readFileSync(wasmPath);
|
||||
}
|
||||
`;
|
||||
|
||||
if (process.argv.includes("--reset")) {
|
||||
await Bun.write(helperPath, placeholder);
|
||||
try {
|
||||
await fs.unlink(wasmCopyPath);
|
||||
} catch (err) {
|
||||
if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const wasmSource = path.join(path.dirname(createRequire(import.meta.url).resolve("mupdf")), "mupdf-wasm.wasm");
|
||||
const wasmFile = Bun.file(wasmSource);
|
||||
if (!(await wasmFile.exists())) {
|
||||
throw new Error(`mupdf wasm not found at ${wasmSource}; run \`bun install\` first.`);
|
||||
}
|
||||
await Bun.write(wasmCopyPath, wasmFile);
|
||||
await Bun.write(helperPath, generated);
|
||||
console.log(`Embedded mupdf wasm (${wasmFile.size} bytes) into ${path.relative(process.cwd(), wasmCopyPath)}`);
|
||||
Reference in New Issue
Block a user