148 lines
6.4 KiB
TypeScript
Executable File
148 lines
6.4 KiB
TypeScript
Executable File
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 ? <CompanionFloatingBubble /> : undefined}
|
|
// Compiled to: bottomFloat={false ? ... : undefined} → bottomFloat={undefined}
|
|
// 2. Non-fullscreen: {feature('BUDDY') && !(companionNarrow && isFullscreenEnvEnabled()) && companionVisible ? <CompanionSprite /> : 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 `<CompanionSprite />` 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)`);
|