




























@@ -1,8 +1,10 @@
11import fs from "node:fs/promises";
22import path from "node:path";
3+import { runCommandWithTimeout } from "../process/exec.js";
34import type { NpmSpecResolution } from "./install-source-utils.js";
45import { readJson, readJsonIfExists, writeJson } from "./json-files.js";
56import type { ParsedRegistryNpmSpec } from "./npm-registry-spec.js";
7+import { createSafeNpmInstallEnv } from "./safe-package-install.js";
6879type ManagedNpmRootManifest = {
810private?: boolean;
@@ -16,6 +18,18 @@ export type ManagedNpmRootInstalledDependency = {
1618resolved?: string;
1719};
182021+type ManagedNpmRootLockfile = {
22+packages?: Record<string, unknown>;
23+dependencies?: Record<string, unknown>;
24+[key: string]: unknown;
25+};
26+27+type ManagedNpmRootLogger = {
28+warn?: (message: string) => void;
29+};
30+31+type ManagedNpmRootRunCommand = typeof runCommandWithTimeout;
32+1933function isRecord(value: unknown): value is Record<string, unknown> {
2034return typeof value === "object" && value !== null && !Array.isArray(value);
2135}
@@ -69,6 +83,168 @@ export async function upsertManagedNpmRootDependency(params: {
6983await writeJson(manifestPath, next, { trailingNewline: true });
7084}
718586+export async function repairManagedNpmRootOpenClawPeer(params: {
87+npmRoot: string;
88+timeoutMs?: number;
89+logger?: ManagedNpmRootLogger;
90+runCommand?: ManagedNpmRootRunCommand;
91+}): Promise<boolean> {
92+await fs.mkdir(params.npmRoot, { recursive: true });
93+94+const manifestPath = path.join(params.npmRoot, "package.json");
95+const manifest = await readManagedNpmRootManifest(manifestPath);
96+const dependencies = readDependencyRecord(manifest.dependencies);
97+const hasManifestDependency = "openclaw" in dependencies;
98+const hasLockDependency = await managedNpmRootLockfileHasOpenClawPeer(params.npmRoot);
99+const hasPackageDir = await pathExists(path.join(params.npmRoot, "node_modules", "openclaw"));
100+if (!hasManifestDependency && !hasLockDependency && !hasPackageDir) {
101+return false;
102+}
103+104+const command = params.runCommand ?? runCommandWithTimeout;
105+const npmArgs = hasManifestDependency
106+ ? [
107+"npm",
108+"uninstall",
109+"--loglevel=error",
110+"--ignore-scripts",
111+"--no-audit",
112+"--no-fund",
113+"--prefix",
114+".",
115+"openclaw",
116+]
117+ : [
118+"npm",
119+"prune",
120+"--loglevel=error",
121+"--ignore-scripts",
122+"--no-audit",
123+"--no-fund",
124+"--prefix",
125+".",
126+];
127+try {
128+const result = await command(npmArgs, {
129+cwd: params.npmRoot,
130+timeoutMs: Math.max(params.timeoutMs ?? 300_000, 300_000),
131+env: createSafeNpmInstallEnv(process.env, { packageLock: true, quiet: true }),
132+});
133+if (result.code !== 0) {
134+params.logger?.warn?.(
135+`npm ${hasManifestDependency ? "uninstall openclaw" : "prune"} failed while repairing managed npm root; falling back to direct cleanup: ${result.stderr.trim() || result.stdout.trim()}`,
136+);
137+}
138+} catch (error) {
139+params.logger?.warn?.(
140+`npm ${hasManifestDependency ? "uninstall openclaw" : "prune"} failed while repairing managed npm root; falling back to direct cleanup: ${String(error)}`,
141+);
142+}
143+144+await scrubManagedNpmRootOpenClawPeer({ npmRoot: params.npmRoot });
145+return true;
146+}
147+148+async function managedNpmRootLockfileHasOpenClawPeer(npmRoot: string): Promise<boolean> {
149+const lockPath = path.join(npmRoot, "package-lock.json");
150+try {
151+const parsed = JSON.parse(await fs.readFile(lockPath, "utf8")) as ManagedNpmRootLockfile;
152+if (isRecord(parsed.packages)) {
153+const rootPackage = parsed.packages[""];
154+if (
155+isRecord(rootPackage) &&
156+isRecord(rootPackage.dependencies) &&
157+"openclaw" in rootPackage.dependencies
158+) {
159+return true;
160+}
161+if ("node_modules/openclaw" in parsed.packages) {
162+return true;
163+}
164+}
165+return isRecord(parsed.dependencies) && "openclaw" in parsed.dependencies;
166+} catch (err) {
167+if ((err as NodeJS.ErrnoException).code === "ENOENT") {
168+return false;
169+}
170+throw err;
171+}
172+}
173+174+async function pathExists(filePath: string): Promise<boolean> {
175+return await fs
176+.lstat(filePath)
177+.then(() => true)
178+.catch((err: NodeJS.ErrnoException) => {
179+if (err.code === "ENOENT") {
180+return false;
181+}
182+throw err;
183+});
184+}
185+186+async function scrubManagedNpmRootOpenClawPeer(params: { npmRoot: string }): Promise<void> {
187+const manifestPath = path.join(params.npmRoot, "package.json");
188+const manifest = await readManagedNpmRootManifest(manifestPath);
189+const dependencies = readDependencyRecord(manifest.dependencies);
190+if ("openclaw" in dependencies) {
191+const { openclaw: _removed, ...nextDependencies } = dependencies;
192+await fs.writeFile(
193+manifestPath,
194+`${JSON.stringify({ ...manifest, private: true, dependencies: nextDependencies }, null, 2)}\n`,
195+"utf8",
196+);
197+}
198+199+const lockPath = path.join(params.npmRoot, "package-lock.json");
200+try {
201+const parsed = JSON.parse(await fs.readFile(lockPath, "utf8")) as ManagedNpmRootLockfile;
202+let lockChanged = false;
203+if (isRecord(parsed.packages)) {
204+const rootPackage = parsed.packages[""];
205+if (isRecord(rootPackage) && isRecord(rootPackage.dependencies)) {
206+const dependencies = { ...rootPackage.dependencies };
207+if ("openclaw" in dependencies) {
208+delete dependencies.openclaw;
209+parsed.packages[""] = { ...rootPackage, dependencies };
210+lockChanged = true;
211+}
212+}
213+if ("node_modules/openclaw" in parsed.packages) {
214+delete parsed.packages["node_modules/openclaw"];
215+lockChanged = true;
216+}
217+}
218+if (isRecord(parsed.dependencies) && "openclaw" in parsed.dependencies) {
219+const dependencies = { ...parsed.dependencies };
220+delete dependencies.openclaw;
221+parsed.dependencies = dependencies;
222+lockChanged = true;
223+}
224+if (lockChanged) {
225+await fs.writeFile(lockPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
226+}
227+} catch (err) {
228+if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
229+throw err;
230+}
231+}
232+233+const openclawPackageDir = path.join(params.npmRoot, "node_modules", "openclaw");
234+if (await pathExists(openclawPackageDir)) {
235+await fs.rm(openclawPackageDir, { recursive: true, force: true });
236+}
237+const binDir = path.join(params.npmRoot, "node_modules", ".bin");
238+await Promise.all(
239+["openclaw", "openclaw.cmd", "openclaw.ps1"].map((binName) =>
240+fs.rm(path.join(binDir, binName), { force: true }),
241+),
242+);
243+await fs.rm(path.join(params.npmRoot, "node_modules", ".package-lock.json"), {
244+force: true,
245+});
246+}
247+72248export async function readManagedNpmRootInstalledDependency(params: {
73249npmRoot: string;
74250packageName: string;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。