Skip to content

Commit b1c1be0

Browse files
authored
fix(sim-cli): an unknown option lists the command's own flags (#8845)
* fix(sim-cli): an unknown option lists the command's own flags Commander suggests only a near spelling, so a guessed flag such as `tables rows batch-delete --row-ids` (the flag is --row) got a bare "unknown option" and the caller had to look the command up before retrying. The parse-error formatter now appends the command's flags; an id that opens with a dash keeps its `--` example instead. * fix(sim-cli): an id-shaped token lists the flags on a command with no positional
1 parent 7abe86b commit b1c1be0

2 files changed

Lines changed: 39 additions & 7 deletions

File tree

‎packages/sim-cli/src/embed-isolation.test.ts‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,29 @@ describe('embedded CLI output ownership', () => {
8787
expect(invalid.stdout).toBe('')
8888
expect(invalid.stderr).toContain('missing required argument')
8989
})
90+
91+
it('answers a guessed flag with the command flags, and an id that opens with a dash with the escape', async () => {
92+
const transport = async () => {
93+
throw new Error('a parse error must not reach the API')
94+
}
95+
const [guessed, dashedId, noArguments] = await Promise.all([
96+
runEmbeddedCli(['tables', 'rows', 'batch-delete', 'tbl_1', '--row-ids', 'row_1', '--yes'], {
97+
...identity,
98+
transport,
99+
}),
100+
runEmbeddedCli(['audit-logs', 'get', '-X9abc'], { ...identity, transport }),
101+
runEmbeddedCli(['logs', 'list', '-X9abc'], { ...identity, transport }),
102+
])
103+
expect(guessed.exitCode).toBe(1)
104+
expect(guessed.stderr).toContain("unknown option '--row-ids'")
105+
expect(guessed.stderr).toContain('Options for sim tables rows batch-delete:')
106+
expect(guessed.stderr).toContain('--row <value...>')
107+
expect(dashedId.exitCode).toBe(1)
108+
expect(dashedId.stderr).toContain('Example: sim audit-logs get -- -X9abc')
109+
expect(dashedId.stderr).not.toContain('Options for')
110+
// With no positional to escape into, an id-shaped token is just an unknown option.
111+
expect(noArguments.exitCode).toBe(1)
112+
expect(noArguments.stderr).toContain('Options for sim logs list:')
113+
expect(noArguments.stderr).not.toContain('Example:')
114+
})
90115
})

‎packages/sim-cli/src/runtime/build.ts‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,16 @@ function looksLikeAnId(token: string): boolean {
9292
}
9393

9494
/**
95-
* Appends a worked example to the parse errors a positional argument causes.
95+
* Appends a worked example to the parse errors a positional argument causes,
96+
* and the command's own flags to an unknown option.
9697
*
9798
* Covers the argument being absent and the argument being swallowed as an
9899
* option because its id opens with a dash; the second needs the `--` escape,
99-
* which commander never mentions.
100+
* which commander never mentions. A guessed flag gets the real list: commander
101+
* suggests only a near spelling, so `--row-ids` for `--row` got a bare error
102+
* and a caller had to look the command up before retrying.
100103
*/
101-
function addArgumentExamples(command: Command): Command {
104+
function addParseErrorGuidance(command: Command): Command {
102105
const outputError = command.configureOutput().outputError
103106
if (!outputError) throw new Error('Commander output formatter is not configured')
104107

@@ -113,10 +116,14 @@ function addArgumentExamples(command: Command): Command {
113116
return
114117
}
115118

116-
if (command.registeredArguments.length === 0) return
117119
const token = UNKNOWN_OPTION_TOKEN.exec(message)?.[1]
118-
if (!token || !looksLikeAnId(token)) return
119-
write(`Example: ${commandPath(command)} -- ${token}\n`)
120+
if (!token) return
121+
if (looksLikeAnId(token) && command.registeredArguments.length > 0) {
122+
write(`Example: ${commandPath(command)} -- ${token}\n`)
123+
return
124+
}
125+
const flags = command.options.filter((option) => !option.hidden).map((option) => option.flags)
126+
write(`Options for ${commandPath(command)}: ${flags.join(', ')}\n`)
120127
},
121128
})
122129
return command
@@ -339,7 +346,7 @@ function configureOperation(
339346
}
340347

341348
function buildLeaf(operation: V2OperationName, spec: CommandSpec, leafName: string): Command {
342-
return addArgumentExamples(configureOperation(new Command(leafName), operation, spec))
349+
return addParseErrorGuidance(configureOperation(new Command(leafName), operation, spec))
343350
}
344351

345352
/**

0 commit comments

Comments
 (0)