fix(cli): gate exported subcli descriptors (#84519)

Summary:
- This PR filters exported sub-CLI descriptors through the private-QA gate, centralizes that filter, adds regr ... ge, and carries small validation repairs in workspace glob and tunnel-timeout tests plus a changelog entry.
- Reproducibility: yes. Current-main source shows the raw SUB_CLI_DESCRIPTORS export can include qa while the helper surfaces filter it, and src/cli/argv.ts consumes that export for root command policy.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(cli): gate exported subcli descriptors
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8451…

Validation:
- ClawSweeper review passed for head ba197a6f30.
- Required merge gates passed before the squash merge.

Prepared head SHA: ba197a6f30
Review: https://github.com/openclaw/openclaw/pull/84519#issuecomment-4496549642

Co-authored-by: Zhaocun <zhaocunsun@gmail.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
This commit is contained in:
Zhaocun Sun
2026-05-21 04:32:01 +08:00
committed by GitHub
parent d5cc0d53b7
commit ca0fe884ff
6 changed files with 237 additions and 33 deletions

View File

@@ -0,0 +1,65 @@
import { afterEach, describe, expect, it, vi } from "vitest";
async function importSubCliDescriptors() {
vi.resetModules();
return import("./subcli-descriptors.js");
}
function descriptorNames(descriptors: ReadonlyArray<{ name: string }>): string[] {
return descriptors.map((descriptor) => descriptor.name);
}
describe("sub-cli descriptors", () => {
const originalPrivateQaCli = process.env.OPENCLAW_ENABLE_PRIVATE_QA_CLI;
afterEach(() => {
if (originalPrivateQaCli === undefined) {
delete process.env.OPENCLAW_ENABLE_PRIVATE_QA_CLI;
} else {
process.env.OPENCLAW_ENABLE_PRIVATE_QA_CLI = originalPrivateQaCli;
}
vi.resetModules();
});
it("keeps the exported descriptor list aligned with private QA visibility when disabled (#83927)", async () => {
delete process.env.OPENCLAW_ENABLE_PRIVATE_QA_CLI;
const { SUB_CLI_DESCRIPTORS, getSubCliEntries } = await importSubCliDescriptors();
const exportedNames = descriptorNames(SUB_CLI_DESCRIPTORS);
expect(exportedNames).toEqual(descriptorNames(getSubCliEntries()));
expect(exportedNames).not.toContain("qa");
});
it("keeps all sub-cli filter surfaces aligned when private QA is disabled (#83926)", async () => {
delete process.env.OPENCLAW_ENABLE_PRIVATE_QA_CLI;
const {
SUB_CLI_DESCRIPTORS,
getSubCliCommandsWithSubcommands,
getSubCliParentDefaultHelpCommands,
} = await importSubCliDescriptors();
const exportedNames = descriptorNames(SUB_CLI_DESCRIPTORS);
expect(exportedNames).not.toContain("qa");
expect(getSubCliCommandsWithSubcommands()).not.toContain("qa");
expect(getSubCliParentDefaultHelpCommands()).not.toContain("qa");
});
it("includes qa in the exported descriptor list when private QA is enabled", async () => {
process.env.OPENCLAW_ENABLE_PRIVATE_QA_CLI = "1";
const {
SUB_CLI_DESCRIPTORS,
getSubCliCommandsWithSubcommands,
getSubCliEntries,
getSubCliParentDefaultHelpCommands,
} = await importSubCliDescriptors();
const exportedNames = descriptorNames(SUB_CLI_DESCRIPTORS);
expect(exportedNames).toEqual(descriptorNames(getSubCliEntries()));
expect(exportedNames).toContain("qa");
expect(getSubCliCommandsWithSubcommands()).toContain("qa");
expect(getSubCliParentDefaultHelpCommands()).not.toContain("qa");
});
});

View File

@@ -179,28 +179,42 @@ const subCliCommandCatalog = defineCommandDescriptorCatalog([
},
] as const satisfies ReadonlyArray<SubCliDescriptor>);
export const SUB_CLI_DESCRIPTORS = subCliCommandCatalog.descriptors;
function filterPrivateQaItems<T>(
items: ReadonlyArray<T>,
getName: (item: T) => string,
): ReadonlyArray<T> {
if (isPrivateQaCliEnabled()) {
return items;
}
return items.filter((item) => getName(item) !== "qa");
}
export const SUB_CLI_DESCRIPTORS = filterPrivateQaItems(
subCliCommandCatalog.descriptors,
(descriptor) => descriptor.name,
);
export function getSubCliEntries(): ReadonlyArray<SubCliDescriptor> {
const descriptors = subCliCommandCatalog.getDescriptors();
if (isPrivateQaCliEnabled()) {
return descriptors;
}
return descriptors.filter((descriptor) => descriptor.name !== "qa");
return filterPrivateQaItems(
subCliCommandCatalog.getDescriptors(),
(descriptor) => descriptor.name,
);
}
export function getSubCliCommandsWithSubcommands(): string[] {
const commands = subCliCommandCatalog.getCommandsWithSubcommands();
if (isPrivateQaCliEnabled()) {
return commands;
}
return commands.filter((command) => command !== "qa");
return [
...filterPrivateQaItems(
subCliCommandCatalog.getCommandsWithSubcommands(),
(command) => command,
),
];
}
export function getSubCliParentDefaultHelpCommands(): string[] {
const commands = subCliCommandCatalog.getParentDefaultHelpCommands();
if (isPrivateQaCliEnabled()) {
return commands;
}
return commands.filter((command) => command !== "qa");
return [
...filterPrivateQaItems(
subCliCommandCatalog.getParentDefaultHelpCommands(),
(command) => command,
),
];
}