/** * Third-party package resolver with an allowlist for app builds. * * Maintains a shared cache at ~/.vellum/package-cache/ so packages are * installed once and reused across all app compilations. */ import { existsSync } from "node:fs"; import { mkdir } from "node:fs/promises"; import { join } from "node:path"; import { ensureBun } from "../util/bun-runtime.js"; import { getLogger } from "../util/logger.js"; import { getWorkspaceDir } from "../util/platform.js"; const log = getLogger("package-resolver"); /** Packages the model is likely to use and that we trust in sandboxed apps. */ export const ALLOWED_PACKAGES: readonly string[] = [ "date-fns", "chart.js", "lodash-es", "zod", "clsx", ] as const; /** Bounds a runaway install; the allowlist above bounds what may be installed. */ const INSTALL_TIMEOUT_MS = 10_000; /** In-flight install promises keyed by package name, to deduplicate concurrent requests. */ const inflight = new Map>(); /** Where all cached packages live on disk. */ export function getCacheDir(): string { return join(getWorkspaceDir(), "package-cache"); } /** * Return true when `name` is a bare specifier that our plugin should handle * (i.e. not a relative/absolute path, not preact/react which are aliased). */ export function isBareImport(name: string): boolean { if (name.startsWith(".") || name.startsWith("/")) { return false; } if ( name.startsWith("preact") || name.startsWith("react") || name.startsWith("react-dom") ) { return false; } return true; } /** Get the top-level package name from a specifier (handles scoped pkgs). */ export function packageName(specifier: string): string { if (specifier.startsWith("@")) { const parts = specifier.split("/"); return parts.slice(0, 2).join("/"); } return specifier.split("/")[0]; } /** * Resolve a third-party package from the shared cache. * * Returns the path to the package inside node_modules, or null if the * package is not allowed or installation failed. */ export async function resolvePackage(name: string): Promise { const pkg = packageName(name); if (!ALLOWED_PACKAGES.includes(pkg)) { return null; } const cacheDir = getCacheDir(); const nodeModulesDir = join(cacheDir, "node_modules"); const pkgDir = join(nodeModulesDir, pkg); // Already cached — skip install if (existsSync(pkgDir)) { return nodeModulesDir; } // Deduplicate concurrent install requests for the same package const existing = inflight.get(pkg); if (existing) { return existing; } const promise = installPackage(pkg, cacheDir, nodeModulesDir, pkgDir); inflight.set(pkg, promise); try { return await promise; } finally { inflight.delete(pkg); } } async function installPackage( pkg: string, cacheDir: string, nodeModulesDir: string, pkgDir: string, ): Promise { // Ensure cache directory exists with a package.json so bun install works await mkdir(cacheDir, { recursive: true }); const pkgJsonPath = join(cacheDir, "package.json"); if (!existsSync(pkgJsonPath)) { const { writeFile } = await import("node:fs/promises"); await writeFile( pkgJsonPath, JSON.stringify({ name: "vellum-pkg-cache", private: true }), ); } log.info({ pkg }, "Installing package into shared cache"); try { const bunPath = await ensureBun(); const proc = Bun.spawn([bunPath, "install", "--no-save", pkg], { cwd: cacheDir, stdout: "pipe", stderr: "pipe", }); // Race against timeout const exited = proc.exited; let timer: ReturnType; const timeout = new Promise<"timeout">((resolve) => { timer = setTimeout(() => resolve("timeout"), INSTALL_TIMEOUT_MS); }); const raceResult = await Promise.race([exited, timeout]); clearTimeout(timer!); if (raceResult === "timeout") { proc.kill(); log.warn({ pkg }, "Package install timed out"); return null; } if (raceResult !== 0) { const stderr = await new Response(proc.stderr).text(); log.warn({ pkg, stderr }, "Package install failed"); return null; } return existsSync(pkgDir) ? nodeModulesDir : null; } catch (err) { log.warn({ pkg, err }, "Package resolution failed"); return null; } }