Skip to content

Commit 9544c42

Browse files
authored
feat: command call mode on command context (#114)
1 parent 0cdb6b7 commit 9544c42

File tree

6 files changed

+70
-9
lines changed

6 files changed

+70
-9
lines changed

src/cli.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ describe('execute command', () => {
1717
const mockFn = vi.fn()
1818
await cli([], mockFn)
1919
expect(mockFn).toBeCalled()
20+
expect(mockFn.mock.calls[0][0].callMode).toEqual('entry')
2021
})
2122

2223
test('entry command', async () => {
@@ -25,6 +26,7 @@ describe('execute command', () => {
2526
run: mockFn
2627
})
2728
expect(mockFn).toBeCalled()
29+
expect(mockFn.mock.calls[0][0].callMode).toEqual('entry')
2830
})
2931

3032
test('entry command with name', async () => {
@@ -34,6 +36,7 @@ describe('execute command', () => {
3436
run: mockFn
3537
})
3638
expect(mockFn).toBeCalled()
39+
expect(mockFn.mock.calls[0][0].callMode).toEqual('entry')
3740
})
3841

3942
test('entry command with arguments', async () => {
@@ -49,6 +52,7 @@ describe('execute command', () => {
4952
})
5053
expect(mockFn.mock.calls[0][0].values).toEqual({ outDir: 'dist/' })
5154
expect(mockFn.mock.calls[0][0].positionals).toEqual(['foo', 'bar'])
55+
expect(mockFn.mock.calls[0][0].callMode).toEqual('entry')
5256
})
5357

5458
test('entry command without arguments', async () => {
@@ -58,6 +62,7 @@ describe('execute command', () => {
5862
})
5963
expect(mockFn.mock.calls[0][0].values).toEqual({})
6064
expect(mockFn.mock.calls[0][0].positionals).toEqual(['dist/', 'test/'])
65+
expect(mockFn.mock.calls[0][0].callMode).toEqual('entry')
6166
})
6267

6368
test('entry strictly command + sub commands', async () => {
@@ -99,12 +104,15 @@ describe('execute command', () => {
99104
await cli(['command2', '--bar=1', 'position2'], show, options)
100105

101106
expect(mockShow).toBeCalledTimes(2)
107+
expect(mockShow.mock.calls[0][0].callMode).toEqual('entry')
102108
expect(mockCommand1).toBeCalledTimes(1)
103109
expect(mockCommand1.mock.calls[0][0].values).toEqual({ foo: 'foo' })
104110
expect(mockCommand1.mock.calls[0][0].positionals).toEqual(['command1', 'position1'])
111+
expect(mockCommand1.mock.calls[0][0].callMode).toEqual('subCommand')
105112
expect(mockCommand2).toBeCalledTimes(1)
106113
expect(mockCommand2.mock.calls[0][0].values).toEqual({ bar: 1 })
107114
expect(mockCommand2.mock.calls[0][0].positionals).toEqual(['command2', 'position2'])
115+
expect(mockCommand2.mock.calls[0][0].callMode).toEqual('subCommand')
108116
})
109117

110118
test('entry loose command + sub commands', async () => {

src/cli.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@ import { renderHeader, renderUsage, renderValidationErrors } from './renderer.ts
1010
import { create, resolveLazyCommand } from './utils.ts'
1111

1212
import type { Args, ArgToken } from 'args-tokens'
13-
import type { Command, CommandContext, CommandOptions, CommandRunner } from './types.ts'
13+
import type {
14+
Command,
15+
CommandCallMode,
16+
CommandContext,
17+
CommandOptions,
18+
CommandRunner
19+
} from './types.ts'
1420

1521
/**
1622
* Run the command.
@@ -28,7 +34,12 @@ export async function cli<A extends Args = Args>(
2834

2935
const subCommand = getSubCommand(tokens)
3036
const resolvedCommandOptions = resolveCommandOptions(opts, entry)
31-
const [name, command] = await resolveCommand(subCommand, entry, resolvedCommandOptions, true)
37+
const [name, command, callMode] = await resolveCommand(
38+
subCommand,
39+
entry,
40+
resolvedCommandOptions,
41+
true
42+
)
3243
if (!command) {
3344
throw new Error(`Command not found: ${name || ''}`)
3445
}
@@ -48,6 +59,7 @@ export async function cli<A extends Args = Args>(
4859
argv,
4960
tokens,
5061
omitted,
62+
callMode,
5163
command,
5264
commandOptions: resolvedCommandOptions
5365
})
@@ -161,27 +173,31 @@ async function resolveCommand<A extends Args>(
161173
entry: Command<A> | CommandRunner<A>,
162174
options: CommandOptions<A>,
163175
needRunResolving: boolean = false
164-
): Promise<[string | undefined, Command<A> | undefined]> {
176+
): Promise<[string | undefined, Command<A> | undefined, CommandCallMode]> {
165177
const omitted = !sub
166178
if (typeof entry === 'function') {
167-
return [undefined, { run: entry }]
179+
return [undefined, { run: entry }, 'entry']
168180
} else {
169181
if (omitted) {
170182
return typeof entry === 'object'
171-
? [resolveEntryName(entry), await resolveLazyCommand(entry, '', needRunResolving)]
172-
: [undefined, undefined]
183+
? [resolveEntryName(entry), await resolveLazyCommand(entry, '', needRunResolving), 'entry']
184+
: [undefined, undefined, 'unexpected']
173185
} else {
174186
if (options.subCommands == null || options.subCommands.size === 0) {
175-
return [resolveEntryName(entry), await resolveLazyCommand(entry, '', needRunResolving)]
187+
return [
188+
resolveEntryName(entry),
189+
await resolveLazyCommand(entry, '', needRunResolving),
190+
'entry'
191+
]
176192
}
177193

178194
const cmd = options.subCommands?.get(sub)
179195

180196
if (cmd == null) {
181-
return [sub, undefined]
197+
return [sub, undefined, 'unexpected']
182198
}
183199

184-
return [sub, await resolveLazyCommand(cmd, sub, needRunResolving)]
200+
return [sub, await resolveLazyCommand(cmd, sub, needRunResolving), 'subCommand']
185201
}
186202
}
187203
}

src/context.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ test('basic', async () => {
6161
argv: ['bar'],
6262
tokens: [], // dummy, due to test
6363
omitted: true,
64+
callMode: 'entry',
6465
command,
6566
commandOptions: {
6667
cwd: '/path/to/cmd1',
@@ -152,6 +153,7 @@ test('default', async () => {
152153
tokens: [], // dummy, due to test
153154
command,
154155
omitted: false,
156+
callMode: 'entry',
155157
commandOptions: {}
156158
})
157159

@@ -198,6 +200,7 @@ describe('translation', () => {
198200
tokens: [], // dummy, due to test
199201
command,
200202
omitted: false,
203+
callMode: 'entry',
201204
commandOptions: {}
202205
})
203206

@@ -258,6 +261,7 @@ describe('translation', () => {
258261
tokens: [], // dummy, due to test
259262
command,
260263
omitted: false,
264+
callMode: 'entry',
261265
commandOptions: {}
262266
})
263267

@@ -332,6 +336,7 @@ describe('translation', () => {
332336
tokens: [], // dummy, due to test
333337
command,
334338
omitted: false,
339+
callMode: 'entry',
335340
commandOptions: {
336341
description: 'this is cmd1',
337342
locale: new Intl.Locale(loadLocale)
@@ -403,6 +408,7 @@ describe('translation adapter', () => {
403408
tokens: [], // dummy, due to test
404409
command,
405410
omitted: false,
411+
callMode: 'entry',
406412
commandOptions: {
407413
description: 'this is cmd1',
408414
locale: new Intl.Locale(loadLocale),
@@ -459,6 +465,7 @@ describe('translation adapter', () => {
459465
tokens: [], // dummy, due to test
460466
command,
461467
omitted: false,
468+
callMode: 'entry',
462469
commandOptions: {
463470
description: 'this is cmd1',
464471
locale: new Intl.Locale(loadLocale),

src/context.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import type { Args, ArgSchema, ArgToken, ArgValues } from 'args-tokens'
3232
import type {
3333
Command,
3434
CommandBuiltinKeys,
35+
CommandCallMode,
3536
CommandContext,
3637
CommandEnvironment,
3738
CommandOptions,
@@ -72,6 +73,10 @@ interface CommandContextParams<A extends Args, V> {
7273
* Whether the command is omitted
7374
*/
7475
omitted: boolean
76+
/**
77+
* Command call mode.
78+
*/
79+
callMode: CommandCallMode
7580
/**
7681
* A target {@link Command | command}
7782
*/
@@ -99,6 +104,7 @@ export async function createCommandContext<
99104
tokens,
100105
command,
101106
commandOptions,
107+
callMode = 'entry',
102108
omitted = false
103109
}: CommandContextParams<A, V>): Promise<Readonly<CommandContext<A, V>>> {
104110
/**
@@ -200,6 +206,7 @@ export async function createCommandContext<
200206
name: command.name,
201207
description: command.description,
202208
omitted,
209+
callMode,
203210
locale,
204211
env,
205212
args: _args,

src/renderer.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ describe('renderHeader', () => {
9797
argv: [],
9898
tokens: [], // dummy, due to test
9999
omitted: true,
100+
callMode: 'entry',
100101
command,
101102
commandOptions: {
102103
cwd: '/path/to/cmd1',
@@ -118,6 +119,7 @@ describe('renderHeader', () => {
118119
argv: [],
119120
tokens: [], // dummy, due to test
120121
omitted: true,
122+
callMode: 'entry',
121123
command,
122124
commandOptions: {
123125
cwd: '/path/to/cmd1',
@@ -138,6 +140,7 @@ describe('renderHeader', () => {
138140
argv: [],
139141
tokens: [], // dummy, due to test
140142
omitted: true,
143+
callMode: 'entry',
141144
command,
142145
commandOptions: { cwd: '/path/to/cmd1' }
143146
})
@@ -154,6 +157,7 @@ describe('renderHeader', () => {
154157
argv: [],
155158
tokens: [], // dummy, due to test
156159
omitted: true,
160+
callMode: 'entry',
157161
command,
158162
commandOptions: {
159163
cwd: '/path/to/cmd1',
@@ -205,6 +209,7 @@ describe('renderUsage', () => {
205209
argv: [],
206210
tokens: [], // dummy, due to test
207211
omitted: false,
212+
callMode: 'entry',
208213
command,
209214
commandOptions: {
210215
cwd: '/path/to/cmd1',
@@ -233,6 +238,7 @@ describe('renderUsage', () => {
233238
argv: [],
234239
tokens: [], // dummy, due to test
235240
omitted: false,
241+
callMode: 'entry',
236242
command,
237243
commandOptions: {
238244
cwd: '/path/to/cmd1',
@@ -275,6 +281,7 @@ describe('renderUsage', () => {
275281
argv: [],
276282
tokens: [], // dummy, due to test
277283
omitted: false,
284+
callMode: 'entry',
278285
command,
279286
commandOptions: {
280287
cwd: '/path/to/cmd1',
@@ -311,6 +318,7 @@ describe('renderUsage', () => {
311318
argv: [],
312319
tokens: [], // dummy, due to test
313320
omitted: false,
321+
callMode: 'entry',
314322
command,
315323
commandOptions: {
316324
cwd: '/path/to/cmd1',
@@ -357,6 +365,7 @@ describe('renderUsage', () => {
357365
argv: [],
358366
tokens: [], // dummy, due to test
359367
omitted: false,
368+
callMode: 'entry',
360369
command,
361370
commandOptions: {
362371
cwd: '/path/to/cmd1',
@@ -405,6 +414,7 @@ describe('renderUsage', () => {
405414
argv: [],
406415
tokens: [], // dummy, due to test
407416
omitted: false,
417+
callMode: 'entry',
408418
command,
409419
commandOptions: {
410420
cwd: '/path/to/cmd1',
@@ -454,6 +464,7 @@ describe('renderUsage', () => {
454464
argv: [],
455465
tokens: [], // dummy, due to test
456466
omitted: false,
467+
callMode: 'entry',
457468
command,
458469
commandOptions: {
459470
usageOptionType: true,
@@ -473,6 +484,7 @@ describe('renderUsage', () => {
473484
args: SHOW.args!,
474485
values: {},
475486
omitted: true,
487+
callMode: 'entry',
476488
positionals: [],
477489
rest: [],
478490
argv: [],
@@ -499,6 +511,7 @@ test('renderValidationErrors', async () => {
499511
argv: [],
500512
tokens: [], // dummy, due to test
501513
omitted: false,
514+
callMode: 'entry',
502515
command: SHOW,
503516
commandOptions: {
504517
cwd: '/path/to/cmd1',

src/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,11 @@ export interface CommandOptions<A extends Args = Args> {
221221
translationAdapterFactory?: TranslationAdapterFactory
222222
}
223223

224+
/**
225+
* Command call mode.
226+
*/
227+
export type CommandCallMode = 'entry' | 'subCommand' | 'unexpected'
228+
224229
/**
225230
* Command context.
226231
* Command context is the context of the command execution.
@@ -277,6 +282,11 @@ export interface CommandContext<A extends Args = Args, V = ArgValues<A>> {
277282
* Whether the currently executing command has been executed with the sub-command name omitted.
278283
*/
279284
omitted: boolean
285+
/**
286+
* Command call mode.
287+
* The command call mode is `entry` when the command is executed as an entry command, and `subCommand` when the command is executed as a sub-command.
288+
*/
289+
callMode: CommandCallMode
280290
/**
281291
* Output a message.
282292
* If {@link CommandEnvironment.usageSilent} is true, the message is not output.

0 commit comments

Comments
 (0)