mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-31 20:21:45 +00:00
* fix: isolate npm plugin installs per package * test: assert isolated npm plugin projects in upgrade survivor * test: assert plugin lifecycle npm project roots * test: resolve npm project deps in live assertions * fix: resolve codex bins from isolated npm projects * docs: document isolated npm plugin projects * ci: configure testbox workflow for crabbox * fix: stabilize npm project fingerprint * fix: keep fetch runtime import side-effect free * test: keep dynamic live model unit hermetic * ci: handle empty node toolcache roots * test: make nounset toolcache probe deterministic
131 lines
4.1 KiB
JavaScript
131 lines
4.1 KiB
JavaScript
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
const home = os.homedir();
|
|
|
|
function openclawPath(...parts) {
|
|
return path.join(home, ".openclaw", ...parts);
|
|
}
|
|
|
|
function readJson(file) {
|
|
try {
|
|
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function records() {
|
|
const index = readJson(openclawPath("plugins", "installs.json"));
|
|
return index.installRecords ?? index.records ?? {};
|
|
}
|
|
|
|
function recordFor(pluginId) {
|
|
return records()[pluginId];
|
|
}
|
|
|
|
function config() {
|
|
return readJson(process.env.OPENCLAW_CONFIG_PATH ?? openclawPath("openclaw.json"));
|
|
}
|
|
|
|
function assert(condition, message) {
|
|
if (!condition) {
|
|
throw new Error(message);
|
|
}
|
|
}
|
|
|
|
function assertVersion(pluginId, version) {
|
|
const record = recordFor(pluginId);
|
|
assert(record, `install record missing for ${pluginId}`);
|
|
assert(record.source === "npm", `expected npm source for ${pluginId}, got ${record.source}`);
|
|
assert(
|
|
record.resolvedVersion === version || record.version === version,
|
|
`expected ${pluginId} record version ${version}, got ${JSON.stringify(record)}`,
|
|
);
|
|
assert(record.installPath, `install path missing for ${pluginId}`);
|
|
const packageJson = readJson(path.join(record.installPath, "package.json"));
|
|
assert(
|
|
packageJson.version === version,
|
|
`expected installed package version ${version}, got ${packageJson.version}`,
|
|
);
|
|
}
|
|
|
|
function assertNpmProjectRoot(pluginId, packageName) {
|
|
const record = recordFor(pluginId);
|
|
assert(record?.installPath, `install path missing for ${pluginId}`);
|
|
const relative = path.relative(openclawPath("npm", "projects"), record.installPath);
|
|
assert(
|
|
!relative.startsWith("..") && !path.isAbsolute(relative),
|
|
`install path outside npm projects: ${record.installPath}`,
|
|
);
|
|
const segments = relative.split(path.sep);
|
|
const packageSegments = packageName.split("/");
|
|
assert(
|
|
segments.length === 2 + packageSegments.length,
|
|
`unexpected npm project install path: ${record.installPath}`,
|
|
);
|
|
assert(Boolean(segments[0]), `missing npm project directory: ${record.installPath}`);
|
|
assert(
|
|
segments[1] === "node_modules",
|
|
`missing project node_modules segment: ${record.installPath}`,
|
|
);
|
|
for (let index = 0; index < packageSegments.length; index++) {
|
|
assert(
|
|
segments[index + 2] === packageSegments[index],
|
|
`package path mismatch: ${record.installPath}`,
|
|
);
|
|
}
|
|
assert(
|
|
!fs.existsSync(openclawPath("npm", "node_modules", ...packageSegments)),
|
|
`legacy flat npm install path exists for ${packageName}`,
|
|
);
|
|
}
|
|
|
|
function assertEnabled(pluginId, expectedRaw) {
|
|
const expected = expectedRaw === "true";
|
|
const entry = config().plugins?.entries?.[pluginId];
|
|
assert(entry?.enabled === expected, `expected ${pluginId} enabled=${expected}`);
|
|
}
|
|
|
|
function printInstallPath(pluginId) {
|
|
const record = recordFor(pluginId);
|
|
assert(record?.installPath, `install path missing for ${pluginId}`);
|
|
process.stdout.write(record.installPath);
|
|
}
|
|
|
|
function assertUninstalled(pluginId) {
|
|
const cfg = config();
|
|
const record = recordFor(pluginId);
|
|
assert(!record, `install record still present for ${pluginId}`);
|
|
assert(!cfg.plugins?.entries?.[pluginId], `plugin config entry still present for ${pluginId}`);
|
|
assert(!(cfg.plugins?.allow ?? []).includes(pluginId), `allowlist still contains ${pluginId}`);
|
|
assert(!(cfg.plugins?.deny ?? []).includes(pluginId), `denylist still contains ${pluginId}`);
|
|
const loadPaths = cfg.plugins?.load?.paths ?? [];
|
|
assert(
|
|
!loadPaths.some((entry) => String(entry).includes(pluginId)),
|
|
`load path still references ${pluginId}: ${loadPaths.join(", ")}`,
|
|
);
|
|
}
|
|
|
|
const [command, pluginId, arg] = process.argv.slice(2);
|
|
switch (command) {
|
|
case "assert-version":
|
|
assertVersion(pluginId, arg);
|
|
break;
|
|
case "assert-npm-project-root":
|
|
assertNpmProjectRoot(pluginId, arg);
|
|
break;
|
|
case "assert-enabled":
|
|
assertEnabled(pluginId, arg);
|
|
break;
|
|
case "install-path":
|
|
printInstallPath(pluginId);
|
|
break;
|
|
case "assert-uninstalled":
|
|
assertUninstalled(pluginId);
|
|
break;
|
|
default:
|
|
throw new Error(`unknown plugin lifecycle matrix probe command: ${command ?? "<missing>"}`);
|
|
}
|