import { readdir, readFile, writeFile } from "fs/promises"; import { join } from "path"; const outdir = "dist"; // Step 1: Clean output directory const { rmSync } = await import("fs"); rmSync(outdir, { recursive: true, force: true }); // Step 2: Bundle with splitting const result = await Bun.build({ entrypoints: ["src/entrypoints/cli.tsx"], outdir, target: "bun", splitting: true, }); if (!result.success) { console.error("Build failed:"); for (const log of result.logs) { console.error(log); } process.exit(1); } // Step 3: Post-process — patch for Node.js compatibility & re-enable feature-flagged code const files = await readdir(outdir); // 3a. Replace import.meta.require with Node.js compat shim const IMPORT_META_REQUIRE = "var __require = import.meta.require;"; const COMPAT_REQUIRE = `var __require = typeof import.meta.require === "function" ? import.meta.require : (await import("module")).createRequire(import.meta.url);`; // 3b. Re-enable feature-flagged code that was DCE'd (dead-code-eliminated) by the bundler. // The Bun bundler resolves `feature('BUDDY')` from bun:bundle to `false` at build time, // turning `if (!feature('BUDDY')) return X` into `if (true) return X` (dead code). // We need to remove these guards to re-enable the buddy system. // Pattern: `if (true)\n return 0;` → remove (companionReservedColumns guard) // Pattern: `if (true)\n return null;` → remove (CompanionSprite render guard) // Pattern: `if (true)\n return [];` → remove (findBuddyTriggerPositions guard) // Pattern: `if (true) {\n return;\n }` → remove (useEffect guard) // // We specifically target buddy-related functions by checking surrounding context. const BUDDY_FUNCTIONS = [ 'companionReservedColumns', 'CompanionSprite', 'CompanionFloatingBubble', 'findBuddyTriggerPositions', ]; let patched = 0; let buddyPatched = 0; for (const file of files) { if (!file.endsWith(".js")) continue; const filePath = join(outdir, file); let content = await readFile(filePath, "utf-8"); let changed = false; // Patch import.meta.require if (content.includes(IMPORT_META_REQUIRE)) { content = content.replace(IMPORT_META_REQUIRE, COMPAT_REQUIRE); patched++; changed = true; } // Re-enable buddy feature flags: remove DCE'd guards // These patterns are generated by the bundler when it resolves feature('BUDDY') to false // and negates it: !false → true, creating `if (true) return X;` dead code guards. if (content.includes('CompanionSprite') || content.includes('companionReservedColumns') || content.includes('findBuddyTriggerPositions') || content.includes('useBuddyNotification')) { // Pattern 1: `if (true)\n return 0;` (single-line early return) content = content.replace(/if \(true\)\n\s*return 0;/g, '/* buddy-enabled */'); // Pattern 2: `if (true)\n return null;` (component early return) content = content.replace(/if \(true\)\n\s*return null;/g, '/* buddy-enabled */'); // Pattern 3: `if (true)\n return \[\];` (array early return) content = content.replace(/if \(true\)\n\s*return \[\];/g, '/* buddy-enabled */'); // Pattern 4: `if (true) {\n return;\n }` (void early return) content = content.replace(/if \(true\) \{\n\s*return;\n\s*\}/g, '/* buddy-enabled */'); // Pattern 5: `if (true) {\n return null;\n }` (block return null) content = content.replace(/if \(true\) \{\n\s*return null;\n\s*\}/g, '/* buddy-enabled */'); // Pattern 6: `if (true) {\n return false;\n }` (block return false) content = content.replace(/if \(true\) \{\n\s*return false;\n\s*\}/g, '/* buddy-enabled */'); buddyPatched++; changed = true; } // Also fix REPL.tsx buddy rendering conditions // The bundler turns `feature('BUDDY') && expr` into `false && expr` → `false` // and `!feature('BUDDY')` into `true` // In REPL, the inline expressions like: // `feature('BUDDY') && companionVisible && ...` become just `false` // We need to restore these. The pattern in the compiled REPL is typically: // `bottomFloat={false}` (was `bottomFloat={feature('BUDDY') && ...}`) // `false && companionNarrow` (was `feature('BUDDY') && companionNarrow`) // These are harder to fix generically. Let's handle the PromptInput/REPL chunk. if (content.includes('companionVisible') && content.includes('companionNarrow')) { // The REPL renders companion in two places: // 1. Fullscreen: bottomFloat={feature('BUDDY') && companionVisible && !companionNarrow ? : undefined} // Compiled to: bottomFloat={false ? ... : undefined} → bottomFloat={undefined} // 2. Non-fullscreen: {feature('BUDDY') && !(companionNarrow && isFullscreenEnvEnabled()) && companionVisible ? : null} // Compiled to: {false ? ... : null} → null // Replace `false && companionNarrow` patterns (used in flex direction) content = content.replace( /false && companionNarrow/g, 'companionNarrow' ); // Fix companion sprite rendering: find places where `null` replaced the companion // Look for the pattern near companionVisible: `null` where `` should be // This is trickier. Let's look for specific compiled patterns. changed = true; } if (changed) { await writeFile(filePath, content); } } // Step 4: Replace shebang from bun to node for npm compatibility const cliPath = join(outdir, "cli.js"); let cliContent = await readFile(cliPath, "utf-8"); // 4a. Replace shebang if (cliContent.startsWith("#!/usr/bin/env bun")) { cliContent = cliContent.replace("#!/usr/bin/env bun", "#!/usr/bin/env node"); } // 4b. Inject Node.js compatibility shim const COMPAT_SHIM = ` // ── Node.js compatibility shim ── if (typeof globalThis.Bun === "undefined") { // Ensure typeof Bun checks return "undefined" (not ReferenceError) } `; const firstNewline = cliContent.indexOf("\n"); if (firstNewline !== -1) { cliContent = cliContent.slice(0, firstNewline + 1) + COMPAT_SHIM + cliContent.slice(firstNewline + 1); } await writeFile(cliPath, cliContent); console.log(`Bundled ${result.outputs.length} files to ${outdir}/ (patched ${patched} require, ${buddyPatched} buddy chunks, shebang → node)`);