refactor(cli): remove configurable option inheritance depth (openclaw#18725, thanks @gumadeiras)

This commit is contained in:
Gustavo Madeira Santana
2026-02-17 20:27:48 -05:00
parent c3e4478af2
commit b7e51cf909
2 changed files with 24 additions and 30 deletions

View File

@@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest";
import { inheritOptionFromParent } from "./command-options.js";
describe("inheritOptionFromParent", () => {
it("inherits only from direct parent by default", async () => {
it("inherits from grandparent when parent does not define the option", async () => {
const program = new Command().option("--token <token>", "Root token");
const gateway = program.command("gateway");
let inherited: string | undefined;
@@ -15,22 +15,6 @@ describe("inheritOptionFromParent", () => {
inherited = inheritOptionFromParent<string>(command, "token");
});
await program.parseAsync(["--token", "root-token", "gateway", "run"], { from: "user" });
expect(inherited).toBeUndefined();
});
it("walks ancestor chain when explicitly configured", async () => {
const program = new Command().option("--token <token>", "Root token");
const gateway = program.command("gateway");
let inherited: string | undefined;
gateway
.command("run")
.option("--token <token>", "Run token")
.action((_opts, command) => {
inherited = inheritOptionFromParent<string>(command, "token", { maxDepth: 3 });
});
await program.parseAsync(["--token", "root-token", "gateway", "run"], { from: "user" });
expect(inherited).toBe("root-token");
});
@@ -44,7 +28,7 @@ describe("inheritOptionFromParent", () => {
.command("run")
.option("--token <token>", "Run token")
.action((_opts, command) => {
inherited = inheritOptionFromParent<string>(command, "token", { maxDepth: 3 });
inherited = inheritOptionFromParent<string>(command, "token");
});
await program.parseAsync(
@@ -65,4 +49,23 @@ describe("inheritOptionFromParent", () => {
expect(inheritOptionFromParent<string>(run, "token")).toBeUndefined();
});
it("does not inherit from ancestors beyond the bounded traversal depth", async () => {
const program = new Command().option("--token <token>", "Root token");
const level1 = program.command("level1");
const level2 = level1.command("level2");
let inherited: string | undefined;
level2
.command("run")
.option("--token <token>", "Run token")
.action((_opts, command) => {
inherited = inheritOptionFromParent<string>(command, "token");
});
await program.parseAsync(["--token", "root-token", "level1", "level2", "run"], {
from: "user",
});
expect(inherited).toBeUndefined();
});
});

View File

@@ -14,15 +14,12 @@ function getOptionSource(command: Command, name: string): string | undefined {
return command.getOptionValueSource(name);
}
type InheritOptionConfig = {
// Defensive default: only direct-parent inheritance unless callers opt into deeper traversal.
maxDepth?: number;
};
// Defensive guardrail: allow expected parent/grandparent inheritance without unbounded deep traversal.
const MAX_INHERIT_DEPTH = 2;
export function inheritOptionFromParent<T = unknown>(
command: Command | undefined,
name: string,
config?: InheritOptionConfig,
): T | undefined {
if (!command) {
return undefined;
@@ -33,15 +30,9 @@ export function inheritOptionFromParent<T = unknown>(
return undefined;
}
const rawMaxDepth = config?.maxDepth;
const maxDepth =
typeof rawMaxDepth === "number" && Number.isFinite(rawMaxDepth)
? Math.max(1, Math.floor(rawMaxDepth))
: 1;
let depth = 0;
let ancestor = command.parent;
while (ancestor && depth < maxDepth) {
while (ancestor && depth < MAX_INHERIT_DEPTH) {
const source = getOptionSource(ancestor, name);
if (source && source !== "default") {
return ancestor.opts<Record<string, unknown>>()[name] as T | undefined;