Skip to content
Open
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
17 changes: 14 additions & 3 deletions src/factory-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@ export function defineGroup (config: GroupConfig, ...commands: OpaqueCommandHand
const group = new Command(config.name)
group.description(config.description)
group.allowExcessArguments(true)
// Defer unknown option validation until after Commander resolves a child.
// Leaf commands still validate their own options during delegated parsing.
group.allowUnknownOption(true)
configureErrorOutput(group)
configureJsonHelp(group)

Expand All @@ -363,10 +366,18 @@ export function defineGroup (config: GroupConfig, ...commands: OpaqueCommandHand

// Default action: error on unknown sub-command, show help otherwise
group.action(function (this: OpaqueCommandHandle) {
if (this.args.length > 0) {
group.error(`unknown command: ${this.args[0]}`)
} else {
const firstArg = this.args[0]
if (firstArg == null) {
group.help()
} else if (firstArg !== '--' && firstArg.startsWith('-')) {
group.error(`unknown option '${firstArg}'`)
} else {
const command = firstArg === '--' ? this.args[1] : firstArg
if (command != null) {
group.error(`unknown command: ${command}`)
} else {
group.help() // Bare '--' with no command following it.
}
}
})

Expand Down
100 changes: 100 additions & 0 deletions test/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,106 @@ describe('elastic CLI -- stack command tree', () => {
})
})

describe('elastic CLI -- command and option error ordering', () => {
it('reports an unknown subcommand before parsing its trailing options', async () => {
const dir = await mkdtemp(join(tmpdir(), 'elastic-cli-unknown-command-'))
await writeFile(join(dir, '.elasticrc.yml'), [
'current_context: local',
'contexts:',
' local:',
' elasticsearch:',
' url: http://localhost:9200',
'',
].join('\n'))

try {
const { code, stderr } = await runCli(
['stack', 'es', 'serch', '--index', 'my-index'],
{ cwd: dir, env: { HOME: dir, USERPROFILE: dir, XDG_CONFIG_HOME: dir } }
)

assert.equal(code, 1)
assert.match(stderr, /unknown command: serch/)
assert.doesNotMatch(stderr, /unknown option/)
} finally {
await rm(dir, { recursive: true })
}
})

it('still reports an unknown option for a valid command', async () => {
const dir = await mkdtemp(join(tmpdir(), 'elastic-cli-unknown-option-'))
await writeFile(join(dir, '.elasticrc.yml'), [
'current_context: local',
'contexts:',
' local:',
' elasticsearch:',
' url: http://localhost:9200',
'',
].join('\n'))

try {
const { code, stderr } = await runCli(
['stack', 'es', 'search', '--not-a-real-option'],
{ cwd: dir, env: { HOME: dir, USERPROFILE: dir, XDG_CONFIG_HOME: dir } }
)

assert.equal(code, 1)
assert.match(stderr, /unknown option '--not-a-real-option'/)
} finally {
await rm(dir, { recursive: true })
}
})

it('reports an unknown option when no subcommand is provided', async () => {
const dir = await mkdtemp(join(tmpdir(), 'elastic-cli-group-unknown-option-'))
await writeFile(join(dir, '.elasticrc.yml'), [
'current_context: local',
'contexts:',
' local:',
' elasticsearch:',
' url: http://localhost:9200',
'',
].join('\n'))

try {
const { code, stderr } = await runCli(
['stack', 'es', '--not-a-real-option'],
{ cwd: dir, env: { HOME: dir, USERPROFILE: dir, XDG_CONFIG_HOME: dir } }
)

assert.equal(code, 1)
assert.match(stderr, /unknown option '--not-a-real-option'/)
} finally {
await rm(dir, { recursive: true })
}
})

it('reports an unknown command after the option separator', async () => {
const dir = await mkdtemp(join(tmpdir(), 'elastic-cli-option-separator-'))
await writeFile(join(dir, '.elasticrc.yml'), [
'current_context: local',
'contexts:',
' local:',
' elasticsearch:',
' url: http://localhost:9200',
'',
].join('\n'))

try {
const { code, stderr } = await runCli(
['stack', 'es', '--', 'serch'],
{ cwd: dir, env: { HOME: dir, USERPROFILE: dir, XDG_CONFIG_HOME: dir } }
)

assert.equal(code, 1)
assert.match(stderr, /unknown command: serch/)
assert.doesNotMatch(stderr, /unknown option/)
} finally {
await rm(dir, { recursive: true })
}
})
})

describe('elastic CLI -- --help --json', () => {
it('`elastic --help --json` emits structured JSON help', async () => {
const dir = await mkdtemp(join(tmpdir(), 'elastic-cli-help-json-'))
Expand Down