refactor(cli): extract shared command-removal and timeout action helpers
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type { Command } from "commander";
|
||||
import { getPrimaryCommand, hasHelpOrVersion } from "../argv.js";
|
||||
import { reparseProgramFromActionArgs } from "./action-reparse.js";
|
||||
import { removeCommandByName } from "./command-tree.js";
|
||||
import type { ProgramContext } from "./context.js";
|
||||
import { registerSubCliCommands } from "./register.subclis.js";
|
||||
|
||||
@@ -229,22 +230,11 @@ export function getCoreCliCommandsWithSubcommands(): string[] {
|
||||
return collectCoreCliCommandNames((command) => command.hasSubcommands);
|
||||
}
|
||||
|
||||
function removeCommand(program: Command, command: Command) {
|
||||
const commands = program.commands as Command[];
|
||||
const index = commands.indexOf(command);
|
||||
if (index >= 0) {
|
||||
commands.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function removeEntryCommands(program: Command, entry: CoreCliEntry) {
|
||||
// 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);
|
||||
}
|
||||
removeCommandByName(program, cmd.name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
39
src/cli/program/command-tree.test.ts
Normal file
39
src/cli/program/command-tree.test.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Command } from "commander";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { removeCommand, removeCommandByName } from "./command-tree.js";
|
||||
|
||||
describe("command-tree", () => {
|
||||
it("removes a command instance when present", () => {
|
||||
const program = new Command();
|
||||
const alpha = program.command("alpha");
|
||||
program.command("beta");
|
||||
|
||||
expect(removeCommand(program, alpha)).toBe(true);
|
||||
expect(program.commands.map((command) => command.name())).toEqual(["beta"]);
|
||||
});
|
||||
|
||||
it("returns false when command instance is already absent", () => {
|
||||
const program = new Command();
|
||||
program.command("alpha");
|
||||
const detached = new Command("beta");
|
||||
|
||||
expect(removeCommand(program, detached)).toBe(false);
|
||||
});
|
||||
|
||||
it("removes by command name", () => {
|
||||
const program = new Command();
|
||||
program.command("alpha");
|
||||
program.command("beta");
|
||||
|
||||
expect(removeCommandByName(program, "alpha")).toBe(true);
|
||||
expect(program.commands.map((command) => command.name())).toEqual(["beta"]);
|
||||
});
|
||||
|
||||
it("returns false when name does not exist", () => {
|
||||
const program = new Command();
|
||||
program.command("alpha");
|
||||
|
||||
expect(removeCommandByName(program, "missing")).toBe(false);
|
||||
expect(program.commands.map((command) => command.name())).toEqual(["alpha"]);
|
||||
});
|
||||
});
|
||||
19
src/cli/program/command-tree.ts
Normal file
19
src/cli/program/command-tree.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { Command } from "commander";
|
||||
|
||||
export function removeCommand(program: Command, command: Command): boolean {
|
||||
const commands = program.commands as Command[];
|
||||
const index = commands.indexOf(command);
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
commands.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function removeCommandByName(program: Command, name: string): boolean {
|
||||
const existing = program.commands.find((command) => command.name() === name);
|
||||
if (!existing) {
|
||||
return false;
|
||||
}
|
||||
return removeCommand(program, existing);
|
||||
}
|
||||
@@ -24,6 +24,21 @@ function parseTimeoutMs(timeout: unknown): number | null | undefined {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
async function runWithVerboseAndTimeout(
|
||||
opts: { verbose?: boolean; debug?: boolean; timeout?: unknown },
|
||||
action: (params: { verbose: boolean; timeoutMs: number | undefined }) => Promise<void>,
|
||||
): Promise<void> {
|
||||
const verbose = resolveVerbose(opts);
|
||||
setVerbose(verbose);
|
||||
const timeoutMs = parseTimeoutMs(opts.timeout);
|
||||
if (timeoutMs === null) {
|
||||
return;
|
||||
}
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
await action({ verbose, timeoutMs });
|
||||
});
|
||||
}
|
||||
|
||||
export function registerStatusHealthSessionsCommands(program: Command) {
|
||||
program
|
||||
.command("status")
|
||||
@@ -56,20 +71,14 @@ export function registerStatusHealthSessionsCommands(program: Command) {
|
||||
`\n${theme.muted("Docs:")} ${formatDocsLink("/cli/status", "docs.openclaw.ai/cli/status")}\n`,
|
||||
)
|
||||
.action(async (opts) => {
|
||||
const verbose = resolveVerbose(opts);
|
||||
setVerbose(verbose);
|
||||
const timeout = parseTimeoutMs(opts.timeout);
|
||||
if (timeout === null) {
|
||||
return;
|
||||
}
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
await runWithVerboseAndTimeout(opts, async ({ verbose, timeoutMs }) => {
|
||||
await statusCommand(
|
||||
{
|
||||
json: Boolean(opts.json),
|
||||
all: Boolean(opts.all),
|
||||
deep: Boolean(opts.deep),
|
||||
usage: Boolean(opts.usage),
|
||||
timeoutMs: timeout,
|
||||
timeoutMs,
|
||||
verbose,
|
||||
},
|
||||
defaultRuntime,
|
||||
@@ -90,17 +99,11 @@ export function registerStatusHealthSessionsCommands(program: Command) {
|
||||
`\n${theme.muted("Docs:")} ${formatDocsLink("/cli/health", "docs.openclaw.ai/cli/health")}\n`,
|
||||
)
|
||||
.action(async (opts) => {
|
||||
const verbose = resolveVerbose(opts);
|
||||
setVerbose(verbose);
|
||||
const timeout = parseTimeoutMs(opts.timeout);
|
||||
if (timeout === null) {
|
||||
return;
|
||||
}
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
await runWithVerboseAndTimeout(opts, async ({ verbose, timeoutMs }) => {
|
||||
await healthCommand(
|
||||
{
|
||||
json: Boolean(opts.json),
|
||||
timeoutMs: timeout,
|
||||
timeoutMs,
|
||||
verbose,
|
||||
},
|
||||
defaultRuntime,
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { isTruthyEnvValue } from "../../infra/env.js";
|
||||
import { getPrimaryCommand, hasHelpOrVersion } from "../argv.js";
|
||||
import { reparseProgramFromActionArgs } from "./action-reparse.js";
|
||||
import { removeCommand, removeCommandByName } from "./command-tree.js";
|
||||
|
||||
type SubCliRegistrar = (program: Command) => Promise<void> | void;
|
||||
|
||||
@@ -296,23 +297,12 @@ export function getSubCliCommandsWithSubcommands(): string[] {
|
||||
return entries.filter((entry) => entry.hasSubcommands).map((entry) => entry.name);
|
||||
}
|
||||
|
||||
function removeCommand(program: Command, command: Command) {
|
||||
const commands = program.commands as Command[];
|
||||
const index = commands.indexOf(command);
|
||||
if (index >= 0) {
|
||||
commands.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
export async function registerSubCliByName(program: Command, name: string): Promise<boolean> {
|
||||
const entry = entries.find((candidate) => candidate.name === name);
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
const existing = program.commands.find((cmd) => cmd.name() === entry.name);
|
||||
if (existing) {
|
||||
removeCommand(program, existing);
|
||||
}
|
||||
removeCommandByName(program, entry.name);
|
||||
await entry.register(program);
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user