UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

202 lines (201 loc) 8.65 kB
import { c as isRecord } from "./record-coerce-DItp3I4t.js"; import { n as isNotFoundPathError, r as isPathInside } from "./path-guards-Cp-mGr3-.js"; import { a as safePathSegmentHashed } from "./install-safe-path-CVdg_-66.js"; import { p as resolvePluginNpmProjectsDir, r as isPluginNpmProjectDir, s as resolveDefaultPluginNpmDir } from "./install-paths-ChnxikBv.js"; import fs from "node:fs"; import path from "node:path"; import fs$1 from "node:fs/promises"; //#region src/plugins/managed-npm-retention-contract.ts /** Marker reason for packages preserved by an explicit `plugins uninstall --keep-files`. */ const RETAINED_MANAGED_NPM_KEEP_FILES_REASON = "removed-managed-npm-install-retained"; //#endregion //#region src/plugins/npm-project-roots.ts function isMissing(error) { return isNotFoundPathError(error); } function sortPaths(paths) { return paths.toSorted((left, right) => left.localeCompare(right)); } /** Lists project-level npm roots managed below the plugin npm root. */ function listManagedPluginNpmProjectRootsSync(npmRoot) { const projectsDir = resolvePluginNpmProjectsDir(npmRoot); try { return sortPaths(fs.readdirSync(projectsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path.join(projectsDir, entry.name))); } catch (error) { if (isMissing(error)) return []; throw error; } } /** Async variant of project-level managed npm root discovery. */ async function listManagedPluginNpmProjectRoots(npmRoot) { const projectsDir = resolvePluginNpmProjectsDir(npmRoot); try { return sortPaths((await fs$1.readdir(projectsDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => path.join(projectsDir, entry.name))); } catch (error) { if (isMissing(error)) return []; throw error; } } /** Returns the root npm install plus all managed project npm roots. */ function listManagedPluginNpmRootsSync(npmRoot) { return [npmRoot, ...listManagedPluginNpmProjectRootsSync(npmRoot)]; } /** Async variant of managed npm root discovery. */ async function listManagedPluginNpmRoots(npmRoot) { return [npmRoot, ...await listManagedPluginNpmProjectRoots(npmRoot)]; } //#endregion //#region src/plugins/managed-npm-retention.ts const RETAINED_MANAGED_NPM_INSTALL_MARKER_DIR = ".openclaw-retained-npm-installs"; function markerPreservesPackageFiles(markerPath) { try { const marker = JSON.parse(fs.readFileSync(markerPath, "utf8")); return isRecord(marker) && marker.reason === "removed-managed-npm-install-retained"; } catch { return false; } } function resolveRetainedManagedNpmInstallPackageInfo(packageDir) { const resolvedPackageDir = path.resolve(packageDir); const packageBase = path.basename(resolvedPackageDir); const parentDir = path.dirname(resolvedPackageDir); const parentBase = path.basename(parentDir); const scopedPackage = parentBase.startsWith("@"); const nodeModulesRoot = scopedPackage ? path.dirname(parentDir) : parentDir; if (path.basename(nodeModulesRoot) !== "node_modules") return null; const packageName = scopedPackage ? `${parentBase}/${packageBase}` : packageBase; if (!packageBase || packageBase === "." || !packageName.trim()) return null; const projectRoot = path.dirname(nodeModulesRoot); return { packageName, projectRoot, markerPath: path.join(projectRoot, RETAINED_MANAGED_NPM_INSTALL_MARKER_DIR, `${safePathSegmentHashed(packageName)}.json`) }; } function resolveRetainedManagedNpmInstallMarkerPath(packageDir) { const info = resolveRetainedManagedNpmInstallPackageInfo(packageDir); if (!info) throw new Error("retained npm install marker requires a node_modules package directory"); return info.markerPath; } function hasRetainedManagedNpmInstallMarker(packageDir) { const info = resolveRetainedManagedNpmInstallPackageInfo(packageDir); return info ? fs.existsSync(info.markerPath) : false; } async function clearRetainedManagedNpmInstallMarker(packageDir) { const info = resolveRetainedManagedNpmInstallPackageInfo(packageDir); if (!info) return false; try { await fs.promises.rm(info.markerPath, { force: true }); } catch (error) { if (error.code === "ENOENT") return false; throw error; } try { await fs.promises.rmdir(path.dirname(info.markerPath)); } catch {} return true; } async function markRetainedManagedNpmInstall(params) { const info = resolveRetainedManagedNpmInstallPackageInfo(params.packageDir); if (!info) return false; let stat; try { stat = await fs.promises.stat(params.packageDir); } catch (error) { if (error.code === "ENOENT") return false; throw error; } if (!stat.isDirectory()) return false; await fs.promises.mkdir(path.dirname(info.markerPath), { recursive: true }); await fs.promises.writeFile(info.markerPath, `${JSON.stringify({ version: 1, pluginId: params.pluginId, retainedAt: params.retainedAt ?? (/* @__PURE__ */ new Date()).toISOString(), reason: params.reason }, null, 2)}\n`, "utf8"); return true; } function listManagedNpmPackageDirs(npmRoot) { const nodeModulesDir = path.join(npmRoot, "node_modules"); let entries; try { entries = fs.readdirSync(nodeModulesDir, { withFileTypes: true }); } catch (error) { if (error.code === "ENOENT") return []; throw error; } return entries.flatMap((entry) => { if (!entry.isDirectory()) return []; if (!entry.name.startsWith("@")) return [path.join(nodeModulesDir, entry.name)]; return fs.readdirSync(path.join(nodeModulesDir, entry.name), { withFileTypes: true }).filter((scopedEntry) => scopedEntry.isDirectory()).map((scopedEntry) => path.join(nodeModulesDir, entry.name, scopedEntry.name)); }); } function isOwnedManagedNpmProject(params) { return listManagedNpmPackageDirs(params.projectRoot).some((packageDir) => { const info = resolveRetainedManagedNpmInstallPackageInfo(packageDir); return Boolean(info && params.markerNames.has(path.basename(info.markerPath)) && isPluginNpmProjectDir({ npmDir: params.npmDir, packageName: info.packageName, projectDir: params.projectRoot })); }); } async function cleanupRetainedLegacyNpmPackages(params) { let removed = 0; for (const packageDir of listManagedNpmPackageDirs(params.npmRoot)) { if (!hasRetainedManagedNpmInstallMarker(packageDir) || markerPreservesPackageFiles(resolveRetainedManagedNpmInstallMarkerPath(packageDir)) || params.activeInstallPaths.some((installPath) => isPathInside(packageDir, installPath))) continue; try { await fs.promises.rm(packageDir, { recursive: true, force: true }); await clearRetainedManagedNpmInstallMarker(packageDir); removed += 1; } catch (error) { params.onError?.(error, packageDir); } } return removed; } async function cleanupRetainedManagedNpmInstallGenerations(params = {}) { const npmDir = params.npmDir ?? resolveDefaultPluginNpmDir(params.env); const projectsDir = resolvePluginNpmProjectsDir(npmDir); const activeInstallPaths = Array.from(params.activeInstallPaths ?? [], (installPath) => path.resolve(installPath)); let removed = 0; for (const projectRoot of listManagedPluginNpmRootsSync(npmDir)) { if (path.resolve(projectRoot) === path.resolve(npmDir)) { removed += await cleanupRetainedLegacyNpmPackages({ npmRoot: projectRoot, activeInstallPaths, onError: params.onError }); continue; } const markerDir = path.join(projectRoot, RETAINED_MANAGED_NPM_INSTALL_MARKER_DIR); let markerEntries; try { markerEntries = fs.readdirSync(markerDir, { withFileTypes: true }).filter((entry) => entry.isFile()); } catch (error) { if (error.code === "ENOENT") continue; params.onError?.(error, projectRoot); continue; } if (markerEntries.length === 0 || markerEntries.some((entry) => markerPreservesPackageFiles(path.join(markerDir, entry.name))) || !isPathInside(projectsDir, projectRoot) || !isOwnedManagedNpmProject({ markerNames: new Set(markerEntries.map((entry) => entry.name)), npmDir, projectRoot }) || activeInstallPaths.some((installPath) => isPathInside(projectRoot, installPath))) continue; try { await fs.promises.rm(projectRoot, { recursive: true, force: true }); removed += 1; } catch (error) { params.onError?.(error, projectRoot); } } return removed; } //#endregion export { resolveRetainedManagedNpmInstallMarkerPath as a, listManagedPluginNpmRoots as c, markRetainedManagedNpmInstall as i, listManagedPluginNpmRootsSync as l, clearRetainedManagedNpmInstallMarker as n, resolveRetainedManagedNpmInstallPackageInfo as o, hasRetainedManagedNpmInstallMarker as r, listManagedPluginNpmProjectRootsSync as s, cleanupRetainedManagedNpmInstallGenerations as t, RETAINED_MANAGED_NPM_KEEP_FILES_REASON as u };