mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:40:44 +00:00
fix(cli): preserve lazy placeholder options
This commit is contained in:
@@ -2,11 +2,17 @@ import { Command } from "commander";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const manageMocks = vi.hoisted(() => {
|
||||
const doctorAction = vi.fn();
|
||||
const statusAction = vi.fn();
|
||||
const registerBrowserManageCommands = vi.fn((browser: Command) => {
|
||||
browser.command("status").description("Show browser status").action(statusAction);
|
||||
browser
|
||||
.command("doctor")
|
||||
.description("Check browser plugin readiness")
|
||||
.option("--deep", "Run a live snapshot probe")
|
||||
.action(doctorAction);
|
||||
});
|
||||
return { registerBrowserManageCommands, statusAction };
|
||||
return { doctorAction, registerBrowserManageCommands, statusAction };
|
||||
});
|
||||
const inspectMocks = vi.hoisted(() => ({
|
||||
registerBrowserInspectCommands: vi.fn(),
|
||||
@@ -37,6 +43,7 @@ describe("registerBrowserCli lazy browser subcommands", () => {
|
||||
beforeEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
manageMocks.registerBrowserManageCommands.mockClear();
|
||||
manageMocks.doctorAction.mockClear();
|
||||
manageMocks.statusAction.mockClear();
|
||||
inspectMocks.registerBrowserInspectCommands.mockClear();
|
||||
actionInputMocks.registerBrowserActionInputCommands.mockClear();
|
||||
@@ -58,7 +65,9 @@ describe("registerBrowserCli lazy browser subcommands", () => {
|
||||
const browser = program.commands.find((command) => command.name() === "browser");
|
||||
expect(browser?.commands.map((command) => command.name())).toContain("status");
|
||||
expect(browser?.commands.map((command) => command.name())).toContain("snapshot");
|
||||
expect(browser?.commands.map((command) => command.name())).toContain("doctor");
|
||||
const doctor = browser?.commands.find((command) => command.name() === "doctor");
|
||||
expect(doctor).toBeDefined();
|
||||
expect(doctor?.options.map((option) => option.long)).toContain("--deep");
|
||||
expect(manageMocks.registerBrowserManageCommands).not.toHaveBeenCalled();
|
||||
expect(inspectMocks.registerBrowserInspectCommands).not.toHaveBeenCalled();
|
||||
expect(actionInputMocks.registerBrowserActionInputCommands).not.toHaveBeenCalled();
|
||||
@@ -80,6 +89,20 @@ describe("registerBrowserCli lazy browser subcommands", () => {
|
||||
expect(manageMocks.statusAction).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("loads browser doctor from the manage group so --deep is available", async () => {
|
||||
const program = new Command();
|
||||
program.name("openclaw");
|
||||
|
||||
registerBrowserCli(program, ["node", "openclaw", "browser", "doctor", "--deep"]);
|
||||
|
||||
await program.parseAsync(["browser", "doctor", "--deep"], { from: "user" });
|
||||
|
||||
expect(manageMocks.registerBrowserManageCommands).toHaveBeenCalledTimes(1);
|
||||
expect(debugMocks.registerBrowserDebugCommands).not.toHaveBeenCalled();
|
||||
expect(manageMocks.doctorAction).toHaveBeenCalledTimes(1);
|
||||
expect(manageMocks.doctorAction.mock.calls[0]?.[0]).toMatchObject({ deep: true });
|
||||
});
|
||||
|
||||
it("can eagerly register all browser groups for compatibility", async () => {
|
||||
vi.stubEnv("OPENCLAW_DISABLE_LAZY_SUBCOMMANDS", "1");
|
||||
const program = new Command();
|
||||
|
||||
@@ -28,9 +28,14 @@ type BrowserCommandGroupDefinition = {
|
||||
register: BrowserCommandRegistrar;
|
||||
};
|
||||
|
||||
const command = (name: string, description: string): CommandGroupPlaceholder => ({
|
||||
const command = (
|
||||
name: string,
|
||||
description: string,
|
||||
options?: CommandGroupPlaceholder["options"],
|
||||
): CommandGroupPlaceholder => ({
|
||||
name,
|
||||
description,
|
||||
...(options ? { options } : {}),
|
||||
});
|
||||
|
||||
const browserCommandGroupDefinitions: readonly BrowserCommandGroupDefinition[] = [
|
||||
@@ -48,6 +53,9 @@ const browserCommandGroupDefinitions: readonly BrowserCommandGroupDefinition[] =
|
||||
command("profiles", "List all browser profiles"),
|
||||
command("create-profile", "Create a new browser profile"),
|
||||
command("delete-profile", "Delete a browser profile"),
|
||||
command("doctor", "Check browser plugin readiness", [
|
||||
{ flags: "--deep", description: "Run a live snapshot probe" },
|
||||
]),
|
||||
],
|
||||
register: async (args) => {
|
||||
const module = await import("./browser-cli-manage.js");
|
||||
@@ -105,7 +113,6 @@ const browserCommandGroupDefinitions: readonly BrowserCommandGroupDefinition[] =
|
||||
command("highlight", "Highlight an element by ref"),
|
||||
command("errors", "Get recent page errors"),
|
||||
command("requests", "Get recent network requests (best-effort)"),
|
||||
command("doctor", "Check browser plugin readiness"),
|
||||
command("trace", "Record a Playwright trace"),
|
||||
],
|
||||
register: async (args) => {
|
||||
|
||||
@@ -5,6 +5,12 @@ import { registerLazyCommand } from "./register-lazy-command.js";
|
||||
export type CommandGroupPlaceholder = {
|
||||
name: string;
|
||||
description: string;
|
||||
options?: readonly CommandGroupPlaceholderOption[];
|
||||
};
|
||||
|
||||
export type CommandGroupPlaceholderOption = {
|
||||
flags: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type CommandGroupEntry = {
|
||||
@@ -53,6 +59,7 @@ export function registerLazyCommandGroup(
|
||||
program,
|
||||
name: placeholder.name,
|
||||
description: placeholder.description,
|
||||
options: placeholder.options,
|
||||
removeNames: [...new Set(getCommandGroupNames(entry))],
|
||||
register: async () => {
|
||||
await entry.register(program);
|
||||
|
||||
@@ -6,21 +6,55 @@ type RegisterLazyCommandParams = {
|
||||
program: Command;
|
||||
name: string;
|
||||
description: string;
|
||||
options?: readonly {
|
||||
flags: string;
|
||||
description: string;
|
||||
}[];
|
||||
removeNames?: string[];
|
||||
register: () => Promise<void> | void;
|
||||
};
|
||||
|
||||
function resolvePlaceholderOptionArgs(command: Command): string[] {
|
||||
const out: string[] = [];
|
||||
for (const option of command.options) {
|
||||
const value = command.getOptionValue(option.attributeName());
|
||||
if (value === undefined || value === false) {
|
||||
continue;
|
||||
}
|
||||
const flag = option.long ?? option.short;
|
||||
if (!flag) {
|
||||
continue;
|
||||
}
|
||||
out.push(flag);
|
||||
if (value !== true) {
|
||||
out.push(String(value));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function registerLazyCommand({
|
||||
program,
|
||||
name,
|
||||
description,
|
||||
options,
|
||||
removeNames,
|
||||
register,
|
||||
}: RegisterLazyCommandParams): void {
|
||||
const placeholder = program.command(name).description(description);
|
||||
for (const option of options ?? []) {
|
||||
placeholder.option(option.flags, option.description);
|
||||
}
|
||||
placeholder.allowUnknownOption(true);
|
||||
placeholder.allowExcessArguments(true);
|
||||
placeholder.action(async (...actionArgs) => {
|
||||
const actionCommand = actionArgs.at(-1) as (Command & { args?: string[] }) | undefined;
|
||||
if (actionCommand) {
|
||||
actionCommand.args = [
|
||||
...resolvePlaceholderOptionArgs(actionCommand),
|
||||
...(actionCommand.args ?? []),
|
||||
];
|
||||
}
|
||||
for (const commandName of new Set(removeNames ?? [name])) {
|
||||
removeCommandByName(program, commandName);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user