UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

115 lines (114 loc) 4.99 kB
import { n as resolveOpenClawPackageRootSync } from "./openclaw-root-CfYY-fyD.js"; import { m as shortenHomePath } from "./utils-P__uGsPB.js"; import { t as note } from "./note-DqHk3fA1.js"; import path from "node:path"; import fs from "node:fs/promises"; //#region src/commands/doctor/shared/plugin-runtime-symlinks.ts const PLUGIN_RUNTIME_DEPS_MARKER = "plugin-runtime-deps"; const MAX_REPORTED = 6; const DEFAULT_FS = { readdir: (dir, options) => fs.readdir(dir, options), lstat: (file) => fs.lstat(file), readlink: (file) => fs.readlink(file), stat: (file) => fs.stat(file), rm: (file, options) => fs.rm(file, options), unlink: (file) => fs.unlink(file) }; /** Find global node_modules symlinks that still point at stale plugin-runtime deps. */ async function collectStalePluginRuntimeSymlinks(packageRoot = resolveOpenClawPackageRootSync({ argv1: process.argv[1], moduleUrl: import.meta.url, cwd: process.cwd() }), options = {}) { if (!packageRoot) return []; const containingNodeModules = path.dirname(packageRoot); if (path.basename(containingNodeModules) !== "node_modules") return []; const fsApi = options.fs ?? DEFAULT_FS; const stale = []; const entries = await fsApi.readdir(containingNodeModules, { withFileTypes: true }).catch(() => []); for (const entry of entries) { if (entry.isDirectory() && entry.name.startsWith("@")) { const scopeDir = path.join(containingNodeModules, entry.name); const scopeEntries = await fsApi.readdir(scopeDir, { withFileTypes: true }).catch(() => []); for (const scopeEntry of scopeEntries) { const fullPath = path.join(scopeDir, scopeEntry.name); const target = await inspectCandidate(fullPath, fsApi); if (target) stale.push({ name: `${entry.name}/${scopeEntry.name}`, path: fullPath, target }); } continue; } if (!entry.isSymbolicLink()) continue; const fullPath = path.join(containingNodeModules, entry.name); const target = await inspectCandidate(fullPath, fsApi); if (target) stale.push({ name: entry.name, path: fullPath, target }); } return stale.toSorted((left, right) => left.name.localeCompare(right.name)); } function stalePluginRuntimeSymlinkToHealthFinding(item) { return { checkId: "core/doctor/stale-plugin-runtime-symlinks", severity: "warning", message: `Stale plugin-runtime symlink ${item.name} points at ${item.target}.`, path: item.path, target: item.path, requirement: "stale-plugin-runtime-symlink-removed", fixHint: "Run `openclaw doctor --fix` to remove stale plugin-runtime symlinks." }; } async function collectStalePluginRuntimeSymlinkHealthFindings(params = {}) { return (await collectStalePluginRuntimeSymlinks(params.packageRoot, params)).map(stalePluginRuntimeSymlinkToHealthFinding); } /** Emit a doctor note describing stale plugin-runtime symlinks, if any exist. */ async function noteStalePluginRuntimeSymlinks(packageRoot, options = {}) { const stale = await collectStalePluginRuntimeSymlinks(packageRoot, options); if (stale.length === 0) return; const shortenPath = options.shortenPath ?? shortenHomePath; const lines = [ "- Plugin-runtime symlinks under the global Node prefix point at pruned", ` ${PLUGIN_RUNTIME_DEPS_MARKER} directories from a previous OpenClaw install.`, "- Bundled plugin ESM imports can fail with ERR_MODULE_NOT_FOUND until repaired." ]; for (const item of stale.slice(0, MAX_REPORTED)) lines.push(` - ${item.name} -> ${shortenPath(item.target)}`); if (stale.length > MAX_REPORTED) lines.push(` - ...and ${stale.length - MAX_REPORTED} more`); lines.push("- Repair: run `openclaw doctor --fix` to remove the dangling symlinks."); (options.noteFn ?? note)(lines.join("\n"), "Plugin-runtime symlinks"); } /** Remove stale plugin-runtime symlinks and report changes/warnings. */ async function removeStalePluginRuntimeSymlinks(packageRoot, options = {}) { const fsApi = options.fs ?? DEFAULT_FS; const changes = []; const warnings = []; for (const item of await collectStalePluginRuntimeSymlinks(packageRoot, options)) try { if (fsApi.unlink) await fsApi.unlink(item.path); else await fsApi.rm(item.path, { force: true }); changes.push(`Removed stale plugin-runtime symlink: ${item.path}`); } catch (error) { warnings.push(`Failed to remove stale plugin-runtime symlink ${item.path}: ${String(error)}`); } return { changes, warnings }; } async function inspectCandidate(fullPath, fsApi) { if (!(await fsApi.lstat(fullPath).catch(() => null))?.isSymbolicLink()) return null; const target = await fsApi.readlink(fullPath).catch(() => null); if (!target || !target.includes(PLUGIN_RUNTIME_DEPS_MARKER)) return null; try { await fsApi.stat(fullPath); return null; } catch (error) { const code = error?.code; return code === "ENOENT" || code === "ENOTDIR" ? target : null; } } //#endregion export { noteStalePluginRuntimeSymlinks as n, removeStalePluginRuntimeSymlinks as r, collectStalePluginRuntimeSymlinkHealthFindings as t };