fix(build): prune stale bundled plugin node_modules

This commit is contained in:
Ayaan Zaidi
2026-04-08 20:18:08 +05:30
parent e4c7ee5856
commit b28fe1b92f
3 changed files with 106 additions and 6 deletions

View File

@@ -2,9 +2,11 @@
// Runs after install to restore bundled extension runtime deps.
// Installed builds can lazy-load bundled plugin code through root dist chunks,
// so runtime dependencies declared in dist/extensions/*/package.json must also
// resolve from the package root node_modules. Skip source checkouts.
// resolve from the package root node_modules. Source checkouts resolve bundled
// plugin deps from the workspace root, so stale plugin-local node_modules must
// not linger under extensions/* and shadow the root graph.
import { spawnSync } from "node:child_process";
import { existsSync, readdirSync, readFileSync } from "node:fs";
import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { resolveNpmRunner } from "./npm-runner.mjs";
@@ -102,7 +104,7 @@ export function createNestedNpmInstallEnv(env = process.env) {
return nextEnv;
}
function isSourceCheckoutRoot(params) {
export function isSourceCheckoutRoot(params) {
const pathExists = params.existsSync ?? existsSync;
return (
pathExists(join(params.packageRoot, ".git")) &&
@@ -111,6 +113,30 @@ function isSourceCheckoutRoot(params) {
);
}
export function pruneBundledPluginSourceNodeModules(params = {}) {
const extensionsDir = params.extensionsDir ?? join(DEFAULT_PACKAGE_ROOT, "extensions");
const pathExists = params.existsSync ?? existsSync;
const readDir = params.readdirSync ?? readdirSync;
const removePath = params.rmSync ?? rmSync;
if (!pathExists(extensionsDir)) {
return;
}
for (const entry of readDir(extensionsDir, { withFileTypes: true })) {
if (!entry.isDirectory()) {
continue;
}
const pluginDir = join(extensionsDir, entry.name);
if (!pathExists(join(pluginDir, "package.json"))) {
continue;
}
removePath(join(pluginDir, "node_modules"), { recursive: true, force: true });
}
}
function shouldRunBundledPluginPostinstall(params) {
if (params.env?.[DISABLE_POSTINSTALL_ENV]?.trim()) {
return false;
@@ -118,9 +144,6 @@ function shouldRunBundledPluginPostinstall(params) {
if (!params.existsSync(params.extensionsDir)) {
return false;
}
if (isSourceCheckoutRoot({ packageRoot: params.packageRoot, existsSync: params.existsSync })) {
return false;
}
return true;
}
@@ -141,6 +164,13 @@ export function runBundledPluginPostinstall(params = {}) {
) {
return;
}
if (isSourceCheckoutRoot({ packageRoot, existsSync: pathExists })) {
pruneBundledPluginSourceNodeModules({
extensionsDir: join(packageRoot, "extensions"),
existsSync: pathExists,
});
return;
}
const runtimeDeps =
params.runtimeDeps ??
discoverBundledPluginRuntimeDeps({ extensionsDir, existsSync: pathExists });

View File

@@ -6,6 +6,10 @@ import path from "node:path";
import { pathToFileURL } from "node:url";
import { BUNDLED_PLUGIN_PATH_PREFIX } from "./lib/bundled-plugin-paths.mjs";
import { resolvePnpmRunner } from "./pnpm-runner.mjs";
import {
isSourceCheckoutRoot,
pruneBundledPluginSourceNodeModules,
} from "./postinstall-bundled-plugins.mjs";
const logLevel = process.env.OPENCLAW_BUILD_VERBOSE ? "info" : "warn";
const extraArgs = process.argv.slice(2);
@@ -43,6 +47,20 @@ function pruneStaleRuntimeSymlinks() {
removeDistPluginNodeModulesSymlinks(path.join(cwd, "dist-runtime"));
}
function pruneSourceCheckoutBundledPluginNodeModules() {
const cwd = process.cwd();
if (!isSourceCheckoutRoot({ packageRoot: cwd, existsSync: fs.existsSync })) {
return;
}
pruneBundledPluginSourceNodeModules({
extensionsDir: path.join(cwd, "extensions"),
existsSync: fs.existsSync,
readdirSync: fs.readdirSync,
rmSync: fs.rmSync,
});
}
pruneSourceCheckoutBundledPluginNodeModules();
pruneStaleRuntimeSymlinks();
function findFatalUnresolvedImport(lines) {