Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions packages/cli/src/utils/reject-unknown-flags.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,37 @@ describe("assertKnownFlags", () => {
);
});

// fails without the fix: the list sent agents to hidden rename shims that only print an error.
it("leaves hidden flags out of the list, as --help does", () => {
const init = {
args: {
example: { type: "string" },
template: { type: "string", hidden: true, description: "[renamed] Use --example instead." },
agent: { type: "boolean", hidden: true },
},
} as unknown as CommandDef<ArgsDef>;
expect(() => assertKnownFlags(init, ["--demo"], "init")).toThrow(
expect.objectContaining({
hint: "Its flags: --example. Run 'hyperframes init --help' for what each does.",
}),
);
});

// fails without the fix: a typo near a hidden shim was told to use the shim.
it("never suggests a hidden flag for a typo", () => {
const init = {
args: {
example: { type: "string" },
template: { type: "string", hidden: true, description: "[renamed] Use --example instead." },
},
} as unknown as CommandDef<ArgsDef>;
expect(() => assertKnownFlags(init, ["--templat", "x"], "init")).toThrow(
expect.objectContaining({
hint: "Its flags: --example. Run 'hyperframes init --help' for what each does.",
}),
);
});

it("points --search at the query flag", () => {
const catalog = {
args: { words: { type: "positional" }, query: { type: "string" }, json: { type: "boolean" } },
Expand Down
22 changes: 8 additions & 14 deletions packages/cli/src/utils/reject-unknown-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ function nameVariants(name: string): string[] {
return [name, kebabCase(name), camel];
}

function declaredLongFlags(args: ArgsDef | undefined): string[] {
function visibleLongFlags(args: ArgsDef | undefined): string[] {
return Object.entries(args ?? {})
.filter(([, def]) => def?.type !== "positional")
.map(([name]) => `--${kebabCase(name)}`);
.filter(([, def]) => def?.type !== "positional" && !(def && "hidden" in def && def.hidden))
Comment thread
heygengenesis[bot] marked this conversation as resolved.
Resolved
.map(([name]) => kebabCase(name));
}

// Every spelling of one declared arg: name variants plus aliases. `def` may be undefined.
Expand Down Expand Up @@ -86,27 +86,21 @@ const FLAG_SYNONYMS: Readonly<Record<string, readonly string[]>> = {
search: ["query"],
};

function unknownFlagError(
flag: string,
known: Set<string>,
declared: string[],
path: string,
): CliUsageError {
function unknownFlagError(flag: string, visible: string[], path: string): CliUsageError {
const command = `hyperframes ${path ? `${path} ` : ""}`.trim();
if (flag === "--json") {
return new CliUsageError(`${command} has no JSON output.`, {
code: "json_unsupported",
hint: "Run it without --json.",
});
}
const longNames = [...known].filter((name) => name.length > 1);
const name = flag.replace(/^--?/, "");
const match =
FLAG_SYNONYMS[name]?.find((s) => known.has(s)) ??
(flag.startsWith("--") ? closestMatch(name, longNames) : undefined);
FLAG_SYNONYMS[name]?.find((s) => visible.includes(s)) ??
(flag.startsWith("--") ? closestMatch(name, visible) : undefined);
const hint = [
match && `Did you mean --${match}?`,
declared.length > 0 && `Its flags: ${declared.join(", ")}.`,
visible.length > 0 && `Its flags: ${visible.map((f) => `--${f}`).join(", ")}.`,
`Run 'hyperframes ${path ? `${path} ` : ""}--help' for what each does.`,
]
.filter(Boolean)
Expand All @@ -130,7 +124,7 @@ export function assertKnownFlags(cmd: CommandDef<ArgsDef>, rawArgs: string[], pa
for (const tok of rawArgs) {
if (tok === "--") break;
const bad = unknownFlagIn(tok, known);
if (bad) throw unknownFlagError(bad, known, declaredLongFlags(args), path);
if (bad) throw unknownFlagError(bad, visibleLongFlags(args), path);
}
}

Expand Down
Loading