Files
letta-code/scripts/postinstall-patches.js
2026-02-09 19:49:44 -08:00

136 lines
4.6 KiB
JavaScript

// Postinstall patcher for vendoring our Ink modifications without patch-package.
// Copies patched runtime files from ./src/vendor into node_modules.
import { execSync } from "node:child_process";
import {
copyFileSync,
existsSync,
mkdirSync,
readFileSync,
writeFileSync,
} from "node:fs";
import { createRequire } from "node:module";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const pkgRoot = dirname(__dirname);
const require = createRequire(import.meta.url);
async function copyToResolved(srcRel, targetSpecifier) {
const src = join(pkgRoot, srcRel);
if (!existsSync(src)) return;
let dest;
try {
// Special handling for Ink internals due to package exports
if (targetSpecifier.startsWith("ink/")) {
// Resolve root of installed ink package; add robust fallbacks for Bun
let buildDir;
try {
// Prefer import.meta.resolve when available
const inkEntryUrl = await import.meta.resolve("ink");
const inkEntryPath = fileURLToPath(inkEntryUrl); // .../node_modules/ink/build/index.js
buildDir = dirname(inkEntryPath); // .../node_modules/ink/build
} catch {}
if (!buildDir) {
try {
const inkPkgPath = require.resolve("ink/package.json");
const inkRoot = dirname(inkPkgPath);
buildDir = join(inkRoot, "build");
} catch {}
}
if (!buildDir) {
// Final fallback: assume standard layout relative to project root
buildDir = join(pkgRoot, "node_modules", "ink", "build");
}
const rel = targetSpecifier.replace(/^ink\//, ""); // e.g. build/components/App.js
const afterBuild = rel.replace(/^build\//, ""); // e.g. components/App.js
dest = join(buildDir, afterBuild);
} else if (targetSpecifier.startsWith("ink-text-input/")) {
// Resolve root of installed ink-text-input in a Node 18+ compatible way
try {
const entryUrl = await import.meta.resolve("ink-text-input");
dest = fileURLToPath(entryUrl); // .../node_modules/ink-text-input/build/index.js
} catch {
try {
const itPkgPath = require.resolve("ink-text-input/package.json");
const itRoot = dirname(itPkgPath);
dest = join(itRoot, "build", "index.js");
} catch {
// Final fallback
dest = join(
pkgRoot,
"node_modules",
"ink-text-input",
"build",
"index.js",
);
}
}
} else {
dest = require.resolve(targetSpecifier);
}
} catch (e) {
console.warn(
`[patch] failed to resolve ${targetSpecifier}:`,
e?.message || e,
);
return;
}
const destDir = dirname(dest);
if (!existsSync(destDir)) mkdirSync(destDir, { recursive: true });
try {
copyFileSync(src, dest);
console.log(`[patch] ${srcRel} -> ${dest}`);
} catch (e) {
console.warn(
`[patch] failed to copy ${srcRel} to ${dest}:`,
e?.message || e,
);
}
}
// Ink internals (resolve actual installed module path)
await copyToResolved(
"vendor/ink/build/components/App.js",
"ink/build/components/App.js",
);
await copyToResolved(
"vendor/ink/build/hooks/use-input.js",
"ink/build/hooks/use-input.js",
);
await copyToResolved("vendor/ink/build/devtools.js", "ink/build/devtools.js");
await copyToResolved("vendor/ink/build/log-update.js", "ink/build/log-update.js");
// ink-text-input (optional vendor with externalCursorOffset support)
await copyToResolved(
"vendor/ink-text-input/build/index.js",
"ink-text-input/build/index.js",
);
console.log("[patch] Ink runtime patched");
// On Unix with Bun available, use polyglot shebang to prefer Bun runtime.
// This enables Bun.secrets for secure keychain storage instead of fallback.
// Windows always uses #!/usr/bin/env node (polyglot shebang breaks npm wrappers).
if (process.platform !== "win32") {
try {
execSync("bun --version", { stdio: "ignore" });
const lettaPath = join(pkgRoot, "letta.js");
if (existsSync(lettaPath)) {
let content = readFileSync(lettaPath, "utf-8");
if (content.startsWith("#!/usr/bin/env node")) {
content = content.replace(
"#!/usr/bin/env node",
`#!/bin/sh
":" //#; exec /usr/bin/env sh -c 'command -v bun >/dev/null && exec bun "$0" "$@" || exec node "$0" "$@"' "$0" "$@"`,
);
writeFileSync(lettaPath, content);
console.log("[patch] Configured letta to prefer Bun runtime");
}
}
} catch {
// Bun not available, keep node shebang
}
}