Skip to content

Commit 26d7bf8

Browse files
authored
fix: show 'did you mean?' instead of root fallback for command typos (#122)
* feat: add command-level aliases * chore: tweaks * refactor: use iife for skillsIdx assignment * refactor: use shared builtinIdx helper for all builtin command matching * fix: show 'did you mean?' instead of root fallback for command typos
1 parent 250e65f commit 26d7bf8

File tree

3 files changed

+26
-3
lines changed

3 files changed

+26
-3
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'incur': patch
3+
---
4+
5+
Fixed root fetch/command fallback bypassing "Did you mean?" suggestions when the input is a typo of a known command.

src/Cli.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3732,6 +3732,15 @@ describe('fetch', async () => {
37323732
`)
37333733
})
37343734

3735+
test('root-level fetch with typo of known command → did you mean', async () => {
3736+
const cli = Cli.create('api', { description: 'API', fetch: app.fetch }).command('upgrade', {
3737+
run: () => ({ upgraded: true }),
3738+
})
3739+
const { output, exitCode } = await serve(cli, ['upgra'])
3740+
expect(exitCode).toBe(1)
3741+
expect(output).toContain("Did you mean 'upgrade'?")
3742+
})
3743+
37353744
test('root-level fetch with no args → root path', async () => {
37363745
const cli = Cli.create('api', { description: 'API', fetch: app.fetch })
37373746
// Hono returns 404 for / since we don't have a root route

src/Cli.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,9 +1079,18 @@ async function serveImpl(
10791079
const resolvedFormat = 'command' in resolved && (resolved as any).command.format
10801080
const format = formatExplicit ? formatFlag : resolvedFormat || options.format || 'toon'
10811081

1082-
// Fall back to root fetch when no subcommand matches
1082+
// Fall back to root fetch/command when no subcommand matches,
1083+
// but only if the token doesn't look like a typo of a known command.
1084+
const rootFallbackBlocked =
1085+
'error' in resolved &&
1086+
!resolved.path &&
1087+
(() => {
1088+
const candidates = [...resolved.commands.keys()]
1089+
for (const b of builtinCommands) candidates.push(b.name)
1090+
return suggest(resolved.error, candidates) !== undefined
1091+
})()
10831092
const effective =
1084-
'error' in resolved && options.rootFetch && !resolved.path
1093+
'error' in resolved && options.rootFetch && !resolved.path && !rootFallbackBlocked
10851094
? {
10861095
fetchGateway: {
10871096
_fetch: true as const,
@@ -1092,7 +1101,7 @@ async function serveImpl(
10921101
path: name,
10931102
rest: filtered,
10941103
}
1095-
: 'error' in resolved && options.rootCommand && !resolved.path
1104+
: 'error' in resolved && options.rootCommand && !resolved.path && !rootFallbackBlocked
10961105
? { command: options.rootCommand, path: name, rest: filtered }
10971106
: resolved
10981107

0 commit comments

Comments
 (0)