diff --git a/src/commands/doctor/shared/plugin-dependency-cleanup.test.ts b/src/commands/doctor/shared/plugin-dependency-cleanup.test.ts index baff0db3627..28dcd34d3ba 100644 --- a/src/commands/doctor/shared/plugin-dependency-cleanup.test.ts +++ b/src/commands/doctor/shared/plugin-dependency-cleanup.test.ts @@ -29,18 +29,33 @@ describe("cleanupLegacyPluginDependencyState", () => { "demo", "node_modules", ); + const legacyExtensionStamp = path.join( + packageRoot, + "dist", + "extensions", + "demo", + ".openclaw-runtime-deps-stamp.json", + ); const legacyManifest = path.join( packageRoot, "extensions", "demo", ".openclaw-runtime-deps.json", ); + const thirdPartyNodeModules = path.join( + stateDir, + "extensions", + "lossless-claw", + "node_modules", + ); await fs.mkdir(legacyRuntimeRoot, { recursive: true }); await fs.mkdir(legacyLocalRoot, { recursive: true }); await fs.mkdir(legacyExtensionNodeModules, { recursive: true }); + await fs.writeFile(legacyExtensionStamp, "{}"); await fs.mkdir(path.dirname(legacyManifest), { recursive: true }); await fs.writeFile(legacyManifest, "{}"); + await fs.mkdir(thirdPartyNodeModules, { recursive: true }); await fs.mkdir(explicitStageDir, { recursive: true }); await fs.mkdir(path.join(stateDirectory, "plugin-runtime-deps"), { recursive: true }); @@ -55,11 +70,13 @@ describe("cleanupLegacyPluginDependencyState", () => { legacyRuntimeRoot, legacyLocalRoot, legacyExtensionNodeModules, + legacyExtensionStamp, legacyManifest, explicitStageDir, path.join(stateDirectory, "plugin-runtime-deps"), ]), ); + expect(targets).not.toContain(thirdPartyNodeModules); const result = await cleanupLegacyPluginDependencyState({ env, packageRoot }); @@ -68,7 +85,9 @@ describe("cleanupLegacyPluginDependencyState", () => { await expect(fs.stat(legacyRuntimeRoot)).rejects.toThrow(); await expect(fs.stat(legacyLocalRoot)).rejects.toThrow(); await expect(fs.stat(legacyExtensionNodeModules)).rejects.toThrow(); + await expect(fs.stat(legacyExtensionStamp)).rejects.toThrow(); await expect(fs.stat(legacyManifest)).rejects.toThrow(); + await expect(fs.stat(thirdPartyNodeModules)).resolves.toBeDefined(); await expect(fs.stat(explicitStageDir)).rejects.toThrow(); await expect(fs.stat(path.join(stateDirectory, "plugin-runtime-deps"))).rejects.toThrow(); }); diff --git a/src/commands/doctor/shared/plugin-dependency-cleanup.ts b/src/commands/doctor/shared/plugin-dependency-cleanup.ts index e223b258062..652ed274eee 100644 --- a/src/commands/doctor/shared/plugin-dependency-cleanup.ts +++ b/src/commands/doctor/shared/plugin-dependency-cleanup.ts @@ -34,14 +34,19 @@ async function pathExists(targetPath: string): Promise { } } -function isLegacyDependencyDebrisName(name: string): boolean { +function isRuntimeDependencyMarkerName(name: string): boolean { return ( - name === "node_modules" || name === ".openclaw-runtime-deps.json" || name === ".openclaw-runtime-deps-stamp.json" || + name.startsWith(".openclaw-runtime-deps-") + ); +} + +function isLegacyDependencyDebrisName(name: string): boolean { + return ( + isRuntimeDependencyMarkerName(name) || name === ".openclaw-pnpm-store" || name === ".openclaw-install-backups" || - name.startsWith(".openclaw-runtime-deps-") || name.startsWith(".openclaw-install-stage-") ); } @@ -59,8 +64,17 @@ async function collectLegacyExtensionDebris(extensionsRoot: string): Promise + isRuntimeDependencyMarkerName(path.basename(childPath)), + ); + for (const childPath of children) { + const basename = path.basename(childPath); + if (basename === "node_modules" && hasRuntimeDepsMarker) { + targets.push(childPath); + continue; + } + if (isLegacyDependencyDebrisName(basename)) { targets.push(childPath); } }