gunshi 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,8 +18,8 @@ Gunshi is a modern javascript command-line library
18
18
  Gunshi is designed to simplify the creation of modern command-line interfaces:
19
19
 
20
20
  - 📏 **Simple**: Run the commands with a simple API.
21
- - 🛡️ **Type Safe**: Arguments parsing and options value resolution type-safely by [args-tokens](https://github.com/kazupon/args-tokens)
22
21
  - ⚙️ **Declarative configuration**: Configure the command modules declaratively.
22
+ - 🛡️ **Type Safe**: Arguments parsing and options value resolution type-safely by [args-tokens](https://github.com/kazupon/args-tokens)
23
23
  - 🧩 **Composable**: Sub-commands that can be composed with modularized commands.
24
24
  - ⏳ **Lazy & Async**: Command modules lazy loading and asynchronously executing.
25
25
  - 📜 **Auto usage generation**: Automatic usage message generation with modularized commands.
@@ -62,52 +62,14 @@ Gunshi has a simple API that is a facade:
62
62
  ```js
63
63
  import { cli } from 'gunshi'
64
64
 
65
- // Run a simple command
66
- cli(process.argv.slice(2), () => {
67
- console.log('Hello from Gunshi!')
65
+ const args = process.argv.slice(2)
66
+ // run a simple command
67
+ cli(args, () => {
68
+ // something logic ...
69
+ console.log('Hello from Gunshi!', args)
68
70
  })
69
71
  ```
70
72
 
71
- ### 🛡️ Type-Safe Arguments
72
-
73
- Gunshi provides type-safe argument parsing with TypeScript:
74
-
75
- ```ts
76
- import { cli } from 'gunshi'
77
- import type { ArgOptions, Command, CommandContext } from 'gunshi'
78
-
79
- // Define interfaces for options and values
80
- interface UserOptions extends ArgOptions {
81
- name: { type: 'string'; short: 'n' }
82
- age: { type: 'number'; short: 'a'; default: number }
83
- verbose: { type: 'boolean'; short: 'v' }
84
- }
85
-
86
- interface UserValues {
87
- name?: string
88
- age: number
89
- verbose?: boolean
90
- }
91
-
92
- // Create a type-safe command
93
- const command: Command<UserOptions> = {
94
- name: 'type-safe',
95
- options: {
96
- name: { type: 'string', short: 'n' },
97
- age: { type: 'number', short: 'a', default: 25 },
98
- verbose: { type: 'boolean', short: 'v' }
99
- },
100
- run: (ctx: CommandContext<UserOptions, UserValues>) => {
101
- const { name, age, verbose } = ctx.values
102
- console.log(`Hello, ${name || 'World'}! You are ${age} years old.`)
103
- }
104
- }
105
-
106
- await cli(process.argv.slice(2), command)
107
- ```
108
-
109
- For more detailed examples, check out the [playground/type-safe](https://github.com/kazupon/gunshi/tree/main/playground/type-safe) in the repository.
110
-
111
73
  ### ⚙️ Declarative Configuration
112
74
 
113
75
  Configure commands declaratively:
@@ -115,7 +77,7 @@ Configure commands declaratively:
115
77
  ```js
116
78
  import { cli } from 'gunshi'
117
79
 
118
- // Define a command with declarative configuration
80
+ // define a command with declarative configuration, using commandable object
119
81
  const command = {
120
82
  name: 'greet',
121
83
  description: 'A greeting command',
@@ -139,6 +101,8 @@ const command = {
139
101
  }
140
102
  }
141
103
 
104
+ // run a command that is defined above
105
+ // (the 3rd argument of `cli` is the command option)
142
106
  cli(process.argv.slice(2), command, {
143
107
  name: 'my-app',
144
108
  version: '1.0.0',
@@ -148,14 +112,60 @@ cli(process.argv.slice(2), command, {
148
112
 
149
113
  For more detailed examples, check out the [playground/declarative](https://github.com/kazupon/gunshi/tree/main/playground/declarative) in the repository.
150
114
 
115
+ ### 🛡️ Type-Safe Arguments
116
+
117
+ Gunshi provides type-safe argument parsing with TypeScript:
118
+
119
+ ```ts
120
+ import { cli } from 'gunshi'
121
+ import type { ArgOptions, Command, CommandContext } from 'gunshi'
122
+
123
+ // type-safe arguments parsing example
124
+ // this demonstrates how to define and use typed command options with `satisfies`
125
+
126
+ // define 'type-safe' command options with types
127
+ const options = {
128
+ // define string option with short alias
129
+ name: {
130
+ type: 'string',
131
+ short: 'n'
132
+ },
133
+ // define number option with default value
134
+ age: {
135
+ type: 'number',
136
+ short: 'a',
137
+ default: 25
138
+ },
139
+ // define boolean flag
140
+ verbose: {
141
+ type: 'boolean',
142
+ short: 'v'
143
+ }
144
+ } satisfies ArgOptions
145
+
146
+ // define 'type-safe' command
147
+ const command = {
148
+ name: 'type-safe',
149
+ options,
150
+ run: (ctx: CommandContext<UserOptions, UserValues>) => {
151
+ const { name, age, verbose } = ctx.values
152
+ console.log(`Hello, ${name || 'World'}! You are ${age} years old.`)
153
+ }
154
+ } satisfies Command<typeof options>
155
+
156
+ await cli(process.argv.slice(2), command)
157
+ ```
158
+
159
+ For more detailed examples, check out the [playground/type-safe](https://github.com/kazupon/gunshi/tree/main/playground/type-safe) in the repository.
160
+
151
161
  ### 🧩 Composable Sub-commands
152
162
 
153
- Create a CLI with composable sub-commands:
163
+ Run a CLI with composable sub-commands:
154
164
 
155
165
  ```js
156
166
  import { cli } from 'gunshi'
157
167
 
158
- // Define sub-commands
168
+ // define 'create' command
159
169
  const createCommand = {
160
170
  name: 'create',
161
171
  description: 'Create a new resource',
@@ -167,6 +177,7 @@ const createCommand = {
167
177
  }
168
178
  }
169
179
 
180
+ // define 'list' command
170
181
  const listCommand = {
171
182
  name: 'list',
172
183
  description: 'List all resources',
@@ -175,12 +186,12 @@ const listCommand = {
175
186
  }
176
187
  }
177
188
 
178
- // Create a Map of sub-commands
189
+ // prepare a Map of sub-commands
179
190
  const subCommands = new Map()
180
191
  subCommands.set('create', createCommand)
181
192
  subCommands.set('list', listCommand)
182
193
 
183
- // Define the main command
194
+ // define the main ('resource-manager') command
184
195
  const mainCommand = {
185
196
  name: 'resource-manager',
186
197
  description: 'Manage resources',
@@ -189,7 +200,7 @@ const mainCommand = {
189
200
  }
190
201
  }
191
202
 
192
- // Run the CLI with composable sub-commands
203
+ // run the CLI with composable sub-commands
193
204
  cli(process.argv.slice(2), mainCommand, {
194
205
  name: 'my-app',
195
206
  version: '1.0.0',
@@ -206,28 +217,28 @@ Load commands lazily and execute them asynchronously:
206
217
  ```js
207
218
  import { cli } from 'gunshi'
208
219
 
209
- // Define a command that will be loaded lazily
220
+ // define a command that will be loaded lazily
210
221
  const lazyCommand = async () => {
211
- // Simulate async loading
222
+ // simulate async loading
212
223
  await new Promise(resolve => setTimeout(resolve, 1000))
213
224
 
214
- // Return the actual command
225
+ // return the actual command
215
226
  return {
216
227
  name: 'lazy',
217
228
  description: 'A command that is loaded lazily',
218
229
  run: async ctx => {
219
- // Async execution
230
+ // async execution
220
231
  await new Promise(resolve => setTimeout(resolve, 500))
221
232
  console.log('Command executed!')
222
233
  }
223
234
  }
224
235
  }
225
236
 
226
- // Create a Map of sub-commands with lazy-loaded commands
237
+ // prepare a Map of sub-commands with lazy-loaded commands
227
238
  const subCommands = new Map()
228
239
  subCommands.set('lazy', lazyCommand)
229
240
 
230
- // Run the CLI with lazy-loaded commands
241
+ // run the CLI with lazy-loaded commands
231
242
  cli(
232
243
  process.argv.slice(2),
233
244
  { name: 'main', run: () => {} },
@@ -255,6 +266,7 @@ const command = {
255
266
  recursive: { type: 'boolean', short: 'r' },
256
267
  operation: { type: 'string', short: 'o', required: true }
257
268
  },
269
+ // define usage with object
258
270
  usage: {
259
271
  options: {
260
272
  path: 'File or directory path',
@@ -264,11 +276,11 @@ const command = {
264
276
  examples: '# Example\n$ my-app --operation list --path ./src'
265
277
  },
266
278
  run: ctx => {
267
- // Command implementation
279
+ // command implementation
268
280
  }
269
281
  }
270
282
 
271
- // Run with --help to see the automatically generated usage information
283
+ // run with --help to see the automatically generated usage information
272
284
  cli(process.argv.slice(2), command, {
273
285
  name: 'my-app',
274
286
  version: '1.0.0'
@@ -284,7 +296,7 @@ Customize the usage message generation:
284
296
  ```js
285
297
  import { cli } from 'gunshi'
286
298
 
287
- // Custom header renderer
299
+ // define custom header renderer
288
300
  const customHeaderRenderer = ctx => {
289
301
  return Promise.resolve(`
290
302
  ╔═══════════════════════╗
@@ -295,7 +307,7 @@ Version: ${ctx.env.version}
295
307
  `)
296
308
  }
297
309
 
298
- // Custom usage renderer
310
+ // define custom usage renderer
299
311
  const customUsageRenderer = ctx => {
300
312
  const lines = []
301
313
  lines.push('USAGE:')
@@ -303,7 +315,7 @@ const customUsageRenderer = ctx => {
303
315
  lines.push('')
304
316
  lines.push('OPTIONS:')
305
317
 
306
- for (const [key, option] of Object.entries(ctx.options || {})) {
318
+ for (const [key, option] of Object.entries(ctx.options || Object.create(null))) {
307
319
  const shortFlag = option.short ? `-${option.short}, ` : ' '
308
320
  lines.push(` ${shortFlag}--${key.padEnd(10)} ${ctx.translation(key)}`)
309
321
  }
@@ -311,7 +323,7 @@ const customUsageRenderer = ctx => {
311
323
  return Promise.resolve(lines.join('\n'))
312
324
  }
313
325
 
314
- // Run with custom renderers
326
+ // run with custom renderers
315
327
  cli(
316
328
  process.argv.slice(2),
317
329
  { name: 'app', run: () => {} },
@@ -341,14 +353,14 @@ const command = {
341
353
  name: { type: 'string', short: 'n' },
342
354
  formal: { type: 'boolean', short: 'f' }
343
355
  },
344
- // Resource fetcher for translations
356
+ // resource fetcher for translations
345
357
  resource: async ctx => {
346
358
  if (ctx.locale.toString() === 'ja-JP') {
347
359
  const resource = await import('./locales/ja-JP.json', { with: { type: 'json' } })
348
360
  return resource.default
349
361
  }
350
362
 
351
- // Default to English
363
+ // default to English
352
364
  return enUS
353
365
  },
354
366
  run: ctx => {
@@ -358,12 +370,12 @@ const command = {
358
370
  }
359
371
  }
360
372
 
361
- // Run with locale support
373
+ // run with locale support
362
374
  cli(process.argv.slice(2), command, {
363
375
  name: 'my-app',
364
376
  version: '1.0.0',
365
- // Set the locale via an environment variable
366
- // If Node v21 or later is used, you can use the built-in `navigator.language` instead)
377
+ // set the locale via an environment variable
378
+ // if Node v21 or later is used, you can use the built-in `navigator.language` instead)
367
379
  locale: new Intl.Locale(process.env.MY_LOCALE || 'en-US')
368
380
  })
369
381
  ```
@@ -380,7 +392,7 @@ If you are interested in contributing to `gunshi`, I highly recommend checking o
380
392
 
381
393
  ## 💖 Credits
382
394
 
383
- This project is inspired by:
395
+ This project is inspired and powered by:
384
396
 
385
397
  - [`citty`](https://github.com/unjs/citty), created by UnJS team and contributors
386
398
  - cline and claude 3.7 sonnet, examples and docs is generated
package/lib/context.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ArgOptions, ArgValues } from 'args-tokens';
2
- import { C as Command, a as CommandOptions, b as CommandContext } from './types.d-veidyA82.js';
2
+ import { C as Command, a as CommandOptions, b as CommandContext } from './types.d-CxaX4FVV.js';
3
3
 
4
4
  /**
5
5
  * The default locale string, which format is BCP 47 language tag
@@ -43,7 +43,7 @@ interface CommandContextParams<
43
43
  * @returns A {@link CommandContext | command context}, which is readonly
44
44
  */
45
45
  declare function createCommandContext<
46
- Options extends ArgOptions,
46
+ Options extends ArgOptions = ArgOptions,
47
47
  Values = ArgValues<Options>
48
48
  >({ options, values, positionals, command, commandOptions, omitted }: CommandContextParams<Options, Values>): Promise<Readonly<CommandContext<Options, Values>>>;
49
49
 
package/lib/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ArgOptions } from 'args-tokens';
2
2
  export { ArgOptionSchema, ArgOptions, ArgValues } from 'args-tokens';
3
- import { C as Command, c as CommandRunner, a as CommandOptions } from './types.d-veidyA82.js';
4
- export { f as CommandBuiltinKeys, d as CommandBuiltinOptionsKeys, e as CommandBuiltinResourceKeys, b as CommandContext, g as CommandEnvironment, h as CommandResource, i as CommandResourceFetcher, L as LazyCommand } from './types.d-veidyA82.js';
3
+ import { C as Command, c as CommandRunner, a as CommandOptions } from './types.d-CxaX4FVV.js';
4
+ export { f as CommandBuiltinKeys, d as CommandBuiltinOptionsKeys, e as CommandBuiltinResourceKeys, b as CommandContext, g as CommandEnvironment, h as CommandResource, i as CommandResourceFetcher, j as Commandable, L as LazyCommand } from './types.d-CxaX4FVV.js';
5
5
 
6
6
  /**
7
7
  * Run the command
@@ -9,6 +9,6 @@ export { f as CommandBuiltinKeys, d as CommandBuiltinOptionsKeys, e as CommandBu
9
9
  * @param entry - A {@link Command | entry command} or an {@link CommandRunner | inline command runner}
10
10
  * @param opts - A {@link CommandOptions | command options}
11
11
  */
12
- declare function cli<Options extends ArgOptions>(args: string[], entry: Command<Options> | CommandRunner<Options>, opts?: CommandOptions<Options>): Promise<void>;
12
+ declare function cli<Options extends ArgOptions = ArgOptions>(args: string[], entry: Command<Options> | CommandRunner<Options>, opts?: CommandOptions<Options>): Promise<string | undefined>;
13
13
 
14
14
  export { Command, CommandOptions, CommandRunner, cli };
package/lib/index.js CHANGED
@@ -26,10 +26,13 @@ async function cli(args, entry, opts = {}) {
26
26
  showVersion(ctx);
27
27
  return;
28
28
  }
29
- await showHeader(ctx);
29
+ const usageBuffer = [];
30
+ const header = await showHeader(ctx);
31
+ if (header) usageBuffer.push(header);
30
32
  if (values.help) {
31
- await showUsage(ctx);
32
- return;
33
+ const usage = await showUsage(ctx);
34
+ if (usage) usageBuffer.push(usage);
35
+ return usageBuffer.join("\n");
33
36
  }
34
37
  if (error) {
35
38
  await showValidationErrors(ctx, error);
@@ -50,8 +53,11 @@ function getSubCommand(tokens) {
50
53
  }
51
54
  async function showUsage(ctx) {
52
55
  if (ctx.env.renderUsage === null) return;
53
- const render = ctx.env.renderUsage || renderUsage;
54
- log(await render(ctx));
56
+ const usage = await (ctx.env.renderUsage || renderUsage)(ctx);
57
+ if (usage) {
58
+ log(usage);
59
+ return usage;
60
+ }
55
61
  }
56
62
  function showVersion(ctx) {
57
63
  log(ctx.env.version);
@@ -62,6 +68,7 @@ async function showHeader(ctx) {
62
68
  if (header) {
63
69
  log(header);
64
70
  log();
71
+ return header;
65
72
  }
66
73
  }
67
74
  async function showValidationErrors(ctx, error) {
@@ -1,19 +1,19 @@
1
1
  import { ArgOptions } from 'args-tokens';
2
- import { b as CommandContext } from '../types.d-veidyA82.js';
2
+ import { b as CommandContext } from '../types.d-CxaX4FVV.js';
3
3
 
4
4
  /**
5
5
  * Render the header
6
6
  * @param ctx A {@link CommandContext | command context}
7
7
  * @returns A rendered header
8
8
  */
9
- declare function renderHeader<Options extends ArgOptions>(ctx: Readonly<CommandContext<Options>>): Promise<string>;
9
+ declare function renderHeader<Options extends ArgOptions = ArgOptions>(ctx: Readonly<CommandContext<Options>>): Promise<string>;
10
10
 
11
11
  /**
12
12
  * Render the usage
13
13
  * @param ctx A {@link CommandContext | command context}
14
14
  * @returns A rendered usage
15
15
  */
16
- declare function renderUsage<Options extends ArgOptions>(ctx: Readonly<CommandContext<Options>>): Promise<string>;
16
+ declare function renderUsage<Options extends ArgOptions = ArgOptions>(ctx: Readonly<CommandContext<Options>>): Promise<string>;
17
17
 
18
18
  /**
19
19
  * Render the validation errors
@@ -21,6 +21,6 @@ declare function renderUsage<Options extends ArgOptions>(ctx: Readonly<CommandCo
21
21
  * @param error An {@link AggregateError} of option in `args-token` validation
22
22
  * @returns A rendered validation error
23
23
  */
24
- declare function renderValidationErrors<Options extends ArgOptions>(_ctx: CommandContext<Options>, error: AggregateError): Promise<string>;
24
+ declare function renderValidationErrors<Options extends ArgOptions = ArgOptions>(_ctx: CommandContext<Options>, error: AggregateError): Promise<string>;
25
25
 
26
26
  export { renderHeader, renderUsage, renderValidationErrors };
@@ -88,7 +88,7 @@ interface CommandEnvironment<Options extends ArgOptions = ArgOptions> {
88
88
  * Sub commands
89
89
  * @see {@link CommandOptions.subCommands}
90
90
  */
91
- subCommands: Map<string, Command<Options> | LazyCommand<Options>> | undefined;
91
+ subCommands: Map<string, Command<any> | LazyCommand<any>> | undefined;
92
92
  /**
93
93
  * Render function the command usage
94
94
  */
@@ -105,7 +105,7 @@ interface CommandEnvironment<Options extends ArgOptions = ArgOptions> {
105
105
  /**
106
106
  * Command options
107
107
  */
108
- interface CommandOptions<Options extends ArgOptions> {
108
+ interface CommandOptions<Options extends ArgOptions = ArgOptions> {
109
109
  /**
110
110
  * Current working directory
111
111
  */
@@ -130,7 +130,7 @@ interface CommandOptions<Options extends ArgOptions> {
130
130
  /**
131
131
  * Sub commands
132
132
  */
133
- subCommands?: Map<string, Command<Options> | LazyCommand<Options>>;
133
+ subCommands?: Map<string, Command<any> | LazyCommand<any>>;
134
134
  /**
135
135
  * Left margin of the command output
136
136
  */
@@ -161,7 +161,7 @@ interface CommandOptions<Options extends ArgOptions> {
161
161
  * @description Command context is the context of the command execution
162
162
  */
163
163
  interface CommandContext<
164
- Options extends ArgOptions,
164
+ Options extends ArgOptions = ArgOptions,
165
165
  Values = ArgValues<Options>
166
166
  > {
167
167
  /**
@@ -227,7 +227,7 @@ interface CommandContext<
227
227
  /**
228
228
  * Command usage
229
229
  */
230
- interface CommandUsage<Options extends ArgOptions> {
230
+ interface CommandUsage<Options extends ArgOptions = ArgOptions> {
231
231
  /**
232
232
  * Options usage
233
233
  */
@@ -240,7 +240,7 @@ interface CommandUsage<Options extends ArgOptions> {
240
240
  /**
241
241
  * Command interface
242
242
  */
243
- interface Command<Options extends ArgOptions> {
243
+ interface Command<Options extends ArgOptions = ArgOptions> {
244
244
  /**
245
245
  * Command name
246
246
  * @description
@@ -282,7 +282,7 @@ interface Command<Options extends ArgOptions> {
282
282
  * Command resource
283
283
  * @experimental
284
284
  */
285
- interface CommandResource<Options extends ArgOptions> {
285
+ interface CommandResource<Options extends ArgOptions = ArgOptions> {
286
286
  /**
287
287
  * Command description
288
288
  */
@@ -302,16 +302,20 @@ interface CommandResource<Options extends ArgOptions> {
302
302
  * @returns A fetched {@link CommandResource | command resource}
303
303
  * @experimental
304
304
  */
305
- type CommandResourceFetcher<Options extends ArgOptions> = (ctx: Readonly<CommandContext<Options>>) => Promise<CommandResource<Options>>;
305
+ type CommandResourceFetcher<Options extends ArgOptions = ArgOptions> = (ctx: Readonly<CommandContext<Options>>) => Promise<CommandResource<Options>>;
306
306
  /**
307
307
  * Command runner
308
308
  * @param ctx A {@link CommandContext | command context}
309
309
  */
310
- type CommandRunner<Options extends ArgOptions> = (ctx: Readonly<CommandContext<Options>>) => Awaitable<void>;
310
+ type CommandRunner<Options extends ArgOptions = ArgOptions> = (ctx: Readonly<CommandContext<Options>>) => Awaitable<void>;
311
311
  /**
312
312
  * Lazy command interface
313
313
  * @description lazy command that's not loaded until it is executed
314
314
  */
315
- type LazyCommand<Options extends ArgOptions> = () => Awaitable<Command<Options>>;
315
+ type LazyCommand<Options extends ArgOptions = ArgOptions> = () => Awaitable<Command<Options>>;
316
+ /**
317
+ * Define a command type
318
+ */
319
+ type Commandable<Options extends ArgOptions> = Command<Options> | LazyCommand<Options>;
316
320
 
317
- export type { Command as C, LazyCommand as L, CommandOptions as a, CommandContext as b, CommandRunner as c, CommandBuiltinOptionsKeys as d, CommandBuiltinResourceKeys as e, CommandBuiltinKeys as f, CommandEnvironment as g, CommandResource as h, CommandResourceFetcher as i };
321
+ export type { Command as C, LazyCommand as L, CommandOptions as a, CommandContext as b, CommandRunner as c, CommandBuiltinOptionsKeys as d, CommandBuiltinResourceKeys as e, CommandBuiltinKeys as f, CommandEnvironment as g, CommandResource as h, CommandResourceFetcher as i, Commandable as j };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gunshi",
3
3
  "description": "Modern javascript command-line library",
4
- "version": "0.5.3",
4
+ "version": "0.6.0",
5
5
  "author": {
6
6
  "name": "kazuya kawaguchi",
7
7
  "email": "kawakazu80@gmail.com"