mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-11 09:26:04 +00:00
registerNodesCli unconditionally registered plugin CLI commands, so lightweight built-in commands like `nodes status`/`nodes list` paid the full plugin CLI/runtime load cost. Only resolve plugin-provided node subcommands (e.g. `nodes canvas`) when the invoked subcommand is not already a built-in, keeping the built-in nodes path fast. Fixes #96697
95 lines
3.3 KiB
TypeScript
95 lines
3.3 KiB
TypeScript
// Nodes CLI plugin registration tests cover node command plugin registration.
|
|
// Built-in node command registration runs for real so the guard is exercised against the actual
|
|
// registered subcommand names; only the plugin-loader boundary is stubbed.
|
|
import { Command } from "commander";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { loggingState } from "../logging/state.js";
|
|
|
|
const registerPluginCliCommandsFromValidatedConfig = vi.fn(async () => ({}));
|
|
|
|
vi.mock("../plugins/cli.js", () => ({
|
|
registerPluginCliCommandsFromValidatedConfig,
|
|
}));
|
|
|
|
const { registerNodesCli } = await import("./nodes-cli/register.js");
|
|
|
|
describe("registerNodesCli plugin registration", () => {
|
|
const originalArgv = process.argv;
|
|
let originalForceConsoleToStderr = false;
|
|
|
|
beforeEach(() => {
|
|
originalForceConsoleToStderr = loggingState.forceConsoleToStderr;
|
|
loggingState.forceConsoleToStderr = false;
|
|
registerPluginCliCommandsFromValidatedConfig.mockClear();
|
|
});
|
|
|
|
afterEach(() => {
|
|
process.argv = originalArgv;
|
|
loggingState.forceConsoleToStderr = originalForceConsoleToStderr;
|
|
});
|
|
|
|
async function registerWithArgv(argv: string[]) {
|
|
process.argv = argv;
|
|
const program = new Command();
|
|
await registerNodesCli(program);
|
|
return program;
|
|
}
|
|
|
|
it("skips plugin CLI/runtime registration for built-in nodes subcommands", async () => {
|
|
for (const subcommand of ["status", "list", "describe", "invoke", "pending", "camera"]) {
|
|
registerPluginCliCommandsFromValidatedConfig.mockClear();
|
|
await registerWithArgv(["node", "openclaw", "nodes", subcommand, "--json"]);
|
|
expect(registerPluginCliCommandsFromValidatedConfig).not.toHaveBeenCalled();
|
|
}
|
|
});
|
|
|
|
it("registers plugin-provided node subcommands lazily and routes their logs to stderr", async () => {
|
|
let forceStderrDuringRegistration = false;
|
|
registerPluginCliCommandsFromValidatedConfig.mockImplementationOnce(async () => {
|
|
forceStderrDuringRegistration = loggingState.forceConsoleToStderr;
|
|
return {};
|
|
});
|
|
|
|
const program = await registerWithArgv([
|
|
"node",
|
|
"openclaw",
|
|
"nodes",
|
|
"canvas",
|
|
"snapshot",
|
|
"--json",
|
|
]);
|
|
|
|
expect(registerPluginCliCommandsFromValidatedConfig).toHaveBeenCalledWith(
|
|
program,
|
|
undefined,
|
|
undefined,
|
|
{ mode: "lazy", primary: "nodes" },
|
|
);
|
|
expect(forceStderrDuringRegistration).toBe(true);
|
|
expect(loggingState.forceConsoleToStderr).toBe(false);
|
|
});
|
|
|
|
it("surfaces plugin subcommands for bare `nodes` listing", async () => {
|
|
const program = await registerWithArgv(["node", "openclaw", "nodes"]);
|
|
expect(registerPluginCliCommandsFromValidatedConfig).toHaveBeenCalledWith(
|
|
program,
|
|
undefined,
|
|
undefined,
|
|
{ mode: "lazy", primary: "nodes" },
|
|
);
|
|
});
|
|
|
|
it("does not route pass-through --json after the terminator", async () => {
|
|
let forceStderrDuringRegistration = true;
|
|
registerPluginCliCommandsFromValidatedConfig.mockImplementationOnce(async () => {
|
|
forceStderrDuringRegistration = loggingState.forceConsoleToStderr;
|
|
return {};
|
|
});
|
|
|
|
await registerWithArgv(["node", "openclaw", "nodes", "canvas", "--", "--json"]);
|
|
|
|
expect(forceStderrDuringRegistration).toBe(false);
|
|
expect(loggingState.forceConsoleToStderr).toBe(false);
|
|
});
|
|
});
|