gunshi 0.8.0 → 0.10.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
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img width="196" src="./assets/logo.webp">
2
+ <img width="196" src="./assets/logo.png">
3
3
  </p>
4
4
  <h1 align="center">🏯 Gunshi</h1>
5
5
 
@@ -10,21 +10,25 @@
10
10
 
11
11
  Gunshi is a modern javascript command-line library
12
12
 
13
+ <!-- eslint-disable markdown/no-missing-label-refs -->
14
+
13
15
  > [!TIP]
14
16
  > gunshi (軍師) is a position in ancient Japanese samurai battle in which a samurai devised strategies and gave orders. That name is inspired by the word "command".
15
17
 
18
+ <!-- eslint-enable markdown/no-missing-label-refs -->
19
+
16
20
  ## ✨ Features
17
21
 
18
22
  Gunshi is designed to simplify the creation of modern command-line interfaces:
19
23
 
20
- - 📏 **Simple**: Run the commands with a simple API.
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
- - 🧩 **Composable**: Sub-commands that can be composed with modularized commands.
24
- - ⏳ **Lazy & Async**: Command modules lazy loading and asynchronously executing.
25
- - 📜 **Auto usage generation**: Automatic usage message generation with modularized commands.
26
- - 🎨 **Custom usage generation**: Usage message generation customizable.
27
- - 🌍 **Internationalization**: I18n out of the box and locale resource lazy loading.
24
+ - 📏 **Simple & Universal**: Run the commands with simple API and support universal runtime.
25
+ - ⚙️ **Declarative configuration**: Configure command modules declaratively for better organization and maintainability.
26
+ - 🛡️ **Type Safe**: TypeScript support with type-safe argument parsing and option resolution by [args-tokens](https://github.com/kazupon/args-tokens)
27
+ - 🧩 **Composable**: Create modular sub-commands that can be composed together for complex CLIs.
28
+ - ⏳ **Lazy & Async**: Load command modules lazily and execute them asynchronously for better performance.
29
+ - 📜 **Auto usage generation**: Generate helpful usage messages automatically for your commands.
30
+ - 🎨 **Custom usage generation**: Customize how usage messages are generated to match your CLI's style.
31
+ - 🌍 **Internationalization**: Support multiple languages with built-in i18n, locale resource lazy loading and i18n library integration.
28
32
 
29
33
  ## 💿 Installation
30
34
 
@@ -64,7 +68,7 @@ import { cli } from 'gunshi'
64
68
 
65
69
  const args = process.argv.slice(2)
66
70
  // run a simple command
67
- cli(args, () => {
71
+ await cli(args, () => {
68
72
  // something logic ...
69
73
  console.log('Hello from Gunshi!', args)
70
74
  })
@@ -82,15 +86,22 @@ const command = {
82
86
  name: 'greet',
83
87
  description: 'A greeting command',
84
88
  options: {
85
- name: { type: 'string', short: 'n' },
86
- greeting: { type: 'string', short: 'g', default: 'Hello' },
87
- times: { type: 'number', short: 't', default: 1 }
88
- },
89
- usage: {
90
- options: {
91
- name: 'Name to greet',
92
- greeting: 'Greeting to use (default: "Hello")',
93
- times: 'Number of times to repeat the greeting (default: 1)'
89
+ name: {
90
+ type: 'string',
91
+ short: 'n',
92
+ description: 'Name to greet'
93
+ },
94
+ greeting: {
95
+ type: 'string',
96
+ short: 'g',
97
+ default: 'Hello',
98
+ description: 'Greeting to use (default: "Hello")'
99
+ },
100
+ times: {
101
+ type: 'number',
102
+ short: 't',
103
+ default: 1,
104
+ description: 'Number of times to repeat the greeting (default: 1)'
94
105
  }
95
106
  },
96
107
  run: ctx => {
@@ -103,7 +114,7 @@ const command = {
103
114
 
104
115
  // run a command that is defined above
105
116
  // (the 3rd argument of `cli` is the command option)
106
- cli(process.argv.slice(2), command, {
117
+ await cli(process.argv.slice(2), command, {
107
118
  name: 'my-app',
108
119
  version: '1.0.0',
109
120
  description: 'My CLI application'
@@ -201,7 +212,7 @@ const mainCommand = {
201
212
  }
202
213
 
203
214
  // run the CLI with composable sub-commands
204
- cli(process.argv.slice(2), mainCommand, {
215
+ await cli(process.argv.slice(2), mainCommand, {
205
216
  name: 'my-app',
206
217
  version: '1.0.0',
207
218
  subCommands
@@ -239,7 +250,7 @@ const subCommands = new Map()
239
250
  subCommands.set('lazy', lazyCommand)
240
251
 
241
252
  // run the CLI with lazy-loaded commands
242
- cli(
253
+ await cli(
243
254
  process.argv.slice(2),
244
255
  { name: 'main', run: () => {} },
245
256
  {
@@ -262,26 +273,32 @@ const command = {
262
273
  name: 'app',
263
274
  description: 'My application',
264
275
  options: {
265
- path: { type: 'string', short: 'p' },
266
- recursive: { type: 'boolean', short: 'r' },
267
- operation: { type: 'string', short: 'o', required: true }
268
- },
269
- // define usage with object
270
- usage: {
271
- options: {
272
- path: 'File or directory path',
273
- recursive: 'Operate recursively on directories',
274
- operation: 'Operation to perform (list, copy, move, delete)'
276
+ path: {
277
+ type: 'string',
278
+ short: 'p',
279
+ description: 'File or directory path'
280
+ },
281
+ recursive: {
282
+ type: 'boolean',
283
+ short: 'r',
284
+ description: 'Operate recursively on directories'
275
285
  },
276
- examples: '# Example\n$ my-app --operation list --path ./src'
286
+ operation: {
287
+ type: 'string',
288
+ short: 'o',
289
+ required: true,
290
+ description: 'Operation to perform (list, copy, move, delete)'
291
+ }
277
292
  },
293
+ // define examples
294
+ examples: '# Example\n$ my-app --operation list --path ./src',
278
295
  run: ctx => {
279
296
  // command implementation
280
297
  }
281
298
  }
282
299
 
283
300
  // run with --help to see the automatically generated usage information
284
- cli(process.argv.slice(2), command, {
301
+ await cli(process.argv.slice(2), command, {
285
302
  name: 'my-app',
286
303
  version: '1.0.0'
287
304
  })
@@ -324,7 +341,7 @@ const customUsageRenderer = ctx => {
324
341
  }
325
342
 
326
343
  // run with custom renderers
327
- cli(
344
+ await cli(
328
345
  process.argv.slice(2),
329
346
  { name: 'app', run: () => {} },
330
347
  {
@@ -350,8 +367,14 @@ import enUS from './locales/en-US.json' with { type: 'json' }
350
367
  const command = {
351
368
  name: 'greeter',
352
369
  options: {
353
- name: { type: 'string', short: 'n' },
354
- formal: { type: 'boolean', short: 'f' }
370
+ name: {
371
+ type: 'string',
372
+ short: 'n'
373
+ },
374
+ formal: {
375
+ type: 'boolean',
376
+ short: 'f'
377
+ }
355
378
  },
356
379
  // resource fetcher for translations
357
380
  resource: async ctx => {
@@ -371,7 +394,7 @@ const command = {
371
394
  }
372
395
 
373
396
  // run with locale support
374
- cli(process.argv.slice(2), command, {
397
+ await cli(process.argv.slice(2), command, {
375
398
  name: 'my-app',
376
399
  version: '1.0.0',
377
400
  // set the locale via an environment variable
@@ -394,7 +417,8 @@ If you are interested in contributing to `gunshi`, I highly recommend checking o
394
417
 
395
418
  This project is inspired and powered by:
396
419
 
397
- - [`citty`](https://github.com/unjs/citty), created by UnJS team and contributors
420
+ - [`citty`](https://github.com/unjs/citty), created by [UnJS team](https://github.com/unjs) and contributors
421
+ - [`ordana`](https://github.com/sapphi-red/ordana), createdy by [sapphi-red](https://github.com/sapphi-red), inspired documentation generation
398
422
  - cline and claude 3.7 sonnet, examples and docs is generated
399
423
 
400
424
  Thank you!
@@ -0,0 +1,96 @@
1
+ import { createCommandContext } from "./context-BeFe0i70.js";
2
+ import { COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, create, resolveLazyCommand } from "./utils-DhI1qcqR.js";
3
+ import { renderHeader, renderUsage, renderValidationErrors } from "./renderer-CZGhbue4.js";
4
+ import { parseArgs, resolveArgs } from "args-tokens";
5
+
6
+ //#region src/cli.ts
7
+ async function cli(args, entry, opts = {}) {
8
+ const tokens = parseArgs(args);
9
+ const subCommand = getSubCommand(tokens);
10
+ const resolvedCommandOptions = resolveCommandOptions(opts, entry);
11
+ const [name, command] = await resolveCommand(subCommand, entry, resolvedCommandOptions);
12
+ if (!command) throw new Error(`Command not found: ${name || ""}`);
13
+ const options = resolveArgOptions(command.options);
14
+ const { values, positionals, error } = resolveArgs(options, tokens);
15
+ const omitted = !subCommand;
16
+ const ctx = await createCommandContext({
17
+ options,
18
+ values,
19
+ positionals,
20
+ omitted,
21
+ command,
22
+ commandOptions: resolvedCommandOptions
23
+ });
24
+ if (values.version) {
25
+ showVersion(ctx);
26
+ return;
27
+ }
28
+ const usageBuffer = [];
29
+ const header = await showHeader(ctx);
30
+ if (header) usageBuffer.push(header);
31
+ if (values.help) {
32
+ const usage = await showUsage(ctx);
33
+ if (usage) usageBuffer.push(usage);
34
+ return usageBuffer.join("\n");
35
+ }
36
+ if (error) {
37
+ await showValidationErrors(ctx, error);
38
+ return;
39
+ }
40
+ await command.run(ctx);
41
+ }
42
+ function resolveArgOptions(options) {
43
+ return Object.assign(create(), options, COMMON_OPTIONS);
44
+ }
45
+ function resolveCommandOptions(options, entry) {
46
+ const subCommands = new Map(options.subCommands);
47
+ if (typeof entry === "object" && entry.name) subCommands.set(entry.name, entry);
48
+ const resolvedOptions = Object.assign(create(), COMMAND_OPTIONS_DEFAULT, options, { subCommands });
49
+ return resolvedOptions;
50
+ }
51
+ function getSubCommand(tokens) {
52
+ const firstToken = tokens[0];
53
+ return firstToken && firstToken.kind === "positional" && firstToken.index === 0 && firstToken.value ? firstToken.value : "";
54
+ }
55
+ async function showUsage(ctx) {
56
+ if (ctx.env.renderUsage === null) return;
57
+ const usage = await (ctx.env.renderUsage || renderUsage)(ctx);
58
+ if (usage) {
59
+ ctx.log(usage);
60
+ return usage;
61
+ }
62
+ }
63
+ function showVersion(ctx) {
64
+ ctx.log(ctx.env.version);
65
+ }
66
+ async function showHeader(ctx) {
67
+ if (ctx.env.renderHeader === null) return;
68
+ const header = await (ctx.env.renderHeader || renderHeader)(ctx);
69
+ if (header) {
70
+ ctx.log(header);
71
+ ctx.log();
72
+ return header;
73
+ }
74
+ }
75
+ async function showValidationErrors(ctx, error) {
76
+ if (ctx.env.renderValidationErrors === null) return;
77
+ const render = ctx.env.renderValidationErrors || renderValidationErrors;
78
+ ctx.log(await render(ctx, error));
79
+ }
80
+ async function resolveCommand(sub, entry, options) {
81
+ const omitted = !sub;
82
+ if (typeof entry === "function") return [void 0, {
83
+ run: entry,
84
+ default: true
85
+ }];
86
+ else if (omitted) return typeof entry === "object" ? [entry.name, await resolveLazyCommand(entry, void 0, true)] : [void 0, void 0];
87
+ else {
88
+ if (options.subCommands == null) return [sub, void 0];
89
+ const cmd = options.subCommands?.get(sub);
90
+ if (cmd == null) return [sub, void 0];
91
+ return [sub, await resolveLazyCommand(cmd, sub)];
92
+ }
93
+ }
94
+
95
+ //#endregion
96
+ export { cli };
@@ -1,4 +1,4 @@
1
- import { BUILT_IN_PREFIX, COMMAND_OPTIONS_DEFAULT, DEFAULT_LOCALE, create, deepFreeze, mapResourceWithBuiltinKey, resolveLazyCommand } from "./utils-CU_LSsUg.js";
1
+ import { BUILT_IN_PREFIX, COMMAND_OPTIONS_DEFAULT, DEFAULT_LOCALE, NOOP, create, deepFreeze, log, mapResourceWithBuiltinKey, resolveLazyCommand } from "./utils-DhI1qcqR.js";
2
2
 
3
3
  //#region locales/en-US.json
4
4
  var COMMAND = "COMMAND";
@@ -43,7 +43,7 @@ var DefaultTranslation = class {
43
43
  getMessage(locale, key) {
44
44
  const resource = this.getResource(locale);
45
45
  if (resource) return resource[key];
46
- return undefined;
46
+ return void 0;
47
47
  }
48
48
  translate(locale, key, _values = create()) {
49
49
  /**
@@ -61,30 +61,21 @@ var DefaultTranslation = class {
61
61
  const BUILT_IN_PREFIX_CODE = BUILT_IN_PREFIX.codePointAt(0);
62
62
  async function createCommandContext({ options, values, positionals, command, commandOptions, omitted = false }) {
63
63
  /**
64
- * tweak the options and values
64
+ * normailize the options schema and values, to avoid prototype pollution
65
65
  */
66
66
  const _options = Object.entries(options).reduce((acc, [key, value]) => {
67
67
  acc[key] = Object.assign(create(), value);
68
68
  return acc;
69
69
  }, create());
70
- const _values = Object.assign(create(), values);
71
- /**
72
- * normalize the usage
73
- */
74
- const usage = Object.assign(create(), command.usage);
75
- const { help: help$1, version: version$1 } = en_US_default;
76
- usage.options = Object.assign(create(), usage.options, {
77
- help: help$1,
78
- version: version$1
79
- });
80
70
  /**
81
71
  * setup the environment
82
72
  */
83
73
  const env = Object.assign(create(), COMMAND_OPTIONS_DEFAULT, commandOptions);
84
74
  const locale = resolveLocale(commandOptions.locale);
75
+ const localeStr = locale.toString();
85
76
  const translationAdapterFactory = commandOptions.translationAdapterFactory || createTranslationAdapter;
86
77
  const adapter = translationAdapterFactory({
87
- locale: locale.toString(),
78
+ locale: localeStr,
88
79
  fallbackLocale: DEFAULT_LOCALE
89
80
  });
90
81
  const localeResources = new Map();
@@ -93,9 +84,9 @@ async function createCommandContext({ options, values, positionals, command, com
93
84
  * load the built-in locale resources
94
85
  */
95
86
  localeResources.set(DEFAULT_LOCALE, mapResourceWithBuiltinKey(en_US_default));
96
- if (DEFAULT_LOCALE !== locale.toString()) try {
97
- builtInLoadedResources = await import(`../locales/${locale.toString()}.json`, { with: { type: "json" } });
98
- localeResources.set(locale.toString(), mapResourceWithBuiltinKey(builtInLoadedResources));
87
+ if (DEFAULT_LOCALE !== localeStr) try {
88
+ builtInLoadedResources = await import(`../locales/${localeStr}.json`, { with: { type: "json" } });
89
+ localeResources.set(localeStr, mapResourceWithBuiltinKey(builtInLoadedResources));
99
90
  } catch {}
100
91
  /**
101
92
  * define the translation function, which is used to {@link CommandContext.translate}.
@@ -104,7 +95,7 @@ async function createCommandContext({ options, values, positionals, command, com
104
95
  function translate(key, values$1 = create()) {
105
96
  const strKey = key;
106
97
  if (strKey.codePointAt(0) === BUILT_IN_PREFIX_CODE) {
107
- const resource = localeResources.get(locale.toString()) || localeResources.get(DEFAULT_LOCALE);
98
+ const resource = localeResources.get(localeStr) || localeResources.get(DEFAULT_LOCALE);
108
99
  return resource[strKey] || strKey;
109
100
  } else return adapter.translate(locale.toString(), strKey, values$1) || "";
110
101
  }
@@ -114,7 +105,7 @@ async function createCommandContext({ options, values, positionals, command, com
114
105
  let cachedCommands;
115
106
  async function loadCommands() {
116
107
  if (cachedCommands) return cachedCommands;
117
- const subCommands = [...env.subCommands || []];
108
+ const subCommands = [...commandOptions.subCommands || []];
118
109
  return cachedCommands = await Promise.all(subCommands.map(async ([name, cmd]) => await resolveLazyCommand(cmd, name)));
119
110
  }
120
111
  /**
@@ -127,25 +118,25 @@ async function createCommandContext({ options, values, positionals, command, com
127
118
  locale,
128
119
  env,
129
120
  options: _options,
130
- values: _values,
121
+ values,
131
122
  positionals,
132
- usage,
123
+ log: commandOptions.usageSilent ? NOOP : log,
133
124
  loadCommands,
134
125
  translate
135
126
  }));
136
127
  /**
137
128
  * load the command resources
138
129
  */
139
- const loadedOptionsResources = Object.entries(usage.options || create()).map(([key, _]) => {
140
- const option = usage.options[key];
141
- return [key, option];
130
+ const loadedOptionsResources = Object.entries(options).map(([key, option]) => {
131
+ const description = option.description || "";
132
+ return [key, description];
142
133
  });
143
134
  const defaultCommandResource = loadedOptionsResources.reduce((res, [key, value]) => {
144
135
  res[key] = value;
145
136
  return res;
146
137
  }, create());
147
138
  defaultCommandResource.description = command.description || "";
148
- defaultCommandResource.examples = usage.examples || "";
139
+ defaultCommandResource.examples = command.examples || "";
149
140
  adapter.setResource(DEFAULT_LOCALE, defaultCommandResource);
150
141
  const originalResource = await loadCommandResource(ctx, command);
151
142
  if (originalResource) {
@@ -157,7 +148,7 @@ async function createCommandContext({ options, values, positionals, command, com
157
148
  resource.help = builtInLoadedResources.help;
158
149
  resource.version = builtInLoadedResources.version;
159
150
  }
160
- adapter.setResource(locale.toString(), resource);
151
+ adapter.setResource(localeStr, resource);
161
152
  }
162
153
  return ctx;
163
154
  }
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-CX4RmDVT.js';
2
+ import { C as Command, a as CommandOptions, b as CommandContext } from './types.d-DTe4N67v.js';
3
3
 
4
4
  /**
5
5
  * Parameters of {@link createCommandContext}
@@ -40,7 +40,7 @@ interface CommandContextParams<
40
40
  */
41
41
  declare function createCommandContext<
42
42
  Options extends ArgOptions = ArgOptions,
43
- Values = ArgValues<Options>
43
+ Values extends ArgValues<Options> = ArgValues<Options>
44
44
  >({ options, values, positionals, command, commandOptions, omitted }: CommandContextParams<Options, Values>): Promise<Readonly<CommandContext<Options, Values>>>;
45
45
 
46
46
  export { createCommandContext };
package/lib/context.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createCommandContext } from "./context-DYokJ5k3.js";
2
- import "./utils-CU_LSsUg.js";
1
+ import { createCommandContext } from "./context-BeFe0i70.js";
2
+ import "./utils-DhI1qcqR.js";
3
3
 
4
4
  export { createCommandContext };
@@ -0,0 +1,13 @@
1
+ import { ArgOptions } from 'args-tokens';
2
+ import { C as Command, c as CommandRunner, a as CommandOptions } from './types.d-DTe4N67v.js';
3
+
4
+ /**
5
+ * Generate the command usage
6
+ * @param command - usage generate command, if you want to generate the usage of the default command where there are target commands and sub-commands, specify `null`.
7
+ * @param entry - A {@link Command | entry command} or an {@link CommandRunner | inline command runner}
8
+ * @param opts - A {@link CommandOptions | command options}
9
+ * @returns A rendered usage
10
+ */
11
+ declare function generate<Options extends ArgOptions = ArgOptions>(command: string | null, entry: Command<Options> | CommandRunner<Options>, opts?: CommandOptions<Options>): Promise<string>;
12
+
13
+ export { generate };
@@ -0,0 +1,17 @@
1
+ import "./context-BeFe0i70.js";
2
+ import { create } from "./utils-DhI1qcqR.js";
3
+ import "./renderer-CZGhbue4.js";
4
+ import { cli } from "./cli-BmK-tBjI.js";
5
+
6
+ //#region src/generator.ts
7
+ async function generate(command, entry, opts = {}) {
8
+ const args = ["-h"];
9
+ if (command != null) args.unshift(command);
10
+ return await cli(args, entry, Object.assign(create(), opts, {
11
+ usageSilent: true,
12
+ __proto__: null
13
+ })) || "";
14
+ }
15
+
16
+ //#endregion
17
+ export { generate };
package/lib/index.d.ts CHANGED
@@ -1,13 +1,14 @@
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, T as TranslationAdapter, d as TranslationAdapterFactoryOptions } from './types.d-CX4RmDVT.js';
4
- export { g as CommandBuiltinKeys, e as CommandBuiltinOptionsKeys, f as CommandBuiltinResourceKeys, b as CommandContext, h as CommandEnvironment, i as CommandResource, j as CommandResourceFetcher, l as Commandable, G as GenerateNamespacedKey, L as LazyCommand, k as TranslationAdapterFactory } from './types.d-CX4RmDVT.js';
3
+ import { C as Command, c as CommandRunner, a as CommandOptions, T as TranslationAdapter, d as TranslationAdapterFactoryOptions } from './types.d-DTe4N67v.js';
4
+ export { g as CommandBuiltinKeys, e as CommandBuiltinOptionsKeys, f as CommandBuiltinResourceKeys, b as CommandContext, h as CommandEnvironment, i as CommandResource, j as CommandResourceFetcher, l as Commandable, G as GenerateNamespacedKey, L as LazyCommand, k as TranslationAdapterFactory } from './types.d-DTe4N67v.js';
5
5
 
6
6
  /**
7
7
  * Run the command
8
8
  * @param args - command line arguments
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
+ * @returns A rendered usage or undefined. if you will use {@link CommandOptions.usageSilent} option, it will return rendered usage string.
11
12
  */
12
13
  declare function cli<Options extends ArgOptions = ArgOptions>(args: string[], entry: Command<Options> | CommandRunner<Options>, opts?: CommandOptions<Options>): Promise<string | undefined>;
13
14
 
package/lib/index.js CHANGED
@@ -1,96 +1,6 @@
1
- import { DefaultTranslation, createCommandContext } from "./context-DYokJ5k3.js";
2
- import { COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, create, log, resolveLazyCommand } from "./utils-CU_LSsUg.js";
3
- import { renderHeader, renderUsage, renderValidationErrors } from "./renderer-KLx2dW-W.js";
4
- import { parseArgs, resolveArgs } from "args-tokens";
1
+ import { DefaultTranslation } from "./context-BeFe0i70.js";
2
+ import "./utils-DhI1qcqR.js";
3
+ import "./renderer-CZGhbue4.js";
4
+ import { cli } from "./cli-BmK-tBjI.js";
5
5
 
6
- //#region src/cli.ts
7
- async function cli(args, entry, opts = {}) {
8
- const tokens = parseArgs(args);
9
- const subCommand = getSubCommand(tokens);
10
- const resolvedCommandOptions = resolveCommandOptions(opts, entry);
11
- const [name, command] = await resolveCommand(subCommand, entry, resolvedCommandOptions);
12
- if (!command) throw new Error(`Command not found: ${name || ""}`);
13
- const options = resolveArgOptions(command.options);
14
- const { values, positionals, error } = resolveArgs(options, tokens);
15
- const omitted = !subCommand;
16
- const ctx = await createCommandContext({
17
- options,
18
- values,
19
- positionals,
20
- omitted,
21
- command,
22
- commandOptions: resolvedCommandOptions
23
- });
24
- if (values.version) {
25
- showVersion(ctx);
26
- return;
27
- }
28
- const usageBuffer = [];
29
- const header = await showHeader(ctx);
30
- if (header) usageBuffer.push(header);
31
- if (values.help) {
32
- const usage = await showUsage(ctx);
33
- if (usage) usageBuffer.push(usage);
34
- return usageBuffer.join("\n");
35
- }
36
- if (error) {
37
- await showValidationErrors(ctx, error);
38
- return;
39
- }
40
- await command.run(ctx);
41
- }
42
- function resolveArgOptions(options) {
43
- return Object.assign(create(), options, COMMON_OPTIONS);
44
- }
45
- function resolveCommandOptions(options, entry) {
46
- const subCommands = new Map(options.subCommands);
47
- if (typeof entry === "object" && entry.name) subCommands.set(entry.name, entry);
48
- const resolvedOptions = Object.assign(create(), COMMAND_OPTIONS_DEFAULT, options, { subCommands });
49
- return resolvedOptions;
50
- }
51
- function getSubCommand(tokens) {
52
- const firstToken = tokens[0];
53
- return firstToken && firstToken.kind === "positional" && firstToken.index === 0 && firstToken.value ? firstToken.value : "";
54
- }
55
- async function showUsage(ctx) {
56
- if (ctx.env.renderUsage === null) return;
57
- const usage = await (ctx.env.renderUsage || renderUsage)(ctx);
58
- if (usage) {
59
- log(usage);
60
- return usage;
61
- }
62
- }
63
- function showVersion(ctx) {
64
- log(ctx.env.version);
65
- }
66
- async function showHeader(ctx) {
67
- if (ctx.env.renderHeader === null) return;
68
- const header = await (ctx.env.renderHeader || renderHeader)(ctx);
69
- if (header) {
70
- log(header);
71
- log();
72
- return header;
73
- }
74
- }
75
- async function showValidationErrors(ctx, error) {
76
- if (ctx.env.renderValidationErrors === null) return;
77
- const render = ctx.env.renderValidationErrors || renderValidationErrors;
78
- log(await render(ctx, error));
79
- }
80
- async function resolveCommand(sub, entry, options) {
81
- const omitted = !sub;
82
- if (typeof entry === "function") return [undefined, {
83
- run: entry,
84
- default: true
85
- }];
86
- else if (omitted) return typeof entry === "object" ? [entry.name, await resolveLazyCommand(entry, undefined, true)] : [undefined, undefined];
87
- else {
88
- if (options.subCommands == null) return [sub, undefined];
89
- const cmd = options.subCommands?.get(sub);
90
- if (cmd == null) return [sub, undefined];
91
- return [sub, await resolveLazyCommand(cmd, sub)];
92
- }
93
- }
94
-
95
- //#endregion
96
6
  export { DefaultTranslation, cli };
@@ -1,5 +1,5 @@
1
1
  import { ArgOptions } from 'args-tokens';
2
- import { b as CommandContext } from '../types.d-CX4RmDVT.js';
2
+ import { b as CommandContext } from '../types.d-DTe4N67v.js';
3
3
 
4
4
  /**
5
5
  * Render the header
@@ -1,4 +1,4 @@
1
- import "../utils-CU_LSsUg.js";
2
- import { renderHeader, renderUsage, renderValidationErrors } from "../renderer-KLx2dW-W.js";
1
+ import "../utils-DhI1qcqR.js";
2
+ import { renderHeader, renderUsage, renderValidationErrors } from "../renderer-CZGhbue4.js";
3
3
 
4
4
  export { renderHeader, renderUsage, renderValidationErrors };
@@ -1,4 +1,4 @@
1
- import { create, resolveBuiltInKey } from "./utils-CU_LSsUg.js";
1
+ import { create, resolveBuiltInKey } from "./utils-DhI1qcqR.js";
2
2
 
3
3
  //#region src/renderer/header.ts
4
4
  function renderHeader(ctx) {
@@ -17,7 +17,8 @@ async function renderUsage(ctx) {
17
17
  messages.push(...await renderUsageSection(ctx), "");
18
18
  if (ctx.omitted && await hasCommands(ctx)) messages.push(...await renderCommandsSection(ctx), "");
19
19
  if (hasOptions(ctx)) messages.push(...await renderOptionsSection(ctx), "");
20
- if (hasExamples(ctx)) messages.push(...renderExamplesSection(ctx), "");
20
+ const examples = renderExamplesSection(ctx);
21
+ if (examples.length > 0) messages.push(...examples, "");
21
22
  return messages.join("\n");
22
23
  }
23
24
  /**
@@ -38,8 +39,11 @@ async function renderOptionsSection(ctx) {
38
39
  */
39
40
  function renderExamplesSection(ctx) {
40
41
  const messages = [];
41
- const examples = ctx.usage.examples.split("\n").map((example) => example.padStart(ctx.env.leftMargin + example.length));
42
- messages.push(`${ctx.translate(resolveBuiltInKey("EXAMPLES"))}:`, ...examples);
42
+ const resolvedExamples = resolveExamples(ctx);
43
+ if (resolvedExamples) {
44
+ const examples = resolvedExamples.split("\n").map((example) => example.padStart(ctx.env.leftMargin + example.length));
45
+ messages.push(`${ctx.translate(resolveBuiltInKey("EXAMPLES"))}:`, ...examples);
46
+ }
43
47
  return messages;
44
48
  }
45
49
  /**
@@ -109,6 +113,17 @@ function resolveDescription(ctx) {
109
113
  return ctx.translate("description") || ctx.description || "";
110
114
  }
111
115
  /**
116
+ * Resolve the command examples
117
+ * @param ctx A {@link CommandContext | command context}
118
+ * @returns resolved command examples, if not resolved, return empty string
119
+ */
120
+ function resolveExamples(ctx) {
121
+ const ret = ctx.translate("examples");
122
+ if (ret) return ret;
123
+ const command = ctx.env.subCommands?.get(ctx.name || "");
124
+ return command?.examples ?? "";
125
+ }
126
+ /**
112
127
  * Check if the command has sub commands
113
128
  * @param ctx A {@link CommandContext | command context}
114
129
  * @returns True if the command has sub commands
@@ -126,14 +141,6 @@ function hasOptions(ctx) {
126
141
  return !!(ctx.options && Object.keys(ctx.options).length > 0);
127
142
  }
128
143
  /**
129
- * Check if the command has examples
130
- * @param ctx A {@link CommandContext | command context}
131
- * @returns True if the command has examples
132
- */
133
- function hasExamples(ctx) {
134
- return !!ctx.usage.examples;
135
- }
136
- /**
137
144
  * Check if all options have default values
138
145
  * @param ctx A {@link CommandContext | command context}
139
146
  * @returns True if all options have default values
@@ -147,7 +154,7 @@ function hasAllDefaultOptions(ctx) {
147
154
  * @returns Options symbols for usage
148
155
  */
149
156
  function generateOptionsSymbols(ctx) {
150
- return hasOptions(ctx) ? hasAllDefaultOptions(ctx) ? `[${ctx.translate("_:OPTIONS")}]` : `<${ctx.translate("_:OPTIONS")}>` : "";
157
+ return hasOptions(ctx) ? hasAllDefaultOptions(ctx) ? `[${ctx.translate(resolveBuiltInKey("OPTIONS"))}]` : `<${ctx.translate(resolveBuiltInKey("OPTIONS"))}>` : "";
151
158
  }
152
159
  /**
153
160
  * Get options pairs for usage
@@ -6,14 +6,17 @@ import { ArgOptions, ArgValues } from 'args-tokens';
6
6
  declare const DEFAULT_LOCALE = "en-US";
7
7
  declare const BUILT_IN_PREFIX = "_";
8
8
  declare const BUILT_IN_KEY_SEPARATOR = ":";
9
+ declare const NOOP: () => void;
9
10
  type CommonOptionType = {
10
11
  readonly help: {
11
12
  readonly type: "boolean"
12
13
  readonly short: "h"
14
+ readonly description: string
13
15
  }
14
16
  readonly version: {
15
17
  readonly type: "boolean"
16
18
  readonly short: "v"
19
+ readonly description: string
17
20
  }
18
21
  };
19
22
  declare const COMMON_OPTIONS: CommonOptionType;
@@ -26,8 +29,9 @@ declare const __constants_COMMAND_BUILTIN_RESOURCE_KEYS: typeof COMMAND_BUILTIN_
26
29
  declare const __constants_COMMAND_OPTIONS_DEFAULT: typeof COMMAND_OPTIONS_DEFAULT;
27
30
  declare const __constants_COMMON_OPTIONS: typeof COMMON_OPTIONS;
28
31
  declare const __constants_DEFAULT_LOCALE: typeof DEFAULT_LOCALE;
32
+ declare const __constants_NOOP: typeof NOOP;
29
33
  declare namespace __constants {
30
- export { __constants_BUILT_IN_KEY_SEPARATOR as BUILT_IN_KEY_SEPARATOR, __constants_BUILT_IN_PREFIX as BUILT_IN_PREFIX, __constants_COMMAND_BUILTIN_RESOURCE_KEYS as COMMAND_BUILTIN_RESOURCE_KEYS, __constants_COMMAND_OPTIONS_DEFAULT as COMMAND_OPTIONS_DEFAULT, __constants_COMMON_OPTIONS as COMMON_OPTIONS, __constants_DEFAULT_LOCALE as DEFAULT_LOCALE };
34
+ export { __constants_BUILT_IN_KEY_SEPARATOR as BUILT_IN_KEY_SEPARATOR, __constants_BUILT_IN_PREFIX as BUILT_IN_PREFIX, __constants_COMMAND_BUILTIN_RESOURCE_KEYS as COMMAND_BUILTIN_RESOURCE_KEYS, __constants_COMMAND_OPTIONS_DEFAULT as COMMAND_OPTIONS_DEFAULT, __constants_COMMON_OPTIONS as COMMON_OPTIONS, __constants_DEFAULT_LOCALE as DEFAULT_LOCALE, __constants_NOOP as NOOP };
31
35
  }
32
36
 
33
37
  /**
@@ -98,6 +102,12 @@ interface CommandEnvironment<Options extends ArgOptions = ArgOptions> {
98
102
  */
99
103
  usageOptionType: boolean;
100
104
  /**
105
+ * Whether to display the command usage
106
+ * @default false
107
+ * @see {@link}
108
+ */
109
+ usageSilent: boolean;
110
+ /**
101
111
  * Sub commands
102
112
  * @see {@link CommandOptions.subCommands}
103
113
  */
@@ -157,6 +167,10 @@ interface CommandOptions<Options extends ArgOptions = ArgOptions> {
157
167
  */
158
168
  usageOptionType?: boolean;
159
169
  /**
170
+ * Whether to display the command usage
171
+ */
172
+ usageSilent?: boolean;
173
+ /**
160
174
  * Render function the command usage
161
175
  */
162
176
  renderUsage?: ((ctx: Readonly<CommandContext<Options>>) => Promise<string>) | null;
@@ -200,7 +214,7 @@ interface CommandContext<
200
214
  * Command environment, that is the environment of the command that is executed
201
215
  * @description The command environment is same {@link CommandEnvironment}
202
216
  */
203
- env: CommandEnvironment<Options>;
217
+ env: Readonly<CommandEnvironment<Options>>;
204
218
  /**
205
219
  * Command options, that is the options of the command that is executed
206
220
  * @description The command options is same {@link Command.options}
@@ -221,10 +235,12 @@ interface CommandContext<
221
235
  */
222
236
  omitted: boolean;
223
237
  /**
224
- * Command usage
225
- * @description Usage of the command is same {@link Command.usage}, and more has `--help` and `--version` options
238
+ * Output a message
239
+ * @description if {@link CommandEnvironment.usageSilent} is true, the message is not output
240
+ * @param message an output message, @see {@link console.log}
241
+ * @param optionalParams an optional parameters, @see {@link console.log}
226
242
  */
227
- usage: CommandUsage<Options>;
243
+ log: (message?: any, ...optionalParams: any[]) => void;
228
244
  /**
229
245
  * Load sub-commands
230
246
  * @description The loaded commands are cached and returned when called again
@@ -244,19 +260,6 @@ interface CommandContext<
244
260
  >(key: Key, values?: Record<string, unknown>) => string;
245
261
  }
246
262
  /**
247
- * Command usage
248
- */
249
- interface CommandUsage<Options extends ArgOptions = ArgOptions> {
250
- /**
251
- * Options usage
252
- */
253
- options?: { [Option in keyof Options] : string };
254
- /**
255
- * Examples usage
256
- */
257
- examples?: string;
258
- }
259
- /**
260
263
  * Command interface
261
264
  */
262
265
  interface Command<Options extends ArgOptions = ArgOptions> {
@@ -268,8 +271,7 @@ interface Command<Options extends ArgOptions = ArgOptions> {
268
271
  name?: string;
269
272
  /**
270
273
  * Command description
271
- * @description
272
- * Command description is used to describe the command in usage, so it's recommended to specify.
274
+ * @description command description is used to describe the command in usage, so it's recommended to specify.
273
275
  */
274
276
  description?: string;
275
277
  /**
@@ -279,14 +281,14 @@ interface Command<Options extends ArgOptions = ArgOptions> {
279
281
  default?: boolean;
280
282
  /**
281
283
  * Command options
284
+ * @description each option can include a description property to describe the option in usage.
282
285
  */
283
286
  options?: Options;
284
287
  /**
285
- * Command usage
286
- * @description
287
- * Command usage is used to describe the command in usage, so it's recommended to specify.
288
+ * Command examples
289
+ * @description examples of how to use the command.
288
290
  */
289
- usage?: CommandUsage<Options>;
291
+ examples?: string;
290
292
  /**
291
293
  * Command runner, that's the command to be executed
292
294
  */
@@ -319,7 +321,10 @@ type CommandResource<Options extends ArgOptions = ArgOptions> = {
319
321
  * @returns A fetched {@link CommandResource | command resource}
320
322
  * @experimental
321
323
  */
322
- type CommandResourceFetcher<Options extends ArgOptions = ArgOptions> = (ctx: Readonly<CommandContext<Options>>) => Promise<CommandResource<Options>>;
324
+ type CommandResourceFetcher<
325
+ Options extends ArgOptions = ArgOptions,
326
+ Values = ArgValues<Options>
327
+ > = (ctx: Readonly<CommandContext<Options, Values>>) => Promise<CommandResource<Options>>;
323
328
  /**
324
329
  * Translation adapter factory
325
330
  */
@@ -3,29 +3,33 @@
3
3
  const DEFAULT_LOCALE = "en-US";
4
4
  const BUILT_IN_PREFIX = "_";
5
5
  const BUILT_IN_KEY_SEPARATOR = ":";
6
+ const NOOP = () => {};
6
7
  const COMMON_OPTIONS = {
7
8
  help: {
8
9
  type: "boolean",
9
- short: "h"
10
+ short: "h",
11
+ description: "Display this help message"
10
12
  },
11
13
  version: {
12
14
  type: "boolean",
13
- short: "v"
15
+ short: "v",
16
+ description: "Display this version"
14
17
  }
15
18
  };
16
19
  const COMMAND_OPTIONS_DEFAULT = {
17
- name: undefined,
18
- description: undefined,
19
- version: undefined,
20
- cwd: undefined,
21
- subCommands: undefined,
20
+ name: void 0,
21
+ description: void 0,
22
+ version: void 0,
23
+ cwd: void 0,
24
+ usageSilent: false,
25
+ subCommands: void 0,
22
26
  leftMargin: 2,
23
27
  middleMargin: 10,
24
28
  usageOptionType: false,
25
- renderHeader: undefined,
26
- renderUsage: undefined,
27
- renderValidationErrors: undefined,
28
- translationAdapterFactory: undefined
29
+ renderHeader: void 0,
30
+ renderUsage: void 0,
31
+ renderValidationErrors: void 0,
32
+ translationAdapterFactory: void 0
29
33
  };
30
34
 
31
35
  //#endregion
@@ -60,4 +64,4 @@ function deepFreeze(obj) {
60
64
  }
61
65
 
62
66
  //#endregion
63
- export { BUILT_IN_PREFIX, COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, DEFAULT_LOCALE, create, deepFreeze, log, mapResourceWithBuiltinKey, resolveBuiltInKey, resolveLazyCommand };
67
+ export { BUILT_IN_PREFIX, COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, DEFAULT_LOCALE, NOOP, create, deepFreeze, log, mapResourceWithBuiltinKey, resolveBuiltInKey, resolveLazyCommand };
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.8.0",
4
+ "version": "0.10.0",
5
5
  "author": {
6
6
  "name": "kazuya kawaguchi",
7
7
  "email": "kawakazu80@gmail.com"
@@ -54,6 +54,12 @@
54
54
  "require": "./lib/renderer/index.js",
55
55
  "default": "./lib/renderer/index.js"
56
56
  },
57
+ "./generator": {
58
+ "types": "./lib/generator.d.ts",
59
+ "import": "./lib/generator.js",
60
+ "require": "./lib/generator.js",
61
+ "default": "./lib/generator.js"
62
+ },
57
63
  "./package.json": "./package.json",
58
64
  "./*": "./*"
59
65
  },
@@ -67,33 +73,39 @@
67
73
  }
68
74
  },
69
75
  "dependencies": {
70
- "args-tokens": "^0.10.2"
76
+ "args-tokens": "^0.12.0"
71
77
  },
72
78
  "devDependencies": {
73
79
  "@eslint/markdown": "^6.2.2",
74
80
  "@intlify/core": "next",
75
- "@kazupon/eslint-config": "^0.22.0",
81
+ "@kazupon/eslint-config": "^0.26.1",
76
82
  "@kazupon/prettier-config": "^0.1.1",
77
83
  "@types/node": "^22.13.9",
78
84
  "@vitest/eslint-plugin": "^1.1.36",
79
85
  "bumpp": "^10.0.3",
80
- "eslint": "^9.21.0",
86
+ "eslint": "^9.22.0",
81
87
  "eslint-config-prettier": "^10.0.2",
88
+ "eslint-import-resolver-typescript": "^4.2.2",
89
+ "eslint-plugin-import": "^2.31.0",
82
90
  "eslint-plugin-jsonc": "^2.19.1",
91
+ "eslint-plugin-module-interop": "^0.3.0",
83
92
  "eslint-plugin-promise": "^7.2.1",
84
93
  "eslint-plugin-regexp": "^2.7.0",
85
- "eslint-plugin-unicorn": "^57.0.0",
94
+ "eslint-plugin-unicorn": "^58.0.0",
95
+ "eslint-plugin-unused-imports": "^4.1.4",
86
96
  "eslint-plugin-yml": "^1.17.0",
87
97
  "gh-changelogen": "^0.2.8",
88
98
  "jsr": "^0.13.4",
89
99
  "knip": "^5.45.0",
90
100
  "lint-staged": "^15.4.3",
91
- "messageformat": "4.0.0-9",
101
+ "messageformat": "4.0.0-10",
92
102
  "pkg-pr-new": "^0.0.41",
93
103
  "prettier": "^3.5.3",
94
104
  "tsdown": "^0.6.4",
95
105
  "typescript": "^5.4.2",
96
106
  "typescript-eslint": "^8.26.0",
107
+ "vitepress": "^1.6.3",
108
+ "vitepress-plugin-group-icons": "^1.3.8",
97
109
  "vitest": "^3.0.7"
98
110
  },
99
111
  "prettier": "@kazupon/prettier-config",
@@ -116,6 +128,9 @@
116
128
  "clean": "git clean -df",
117
129
  "dev": "pnpx @eslint/config-inspector --config eslint.config.ts",
118
130
  "dev:eslint": "pnpx @eslint/config-inspector --config eslint.config.ts",
131
+ "docs:build": "vitepress build docs",
132
+ "docs:dev": "vitepress dev docs",
133
+ "docs:preview": "vitepress preview docs",
119
134
  "fix": "pnpm run --stream --color \"/^fix:/\"",
120
135
  "fix:eslint": "eslint . --fix",
121
136
  "fix:knip": "knip --fix --no-exit-code",