openclaw/scripts/lib/bundled-plugin-dependency-links.mjs
Doksanbir a887336b1e
fix(docker): stage bundled plugin runtime dependencies under the packaged plugin root (#139117)
The official 2026.9.1 image cannot load its bundled externally distributed
plugins (codex, diagnostics-otel): doctor and plugins inspect report every
declared dependency as missing. Since the workspace moved to pnpm's isolated
linker (#135728) those packages live only in /app/node_modules/.pnpm and are
linked from /app/extensions/<id>/node_modules, neither of which is an ancestor
of the packaged root /app/dist/extensions/<id> that the dependency lookup
(src/plugins/status-dependencies-core.ts) and the managed Codex launcher
resolution (extensions/codex/src/app-server/managed-binary.ts) start from.
2026.8.2 worked only because the hoisted linker placed them in
/app/node_modules. Docker-selected plugins are built as unified outputs, so
the source-checkout dependency links added in #135903 never applied to them.

Move linkSourcePluginDependencies into a plain-Node module and have the
Docker assembly stage link each retained root-package-excluded plugin's
production install under dist/extensions/<id>/node_modules after package
lifecycle cleanup, failing the image build when a declared dependency still
does not resolve from the packaged root.

Fixes #138777. Reported by 8bitsforge.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-09-06 11:07:53 +08:00

36 lines
1.5 KiB
JavaScript

// Links a plugin's source-installed dependency packages under its packaged root.
import fs from "node:fs";
import path from "node:path";
/**
* Link every package installed under `<pluginDir>/node_modules` into `distNodeModules` so
* dependency lookups rooted at the packaged plugin resolve the plugin-owned install.
*/
export function linkSourcePluginDependencies(pluginDir, distNodeModules) {
const sourceModules = path.join(pluginDir, "node_modules");
if (!fs.existsSync(sourceModules)) {
return;
}
const packages = fs.readdirSync(sourceModules).flatMap((name) => {
if (name.startsWith(".") && name !== ".bin") {
return [];
}
return name.startsWith("@")
? fs.readdirSync(path.join(sourceModules, name)).map((child) => path.join(name, child))
: [name];
});
// An outer node_modules junction misresolves pnpm's relative links on Windows.
// Link canonical package roots individually; keep scopes real and payloads source-owned.
// Preserve .bin for managed launchers that resolve the plugin's private CLI shim.
for (const name of packages) {
const target = path.join(distNodeModules, name);
fs.mkdirSync(path.dirname(target), { recursive: true });
const canonical = fs.realpathSync(path.join(sourceModules, name));
// POSIX release checkouts relocate as a unit; Windows junctions require absolute targets.
fs.symlinkSync(
process.platform === "win32" ? canonical : path.relative(path.dirname(target), canonical),
target,
"junction",
);
}
}