mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-14 06:33:09 -06:00
5a643e3543
* fix(plugins): own packed entry lifecycles by package Persist package ownership per runtime child, route lifecycle actions through one closed resolver, reconcile removed child policy during updates, and retain rollback generations until durable config/index commit. * fix(plugins): break uninstall policy import cycle * test(plugins): model package ownership in lifecycle fixtures
207 lines
7.8 KiB
JavaScript
207 lines
7.8 KiB
JavaScript
// Plugin fixture writer commands for E2E scenarios.
|
|
import path from "node:path";
|
|
import { requireArg, write, writeJson } from "./common.mjs";
|
|
|
|
function writePluginManifest(file, id, extra = {}) {
|
|
writeJson(file, { id, ...extra, configSchema: { type: "object", properties: {} } });
|
|
}
|
|
|
|
function writeFakeIsNumberPackage(dir) {
|
|
writeJson(path.join(dir, "package.json"), {
|
|
name: "is-number",
|
|
version: "7.0.0",
|
|
main: "index.js",
|
|
});
|
|
write(path.join(dir, "index.js"), "module.exports = (value) => typeof value === 'number';\n");
|
|
}
|
|
|
|
function writePluginDemo([dir]) {
|
|
write(
|
|
path.join(requireArg(dir, "dir"), "index.js"),
|
|
'module.exports = { id: "demo-plugin", name: "Demo Plugin", description: "Docker E2E demo plugin", register(api) { api.registerTool(() => null, { name: "demo_tool" }); api.registerGatewayMethod("demo.ping", async () => ({ ok: true })); api.registerCli(() => {}, { commands: ["demo"] }); api.registerService({ id: "demo-service", start: () => {} }); }, };\n',
|
|
);
|
|
writePluginManifest(path.join(dir, "openclaw.plugin.json"), "demo-plugin", {
|
|
contracts: { tools: ["demo_tool"] },
|
|
});
|
|
}
|
|
|
|
function writePlugin([dir, id, version, method, name]) {
|
|
for (const [value, label] of [
|
|
[dir, "dir"],
|
|
[id, "id"],
|
|
[version, "version"],
|
|
[method, "method"],
|
|
[name, "name"],
|
|
]) {
|
|
requireArg(value, label);
|
|
}
|
|
writeJson(path.join(dir, "package.json"), {
|
|
name: `@openclaw/${id}`,
|
|
version,
|
|
openclaw: { extensions: ["./index.js"] },
|
|
});
|
|
write(
|
|
path.join(dir, "index.js"),
|
|
`module.exports = { id: ${JSON.stringify(id)}, name: ${JSON.stringify(name)}, register(api) { api.registerGatewayMethod(${JSON.stringify(method)}, async () => ({ ok: true })); }, };\n`,
|
|
);
|
|
writePluginManifest(path.join(dir, "openclaw.plugin.json"), id);
|
|
}
|
|
|
|
function writePluginPack([dir, id, version, entryList]) {
|
|
for (const [value, label] of [
|
|
[dir, "dir"],
|
|
[id, "id"],
|
|
[version, "version"],
|
|
]) {
|
|
requireArg(value, label);
|
|
}
|
|
const entries = entryList
|
|
? entryList
|
|
.split(",")
|
|
.map((entry) => entry.trim())
|
|
.filter(Boolean)
|
|
: ["one", "two"];
|
|
if (entries.length === 0) {
|
|
throw new Error("plugin-pack entries must not be empty");
|
|
}
|
|
writeJson(path.join(dir, "package.json"), {
|
|
name: `@openclaw/${id}`,
|
|
version,
|
|
openclaw: { extensions: entries.map((entry) => `./${entry}.js`) },
|
|
});
|
|
for (const entry of entries) {
|
|
const childId = `${id}/${entry}`;
|
|
write(
|
|
path.join(dir, `${entry}.js`),
|
|
`module.exports = { id: ${JSON.stringify(childId)}, name: ${JSON.stringify(childId)}, register(api) { api.registerGatewayMethod(${JSON.stringify(`${id}.${entry}`)}, async () => ({ version: ${JSON.stringify(version)} })); }, };\n`,
|
|
);
|
|
}
|
|
writePluginManifest(path.join(dir, "openclaw.plugin.json"), id);
|
|
}
|
|
|
|
function writePluginWithVendoredDependency([dir, id, version, method, name]) {
|
|
writePlugin([dir, id, version, method, name]);
|
|
const packageJsonPath = path.join(dir, "package.json");
|
|
writeJson(packageJsonPath, {
|
|
name: `@openclaw/${id}`,
|
|
version,
|
|
dependencies: { "is-number": "7.0.0" },
|
|
openclaw: { extensions: ["./index.js"] },
|
|
});
|
|
write(
|
|
path.join(dir, "index.js"),
|
|
`const isNumber = require("is-number");\nmodule.exports = { id: ${JSON.stringify(id)}, name: ${JSON.stringify(name)}, register(api) { api.registerGatewayMethod(${JSON.stringify(method)}, async () => ({ ok: isNumber(42) })); }, };\n`,
|
|
);
|
|
writeFakeIsNumberPackage(path.join(dir, "node_modules", "is-number"));
|
|
}
|
|
|
|
function writePluginWithCli([dir, id, version, method, name, cliRoot, cliOutput]) {
|
|
for (const [value, label] of [
|
|
[dir, "dir"],
|
|
[id, "id"],
|
|
[version, "version"],
|
|
[method, "method"],
|
|
[name, "name"],
|
|
[cliRoot, "cliRoot"],
|
|
[cliOutput, "cliOutput"],
|
|
]) {
|
|
requireArg(value, label);
|
|
}
|
|
writeJson(path.join(dir, "package.json"), {
|
|
name: `@openclaw/${id}`,
|
|
version,
|
|
dependencies: { "is-number": "file:./deps/is-number" },
|
|
openclaw: { extensions: ["./index.js"] },
|
|
});
|
|
writeFakeIsNumberPackage(path.join(dir, "deps", "is-number"));
|
|
write(
|
|
path.join(dir, "index.js"),
|
|
`const isNumber = require("is-number");\nmodule.exports = { id: ${JSON.stringify(id)}, name: ${JSON.stringify(name)}, register(api) { api.registerGatewayMethod(${JSON.stringify(method)}, async () => ({ ok: isNumber(42) })); api.registerCli(({ program }) => { const root = program.command(${JSON.stringify(cliRoot)}).description(${JSON.stringify(`${name} fixture command`)}); root.command("ping").description("Print fixture ping output").action(() => { console.log(${JSON.stringify(cliOutput)}); }); }, { descriptors: [{ name: ${JSON.stringify(cliRoot)}, description: ${JSON.stringify(`${name} fixture command`)}, hasSubcommands: true }] }); }, };\n`,
|
|
);
|
|
writePluginManifest(path.join(dir, "openclaw.plugin.json"), id);
|
|
}
|
|
|
|
function writePluginWithCliRegistryDependency([
|
|
dir,
|
|
id,
|
|
version,
|
|
method,
|
|
name,
|
|
cliRoot,
|
|
cliOutput,
|
|
]) {
|
|
for (const [value, label] of [
|
|
[dir, "dir"],
|
|
[id, "id"],
|
|
[version, "version"],
|
|
[method, "method"],
|
|
[name, "name"],
|
|
[cliRoot, "cliRoot"],
|
|
[cliOutput, "cliOutput"],
|
|
]) {
|
|
requireArg(value, label);
|
|
}
|
|
writeJson(path.join(dir, "package.json"), {
|
|
name: `@openclaw/${id}`,
|
|
version,
|
|
dependencies: { "is-number": "7.0.0" },
|
|
openclaw: { extensions: ["./index.js"] },
|
|
});
|
|
write(
|
|
path.join(dir, "index.js"),
|
|
`const isNumber = require("is-number");\nmodule.exports = { id: ${JSON.stringify(id)}, name: ${JSON.stringify(name)}, register(api) { api.registerGatewayMethod(${JSON.stringify(method)}, async () => ({ ok: isNumber(42) })); api.registerCli(({ program }) => { const root = program.command(${JSON.stringify(cliRoot)}).description(${JSON.stringify(`${name} fixture command`)}); root.command("ping").description("Print fixture ping output").action(() => { console.log(${JSON.stringify(cliOutput)}); }); }, { descriptors: [{ name: ${JSON.stringify(cliRoot)}, description: ${JSON.stringify(`${name} fixture command`)}, hasSubcommands: true }] }); }, };\n`,
|
|
);
|
|
writePluginManifest(path.join(dir, "openclaw.plugin.json"), id);
|
|
}
|
|
|
|
function writeClaudeBundle(args) {
|
|
const root = requireArg(args[0], "root");
|
|
writeJson(path.join(root, ".claude-plugin", "plugin.json"), { name: "claude-bundle-e2e" });
|
|
write(
|
|
path.join(root, "commands", "office-hours.md"),
|
|
"---\ndescription: Help with architecture and rollout planning\n---\nAct as an engineering advisor.\n\nFocus on:\n$ARGUMENTS\n",
|
|
);
|
|
}
|
|
|
|
function writePluginMarketplace(args) {
|
|
const root = requireArg(args[0], "root");
|
|
writeJson(path.join(root, ".claude-plugin", "marketplace.json"), {
|
|
name: "Fixture Marketplace",
|
|
version: "1.0.0",
|
|
plugins: [
|
|
{
|
|
name: "marketplace-shortcut",
|
|
version: "0.0.1",
|
|
description: "Shortcut install fixture",
|
|
source: "./plugins/marketplace-shortcut",
|
|
},
|
|
{
|
|
name: "marketplace-direct",
|
|
version: "0.0.1",
|
|
description: "Explicit marketplace fixture",
|
|
source: { type: "path", path: "./plugins/marketplace-direct" },
|
|
},
|
|
],
|
|
});
|
|
writeJson(path.join(process.env.HOME, ".claude", "plugins", "known_marketplaces.json"), {
|
|
"claude-fixtures": {
|
|
installLocation: root,
|
|
source: { type: "github", repo: "openclaw/fixture-marketplace" },
|
|
},
|
|
});
|
|
}
|
|
|
|
export const pluginCommands = {
|
|
"plugin-demo": writePluginDemo,
|
|
plugin: writePlugin,
|
|
"plugin-pack": writePluginPack,
|
|
"plugin-vendored-dep": writePluginWithVendoredDependency,
|
|
"plugin-cli": writePluginWithCli,
|
|
"plugin-cli-registry-dep": writePluginWithCliRegistryDependency,
|
|
"fake-is-number-package": ([dir]) => writeFakeIsNumberPackage(requireArg(dir, "dir")),
|
|
"plugin-manifest": ([file, id]) =>
|
|
writePluginManifest(requireArg(file, "file"), requireArg(id, "id")),
|
|
"claude-bundle": writeClaudeBundle,
|
|
marketplace: writePluginMarketplace,
|
|
};
|