
























@@ -21,6 +21,7 @@ import {
2121materializeBundledRuntimeMirrorDistFile,
2222repairBundledRuntimeDepsInstallRootAsync,
2323resolveBundledRuntimeDependencyInstallRoot,
24+resolveBundledRuntimeDependencyInstallRootPlan,
2425resolveBundledRuntimeDepsNpmRunner,
2526scanBundledPluginRuntimeDeps,
2627type BundledRuntimeDepsInstallParams,
@@ -989,6 +990,47 @@ describe("scanBundledPluginRuntimeDeps config policy", () => {
989990expect(result.deps[0]?.pluginIds).toEqual(["logger-plugin", "openclaw-core"]);
990991expect(result.missing.map((dep) => `${dep.name}@${dep.version}`)).toEqual(["tslog@^4.10.2"]);
991992});
993+994+it("resolves runtime deps from layered external stage dirs", () => {
995+const packageRoot = makeTempDir();
996+const baselineStageDir = makeTempDir();
997+const writableStageDir = makeTempDir();
998+fs.writeFileSync(
999+path.join(packageRoot, "package.json"),
1000+JSON.stringify({ name: "openclaw", version: "2026.4.25" }),
1001+);
1002+const pluginRoot = writeBundledPluginPackage({
1003+ packageRoot,
1004+pluginId: "slack",
1005+deps: {
1006+"@slack/web-api": "7.15.1",
1007+grammy: "1.37.0",
1008+},
1009+enabledByDefault: true,
1010+});
1011+const env = {
1012+OPENCLAW_PLUGIN_STAGE_DIR: [baselineStageDir, writableStageDir].join(path.delimiter),
1013+};
1014+const installRootPlan = resolveBundledRuntimeDependencyInstallRootPlan(pluginRoot, { env });
1015+writeInstalledPackage(
1016+installRootPlan.searchRoots[0] ?? baselineStageDir,
1017+"@slack/web-api",
1018+"7.15.1",
1019+);
1020+1021+const result = scanBundledPluginRuntimeDeps({
1022+ packageRoot,
1023+config: {},
1024+ env,
1025+});
1026+1027+expect(installRootPlan.installRoot).toContain(writableStageDir);
1028+expect(result.deps.map((dep) => `${dep.name}@${dep.version}`)).toEqual([
1029+"@slack/web-api@7.15.1",
1030+"grammy@1.37.0",
1031+]);
1032+expect(result.missing.map((dep) => `${dep.name}@${dep.version}`)).toEqual(["grammy@1.37.0"]);
1033+});
9921034});
99310359941036describe("ensureBundledPluginRuntimeDeps", () => {
@@ -1238,6 +1280,64 @@ describe("ensureBundledPluginRuntimeDeps", () => {
12381280expect(second).toEqual({ installedSpecs: [], retainSpecs: [] });
12391281});
124012821283+it("installs only missing deps into the final layered stage dir", () => {
1284+const packageRoot = makeTempDir();
1285+const baselineStageDir = makeTempDir();
1286+const writableStageDir = makeTempDir();
1287+fs.writeFileSync(
1288+path.join(packageRoot, "package.json"),
1289+JSON.stringify({ name: "openclaw", version: "2026.4.25" }),
1290+);
1291+const pluginRoot = path.join(packageRoot, "dist", "extensions", "slack");
1292+fs.mkdirSync(pluginRoot, { recursive: true });
1293+fs.writeFileSync(
1294+path.join(pluginRoot, "package.json"),
1295+JSON.stringify({
1296+dependencies: {
1297+"@slack/web-api": "7.15.1",
1298+grammy: "1.37.0",
1299+},
1300+}),
1301+);
1302+const env = {
1303+OPENCLAW_PLUGIN_STAGE_DIR: [baselineStageDir, writableStageDir].join(path.delimiter),
1304+};
1305+const installRootPlan = resolveBundledRuntimeDependencyInstallRootPlan(pluginRoot, { env });
1306+const baselineRoot = installRootPlan.searchRoots[0] ?? baselineStageDir;
1307+writeInstalledPackage(baselineRoot, "@slack/web-api", "7.15.1");
1308+1309+const calls: BundledRuntimeDepsInstallParams[] = [];
1310+const result = ensureBundledPluginRuntimeDeps({
1311+ env,
1312+installDeps: (params) => {
1313+calls.push(params);
1314+fs.rmSync(path.join(params.installRoot, "node_modules", "@slack", "web-api"), {
1315+recursive: true,
1316+force: true,
1317+});
1318+writeInstalledPackage(params.installRoot, "grammy", "1.37.0");
1319+},
1320+pluginId: "slack",
1321+ pluginRoot,
1322+});
1323+1324+expect(installRootPlan.installRoot).toContain(writableStageDir);
1325+expect(result).toEqual({
1326+installedSpecs: ["grammy@1.37.0"],
1327+retainSpecs: ["grammy@1.37.0"],
1328+});
1329+expect(calls).toEqual([
1330+{
1331+installRoot: installRootPlan.installRoot,
1332+missingSpecs: ["grammy@1.37.0"],
1333+installSpecs: ["grammy@1.37.0"],
1334+},
1335+]);
1336+expect(
1337+fs.realpathSync(path.join(installRootPlan.installRoot, "node_modules", "@slack", "web-api")),
1338+).toBe(fs.realpathSync(path.join(baselineRoot, "node_modules", "@slack", "web-api")));
1339+});
1340+12411341it("retains external staged deps across separate loader passes", () => {
12421342const packageRoot = makeTempDir();
12431343const stageDir = makeTempDir();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。