CLI: preserve argv for lazy subclis

This commit is contained in:
He-Xun 2026-01-30 15:33:48 +08:00
parent 8d4270bcd6
commit 05fb8fa1dd
2 changed files with 34 additions and 4 deletions

View File

@ -18,7 +18,17 @@ const { nodesAction, registerNodesCli } = vi.hoisted(() => {
return { nodesAction: action, registerNodesCli: register };
});
const { gatewayRunAction, registerGatewayCli } = vi.hoisted(() => {
const action = vi.fn();
const register = vi.fn((program: Command) => {
const gateway = program.command("gateway");
gateway.command("run").option("--port <port>").action(action);
});
return { gatewayRunAction: action, registerGatewayCli: register };
});
vi.mock("../acp-cli.js", () => ({ registerAcpCli }));
vi.mock("../gateway-cli.js", () => ({ registerGatewayCli }));
vi.mock("../nodes-cli.js", () => ({ registerNodesCli }));
const { registerSubCliByName, registerSubCliCommands } = await import("./register.subclis.js");
@ -32,6 +42,8 @@ describe("registerSubCliCommands", () => {
delete process.env.OPENCLAW_DISABLE_LAZY_SUBCOMMANDS;
registerAcpCli.mockClear();
acpAction.mockClear();
registerGatewayCli.mockClear();
gatewayRunAction.mockClear();
registerNodesCli.mockClear();
nodesAction.mockClear();
});
@ -79,6 +91,24 @@ describe("registerSubCliCommands", () => {
expect(nodesAction).toHaveBeenCalledTimes(1);
});
it("preserves options for lazy subcommands", async () => {
process.argv = ["node", "openclaw", "gateway", "run", "--port", "18889"];
const program = new Command();
program.name("openclaw");
registerSubCliCommands(program, process.argv);
expect(program.commands.map((cmd) => cmd.name())).toEqual(["gateway"]);
await program.parseAsync(process.argv);
expect(registerGatewayCli).toHaveBeenCalledTimes(1);
expect(gatewayRunAction).toHaveBeenCalledTimes(1);
expect(gatewayRunAction).toHaveBeenCalledWith(
expect.objectContaining({ port: "18889" }),
expect.anything(),
);
});
it("replaces placeholder when registering a subcommand by name", async () => {
process.argv = ["node", "openclaw", "acp", "--help"];
const program = new Command();

View File

@ -246,7 +246,7 @@ export async function registerSubCliByName(program: Command, name: string): Prom
return true;
}
function registerLazyCommand(program: Command, entry: SubCliEntry) {
function registerLazyCommand(program: Command, entry: SubCliEntry, argvOverride?: string[]) {
const placeholder = program.command(entry.name).description(entry.description);
placeholder.allowUnknownOption(true);
placeholder.allowExcessArguments(true);
@ -255,7 +255,7 @@ function registerLazyCommand(program: Command, entry: SubCliEntry) {
await entry.register(program);
const actionCommand = actionArgs.at(-1) as Command | undefined;
const root = actionCommand?.parent ?? program;
const rawArgs = (root as Command & { rawArgs?: string[] }).rawArgs;
const rawArgs = (root as Command & { rawArgs?: string[] }).rawArgs ?? argvOverride;
const actionArgsList = resolveActionArgs(actionCommand);
const fallbackArgv = actionCommand?.name()
? [actionCommand.name(), ...actionArgsList]
@ -280,11 +280,11 @@ export function registerSubCliCommands(program: Command, argv: string[] = proces
if (primary && shouldRegisterPrimaryOnly(argv)) {
const entry = entries.find((candidate) => candidate.name === primary);
if (entry) {
registerLazyCommand(program, entry);
registerLazyCommand(program, entry, argv);
return;
}
}
for (const candidate of entries) {
registerLazyCommand(program, candidate);
registerLazyCommand(program, candidate, argv);
}
}