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
25 changes: 25 additions & 0 deletions packages/sim-cli/src/embed-isolation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,29 @@ describe('embedded CLI output ownership', () => {
expect(invalid.stdout).toBe('')
expect(invalid.stderr).toContain('missing required argument')
})

it('answers a guessed flag with the command flags, and an id that opens with a dash with the escape', async () => {
const transport = async () => {
throw new Error('a parse error must not reach the API')
}
const [guessed, dashedId, noArguments] = await Promise.all([
runEmbeddedCli(['tables', 'rows', 'batch-delete', 'tbl_1', '--row-ids', 'row_1', '--yes'], {
...identity,
transport,
}),
runEmbeddedCli(['audit-logs', 'get', '-X9abc'], { ...identity, transport }),
runEmbeddedCli(['logs', 'list', '-X9abc'], { ...identity, transport }),
])
expect(guessed.exitCode).toBe(1)
expect(guessed.stderr).toContain("unknown option '--row-ids'")
expect(guessed.stderr).toContain('Options for sim tables rows batch-delete:')
expect(guessed.stderr).toContain('--row <value...>')
expect(dashedId.exitCode).toBe(1)
expect(dashedId.stderr).toContain('Example: sim audit-logs get -- -X9abc')
expect(dashedId.stderr).not.toContain('Options for')
// With no positional to escape into, an id-shaped token is just an unknown option.
expect(noArguments.exitCode).toBe(1)
expect(noArguments.stderr).toContain('Options for sim logs list:')
expect(noArguments.stderr).not.toContain('Example:')
})
})
21 changes: 14 additions & 7 deletions packages/sim-cli/src/runtime/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,16 @@ function looksLikeAnId(token: string): boolean {
}

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

Expand All @@ -113,10 +116,14 @@ function addArgumentExamples(command: Command): Command {
return
}

if (command.registeredArguments.length === 0) return
const token = UNKNOWN_OPTION_TOKEN.exec(message)?.[1]
if (!token || !looksLikeAnId(token)) return
write(`Example: ${commandPath(command)} -- ${token}\n`)
if (!token) return
if (looksLikeAnId(token) && command.registeredArguments.length > 0) {
write(`Example: ${commandPath(command)} -- ${token}\n`)
return
}
const flags = command.options.filter((option) => !option.hidden).map((option) => option.flags)
write(`Options for ${commandPath(command)}: ${flags.join(', ')}\n`)
},
})
return command
Expand Down Expand Up @@ -339,7 +346,7 @@ function configureOperation(
}

function buildLeaf(operation: V2OperationName, spec: CommandSpec, leafName: string): Command {
return addArgumentExamples(configureOperation(new Command(leafName), operation, spec))
return addParseErrorGuidance(configureOperation(new Command(leafName), operation, spec))
}

/**
Expand Down
Loading