fix(cli): preserve literal --update subcommand args (#106144)

Co-authored-by: xydt-juyaohui <xydt-juyaohui@users.noreply.github.com>
This commit is contained in:
juyaohuidt
2026-07-13 15:53:59 +08:00
committed by GitHub
parent c2521d7c47
commit 78bfcd2c2e
2 changed files with 64 additions and 5 deletions

View File

@@ -4,6 +4,7 @@ import {
normalizeOptionalLowercaseString,
} from "@openclaw/normalization-core/string-coerce";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { consumeRootOptionToken, FLAG_TERMINATOR } from "../infra/cli-root-options.js";
import {
resolveManifestCommandAliasOwnerInRegistry,
resolveManifestToolOwnerInRegistry,
@@ -37,14 +38,33 @@ function isBareParentDefaultHelpArgv(argv: string[]): boolean {
export function rewriteUpdateFlagArgv(argv: string[]): string[] {
// Preserve the old root --update spelling by rewriting before Commander registration.
const index = argv.indexOf("--update");
if (index === -1) {
// Only rewrite --update while scanning the root-option prefix; once a command
// or `--` appears, later --update tokens belong to that command's arguments.
const updateIndex = argv.indexOf("--update");
if (updateIndex === -1) {
return argv;
}
const next = [...argv];
next.splice(index, 1, "update");
return next;
for (let i = 2; i < argv.length; i++) {
const arg = argv[i];
if (!arg || arg === FLAG_TERMINATOR) {
return argv;
}
if (i === updateIndex) {
const next = [...argv];
next.splice(updateIndex, 1, "update");
return next;
}
const consumed = consumeRootOptionToken(argv, i);
if (consumed > 0) {
i += consumed - 1;
continue;
}
if (!arg.startsWith("-")) {
return argv;
}
}
return argv;
}
export function shouldEnsureCliPath(argv: string[]): boolean {

View File

@@ -130,6 +130,45 @@ describe("rewriteUpdateFlagArgv", () => {
"--json",
]);
});
it("does not rewrite --update after -- positional terminator", () => {
expect(
rewriteUpdateFlagArgv(["node", "entry.js", "config", "set", "foo", "--", "--update"]),
).toEqual(["node", "entry.js", "config", "set", "foo", "--", "--update"]);
});
it("does not rewrite --update when a subcommand appears before it", () => {
expect(
rewriteUpdateFlagArgv(["node", "entry.js", "config", "set", "update.channel", "--update"]),
).toEqual(["node", "entry.js", "config", "set", "update.channel", "--update"]);
});
it("does not rewrite --update that appears as a value after a subcommand", () => {
expect(rewriteUpdateFlagArgv(["node", "entry.js", "config", "set", "foo", "--update"])).toEqual(
["node", "entry.js", "config", "set", "foo", "--update"],
);
});
it("rewrites --update when it appears before any subcommand", () => {
expect(rewriteUpdateFlagArgv(["node", "entry.js", "--update", "config", "set", "foo"])).toEqual(
["node", "entry.js", "update", "config", "set", "foo"],
);
});
it("rewrites --update after root boolean flags", () => {
expect(rewriteUpdateFlagArgv(["node", "entry.js", "--no-color", "--update"])).toEqual([
"node",
"entry.js",
"--no-color",
"update",
]);
});
it("does not skip root boolean flag followers as option values", () => {
expect(rewriteUpdateFlagArgv(["node", "entry.js", "--no-color", "status", "--update"])).toEqual(
["node", "entry.js", "--no-color", "status", "--update"],
);
});
});
describe("shouldEnsureCliPath", () => {