fix(cli): remove grouped placeholders before register

This commit is contained in:
Peter Steinberger
2026-02-14 20:09:14 +00:00
parent 519ffd59d4
commit ee29703368
2 changed files with 36 additions and 2 deletions

View File

@@ -1,5 +1,5 @@
import { Command } from "commander";
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import type { ProgramContext } from "./context.js";
import {
getCoreCliCommandNames,
@@ -7,6 +7,14 @@ import {
registerCoreCliCommands,
} from "./command-registry.js";
vi.mock("./register.status-health-sessions.js", () => ({
registerStatusHealthSessionsCommands: (program: Command) => {
program.command("status");
program.command("health");
program.command("sessions");
},
}));
const testProgramContext: ProgramContext = {
programVersion: "0.0.0-test",
channelOptions: [],
@@ -57,4 +65,23 @@ describe("command-registry", () => {
expect(names).toContain("uninstall");
expect(names).not.toContain("maintenance");
});
it("registers grouped core entry placeholders without duplicate command errors", async () => {
const program = new Command();
registerCoreCliCommands(program, testProgramContext, ["node", "openclaw", "vitest"]);
const prevArgv = process.argv;
process.argv = ["node", "openclaw", "status"];
try {
program.exitOverride();
await program.parseAsync(["node", "openclaw", "status"]);
} finally {
process.argv = prevArgv;
}
const names = program.commands.map((command) => command.name());
expect(names).toContain("status");
expect(names).toContain("health");
expect(names).toContain("sessions");
});
});

View File

@@ -148,7 +148,14 @@ function registerLazyCoreCommand(
placeholder.allowUnknownOption(true);
placeholder.allowExcessArguments(true);
placeholder.action(async (...actionArgs) => {
removeCommand(program, placeholder);
// Some registrars install multiple top-level commands (e.g. status/health/sessions).
// Remove placeholders/old registrations for all names in the entry before re-registering.
for (const cmd of entry.commands) {
const existing = program.commands.find((c) => c.name() === cmd.name);
if (existing) {
removeCommand(program, existing);
}
}
await entry.register({ program, ctx, argv: process.argv });
const actionCommand = actionArgs.at(-1) as Command | undefined;
const root = actionCommand?.parent ?? program;