mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 17:31:06 +00:00
158 lines
6.2 KiB
JavaScript
158 lines
6.2 KiB
JavaScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
|
|
const readJson = (file) => JSON.parse(fs.readFileSync(file, "utf8"));
|
|
|
|
function loadManifestEntries() {
|
|
const explicit = (process.env.OPENCLAW_BUNDLED_PLUGIN_SWEEP_IDS || "")
|
|
.split(/[,\s]+/u)
|
|
.map((entry) => entry.trim())
|
|
.filter(Boolean);
|
|
const extensionRoot = path.join(process.cwd(), "dist", "extensions");
|
|
const manifestEntries = fs
|
|
.readdirSync(extensionRoot, { withFileTypes: true })
|
|
.filter((entry) => entry.isDirectory())
|
|
.map((entry) => {
|
|
const manifestPath = path.join(extensionRoot, entry.name, "openclaw.plugin.json");
|
|
if (!fs.existsSync(manifestPath)) {
|
|
return null;
|
|
}
|
|
const manifest = readJson(manifestPath);
|
|
const id = typeof manifest.id === "string" ? manifest.id.trim() : "";
|
|
if (!id) {
|
|
throw new Error(`Bundled plugin manifest is missing id: ${manifestPath}`);
|
|
}
|
|
const required = manifest.configSchema?.required;
|
|
return {
|
|
id,
|
|
dir: entry.name,
|
|
requiresConfig:
|
|
Array.isArray(required) && required.some((value) => typeof value === "string"),
|
|
};
|
|
})
|
|
.filter(Boolean)
|
|
.toSorted((a, b) => a.id.localeCompare(b.id));
|
|
|
|
if (explicit.length === 0) {
|
|
return manifestEntries;
|
|
}
|
|
return explicit.map(
|
|
(lookup) =>
|
|
manifestEntries.find((entry) => entry.id === lookup || entry.dir === lookup) || {
|
|
id: lookup,
|
|
dir: lookup,
|
|
requiresConfig: false,
|
|
},
|
|
);
|
|
}
|
|
|
|
function selectedManifestEntries() {
|
|
const allEntries = loadManifestEntries();
|
|
const total = Number.parseInt(process.env.OPENCLAW_BUNDLED_PLUGIN_SWEEP_TOTAL || "1", 10);
|
|
const index = Number.parseInt(process.env.OPENCLAW_BUNDLED_PLUGIN_SWEEP_INDEX || "0", 10);
|
|
if (!Number.isInteger(total) || total < 1) {
|
|
throw new Error(
|
|
`OPENCLAW_BUNDLED_PLUGIN_SWEEP_TOTAL must be >= 1, got ${process.env.OPENCLAW_BUNDLED_PLUGIN_SWEEP_TOTAL}`,
|
|
);
|
|
}
|
|
if (!Number.isInteger(index) || index < 0 || index >= total) {
|
|
throw new Error(
|
|
`OPENCLAW_BUNDLED_PLUGIN_SWEEP_INDEX must be in [0, ${total - 1}], got ${process.env.OPENCLAW_BUNDLED_PLUGIN_SWEEP_INDEX}`,
|
|
);
|
|
}
|
|
|
|
const selected = allEntries.filter((_, candidateIndex) => candidateIndex % total === index);
|
|
if (selected.length === 0) {
|
|
throw new Error(`No bundled plugin ids selected for shard ${index}/${total}`);
|
|
}
|
|
return selected;
|
|
}
|
|
|
|
function assertInstalled(pluginId, pluginDir, requiresConfig) {
|
|
const configPath = path.join(process.env.HOME, ".openclaw", "openclaw.json");
|
|
const indexPath = path.join(process.env.HOME, ".openclaw", "plugins", "installs.json");
|
|
const config = readJson(configPath);
|
|
const index = readJson(indexPath);
|
|
const records = index.installRecords ?? index.records ?? {};
|
|
const record = records[pluginId];
|
|
if (!record) {
|
|
throw new Error(`missing install record for ${pluginId}`);
|
|
}
|
|
if (record.source !== "path") {
|
|
throw new Error(
|
|
`expected bundled install record source=path for ${pluginId}, got ${record.source}`,
|
|
);
|
|
}
|
|
if (
|
|
typeof record.sourcePath !== "string" ||
|
|
!record.sourcePath.includes(`/dist/extensions/${pluginDir}`)
|
|
) {
|
|
throw new Error(`unexpected bundled source path for ${pluginId}: ${record.sourcePath}`);
|
|
}
|
|
if (record.installPath !== record.sourcePath) {
|
|
throw new Error(`bundled install path should equal source path for ${pluginId}`);
|
|
}
|
|
const paths = config.plugins?.load?.paths || [];
|
|
if (paths.some((entry) => String(entry).includes(`/dist/extensions/${pluginDir}`))) {
|
|
throw new Error(`config load paths should not include bundled install path for ${pluginId}`);
|
|
}
|
|
if (requiresConfig && config.plugins?.entries?.[pluginId]?.enabled === true) {
|
|
throw new Error(
|
|
`plugin requiring config should not be enabled immediately after install for ${pluginId}`,
|
|
);
|
|
}
|
|
if (!requiresConfig && config.plugins?.entries?.[pluginId]?.enabled !== true) {
|
|
throw new Error(`config entry is not enabled after install for ${pluginId}`);
|
|
}
|
|
const allow = config.plugins?.allow || [];
|
|
if (Array.isArray(allow) && allow.length > 0 && !allow.includes(pluginId)) {
|
|
throw new Error(`existing allowlist does not include ${pluginId} after install`);
|
|
}
|
|
if ((config.plugins?.deny || []).includes(pluginId)) {
|
|
throw new Error(`denylist contains ${pluginId} after install`);
|
|
}
|
|
}
|
|
|
|
function assertUninstalled(pluginId, pluginDir) {
|
|
const configPath = path.join(process.env.HOME, ".openclaw", "openclaw.json");
|
|
const indexPath = path.join(process.env.HOME, ".openclaw", "plugins", "installs.json");
|
|
const config = fs.existsSync(configPath) ? readJson(configPath) : {};
|
|
const index = fs.existsSync(indexPath) ? readJson(indexPath) : {};
|
|
const records = index.installRecords ?? index.records ?? {};
|
|
if (records[pluginId]) {
|
|
throw new Error(`install record still present after uninstall for ${pluginId}`);
|
|
}
|
|
const paths = config.plugins?.load?.paths || [];
|
|
if (paths.some((entry) => String(entry).includes(`/dist/extensions/${pluginDir}`))) {
|
|
throw new Error(`load path still present after uninstall for ${pluginId}`);
|
|
}
|
|
if (config.plugins?.entries?.[pluginId]) {
|
|
throw new Error(`config entry still present after uninstall for ${pluginId}`);
|
|
}
|
|
if ((config.plugins?.allow || []).includes(pluginId)) {
|
|
throw new Error(`allowlist still contains ${pluginId} after uninstall`);
|
|
}
|
|
if ((config.plugins?.deny || []).includes(pluginId)) {
|
|
throw new Error(`denylist still contains ${pluginId} after uninstall`);
|
|
}
|
|
const managedPath = path.join(process.env.HOME, ".openclaw", "extensions", pluginId);
|
|
if (fs.existsSync(managedPath)) {
|
|
throw new Error(
|
|
`managed install directory unexpectedly exists for bundled plugin ${pluginId}: ${managedPath}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
const [command, pluginId, pluginDir, requiresConfig] = process.argv.slice(2);
|
|
if (command === "select") {
|
|
for (const entry of selectedManifestEntries()) {
|
|
console.log(`${entry.id}\t${entry.dir}\t${entry.requiresConfig ? "1" : "0"}`);
|
|
}
|
|
} else if (command === "assert-installed") {
|
|
assertInstalled(pluginId, pluginDir, requiresConfig === "1");
|
|
} else if (command === "assert-uninstalled") {
|
|
assertUninstalled(pluginId, pluginDir);
|
|
} else {
|
|
throw new Error(`Unknown bundled plugin probe command: ${command || "(missing)"}`);
|
|
}
|