import fs from "node:fs"; import http from "node:http"; import os from "node:os"; import path from "node:path"; import { legacyPackageAcceptanceCompat } from "../package-compat.mjs"; const home = os.homedir(); const readJson = (file) => { try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch { return {}; } }; const pluginRecordSnapshot = () => { const config = readJson(openclawPath("openclaw.json")); const index = readJson(openclawPath("plugins", "installs.json")); const records = index.installRecords ?? index.records ?? config.plugins?.installs ?? {}; const record = records["lossless-claw"] ?? records["@example/lossless-claw"]; if (!record) { throw new Error("missing plugin install record"); } const { source, spec, resolvedName, resolvedVersion, resolvedSpec, integrity, shasum } = record; return { source, spec, resolvedName, resolvedVersion, resolvedSpec, integrity, shasum }; }; function openclawPath(...parts) { return path.join(home, ".openclaw", ...parts); } function writeJson(file, value) { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`); } function seedInstallState() { writeJson(openclawPath("extensions", "lossless-claw", "package.json"), { name: "@example/lossless-claw", version: "0.9.0", }); writeJson(process.env.OPENCLAW_CONFIG_PATH, { plugins: {} }); writeJson(openclawPath("plugins", "installs.json"), { version: 1, warning: "DO NOT EDIT. This file is generated by OpenClaw plugin registry commands.", hostContractVersion: "docker-e2e", compatRegistryVersion: "docker-e2e", migrationVersion: 1, policyHash: "docker-e2e", generatedAtMs: 1777118400000, installRecords: { "lossless-claw": { source: "npm", spec: "@example/lossless-claw@0.9.0", installPath: "~/.openclaw/extensions/lossless-claw", resolvedName: "@example/lossless-claw", resolvedVersion: "0.9.0", resolvedSpec: "@example/lossless-claw@0.9.0", integrity: "sha512-same", shasum: "same", }, }, plugins: [], diagnostics: [], }); } async function waitRegistry() { for (let attempt = 0; attempt < 50; attempt += 1) { if (await registryHealthy()) { return; } await new Promise((resolve) => setTimeout(resolve, 100)); } throw new Error("Local npm metadata registry failed to start"); } function registryHealthy() { return new Promise((resolve) => { const req = http.get("http://127.0.0.1:4873/@example%2flossless-claw", (res) => { resolve(res.statusCode === 200); res.resume(); }); req.on("error", () => resolve(false)); req.setTimeout(200, () => { req.destroy(); resolve(false); }); }); } function assertSnapshot(beforePath) { const before = readJson(beforePath); const after = pluginRecordSnapshot(); if (JSON.stringify(before) !== JSON.stringify(after)) { throw new Error( `plugin install record changed unexpectedly: ${JSON.stringify({ before, after })}`, ); } } function assertOutput(logPath) { const output = fs.readFileSync(logPath, "utf8"); const failure = output.includes("Downloading @example/lossless-claw") ? "Unexpected npm download/reinstall path" : !output.includes("lossless-claw is up to date (0.9.0).") ? "Expected up-to-date output missing" : ""; if (failure) { throw new Error(`${failure}\n${output}`); } } function assertCorruptUpdate(updateJsonPath, pluginId) { const payload = readJson(updateJsonPath); if (payload.status !== "ok") { throw new Error(`expected core update status ok, got ${JSON.stringify(payload.status)}`); } const plugins = payload.postUpdate?.plugins; if (!plugins) { throw new Error(`missing postUpdate.plugins in update output: ${JSON.stringify(payload)}`); } if (plugins.status !== "warning") { throw new Error( `expected post-update plugin status warning, got ${JSON.stringify(plugins.status)}`, ); } assertCorruptPluginDetails(plugins, pluginId); } function assertCorruptPluginResult(pluginJsonPath, pluginId) { const plugins = readJson(pluginJsonPath); if (plugins.status !== "warning") { throw new Error( `expected post-update plugin status warning, got ${JSON.stringify(plugins.status)}`, ); } assertCorruptPluginDetails(plugins, pluginId); } function assertCorruptPluginDetails(plugins, pluginId) { const outcomes = plugins.npm?.outcomes ?? []; const outcome = outcomes.find((entry) => entry?.pluginId === pluginId); if (!outcome || outcome.status !== "error") { throw new Error( `expected error outcome for ${pluginId}, got ${JSON.stringify({ outcomes, warnings: plugins.warnings ?? [], sync: plugins.sync, integrityDrifts: plugins.integrityDrifts ?? [], })}`, ); } const warnings = plugins.warnings ?? []; const warning = warnings.find((entry) => entry?.pluginId === pluginId); if (!warning) { throw new Error(`expected warning for ${pluginId}, got ${JSON.stringify(warnings)}`); } const text = JSON.stringify({ outcome, warning }); for (const expected of [ "package.json is missing", "Run openclaw doctor --fix to attempt automatic repair.", `Run openclaw plugins inspect ${pluginId} --runtime --json for details.`, ]) { if (!text.includes(expected)) { throw new Error(`expected update output to include ${expected}: ${text}`); } } } function assertLegacyPostUpdatePluginFailure(updateJsonPath) { const payload = readJson(updateJsonPath); if (payload.status !== "error" || payload.reason !== "post-update-plugins") { throw new Error( `expected legacy post-update plugin failure, got ${JSON.stringify({ status: payload.status, reason: payload.reason, })}`, ); } if (!payload.after?.version) { throw new Error(`expected core update to install a new version: ${JSON.stringify(payload)}`); } } const [command, arg, arg2] = process.argv.slice(2); const commands = { "legacy-compat": () => console.log(legacyPackageAcceptanceCompat(arg || "") ? "1" : "0"), seed: seedInstallState, "wait-registry": waitRegistry, snapshot: () => process.stdout.write(JSON.stringify(pluginRecordSnapshot(), null, 2)), "assert-snapshot": () => assertSnapshot(arg), "assert-output": () => assertOutput(arg), "assert-corrupt-update": () => assertCorruptUpdate(arg, arg2), "assert-corrupt-plugin-result": () => assertCorruptPluginResult(arg, arg2), "assert-legacy-post-update-plugin-failure": () => assertLegacyPostUpdatePluginFailure(arg), }; const run = commands[command]; await ( run ?? (() => { throw new Error(`Unknown plugin update probe command: ${command || "(missing)"}`); }) )();