gunshi 0.5.2 → 0.5.4

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,12 @@ 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
65
+ // run a simple command
66
66
  cli(process.argv.slice(2), () => {
67
67
  console.log('Hello from Gunshi!')
68
68
  })
69
69
  ```
70
70
 
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
71
  ### ⚙️ Declarative Configuration
112
72
 
113
73
  Configure commands declaratively:
@@ -115,7 +75,7 @@ Configure commands declaratively:
115
75
  ```js
116
76
  import { cli } from 'gunshi'
117
77
 
118
- // Define a command with declarative configuration
78
+ // define a command with declarative configuration
119
79
  const command = {
120
80
  name: 'greet',
121
81
  description: 'A greeting command',
@@ -148,6 +108,52 @@ cli(process.argv.slice(2), command, {
148
108
 
149
109
  For more detailed examples, check out the [playground/declarative](https://github.com/kazupon/gunshi/tree/main/playground/declarative) in the repository.
150
110
 
111
+ ### 🛡️ Type-Safe Arguments
112
+
113
+ Gunshi provides type-safe argument parsing with TypeScript:
114
+
115
+ ```ts
116
+ import { cli } from 'gunshi'
117
+ import type { ArgOptions, Command, CommandContext } from 'gunshi'
118
+
119
+ // type-safe arguments parsing example
120
+ // this demonstrates how to define and use typed command options with `satisfies`
121
+
122
+ // define options with types
123
+ const options = {
124
+ // define string option with short alias
125
+ name: {
126
+ type: 'string',
127
+ short: 'n'
128
+ },
129
+ // define number option with default value
130
+ age: {
131
+ type: 'number',
132
+ short: 'a',
133
+ default: 25
134
+ },
135
+ // define boolean flag
136
+ verbose: {
137
+ type: 'boolean',
138
+ short: 'v'
139
+ }
140
+ } satisfies ArgOptions
141
+
142
+ // create a type-safe command
143
+ const command = {
144
+ name: 'type-safe',
145
+ options,
146
+ run: (ctx: CommandContext<UserOptions, UserValues>) => {
147
+ const { name, age, verbose } = ctx.values
148
+ console.log(`Hello, ${name || 'World'}! You are ${age} years old.`)
149
+ }
150
+ } satisfies Command<typeof options>
151
+
152
+ await cli(process.argv.slice(2), command)
153
+ ```
154
+
155
+ For more detailed examples, check out the [playground/type-safe](https://github.com/kazupon/gunshi/tree/main/playground/type-safe) in the repository.
156
+
151
157
  ### 🧩 Composable Sub-commands
152
158
 
153
159
  Create a CLI with composable sub-commands:
@@ -155,7 +161,7 @@ Create a CLI with composable sub-commands:
155
161
  ```js
156
162
  import { cli } from 'gunshi'
157
163
 
158
- // Define sub-commands
164
+ // define sub-commands
159
165
  const createCommand = {
160
166
  name: 'create',
161
167
  description: 'Create a new resource',
@@ -175,12 +181,12 @@ const listCommand = {
175
181
  }
176
182
  }
177
183
 
178
- // Create a Map of sub-commands
184
+ // create a Map of sub-commands
179
185
  const subCommands = new Map()
180
186
  subCommands.set('create', createCommand)
181
187
  subCommands.set('list', listCommand)
182
188
 
183
- // Define the main command
189
+ // define the main command
184
190
  const mainCommand = {
185
191
  name: 'resource-manager',
186
192
  description: 'Manage resources',
@@ -189,7 +195,7 @@ const mainCommand = {
189
195
  }
190
196
  }
191
197
 
192
- // Run the CLI with composable sub-commands
198
+ // run the CLI with composable sub-commands
193
199
  cli(process.argv.slice(2), mainCommand, {
194
200
  name: 'my-app',
195
201
  version: '1.0.0',
@@ -206,28 +212,28 @@ Load commands lazily and execute them asynchronously:
206
212
  ```js
207
213
  import { cli } from 'gunshi'
208
214
 
209
- // Define a command that will be loaded lazily
215
+ // define a command that will be loaded lazily
210
216
  const lazyCommand = async () => {
211
- // Simulate async loading
217
+ // simulate async loading
212
218
  await new Promise(resolve => setTimeout(resolve, 1000))
213
219
 
214
- // Return the actual command
220
+ // return the actual command
215
221
  return {
216
222
  name: 'lazy',
217
223
  description: 'A command that is loaded lazily',
218
224
  run: async ctx => {
219
- // Async execution
225
+ // async execution
220
226
  await new Promise(resolve => setTimeout(resolve, 500))
221
227
  console.log('Command executed!')
222
228
  }
223
229
  }
224
230
  }
225
231
 
226
- // Create a Map of sub-commands with lazy-loaded commands
232
+ // create a Map of sub-commands with lazy-loaded commands
227
233
  const subCommands = new Map()
228
234
  subCommands.set('lazy', lazyCommand)
229
235
 
230
- // Run the CLI with lazy-loaded commands
236
+ // run the CLI with lazy-loaded commands
231
237
  cli(
232
238
  process.argv.slice(2),
233
239
  { name: 'main', run: () => {} },
@@ -264,11 +270,11 @@ const command = {
264
270
  examples: '# Example\n$ my-app --operation list --path ./src'
265
271
  },
266
272
  run: ctx => {
267
- // Command implementation
273
+ // command implementation
268
274
  }
269
275
  }
270
276
 
271
- // Run with --help to see the automatically generated usage information
277
+ // run with --help to see the automatically generated usage information
272
278
  cli(process.argv.slice(2), command, {
273
279
  name: 'my-app',
274
280
  version: '1.0.0'
@@ -284,7 +290,7 @@ Customize the usage message generation:
284
290
  ```js
285
291
  import { cli } from 'gunshi'
286
292
 
287
- // Custom header renderer
293
+ // custom header renderer
288
294
  const customHeaderRenderer = ctx => {
289
295
  return Promise.resolve(`
290
296
  ╔═══════════════════════╗
@@ -295,7 +301,7 @@ Version: ${ctx.env.version}
295
301
  `)
296
302
  }
297
303
 
298
- // Custom usage renderer
304
+ // custom usage renderer
299
305
  const customUsageRenderer = ctx => {
300
306
  const lines = []
301
307
  lines.push('USAGE:')
@@ -303,7 +309,7 @@ const customUsageRenderer = ctx => {
303
309
  lines.push('')
304
310
  lines.push('OPTIONS:')
305
311
 
306
- for (const [key, option] of Object.entries(ctx.options || {})) {
312
+ for (const [key, option] of Object.entries(ctx.options || Object.create(null))) {
307
313
  const shortFlag = option.short ? `-${option.short}, ` : ' '
308
314
  lines.push(` ${shortFlag}--${key.padEnd(10)} ${ctx.translation(key)}`)
309
315
  }
@@ -311,7 +317,7 @@ const customUsageRenderer = ctx => {
311
317
  return Promise.resolve(lines.join('\n'))
312
318
  }
313
319
 
314
- // Run with custom renderers
320
+ // run with custom renderers
315
321
  cli(
316
322
  process.argv.slice(2),
317
323
  { name: 'app', run: () => {} },
@@ -341,14 +347,14 @@ const command = {
341
347
  name: { type: 'string', short: 'n' },
342
348
  formal: { type: 'boolean', short: 'f' }
343
349
  },
344
- // Resource fetcher for translations
350
+ // resource fetcher for translations
345
351
  resource: async ctx => {
346
352
  if (ctx.locale.toString() === 'ja-JP') {
347
353
  const resource = await import('./locales/ja-JP.json', { with: { type: 'json' } })
348
354
  return resource.default
349
355
  }
350
356
 
351
- // Default to English
357
+ // default to English
352
358
  return enUS
353
359
  },
354
360
  run: ctx => {
@@ -358,12 +364,12 @@ const command = {
358
364
  }
359
365
  }
360
366
 
361
- // Run with locale support
367
+ // run with locale support
362
368
  cli(process.argv.slice(2), command, {
363
369
  name: 'my-app',
364
370
  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)
371
+ // set the locale via an environment variable
372
+ // if Node v21 or later is used, you can use the built-in `navigator.language` instead)
367
373
  locale: new Intl.Locale(process.env.MY_LOCALE || 'en-US')
368
374
  })
369
375
  ```
@@ -1,3 +1,4 @@
1
+ import { create, deepFreeze, resolveLazyCommand } from "./utils-NHs5DuHk.js";
1
2
 
2
3
  //#region locales/en-US.json
3
4
  var COMMAND = "COMMAND";
@@ -56,28 +57,6 @@ const COMMAND_I18N_RESOURCE_KEYS = [
56
57
  "FORMORE"
57
58
  ];
58
59
 
59
- //#endregion
60
- //#region src/utils.ts
61
- async function resolveLazyCommand(cmd, name, entry = false) {
62
- const resolved = Object.assign(create(), typeof cmd == "function" ? await cmd() : cmd, { default: entry });
63
- if (resolved.name == null && name) resolved.name = name;
64
- return deepFreeze(resolved);
65
- }
66
- function create(obj = null) {
67
- return Object.create(obj);
68
- }
69
- function log(...args) {
70
- console.log(...args);
71
- }
72
- function deepFreeze(obj) {
73
- if (obj === null || typeof obj !== "object") return obj;
74
- for (const key of Object.keys(obj)) {
75
- const value = obj[key];
76
- if (typeof value === "object" && value !== null) deepFreeze(value);
77
- }
78
- return Object.freeze(obj);
79
- }
80
-
81
60
  //#endregion
82
61
  //#region src/context.ts
83
62
  const DEFAULT_LOCALE = "en-US";
@@ -196,4 +175,4 @@ async function loadCommandResource(ctx, command) {
196
175
  }
197
176
 
198
177
  //#endregion
199
- export { COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, DEFAULT_LOCALE, create, createCommandContext, log, resolveLazyCommand };
178
+ export { COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, DEFAULT_LOCALE, createCommandContext };
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/context.js CHANGED
@@ -1,3 +1,4 @@
1
- import { DEFAULT_LOCALE, createCommandContext } from "./context-B5z7lnoV.js";
1
+ import { DEFAULT_LOCALE, createCommandContext } from "./context-DmZAeiph.js";
2
+ import "./utils-NHs5DuHk.js";
2
3
 
3
4
  export { DEFAULT_LOCALE, createCommandContext };
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<void>;
13
13
 
14
14
  export { Command, CommandOptions, CommandRunner, cli };
package/lib/index.js CHANGED
@@ -1,195 +1,8 @@
1
- import { COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, create, createCommandContext, log, resolveLazyCommand } from "./context-B5z7lnoV.js";
1
+ import { COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, createCommandContext } from "./context-DmZAeiph.js";
2
+ import { create, log, resolveLazyCommand } from "./utils-NHs5DuHk.js";
3
+ import { renderHeader, renderUsage, renderValidationErrors } from "./renderer-MpQ9U28q.js";
2
4
  import { parseArgs, resolveArgs } from "args-tokens";
3
5
 
4
- //#region src/renderer/header.ts
5
- function renderHeader(ctx) {
6
- const title = ctx.env.description || ctx.env.name || "";
7
- return Promise.resolve(title ? `${title} (${ctx.env.name || ""}${ctx.env.version ? ` v${ctx.env.version}` : ""})` : title);
8
- }
9
-
10
- //#endregion
11
- //#region src/renderer/usage.ts
12
- async function renderUsage(ctx) {
13
- const messages = [];
14
- if (!ctx.omitted && hasDescription(ctx)) messages.push(ctx.description, "");
15
- messages.push(...await renderUsageSection(ctx), "");
16
- if (ctx.omitted && await hasCommands(ctx)) messages.push(...await renderCommandsSection(ctx), "");
17
- if (hasOptions(ctx)) messages.push(...await renderOptionsSection(ctx), "");
18
- if (hasExamples(ctx)) messages.push(...renderExamplesSection(ctx), "");
19
- return messages.join("\n");
20
- }
21
- /**
22
- * Render the options section
23
- * @param ctx A {@link CommandContext | command context}
24
- * @returns A rendered options section
25
- */
26
- async function renderOptionsSection(ctx) {
27
- const messages = [];
28
- messages.push(`${ctx.translation("OPTIONS")}:`);
29
- const optionsPairs = getOptionsPairs(ctx);
30
- messages.push(await generateOptionsUsage(ctx, optionsPairs));
31
- return messages;
32
- }
33
- /**
34
- * Render the examples section
35
- * @param ctx A {@link CommandContext | command context}
36
- * @returns A rendered examples section
37
- */
38
- function renderExamplesSection(ctx) {
39
- const messages = [];
40
- const examples = ctx.usage.examples.split("\n").map((example) => example.padStart(ctx.env.leftMargin + example.length));
41
- messages.push(`${ctx.translation("EXAMPLES")}:`, ...examples);
42
- return messages;
43
- }
44
- /**
45
- * Render the usage section
46
- * @param ctx A {@link CommandContext | command context}
47
- * @returns A rendered usage section
48
- */
49
- async function renderUsageSection(ctx) {
50
- const messages = [`${ctx.translation("USAGE")}:`];
51
- if (ctx.omitted) {
52
- const defaultCommand = `${resolveEntry(ctx)}${await hasCommands(ctx) ? ` [${resolveSubCommand(ctx)}]` : ""} ${hasOptions(ctx) ? `<${ctx.translation("OPTIONS")}>` : ""} `;
53
- messages.push(defaultCommand.padStart(ctx.env.leftMargin + defaultCommand.length));
54
- if (await hasCommands(ctx)) {
55
- const commandsUsage = `${resolveEntry(ctx)} <${ctx.translation("COMMANDS")}>`;
56
- messages.push(commandsUsage.padStart(ctx.env.leftMargin + commandsUsage.length));
57
- }
58
- } else {
59
- const usageStr = `${resolveEntry(ctx)} ${resolveSubCommand(ctx)} ${generateOptionsSymbols(ctx)}`;
60
- messages.push(usageStr.padStart(ctx.env.leftMargin + usageStr.length));
61
- }
62
- return messages;
63
- }
64
- /**
65
- * Render the commands section
66
- * @param ctx A {@link CommandContext | command context}
67
- * @returns A rendered commands section
68
- */
69
- async function renderCommandsSection(ctx) {
70
- const messages = [`${ctx.translation("COMMANDS")}:`];
71
- const loadedCommands = await ctx.loadCommands();
72
- const commandMaxLength = Math.max(...loadedCommands.map((cmd) => (cmd.name || "").length));
73
- const commandsStr = await Promise.all(loadedCommands.map((cmd) => {
74
- const key = cmd.name || "";
75
- const desc = cmd.description || "";
76
- const command = `${key.padEnd(commandMaxLength + ctx.env.middleMargin)}${desc} `;
77
- return `${command.padStart(ctx.env.leftMargin + command.length)} `;
78
- }));
79
- messages.push(...commandsStr, "", ctx.translation("FORMORE"));
80
- messages.push(...loadedCommands.map((cmd) => {
81
- const commandHelp = `${ctx.env.name} ${cmd.name} --help`;
82
- return `${commandHelp.padStart(ctx.env.leftMargin + commandHelp.length)}`;
83
- }));
84
- return messages;
85
- }
86
- /**
87
- * Resolve the entry command name
88
- * @param ctx A {@link CommandContext | command context}
89
- * @returns The entry command name
90
- */
91
- function resolveEntry(ctx) {
92
- return ctx.env.name || ctx.translation("COMMAND");
93
- }
94
- /**
95
- * Resolve the sub command name
96
- * @param ctx A {@link CommandContext | command context}
97
- * @returns The sub command name
98
- */
99
- function resolveSubCommand(ctx) {
100
- return ctx.name || ctx.translation("SUBCOMMAND");
101
- }
102
- /**
103
- * Check if the command has a description
104
- * @param ctx A {@link CommandContext | command context}
105
- * @returns True if the command has a description
106
- */
107
- function hasDescription(ctx) {
108
- return !!ctx.description;
109
- }
110
- /**
111
- * Check if the command has sub commands
112
- * @param ctx A {@link CommandContext | command context}
113
- * @returns True if the command has sub commands
114
- */
115
- async function hasCommands(ctx) {
116
- const loadedCommands = await ctx.loadCommands();
117
- return loadedCommands.length > 1;
118
- }
119
- /**
120
- * Check if the command has options
121
- * @param ctx A {@link CommandContext | command context}
122
- * @returns True if the command has options
123
- */
124
- function hasOptions(ctx) {
125
- return !!(ctx.options && Object.keys(ctx.options).length > 0);
126
- }
127
- /**
128
- * Check if the command has examples
129
- * @param ctx A {@link CommandContext | command context}
130
- * @returns True if the command has examples
131
- */
132
- function hasExamples(ctx) {
133
- return !!ctx.usage.examples;
134
- }
135
- /**
136
- * Check if all options have default values
137
- * @param ctx A {@link CommandContext | command context}
138
- * @returns True if all options have default values
139
- */
140
- function hasAllDefaultOptions(ctx) {
141
- return !!(ctx.options && Object.values(ctx.options).every((opt) => opt.default));
142
- }
143
- /**
144
- * Generate options symbols for usage
145
- * @param ctx A {@link CommandContext | command context}
146
- * @returns Options symbols for usage
147
- */
148
- function generateOptionsSymbols(ctx) {
149
- return hasOptions(ctx) ? hasAllDefaultOptions(ctx) ? `[${ctx.translation("OPTIONS")}]` : `<${ctx.translation("OPTIONS")}>` : "";
150
- }
151
- /**
152
- * Get options pairs for usage
153
- * @param ctx A {@link CommandContext | command context}
154
- * @returns Options pairs for usage
155
- */
156
- function getOptionsPairs(ctx) {
157
- return Object.entries(ctx.options).reduce((acc, [name, value]) => {
158
- let key = `--${name}`;
159
- if (value.short) key = `-${value.short}, ${key}`;
160
- if (value.type !== "boolean") key = value.default ? `${key} [${name}]` : `${key} <${name}>`;
161
- acc[name] = key;
162
- return acc;
163
- }, create());
164
- }
165
- /**
166
- * Generate options usage
167
- * @param ctx A {@link CommandContext | command context}
168
- * @param optionsPairs Options pairs for usage
169
- * @returns Generated options usage
170
- */
171
- async function generateOptionsUsage(ctx, optionsPairs) {
172
- const optionsMaxLength = Math.max(...Object.entries(optionsPairs).map(([_, value]) => value.length));
173
- const optionSchemaMaxLength = ctx.env.usageOptionType ? Math.max(...Object.entries(optionsPairs).map(([key, _]) => ctx.options[key].type.length)) : 0;
174
- const usages = await Promise.all(Object.entries(optionsPairs).map(([key, value]) => {
175
- const rawDesc = ctx.translation(key);
176
- const optionsSchema = ctx.env.usageOptionType ? `[${ctx.options[key].type}] ` : "";
177
- const desc = `${optionsSchema ? optionsSchema.padEnd(optionSchemaMaxLength + 3) : ""}${rawDesc}`;
178
- const option = `${value.padEnd(optionsMaxLength + ctx.env.middleMargin)}${desc}`;
179
- return `${option.padStart(ctx.env.leftMargin + option.length)}`;
180
- }));
181
- return usages.join("\n");
182
- }
183
-
184
- //#endregion
185
- //#region src/renderer/validation.ts
186
- function renderValidationErrors(_ctx, error) {
187
- const messages = [];
188
- for (const err of error.errors) messages.push(err.message);
189
- return Promise.resolve(messages.join("\n"));
190
- }
191
-
192
- //#endregion
193
6
  //#region src/cli.ts
194
7
  async function cli(args, entry, opts = {}) {
195
8
  const tokens = parseArgs(args);
@@ -0,0 +1,26 @@
1
+ import { ArgOptions } from 'args-tokens';
2
+ import { b as CommandContext } from '../types.d-CxaX4FVV.js';
3
+
4
+ /**
5
+ * Render the header
6
+ * @param ctx A {@link CommandContext | command context}
7
+ * @returns A rendered header
8
+ */
9
+ declare function renderHeader<Options extends ArgOptions = ArgOptions>(ctx: Readonly<CommandContext<Options>>): Promise<string>;
10
+
11
+ /**
12
+ * Render the usage
13
+ * @param ctx A {@link CommandContext | command context}
14
+ * @returns A rendered usage
15
+ */
16
+ declare function renderUsage<Options extends ArgOptions = ArgOptions>(ctx: Readonly<CommandContext<Options>>): Promise<string>;
17
+
18
+ /**
19
+ * Render the validation errors
20
+ * @param ctx A {@link CommandContext | command context}
21
+ * @param error An {@link AggregateError} of option in `args-token` validation
22
+ * @returns A rendered validation error
23
+ */
24
+ declare function renderValidationErrors<Options extends ArgOptions = ArgOptions>(_ctx: CommandContext<Options>, error: AggregateError): Promise<string>;
25
+
26
+ export { renderHeader, renderUsage, renderValidationErrors };
@@ -0,0 +1,4 @@
1
+ import "../utils-NHs5DuHk.js";
2
+ import { renderHeader, renderUsage, renderValidationErrors } from "../renderer-MpQ9U28q.js";
3
+
4
+ export { renderHeader, renderUsage, renderValidationErrors };
@@ -0,0 +1,192 @@
1
+ import { create } from "./utils-NHs5DuHk.js";
2
+
3
+ //#region src/renderer/header.ts
4
+ function renderHeader(ctx) {
5
+ const title = ctx.env.description || ctx.env.name || "";
6
+ return Promise.resolve(title ? `${title} (${ctx.env.name || ""}${ctx.env.version ? ` v${ctx.env.version}` : ""})` : title);
7
+ }
8
+
9
+ //#endregion
10
+ //#region src/renderer/usage.ts
11
+ async function renderUsage(ctx) {
12
+ const messages = [];
13
+ if (!ctx.omitted && hasDescription(ctx)) messages.push(ctx.description, "");
14
+ messages.push(...await renderUsageSection(ctx), "");
15
+ if (ctx.omitted && await hasCommands(ctx)) messages.push(...await renderCommandsSection(ctx), "");
16
+ if (hasOptions(ctx)) messages.push(...await renderOptionsSection(ctx), "");
17
+ if (hasExamples(ctx)) messages.push(...renderExamplesSection(ctx), "");
18
+ return messages.join("\n");
19
+ }
20
+ /**
21
+ * Render the options section
22
+ * @param ctx A {@link CommandContext | command context}
23
+ * @returns A rendered options section
24
+ */
25
+ async function renderOptionsSection(ctx) {
26
+ const messages = [];
27
+ messages.push(`${ctx.translation("OPTIONS")}:`);
28
+ const optionsPairs = getOptionsPairs(ctx);
29
+ messages.push(await generateOptionsUsage(ctx, optionsPairs));
30
+ return messages;
31
+ }
32
+ /**
33
+ * Render the examples section
34
+ * @param ctx A {@link CommandContext | command context}
35
+ * @returns A rendered examples section
36
+ */
37
+ function renderExamplesSection(ctx) {
38
+ const messages = [];
39
+ const examples = ctx.usage.examples.split("\n").map((example) => example.padStart(ctx.env.leftMargin + example.length));
40
+ messages.push(`${ctx.translation("EXAMPLES")}:`, ...examples);
41
+ return messages;
42
+ }
43
+ /**
44
+ * Render the usage section
45
+ * @param ctx A {@link CommandContext | command context}
46
+ * @returns A rendered usage section
47
+ */
48
+ async function renderUsageSection(ctx) {
49
+ const messages = [`${ctx.translation("USAGE")}:`];
50
+ if (ctx.omitted) {
51
+ const defaultCommand = `${resolveEntry(ctx)}${await hasCommands(ctx) ? ` [${resolveSubCommand(ctx)}]` : ""} ${hasOptions(ctx) ? `<${ctx.translation("OPTIONS")}>` : ""} `;
52
+ messages.push(defaultCommand.padStart(ctx.env.leftMargin + defaultCommand.length));
53
+ if (await hasCommands(ctx)) {
54
+ const commandsUsage = `${resolveEntry(ctx)} <${ctx.translation("COMMANDS")}>`;
55
+ messages.push(commandsUsage.padStart(ctx.env.leftMargin + commandsUsage.length));
56
+ }
57
+ } else {
58
+ const usageStr = `${resolveEntry(ctx)} ${resolveSubCommand(ctx)} ${generateOptionsSymbols(ctx)}`;
59
+ messages.push(usageStr.padStart(ctx.env.leftMargin + usageStr.length));
60
+ }
61
+ return messages;
62
+ }
63
+ /**
64
+ * Render the commands section
65
+ * @param ctx A {@link CommandContext | command context}
66
+ * @returns A rendered commands section
67
+ */
68
+ async function renderCommandsSection(ctx) {
69
+ const messages = [`${ctx.translation("COMMANDS")}:`];
70
+ const loadedCommands = await ctx.loadCommands();
71
+ const commandMaxLength = Math.max(...loadedCommands.map((cmd) => (cmd.name || "").length));
72
+ const commandsStr = await Promise.all(loadedCommands.map((cmd) => {
73
+ const key = cmd.name || "";
74
+ const desc = cmd.description || "";
75
+ const command = `${key.padEnd(commandMaxLength + ctx.env.middleMargin)}${desc} `;
76
+ return `${command.padStart(ctx.env.leftMargin + command.length)} `;
77
+ }));
78
+ messages.push(...commandsStr, "", ctx.translation("FORMORE"));
79
+ messages.push(...loadedCommands.map((cmd) => {
80
+ const commandHelp = `${ctx.env.name} ${cmd.name} --help`;
81
+ return `${commandHelp.padStart(ctx.env.leftMargin + commandHelp.length)}`;
82
+ }));
83
+ return messages;
84
+ }
85
+ /**
86
+ * Resolve the entry command name
87
+ * @param ctx A {@link CommandContext | command context}
88
+ * @returns The entry command name
89
+ */
90
+ function resolveEntry(ctx) {
91
+ return ctx.env.name || ctx.translation("COMMAND");
92
+ }
93
+ /**
94
+ * Resolve the sub command name
95
+ * @param ctx A {@link CommandContext | command context}
96
+ * @returns The sub command name
97
+ */
98
+ function resolveSubCommand(ctx) {
99
+ return ctx.name || ctx.translation("SUBCOMMAND");
100
+ }
101
+ /**
102
+ * Check if the command has a description
103
+ * @param ctx A {@link CommandContext | command context}
104
+ * @returns True if the command has a description
105
+ */
106
+ function hasDescription(ctx) {
107
+ return !!ctx.description;
108
+ }
109
+ /**
110
+ * Check if the command has sub commands
111
+ * @param ctx A {@link CommandContext | command context}
112
+ * @returns True if the command has sub commands
113
+ */
114
+ async function hasCommands(ctx) {
115
+ const loadedCommands = await ctx.loadCommands();
116
+ return loadedCommands.length > 1;
117
+ }
118
+ /**
119
+ * Check if the command has options
120
+ * @param ctx A {@link CommandContext | command context}
121
+ * @returns True if the command has options
122
+ */
123
+ function hasOptions(ctx) {
124
+ return !!(ctx.options && Object.keys(ctx.options).length > 0);
125
+ }
126
+ /**
127
+ * Check if the command has examples
128
+ * @param ctx A {@link CommandContext | command context}
129
+ * @returns True if the command has examples
130
+ */
131
+ function hasExamples(ctx) {
132
+ return !!ctx.usage.examples;
133
+ }
134
+ /**
135
+ * Check if all options have default values
136
+ * @param ctx A {@link CommandContext | command context}
137
+ * @returns True if all options have default values
138
+ */
139
+ function hasAllDefaultOptions(ctx) {
140
+ return !!(ctx.options && Object.values(ctx.options).every((opt) => opt.default));
141
+ }
142
+ /**
143
+ * Generate options symbols for usage
144
+ * @param ctx A {@link CommandContext | command context}
145
+ * @returns Options symbols for usage
146
+ */
147
+ function generateOptionsSymbols(ctx) {
148
+ return hasOptions(ctx) ? hasAllDefaultOptions(ctx) ? `[${ctx.translation("OPTIONS")}]` : `<${ctx.translation("OPTIONS")}>` : "";
149
+ }
150
+ /**
151
+ * Get options pairs for usage
152
+ * @param ctx A {@link CommandContext | command context}
153
+ * @returns Options pairs for usage
154
+ */
155
+ function getOptionsPairs(ctx) {
156
+ return Object.entries(ctx.options).reduce((acc, [name, value]) => {
157
+ let key = `--${name}`;
158
+ if (value.short) key = `-${value.short}, ${key}`;
159
+ if (value.type !== "boolean") key = value.default ? `${key} [${name}]` : `${key} <${name}>`;
160
+ acc[name] = key;
161
+ return acc;
162
+ }, create());
163
+ }
164
+ /**
165
+ * Generate options usage
166
+ * @param ctx A {@link CommandContext | command context}
167
+ * @param optionsPairs Options pairs for usage
168
+ * @returns Generated options usage
169
+ */
170
+ async function generateOptionsUsage(ctx, optionsPairs) {
171
+ const optionsMaxLength = Math.max(...Object.entries(optionsPairs).map(([_, value]) => value.length));
172
+ const optionSchemaMaxLength = ctx.env.usageOptionType ? Math.max(...Object.entries(optionsPairs).map(([key, _]) => ctx.options[key].type.length)) : 0;
173
+ const usages = await Promise.all(Object.entries(optionsPairs).map(([key, value]) => {
174
+ const rawDesc = ctx.translation(key);
175
+ const optionsSchema = ctx.env.usageOptionType ? `[${ctx.options[key].type}] ` : "";
176
+ const desc = `${optionsSchema ? optionsSchema.padEnd(optionSchemaMaxLength + 3) : ""}${rawDesc}`;
177
+ const option = `${value.padEnd(optionsMaxLength + ctx.env.middleMargin)}${desc}`;
178
+ return `${option.padStart(ctx.env.leftMargin + option.length)}`;
179
+ }));
180
+ return usages.join("\n");
181
+ }
182
+
183
+ //#endregion
184
+ //#region src/renderer/validation.ts
185
+ function renderValidationErrors(_ctx, error) {
186
+ const messages = [];
187
+ for (const err of error.errors) messages.push(err.message);
188
+ return Promise.resolve(messages.join("\n"));
189
+ }
190
+
191
+ //#endregion
192
+ 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 };
@@ -0,0 +1,24 @@
1
+
2
+ //#region src/utils.ts
3
+ async function resolveLazyCommand(cmd, name, entry = false) {
4
+ const resolved = Object.assign(create(), typeof cmd == "function" ? await cmd() : cmd, { default: entry });
5
+ if (resolved.name == null && name) resolved.name = name;
6
+ return deepFreeze(resolved);
7
+ }
8
+ function create(obj = null) {
9
+ return Object.create(obj);
10
+ }
11
+ function log(...args) {
12
+ console.log(...args);
13
+ }
14
+ function deepFreeze(obj) {
15
+ if (obj === null || typeof obj !== "object") return obj;
16
+ for (const key of Object.keys(obj)) {
17
+ const value = obj[key];
18
+ if (typeof value === "object" && value !== null) deepFreeze(value);
19
+ }
20
+ return Object.freeze(obj);
21
+ }
22
+
23
+ //#endregion
24
+ export { create, deepFreeze, log, 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.5.2",
4
+ "version": "0.5.4",
5
5
  "author": {
6
6
  "name": "kazuya kawaguchi",
7
7
  "email": "kawakazu80@gmail.com"
@@ -49,10 +49,10 @@
49
49
  "default": "./lib/context.js"
50
50
  },
51
51
  "./renderer": {
52
- "types": "./lib/renderer.d.ts",
53
- "import": "./lib/renderer.js",
54
- "require": "./lib/renderer.js",
55
- "default": "./lib/renderer.js"
52
+ "types": "./lib/renderer/index.d.ts",
53
+ "import": "./lib/renderer/index.js",
54
+ "require": "./lib/renderer/index.js",
55
+ "default": "./lib/renderer/index.js"
56
56
  },
57
57
  "./package.json": "./package.json",
58
58
  "./*": "./*"