#!/usr/bin/env bun /** * Builds isolate-compatible bundles for the document-generation libraries. * * Each library is bundled with `target=browser, format=iife` so it can be * evaluated inside a V8 isolate that has no Node APIs (`require`, `process`, * `fs`). The emitted files attach their exports to `globalThis.__bundles[name]` * and are checked in so production images don't need the bundler at runtime. * * Run via: `bun run build:sandbox-bundles`. */ import { mkdirSync, rmSync, writeFileSync } from 'node:fs' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' import { createLogger } from '@sim/logger' const logger = createLogger('SandboxBundleBuild') interface BunBuildResult { success: boolean logs: unknown[] outputs: Array<{ text: () => Promise }> } interface BunBuildOptions { entrypoints: string[] target: string format: string minify: boolean sourcemap: string root: string } declare const Bun: { build: (opts: BunBuildOptions) => Promise } const HERE = dirname(fileURLToPath(import.meta.url)) const BUNDLES_DIR = HERE const ENTRIES_DIR = join(HERE, '.entries') const APP_SIM_ROOT = join(HERE, '..', '..', '..', '..') interface BundleSpec { /** Key on `globalThis.__bundles`. */ name: string /** Short filename written under `bundles/.cjs`. */ outFile: string /** Source of the entry file bun will bundle. */ entry: string } const POLYFILLS_PATH = join(HERE, '_polyfills.ts') const POLYFILL_PRELUDE = ` // Isolate-side polyfills must execute BEFORE any other import (process/browser // captures setTimeout at module-init time). Keep this as the first import. import '${POLYFILLS_PATH}' import { Buffer as __BufferPolyfill } from 'buffer' import * as __processPolyfill from 'process/browser' if (typeof globalThis.Buffer === 'undefined') globalThis.Buffer = __BufferPolyfill if (typeof globalThis.process === 'undefined') globalThis.process = __processPolyfill ` const BUNDLES: ReadonlyArray = [ { name: 'pdf-lib', outFile: 'pdf-lib.cjs', entry: ` ${POLYFILL_PRELUDE} import * as mod from 'pdf-lib' globalThis.__bundles = globalThis.__bundles || {} globalThis.__bundles['pdf-lib'] = mod `, }, { name: 'docx', outFile: 'docx.cjs', entry: ` ${POLYFILL_PRELUDE} import * as mod from 'docx' globalThis.__bundles = globalThis.__bundles || {} globalThis.__bundles['docx'] = mod `, }, { name: 'pptxgenjs', outFile: 'pptxgenjs.cjs', entry: ` ${POLYFILL_PRELUDE} import PptxGenJS from 'pptxgenjs' globalThis.__bundles = globalThis.__bundles || {} globalThis.__bundles['pptxgenjs'] = PptxGenJS `, }, ] async function main(): Promise { rmSync(ENTRIES_DIR, { recursive: true, force: true }) mkdirSync(ENTRIES_DIR, { recursive: true }) mkdirSync(BUNDLES_DIR, { recursive: true }) for (const spec of BUNDLES) { const entryPath = join(ENTRIES_DIR, `${spec.name}.entry.ts`) writeFileSync(entryPath, spec.entry, 'utf-8') const result = await Bun.build({ entrypoints: [entryPath], target: 'browser', format: 'iife', minify: true, sourcemap: 'none', root: APP_SIM_ROOT, }) if (!result.success) { for (const log of result.logs) { logger.error(String(log)) } throw new Error(`Failed to build sandbox bundle: ${spec.name}`) } if (result.outputs.length === 0) { throw new Error(`No output produced for sandbox bundle: ${spec.name}`) } const code = await result.outputs[0].text() const banner = `// sandbox bundle: ${spec.name}\n// generated by apps/sim/lib/execution/sandbox/bundles/build.ts\n// do not edit by hand. run \`bun run build:sandbox-bundles\` to regenerate.\n` writeFileSync(join(BUNDLES_DIR, spec.outFile), banner + code, 'utf-8') logger.info(`built ${spec.outFile} (${code.length.toLocaleString()} chars)`) } rmSync(ENTRIES_DIR, { recursive: true, force: true }) } main().catch((err) => { logger.error('sandbox bundle build failed', err) process.exit(1) })