From b624921badd8252d70fde60ce9e810c20819b714 Mon Sep 17 00:00:00 2001 From: Advait Paliwal Date: Tue, 24 Mar 2026 14:19:04 -0700 Subject: [PATCH] Prune packaged runtime dependencies --- scripts/build-native-bundle.mjs | 9 +- scripts/prepare-runtime-workspace.mjs | 15 ++- scripts/prune-runtime-deps.mjs | 140 ++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 scripts/prune-runtime-deps.mjs diff --git a/scripts/build-native-bundle.mjs b/scripts/build-native-bundle.mjs index e743143..abd4275 100644 --- a/scripts/build-native-bundle.mjs +++ b/scripts/build-native-bundle.mjs @@ -136,6 +136,7 @@ function ensureBundledWorkspace() { } function copyPackageFiles(appDir) { + const releaseDir = resolve(appRoot, "dist", "release"); cpSync(resolve(appRoot, "package.json"), resolve(appDir, "package.json")); for (const entry of packageJson.files) { const normalized = entry.endsWith("/") ? entry.slice(0, -1) : entry; @@ -143,7 +144,10 @@ function copyPackageFiles(appDir) { if (!existsSync(source)) continue; const destination = resolve(appDir, normalized); mkdirSync(dirname(destination), { recursive: true }); - cpSync(source, destination, { recursive: true }); + cpSync(source, destination, { + recursive: true, + filter: (path) => path !== releaseDir && !path.startsWith(`${releaseDir}/`), + }); } cpSync(packageLockPath, resolve(appDir, "package-lock.json")); @@ -160,6 +164,9 @@ function installAppDependencies(appDir, stagingRoot) { run("npm", ["ci", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund", "--loglevel", "error"], { cwd: depsDir, }); + run(process.execPath, [resolve(appRoot, "scripts", "prune-runtime-deps.mjs"), depsDir], { + cwd: appRoot, + }); cpSync(resolve(depsDir, "node_modules"), resolve(appDir, "node_modules"), { recursive: true }); } diff --git a/scripts/prepare-runtime-workspace.mjs b/scripts/prepare-runtime-workspace.mjs index 5aad399..1e79fc6 100644 --- a/scripts/prepare-runtime-workspace.mjs +++ b/scripts/prepare-runtime-workspace.mjs @@ -10,6 +10,7 @@ const workspaceNodeModulesDir = resolve(workspaceDir, "node_modules"); const manifestPath = resolve(workspaceDir, ".runtime-manifest.json"); const workspacePackageJsonPath = resolve(workspaceDir, "package.json"); const workspaceArchivePath = resolve(feynmanDir, "runtime-workspace.tgz"); +const PRUNE_VERSION = 1; function readPackageSpecs() { const settings = JSON.parse(readFileSync(settingsPath, "utf8")); @@ -44,7 +45,8 @@ function workspaceIsCurrent(packageSpecs) { if ( manifest.nodeAbi !== process.versions.modules || manifest.platform !== process.platform || - manifest.arch !== process.arch + manifest.arch !== process.arch || + manifest.pruneVersion !== PRUNE_VERSION ) { return false; } @@ -102,6 +104,7 @@ function writeManifest(packageSpecs) { nodeVersion: process.version, platform: process.platform, arch: process.arch, + pruneVersion: PRUNE_VERSION, }, null, 2, @@ -110,6 +113,15 @@ function writeManifest(packageSpecs) { ); } +function pruneWorkspace() { + const result = spawnSync(process.execPath, [resolve(appRoot, "scripts", "prune-runtime-deps.mjs"), workspaceDir], { + stdio: "inherit", + }); + if (result.status !== 0) { + process.exit(result.status ?? 1); + } +} + function archiveIsCurrent() { if (!existsSync(workspaceArchivePath) || !existsSync(manifestPath)) { return false; @@ -144,6 +156,7 @@ if (workspaceIsCurrent(packageSpecs)) { console.log("[feynman] preparing vendored runtime workspace..."); prepareWorkspace(packageSpecs); +pruneWorkspace(); writeManifest(packageSpecs); createWorkspaceArchive(); console.log("[feynman] vendored runtime workspace ready"); diff --git a/scripts/prune-runtime-deps.mjs b/scripts/prune-runtime-deps.mjs new file mode 100644 index 0000000..810f862 --- /dev/null +++ b/scripts/prune-runtime-deps.mjs @@ -0,0 +1,140 @@ +import { existsSync, readdirSync, rmSync, statSync } from "node:fs"; +import { basename, join, resolve } from "node:path"; + +const root = resolve(process.argv[2] ?? "."); +const nodeModulesDir = resolve(root, "node_modules"); + +const STRIP_DIR_NAMES = new Set([ + ".github", + "__mocks__", + "__tests__", + "coverage", + "doc", + "docs", + "example", + "examples", + "test", + "tests", +]); + +const STRIP_FILE_PATTERNS = [ + /\.map$/i, + /\.d\.cts$/i, + /\.d\.ts$/i, + /^README(\..+)?$/i, + /^CHANGELOG(\..+)?$/i, +]; + +function safeStat(path) { + try { + return statSync(path); + } catch { + return null; + } +} + +function removePath(path) { + rmSync(path, { recursive: true, force: true }); +} + +function walkAndPrune(dir) { + if (!existsSync(dir)) return; + + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const path = join(dir, entry.name); + const stats = entry.isSymbolicLink() ? safeStat(path) : null; + const isDirectory = entry.isDirectory() || stats?.isDirectory(); + const isFile = entry.isFile() || stats?.isFile(); + + if (isDirectory) { + if (STRIP_DIR_NAMES.has(entry.name)) { + removePath(path); + continue; + } + + walkAndPrune(path); + continue; + } + + if (isFile && STRIP_FILE_PATTERNS.some((pattern) => pattern.test(entry.name))) { + removePath(path); + } + } +} + +function currentKoffiVariant() { + if (process.platform === "darwin" && process.arch === "arm64") return "darwin_arm64"; + if (process.platform === "darwin" && process.arch === "x64") return "darwin_x64"; + if (process.platform === "linux" && process.arch === "arm64") return "linux_arm64"; + if (process.platform === "linux" && process.arch === "x64") return "linux_x64"; + if (process.platform === "win32" && process.arch === "arm64") return "win32_arm64"; + if (process.platform === "win32" && process.arch === "x64") return "win32_x64"; + return null; +} + +function pruneKoffi(nodeModulesRoot) { + const koffiRoot = join(nodeModulesRoot, "koffi"); + if (!existsSync(koffiRoot)) return; + + for (const dirName of ["doc", "src", "vendor"]) { + removePath(join(koffiRoot, dirName)); + } + + const buildRoot = join(koffiRoot, "build", "koffi"); + if (!existsSync(buildRoot)) return; + + const keep = currentKoffiVariant(); + for (const entry of readdirSync(buildRoot, { withFileTypes: true })) { + if (entry.name === keep) continue; + removePath(join(buildRoot, entry.name)); + } +} + +function pruneBetterSqlite3(nodeModulesRoot) { + const pkgRoot = join(nodeModulesRoot, "better-sqlite3"); + if (!existsSync(pkgRoot)) return; + + removePath(join(pkgRoot, "deps")); + removePath(join(pkgRoot, "src")); + removePath(join(pkgRoot, "binding.gyp")); + + const buildRoot = join(pkgRoot, "build"); + const releaseRoot = join(buildRoot, "Release"); + if (existsSync(releaseRoot)) { + for (const entry of readdirSync(releaseRoot, { withFileTypes: true })) { + if (entry.name === "better_sqlite3.node") continue; + removePath(join(releaseRoot, entry.name)); + } + } + + for (const entry of ["Makefile", "binding.Makefile", "config.gypi", "deps", "gyp-mac-tool", "test_extension.target.mk", "better_sqlite3.target.mk"]) { + removePath(join(buildRoot, entry)); + } +} + +function pruneLiteparse(nodeModulesRoot) { + const pkgRoot = join(nodeModulesRoot, "@llamaindex", "liteparse"); + if (!existsSync(pkgRoot)) return; + if (existsSync(join(pkgRoot, "dist"))) { + removePath(join(pkgRoot, "src")); + } +} + +function prunePiCodingAgent(nodeModulesRoot) { + const pkgRoot = join(nodeModulesRoot, "@mariozechner", "pi-coding-agent"); + if (!existsSync(pkgRoot)) return; + removePath(join(pkgRoot, "docs")); + removePath(join(pkgRoot, "examples")); +} + +if (!existsSync(nodeModulesDir)) { + process.exit(0); +} + +walkAndPrune(nodeModulesDir); +pruneKoffi(nodeModulesDir); +pruneBetterSqlite3(nodeModulesDir); +pruneLiteparse(nodeModulesDir); +prunePiCodingAgent(nodeModulesDir); + +console.log(`[feynman] pruned runtime deps in ${basename(root)}`);