mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(testing): probe plugin CLI help while installed
This commit is contained in:
@@ -595,6 +595,24 @@ export function hasGauntletWorkRows(rows) {
|
||||
return rows.some((row) => row.phase !== "prebuild");
|
||||
}
|
||||
|
||||
function isPluginOwnedCliAlias(alias) {
|
||||
return alias.kind === "runtime-slash" && alias.cliCommand === alias.name;
|
||||
}
|
||||
|
||||
function buildSlashHelpProbe(params) {
|
||||
const command = params.alias.cliCommand ?? params.alias.name;
|
||||
return {
|
||||
cwd: params.repoRoot,
|
||||
env: params.env,
|
||||
logDir: path.join(params.outputDir, "logs", "slash-help"),
|
||||
...openclawCommand(params.repoRoot, [command, "--help"]),
|
||||
label: `${params.plugin.id}-slash-${params.alias.name}`,
|
||||
phase: "slash:help",
|
||||
pluginId: params.plugin.id,
|
||||
timeoutMs: params.commandTimeoutMs,
|
||||
};
|
||||
}
|
||||
|
||||
async function runPluginLifecycle(params) {
|
||||
for (const plugin of params.plugins) {
|
||||
const commands = [
|
||||
@@ -603,13 +621,34 @@ async function runPluginLifecycle(params) {
|
||||
args: ["install", plugin.id],
|
||||
},
|
||||
{ phase: "inspect", args: ["inspect", plugin.id, "--json"] },
|
||||
...(params.skipSlashHelp
|
||||
? []
|
||||
: plugin.cliCommandAliases
|
||||
.filter(isPluginOwnedCliAlias)
|
||||
.map((alias) => ({ phase: `slash-help:${alias.name}`, alias }))),
|
||||
{ phase: "disable", args: ["disable", plugin.id] },
|
||||
...(plugin.hasRequiredConfigFields ? [] : [{ phase: "enable", args: ["enable", plugin.id] }]),
|
||||
{ phase: "doctor", args: ["doctor"] },
|
||||
{ phase: "uninstall", args: ["uninstall", plugin.id, "--force"] },
|
||||
];
|
||||
for (const { phase, args } of commands) {
|
||||
for (const { phase, args, alias } of commands) {
|
||||
process.stderr.write(`[plugin-gauntlet] ${plugin.id} ${phase}\n`);
|
||||
if (alias) {
|
||||
params.rows.push(
|
||||
await runMeasuredCommand({
|
||||
...buildSlashHelpProbe({
|
||||
repoRoot: params.repoRoot,
|
||||
outputDir: params.outputDir,
|
||||
env: params.env,
|
||||
plugin,
|
||||
alias,
|
||||
commandTimeoutMs: params.commandTimeoutMs,
|
||||
}),
|
||||
label: `${plugin.id}-${phase}`,
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
params.rows.push(
|
||||
await runMeasuredCommand({
|
||||
cwd: params.repoRoot,
|
||||
@@ -628,19 +667,21 @@ async function runPluginLifecycle(params) {
|
||||
|
||||
async function runSlashHelpProbes(params) {
|
||||
for (const plugin of params.plugins) {
|
||||
for (const alias of plugin.cliCommandAliases) {
|
||||
const command = alias.cliCommand ?? alias.name;
|
||||
const aliases = params.includePluginOwnedCliAliases
|
||||
? plugin.cliCommandAliases
|
||||
: plugin.cliCommandAliases.filter((entry) => !isPluginOwnedCliAlias(entry));
|
||||
for (const alias of aliases) {
|
||||
process.stderr.write(`[plugin-gauntlet] ${plugin.id} slash-help /${alias.name}\n`);
|
||||
params.rows.push(
|
||||
await runMeasuredCommand({
|
||||
cwd: params.repoRoot,
|
||||
env: params.env,
|
||||
logDir: path.join(params.outputDir, "logs", "slash-help"),
|
||||
...openclawCommand(params.repoRoot, [command, "--help"]),
|
||||
label: `${plugin.id}-slash-${alias.name}`,
|
||||
phase: "slash:help",
|
||||
pluginId: plugin.id,
|
||||
timeoutMs: params.commandTimeoutMs,
|
||||
...buildSlashHelpProbe({
|
||||
repoRoot: params.repoRoot,
|
||||
outputDir: params.outputDir,
|
||||
env: params.env,
|
||||
plugin,
|
||||
alias,
|
||||
commandTimeoutMs: params.commandTimeoutMs,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -816,6 +857,7 @@ async function main() {
|
||||
plugins: selectedPlugins,
|
||||
rows,
|
||||
commandTimeoutMs: options.commandTimeoutMs,
|
||||
skipSlashHelp: options.skipSlashHelp,
|
||||
});
|
||||
}
|
||||
if (!prebuildFailed && !options.skipSlashHelp) {
|
||||
@@ -826,6 +868,7 @@ async function main() {
|
||||
plugins: selectedPlugins,
|
||||
rows,
|
||||
commandTimeoutMs: options.commandTimeoutMs,
|
||||
includePluginOwnedCliAliases: options.skipLifecycle,
|
||||
});
|
||||
}
|
||||
const qaSummaries =
|
||||
|
||||
@@ -745,6 +745,169 @@ setInterval(() => {}, 1000);
|
||||
await expect(fs.stat(summary.isolatedRunRoot)).rejects.toHaveProperty("code", "ENOENT");
|
||||
});
|
||||
|
||||
it("probes plugin-owned slash help while the plugin is installed", async () => {
|
||||
const outputDir = path.join(repoRoot, "artifacts");
|
||||
await writeManifest(
|
||||
"workboard",
|
||||
"openclaw.plugin.json",
|
||||
JSON.stringify({
|
||||
id: "workboard",
|
||||
commandAliases: [
|
||||
{
|
||||
name: "workboard",
|
||||
kind: "runtime-slash",
|
||||
cliCommand: "workboard",
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
await fs.writeFile(path.join(repoRoot, "extensions", "workboard", "index.ts"), "export {};\n");
|
||||
await fs.mkdir(path.join(repoRoot, "dist"), { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(repoRoot, "dist", "entry.js"),
|
||||
[
|
||||
'const fs = require("node:fs");',
|
||||
'const path = require("node:path");',
|
||||
'const stateDir = process.env.OPENCLAW_STATE_DIR ?? process.cwd();',
|
||||
'const marker = path.join(stateDir, "workboard-enabled");',
|
||||
"const args = process.argv.slice(2);",
|
||||
'if (args[0] === "plugins") {',
|
||||
' if (args[1] === "install" || args[1] === "enable") fs.writeFileSync(marker, "1");',
|
||||
' if (args[1] === "disable" || args[1] === "uninstall") fs.rmSync(marker, { force: true });',
|
||||
' if (args[1] === "inspect") console.log("{}");',
|
||||
" process.exit(0);",
|
||||
"}",
|
||||
'if (args[0] === "workboard" && args[1] === "--help") {',
|
||||
" if (fs.existsSync(marker)) {",
|
||||
' console.log("Usage: openclaw workboard");',
|
||||
" process.exit(0);",
|
||||
" }",
|
||||
' console.error("workboard help was probed after uninstall");',
|
||||
" process.exit(1);",
|
||||
"}",
|
||||
"process.exit(0);",
|
||||
].join("\n"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
path.resolve("scripts/check-plugin-gateway-gauntlet.mjs"),
|
||||
"--repo-root",
|
||||
repoRoot,
|
||||
"--output-dir",
|
||||
outputDir,
|
||||
"--skip-prebuild",
|
||||
"--skip-qa",
|
||||
"--plugin",
|
||||
"workboard",
|
||||
],
|
||||
{
|
||||
cwd: path.resolve("."),
|
||||
encoding: "utf8",
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.status, result.stderr).toBe(0);
|
||||
const summary = JSON.parse(
|
||||
await fs.readFile(path.join(outputDir, "plugin-gateway-gauntlet-summary.json"), "utf8"),
|
||||
);
|
||||
expect(summary.failures).toEqual([]);
|
||||
const slashHelpRow = summary.rows.find(
|
||||
(row: { label?: string; logPath?: string }) =>
|
||||
row.label === "workboard-slash-help:workboard",
|
||||
);
|
||||
expect(summary.rows).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
label: "workboard-slash-help:workboard",
|
||||
phase: "slash:help",
|
||||
pluginId: "workboard",
|
||||
status: 0,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
const slashHelpLogPath = slashHelpRow?.logPath;
|
||||
expect(slashHelpLogPath).toEqual(expect.any(String));
|
||||
await expect(fs.readFile(slashHelpLogPath as string, "utf8")).resolves.toContain(
|
||||
"Usage: openclaw workboard",
|
||||
);
|
||||
|
||||
const skipOutputDir = path.join(repoRoot, "artifacts-skip");
|
||||
const skipResult = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
path.resolve("scripts/check-plugin-gateway-gauntlet.mjs"),
|
||||
"--repo-root",
|
||||
repoRoot,
|
||||
"--output-dir",
|
||||
skipOutputDir,
|
||||
"--skip-prebuild",
|
||||
"--skip-qa",
|
||||
"--skip-slash-help",
|
||||
"--plugin",
|
||||
"workboard",
|
||||
],
|
||||
{
|
||||
cwd: path.resolve("."),
|
||||
encoding: "utf8",
|
||||
},
|
||||
);
|
||||
|
||||
expect(skipResult.status, skipResult.stderr).toBe(0);
|
||||
const skipSummary = JSON.parse(
|
||||
await fs.readFile(path.join(skipOutputDir, "plugin-gateway-gauntlet-summary.json"), "utf8"),
|
||||
);
|
||||
expect(skipSummary.failures).toEqual([]);
|
||||
expect(skipSummary.rows).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
phase: "slash:help",
|
||||
pluginId: "workboard",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const slashOnlyOutputDir = path.join(repoRoot, "artifacts-slash-only");
|
||||
const slashOnlyResult = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
path.resolve("scripts/check-plugin-gateway-gauntlet.mjs"),
|
||||
"--repo-root",
|
||||
repoRoot,
|
||||
"--output-dir",
|
||||
slashOnlyOutputDir,
|
||||
"--skip-prebuild",
|
||||
"--skip-lifecycle",
|
||||
"--skip-qa",
|
||||
"--plugin",
|
||||
"workboard",
|
||||
],
|
||||
{
|
||||
cwd: path.resolve("."),
|
||||
encoding: "utf8",
|
||||
},
|
||||
);
|
||||
|
||||
expect(slashOnlyResult.status, slashOnlyResult.stderr).toBe(1);
|
||||
const slashOnlySummary = JSON.parse(
|
||||
await fs.readFile(
|
||||
path.join(slashOnlyOutputDir, "plugin-gateway-gauntlet-summary.json"),
|
||||
"utf8",
|
||||
),
|
||||
);
|
||||
expect(slashOnlySummary.guardFailures).toEqual([]);
|
||||
expect(slashOnlySummary.failures).toEqual([
|
||||
expect.objectContaining({
|
||||
label: "workboard-slash-workboard",
|
||||
phase: "slash:help",
|
||||
pluginId: "workboard",
|
||||
status: 1,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("carries bounded build ids into QA run-node chunks", async () => {
|
||||
const outputDir = path.join(repoRoot, "artifacts");
|
||||
const qaSummaryJson = JSON.stringify(
|
||||
|
||||
Reference in New Issue
Block a user