cli-nano 1.1.3 → 1.2.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
@@ -9,11 +9,11 @@
9
9
 
10
10
  ## cli-nano
11
11
 
12
- Simple library to create command-line tool (aka CLI) which is quite similar to [`Yargs`](https://github.com/yargs/yargs), it is as configurable as Yargs but is a fraction of its size. The library is also inspired by NodeJS `parseArgs()` but is again more configurable so that we really get what we would expect from a more complete CLI builder.
12
+ Small library to create command-line tool (aka CLI) which is quite similar to [`Yargs`](https://github.com/yargs/yargs), it is as configurable as Yargs but is a fraction of its size. The library is also inspired by NodeJS `parseArgs()` but is a lot more configurable in order to get what we would expect from a more complete CLI builder tool.
13
13
 
14
14
  ### Features
15
15
  - Parses arguments
16
- - Supports defining Positional arguments
16
+ - Supports defining Positional (input) arguments
17
17
  - Supports Variadic args (1 or more positional args)
18
18
  - Automatically converts flags to camelCase to match config options
19
19
  - accepts both `--camelCase` and `--kebab-case`
@@ -22,6 +22,7 @@ Simple library to create command-line tool (aka CLI) which is quite similar to [
22
22
  - Outputs description and supplied help text by using `--help`
23
23
  - Supports defining `required` options
24
24
  - Supports `default` values
25
+ - Supports `group` for grouping command options in help
25
26
  - No dependencies!
26
27
 
27
28
  ### Install
@@ -39,11 +40,18 @@ import { type Config, parseArgs } from 'cli-nano';
39
40
  const config: Config = {
40
41
  command: {
41
42
  name: 'serve',
42
- description: 'Start a server with the given options',
43
+ describe: 'Start a server with the given options',
44
+ examples: [
45
+ { cmd: '$0 ./www/index.html 8080 --open', describe: 'Start web server on port 8080 and open browser' },
46
+ {
47
+ cmd: '$0 ./index.html 8081 --no-open --verbose',
48
+ describe: 'Start web server on port 8081 without opening browser and print more debugging logging to the console',
49
+ },
50
+ ],
43
51
  positionals: [
44
52
  {
45
53
  name: 'input',
46
- description: 'serving files or directory',
54
+ describe: 'serving files or directory',
47
55
  type: 'string',
48
56
  variadic: true, // 1 or more
49
57
  required: true,
@@ -51,62 +59,77 @@ const config: Config = {
51
59
  {
52
60
  name: 'port',
53
61
  type: 'number',
54
- description: 'port to bind on',
62
+ describe: 'port to bind on',
55
63
  required: false,
56
64
  default: 5000, // optional default value
57
- },
65
+ },
58
66
  ],
59
67
  },
60
68
  options: {
61
69
  dryRun: {
62
70
  alias: 'd',
63
71
  type: 'boolean',
64
- description: 'Show what would be done, but do not actually start the server',
72
+ describe: 'Show what would be done, but do not actually start the server',
65
73
  default: false, // optional default value
66
74
  },
75
+ display: {
76
+ group: 'Advanced Options',
77
+ alias: 'D',
78
+ required: true,
79
+ type: 'boolean',
80
+ describe: 'a required display option',
81
+ },
67
82
  exclude: {
68
83
  alias: 'e',
69
84
  type: 'array',
70
- description: 'pattern or glob to exclude (may be passed multiple times)',
71
- },
72
- rainbow: {
73
- type: 'boolean',
74
- alias: 'r',
75
- description: 'Enable rainbow mode',
76
- default: true,
85
+ describe: 'pattern or glob to exclude (may be passed multiple times)',
77
86
  },
78
87
  verbose: {
79
88
  alias: 'V',
80
89
  type: 'boolean',
81
- description: 'print more information to console',
90
+ describe: 'print more information to console',
91
+ },
92
+ open: {
93
+ alias: 'o',
94
+ type: 'boolean',
95
+ describe: 'open browser when starting server',
96
+ default: true,
82
97
  },
83
- up: {
98
+ cache: {
84
99
  type: 'number',
85
- description: 'slice a path off the bottom of the paths',
86
- default: 1,
100
+ describe: 'Set cache time (in seconds) for cache-control max-age header',
101
+ default: 3600,
87
102
  },
88
- display: {
89
- alias: 'D',
103
+ address: {
104
+ type: 'string',
105
+ describe: 'Address to use',
90
106
  required: true,
107
+ },
108
+ rainbow: {
109
+ group: 'Advanced Options',
91
110
  type: 'boolean',
92
- description: 'a required display option',
93
- }
111
+ alias: 'r',
112
+ describe: 'Enable rainbow mode',
113
+ default: true,
114
+ },
94
115
  },
95
116
  version: '0.1.6',
96
- helpOptLength: 18, // option name length shown in help (defaults to 20)
97
- helpDescLength: 60, // description length shown in help (defaults to 65)
117
+ helpFlagCasing: 'camel', // show help flag option in which casing (camel/kebab) (defaults to 'kebab')
118
+ helpDescMinLength: 40, // min description length shown in help (defaults to 50)
119
+ helpDescMaxLength: 120, // max description length shown in help (defaults to 100), will show ellipsis (...) when greater
98
120
  };
99
121
 
100
122
  const args = parseArgs(config);
101
123
  console.log(args);
102
124
 
103
- // do something with parse arguments, for example
104
- // startServer(args);
125
+ // do something with parsed arguments, for example
126
+ // const { input, port, open } = args;
127
+ // startServer({ input, port, open });
105
128
  ```
106
129
 
107
130
  ### Usage with Type Inference
108
131
 
109
- For full TypeScript auto-inference and IntelliSense of parsed arguments, define your config as a `const` and use `as const`:
132
+ For full TypeScript auto-inference and intelliSense of parsed arguments, define your config as a `const` and cast it `as const`:
110
133
 
111
134
  ```ts
112
135
  const config = {
@@ -124,10 +147,10 @@ args.display; // boolean (required)
124
147
 
125
148
  > **Tip:**
126
149
  > Using `as const` preserves literal types and tuple information, so TypeScript can infer required/optional fields and argument types automatically.
127
- > If you use `const config: Config = { ... }`, you get type checking but not full IntelliSense for parsed arguments.
150
+ > If you use `const config: Config = { ... }`, you get type checking but not full intelliSense for parsed arguments.
128
151
 
129
152
  > [!NOTE]
130
- > For required+variadic positionals, the type is `[string, ...string[]]` (at least one value required). For optional variadic, it's string[]. For non-variadic, it's string.
153
+ > For required+variadic positionals, the type is `[string, ...string[]]` (at least one value required). For optional variadic, it's `string[]`. For non-variadic, it's `string`.
131
154
 
132
155
  #### Example CLI Calls
133
156
 
@@ -144,13 +167,13 @@ serve dist/index.html
144
167
  # With required and optional positionals
145
168
  serve index1.html index2.html 8080 -D value
146
169
 
147
- # With boolean and array options
170
+ # With boolean and array options entered as camelCase (kebab-case works too)
148
171
  serve index.html 7000 --dryRun --exclude pattern1 --exclude pattern2 -D value
149
172
 
150
- # With negated boolean
173
+ # With negated boolean entered as kebab-case
151
174
  serve index.html 7000 --no-dryRun -D value
152
175
 
153
- # With short aliases
176
+ # With short aliases (case sensitive)
154
177
  serve index.html 7000 -d -e pattern1 -e pattern2 -D value
155
178
 
156
179
  # With number option
@@ -159,7 +182,7 @@ serve index.html 7000 --up 2 -D value
159
182
 
160
183
  #### Notes
161
184
 
162
- - **Default values**: Use the `default` property in an option or positional argument to specify a value if the user does not provide one.
185
+ - **Default values**: Use the `default` property in an option or positional argument to specify a value when the user does not provide one.
163
186
  - Example for option: `{ type: 'boolean', default: false }`
164
187
  - Example for positional: `{ name: 'port', type: 'number', default: 5000 }`
165
188
  - **Variadic positionals**: Use `variadic: true` for arguments that accept multiple values.
@@ -167,6 +190,7 @@ serve index.html 7000 --up 2 -D value
167
190
  - **Negated booleans**: Use `--no-flag` to set a boolean option to `false`.
168
191
  - **Array options**: Repeat the flag to collect multiple values (e.g., `--exclude a --exclude b`).
169
192
  - **Aliases**: Use `alias` for short flags (e.g., `-d` for `--dryRun`).
193
+ - **Groups**: Use `group` for grouping some commands in help (e.g., `{ group: 'Extra Commands' }`).
170
194
 
171
195
  See [examples/](examples/) for more usage patterns.
172
196
 
@@ -179,7 +203,7 @@ See [examples/](examples/) for more usage patterns.
179
203
 
180
204
  ## Help Example
181
205
 
182
- You can see below an example of a CLI help (which is the result of calling `--help` with the config shown avove).
206
+ You can see below an example of a CLI help (which is the result of calling `--help` with the [config](#usage) shown above).
183
207
 
184
208
  Please note:
185
209
 
@@ -188,19 +212,27 @@ Please note:
188
212
 
189
213
  ```
190
214
  Usage:
191
- serve <input..> [port] [options] Start a server with the given options
215
+ serve <input..> [port] [options] Start a server with the given options
216
+
217
+ Examples:
218
+ serve ./www/index.html 8080 --open Start web server on port 8080 and open browser
219
+ serve ./index.html 8081 --no-open --verbose Start web server on port 8081 without opening browser and print more debugging logging to the console
192
220
 
193
221
  Arguments:
194
- input serving files or directory <string..>
195
- port port to bind on [number]
222
+ input serving files or directory <string..>
223
+ port port to bind on [number]
196
224
 
197
225
  Options:
198
- -d, --dryRun Show what would be done, but do not actually start the se... [boolean]
199
- -e, --exclude pattern or glob to exclude (may be passed multiple times) [array]
200
- -r, --rainbow Enable rainbow mode [boolean]
201
- -V, --verbose print more information to console [boolean]
202
- --up slice a path off the bottom of the paths [number]
203
- -D, --display a required display option <boolean>
204
- -h, --help Show help [boolean]
205
- -v, --version Show version number [boolean]
206
- ```
226
+ -d, --dry-run Show what would be done, but do not actually start the server [boolean]
227
+ -e, --exclude pattern or glob to exclude (may be passed multiple times) [array]
228
+ -V, --verbose print more information to console [boolean]
229
+ -o, --open open browser when starting server [boolean]
230
+ --cache Set cache time (in seconds) for cache-control max-age header [number]
231
+ --address Address to use <string>
232
+ -h, --help Show help [boolean]
233
+ -v, --version Show version number [boolean]
234
+
235
+ Advanced Options:
236
+ -D, --display a required display option <boolean>
237
+ -r, --rainbow Enable rainbow mode [boolean]
238
+ ```
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAmB,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAE3E,mBAAmB,iBAAiB,CAAC;AAOrC,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAgMpE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAc,MAAM,iBAAiB,CAAC;AAEtE,mBAAmB,iBAAiB,CAAC;AAOrC,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAgMpE"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const defaultOptions = {
2
- help: { alias: 'h', description: 'Show help', type: 'boolean' },
3
- version: { alias: 'v', description: 'Show version number', type: 'boolean' },
2
+ help: { alias: 'h', describe: 'Show help', type: 'boolean' },
3
+ version: { alias: 'v', describe: 'Show version number', type: 'boolean' },
4
4
  };
5
5
  export function parseArgs(config) {
6
6
  const { command, options, version } = config;
@@ -227,22 +227,63 @@ function findOption(options, arg) {
227
227
  }
228
228
  /** Print CLI help documentation to the screen */
229
229
  function printHelp(config) {
230
- const { command, options, version, helpOptLength = 20, helpDescLength = 65 } = config;
230
+ const { command, options, version, helpDescMinLength = 50, helpDescMaxLength = 100 } = config;
231
231
  const usagePositionals = buildUsagePositionals(command.positionals);
232
232
  console.log('Usage:');
233
- console.log(` ${command.name} ${usagePositionals} [options] ${command.description}`);
233
+ console.log(` ${command.name} ${usagePositionals} [options] ${command.describe}`);
234
+ // display any examples (when provided)
235
+ if (Array.isArray(command.examples) && command.examples.length) {
236
+ console.log('\nExamples:');
237
+ command.examples.forEach(ex => {
238
+ console.log(` ${ex.cmd.replace('$0', command.name)} ${ex.describe || ''}`);
239
+ });
240
+ }
241
+ // calculate longest description length
242
+ let longestOptNameLn = 0;
243
+ let longestOptDescLn = 0;
244
+ for (const [key, option] of Object.entries({ ...options, ...defaultOptions })) {
245
+ const flagLn = (config.helpFlagCasing === 'camel' ? key : camelToKebab(key)).length;
246
+ if (flagLn > longestOptNameLn) {
247
+ longestOptNameLn = key.length;
248
+ }
249
+ if ((option.describe?.length ?? 0) > longestOptDescLn) {
250
+ longestOptDescLn = option.describe.length;
251
+ }
252
+ }
253
+ // make sure the length to use is between our defined min/max
254
+ if (longestOptDescLn < helpDescMinLength) {
255
+ longestOptDescLn = helpDescMinLength;
256
+ }
257
+ else if (longestOptDescLn > helpDescMaxLength) {
258
+ longestOptDescLn = helpDescMaxLength;
259
+ }
260
+ // reserve some extra spaces between option name/desc
261
+ longestOptDescLn += 2;
262
+ longestOptNameLn += 3;
234
263
  console.log('\nArguments:');
235
264
  command.positionals?.forEach(arg => {
236
- console.log(` ${formatHelpText(arg.name, helpOptLength)}${formatHelpText(arg.description, helpDescLength)} ${formatOptionType(arg.type, arg.variadic, arg.required)}`);
265
+ console.log(` ${formatHelpText(arg.name, longestOptNameLn + 6)}${formatHelpText(arg.describe, longestOptDescLn)} ${formatOptionType(arg.type, arg.variadic, arg.required)}`);
237
266
  });
238
- console.log('\nOptions:');
239
- for (const [key, option] of Object.entries({ ...options, ...defaultOptions })) {
240
- const aliasStr = option.alias ? `-${option.alias}, ` : '';
241
- if (!version && key === 'version') {
242
- continue;
267
+ // Group options by their group property
268
+ const groupedOptions = Object.entries({ ...options, ...defaultOptions }).reduce((acc, [key, option]) => {
269
+ const group = option.group || 'Options';
270
+ if (!acc[group]) {
271
+ acc[group] = [];
243
272
  }
244
- console.log(` ${aliasStr.padEnd(4)}--${formatHelpText(key, helpOptLength - 6)}${formatHelpText(option.description || '', helpDescLength)} ${formatOptionType(option.type, false, option.required)}`);
245
- }
273
+ acc[group].push([key, option]);
274
+ return acc;
275
+ }, {});
276
+ Object.keys(groupedOptions).forEach(group => {
277
+ console.log(`\n${group}:`);
278
+ groupedOptions[group].forEach(([key, option]) => {
279
+ const aliasStr = option.alias ? `-${option.alias}, ` : '';
280
+ if (!version && key === 'version') {
281
+ return;
282
+ }
283
+ const flagName = config.helpFlagCasing === 'camel' ? key : camelToKebab(key);
284
+ console.log(` ${aliasStr.padEnd(4)}--${formatHelpText(flagName, longestOptNameLn)}${formatHelpText(option.describe || '', longestOptDescLn)} ${formatOptionType(option.type, false, option.required)}`);
285
+ });
286
+ });
246
287
  }
247
288
  /** Utility to convert kebab-case to camelCase */
248
289
  function kebabToCamel(str) {
@@ -1,20 +1,22 @@
1
- export interface ArgumentOptions {
1
+ export interface FlagOption {
2
2
  /** option type */
3
3
  type?: 'string' | 'boolean' | 'number' | 'array';
4
- /** description of the flag option */
5
- description: string;
4
+ /** describe the flag option */
5
+ describe: string;
6
6
  /** defaults to undefined, provide shorter alias as command options */
7
7
  alias?: string;
8
8
  /** default value for the option if not provided */
9
9
  default?: any;
10
+ /** optional group for grouping some commands */
11
+ group?: string;
10
12
  /** defaults to false, is the option required? */
11
13
  required?: boolean;
12
14
  }
13
15
  export interface PositionalArgument {
14
16
  /** positional argument name (it will be displayed in the help docs) */
15
17
  name: string;
16
- /** positional argument description */
17
- description: string;
18
+ /** describe positional argument */
19
+ describe: string;
18
20
  /** postional argument type */
19
21
  type?: 'string' | 'boolean' | 'number' | 'array';
20
22
  /** defaults to false, allows multiple values for this positional argument */
@@ -24,24 +26,37 @@ export interface PositionalArgument {
24
26
  /** defaults to false, is the positional argument required? */
25
27
  required?: boolean;
26
28
  }
27
- export interface CommandOptions {
29
+ export interface CommandOption {
28
30
  /** command name, used in the help docs */
29
31
  name: string;
30
- /** command description */
31
- description: string;
32
+ /** describe command */
33
+ describe: string;
34
+ /** give some example invocations of your program */
35
+ examples?: readonly ExampleOption[];
32
36
  /** list of positional arguments */
33
37
  positionals?: readonly PositionalArgument[];
34
38
  }
39
+ export interface ExampleOption {
40
+ /** script example command, the string `$0` will get interpolated to the current script name or node command */
41
+ cmd: string;
42
+ /** describe what the script command does */
43
+ describe: string;
44
+ }
35
45
  /** CLI options */
36
46
  export interface Config {
37
47
  /** CLI definition */
38
- command: CommandOptions;
39
- /** option name length (w/wo alias) shown in the help (defaults to 20) */
40
- helpOptLength?: number;
41
- /** description length shown in the help (defaults to 65) */
42
- helpDescLength?: number;
48
+ command: CommandOption;
49
+ /**
50
+ * Show flag option in which casing (camelCase or kebab-case) in the help (defaults to 'kebab').
51
+ * Note: this is only for the help print, the parsing will always support both camel/kebab casing
52
+ */
53
+ helpFlagCasing?: 'camel' | 'kebab';
54
+ /** min description length shown in the help (defaults to 50) */
55
+ helpDescMinLength?: number;
56
+ /** max description length shown in the help (defaults to 100) */
57
+ helpDescMaxLength?: number;
43
58
  /** CLI list of flag options */
44
- options: Record<string, ArgumentOptions>;
59
+ options: Record<string, FlagOption>;
45
60
  /** CLI or package version */
46
61
  version?: string;
47
62
  }
@@ -1 +1 @@
1
- {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,kBAAkB;IAClB,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;IAEjD,qCAAqC;IACrC,WAAW,EAAE,MAAM,CAAC;IAEpB,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,mDAAmD;IACnD,OAAO,CAAC,EAAE,GAAG,CAAC;IAEd,iDAAiD;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IAEb,sCAAsC;IACtC,WAAW,EAAE,MAAM,CAAC;IAEpB,8BAA8B;IAC9B,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;IAEjD,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,mDAAmD;IACnD,OAAO,CAAC,EAAE,GAAG,CAAC;IAEd,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IAEb,0BAA0B;IAC1B,WAAW,EAAE,MAAM,CAAC;IAEpB,mCAAmC;IACnC,WAAW,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAC7C;AAED,kBAAkB;AAClB,MAAM,WAAW,MAAM;IACrB,qBAAqB;IACrB,OAAO,EAAE,cAAc,CAAC;IAExB,yEAAyE;IACzE,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAEzC,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,iFAAiF;AACjF,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,GAAG,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,SAAS,GACtI,OAAO,GACP,CAAC,CAAC,MAAM,CAAC,SAAS,QAAQ,GACxB,MAAM,GACN,CAAC,CAAC,MAAM,CAAC,SAAS,OAAO,GACvB,CAAC,SAAS;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAC1B,CAAC,SAAS;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAC1B,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,GACrB,MAAM,EAAE,GACV,MAAM,GAAG,MAAM,EAAE,GACnB,CAAC,CAAC,MAAM,CAAC,SAAS,QAAQ,GACxB,CAAC,SAAS;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAC1B,CAAC,SAAS;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAC1B,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,GACrB,MAAM,EAAE,GACV,MAAM,GACR,CAAC,CAAC,MAAM,CAAC,SAAS,SAAS,GACzB,CAAC,SAAS;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAC1B,CAAC,SAAS;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAC1B,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,GACrB,MAAM,EAAE,GACV,MAAM,GACR,CAAC,CAAC,SAAS,CAAC,SAAS,SAAS,GAC5B,MAAM,GACN,CAAC,CAAC,SAAS,CAAC,CAAC;AAE3B,kCAAkC;AAClC,KAAK,YAAY,CAAC,CAAC,IAAI;KACpB,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS;QAAE,QAAQ,EAAE,IAAI,CAAA;KAAE,GAAG,CAAC,GAAG,KAAK;CAC5D,CAAC,MAAM,CAAC,CAAC,CAAC;AAEX,kCAAkC;AAClC,KAAK,YAAY,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;AAEzD,6EAA6E;AAC7E,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI;KAAG,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG;KAC3G,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC5C,CAAC;AAEF,gFAAgF;AAChF,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,SAAS,kBAAkB,EAAE,GAAG,SAAS,IAAI,CAAC,SAAS,SAAS,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,IAAI,CAAC,GAC9H,CAAC,SAAS,kBAAkB,GAC1B,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,IAAI,GAAG;KAAG,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC;CAAE,GAAG;KAAG,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC;CAAE,CAAC,GAC3G,mBAAmB,CAAC,IAAI,SAAS,SAAS,kBAAkB,EAAE,GAAG,IAAI,GAAG,EAAE,CAAC,GAC7E,mBAAmB,CAAC,IAAI,SAAS,SAAS,kBAAkB,EAAE,GAAG,IAAI,GAAG,EAAE,CAAC,GAC7E;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAA;CAAE,CAAC;AAE7B,yCAAyC;AACzC,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,aAAa,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cli-nano",
3
- "version": "1.1.3",
4
- "description": "Mini command-line tool similar to `yargs` or `parseArgs` from Node.js that accepts positional arguments, flags and options.",
3
+ "version": "1.2.0",
4
+ "description": "Small command-line tool similar to `yargs` or `parseArgs` from Node.js to create a CLI accepting positional arguments, flags and options.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "exports": {
package/src/index.ts CHANGED
@@ -1,10 +1,10 @@
1
- import type { ArgsResult, ArgumentOptions, Config } from './interfaces.js';
1
+ import type { ArgsResult, Config, FlagOption } from './interfaces.js';
2
2
 
3
3
  export type * from './interfaces.js';
4
4
 
5
- const defaultOptions: Record<string, ArgumentOptions> = {
6
- help: { alias: 'h', description: 'Show help', type: 'boolean' },
7
- version: { alias: 'v', description: 'Show version number', type: 'boolean' },
5
+ const defaultOptions: Record<string, FlagOption> = {
6
+ help: { alias: 'h', describe: 'Show help', type: 'boolean' },
7
+ version: { alias: 'v', describe: 'Show version number', type: 'boolean' },
8
8
  };
9
9
 
10
10
  export function parseArgs<C extends Config>(config: C): ArgsResult<C> {
@@ -117,7 +117,7 @@ export function parseArgs<C extends Config>(config: C): ArgsResult<C> {
117
117
  }
118
118
  const argOrg = args[argIndex] || '';
119
119
  let arg = argOrg;
120
- let option: ArgumentOptions | undefined;
120
+ let option: FlagOption | undefined;
121
121
  let configKey: string | undefined;
122
122
 
123
123
  if (argOrg.startsWith('-')) {
@@ -225,7 +225,7 @@ function formatOptionType(type: string | undefined, variadic?: boolean, required
225
225
  }
226
226
 
227
227
  /** Helper to find an option and its config key by argument name or alias. */
228
- function findOption(options: Record<string, ArgumentOptions>, arg: string): [ArgumentOptions | undefined, string | undefined] {
228
+ function findOption(options: Record<string, FlagOption>, arg: string): [FlagOption | undefined, string | undefined] {
229
229
  // Try all forms: as-is, kebab-to-camel, camel-to-kebab
230
230
  const option = options[arg] || options[kebabToCamel(arg)] || options[camelToKebab(arg).replace(/-/g, '')];
231
231
  if (option) {
@@ -244,28 +244,77 @@ function findOption(options: Record<string, ArgumentOptions>, arg: string): [Arg
244
244
 
245
245
  /** Print CLI help documentation to the screen */
246
246
  function printHelp(config: Config) {
247
- const { command, options, version, helpOptLength = 20, helpDescLength = 65 } = config;
247
+ const { command, options, version, helpDescMinLength = 50, helpDescMaxLength = 100 } = config;
248
248
  const usagePositionals = buildUsagePositionals(command.positionals);
249
249
 
250
250
  console.log('Usage:');
251
- console.log(` ${command.name} ${usagePositionals} [options] ${command.description}`);
251
+ console.log(` ${command.name} ${usagePositionals} [options] ${command.describe}`);
252
+
253
+ // display any examples (when provided)
254
+ if (Array.isArray(command.examples) && command.examples.length) {
255
+ console.log('\nExamples:');
256
+ command.examples.forEach(ex => {
257
+ console.log(` ${ex.cmd.replace('$0', command.name)} ${ex.describe || ''}`);
258
+ });
259
+ }
260
+
261
+ // calculate longest description length
262
+ let longestOptNameLn = 0;
263
+ let longestOptDescLn = 0;
264
+ for (const [key, option] of Object.entries({ ...options, ...defaultOptions })) {
265
+ const flagLn = (config.helpFlagCasing === 'camel' ? key : camelToKebab(key)).length;
266
+ if (flagLn > longestOptNameLn) {
267
+ longestOptNameLn = key.length;
268
+ }
269
+ if ((option.describe?.length ?? 0) > longestOptDescLn) {
270
+ longestOptDescLn = option.describe.length;
271
+ }
272
+ }
273
+
274
+ // make sure the length to use is between our defined min/max
275
+ if (longestOptDescLn < helpDescMinLength) {
276
+ longestOptDescLn = helpDescMinLength;
277
+ } else if (longestOptDescLn > helpDescMaxLength) {
278
+ longestOptDescLn = helpDescMaxLength;
279
+ }
280
+
281
+ // reserve some extra spaces between option name/desc
282
+ longestOptDescLn += 2;
283
+ longestOptNameLn += 3;
284
+
252
285
  console.log('\nArguments:');
253
286
  command.positionals?.forEach(arg => {
254
287
  console.log(
255
- ` ${formatHelpText(arg.name, helpOptLength)}${formatHelpText(arg.description, helpDescLength)} ${formatOptionType(arg.type, arg.variadic, arg.required)}`,
288
+ ` ${formatHelpText(arg.name, longestOptNameLn + 6)}${formatHelpText(arg.describe, longestOptDescLn)} ${formatOptionType(arg.type, arg.variadic, arg.required)}`,
256
289
  );
257
290
  });
258
291
 
259
- console.log('\nOptions:');
260
- for (const [key, option] of Object.entries({ ...options, ...defaultOptions })) {
261
- const aliasStr = option.alias ? `-${option.alias}, ` : '';
262
- if (!version && key === 'version') {
263
- continue;
264
- }
265
- console.log(
266
- ` ${aliasStr.padEnd(4)}--${formatHelpText(key, helpOptLength - 6)}${formatHelpText(option.description || '', helpDescLength)} ${formatOptionType(option.type, false, option.required)}`,
267
- );
268
- }
292
+ // Group options by their group property
293
+ const groupedOptions = Object.entries({ ...options, ...defaultOptions }).reduce(
294
+ (acc, [key, option]) => {
295
+ const group = option.group || 'Options';
296
+ if (!acc[group]) {
297
+ acc[group] = [];
298
+ }
299
+ acc[group].push([key, option]);
300
+ return acc;
301
+ },
302
+ {} as Record<string, [string, FlagOption][]>,
303
+ );
304
+
305
+ Object.keys(groupedOptions).forEach(group => {
306
+ console.log(`\n${group}:`);
307
+ groupedOptions[group].forEach(([key, option]) => {
308
+ const aliasStr = option.alias ? `-${option.alias}, ` : '';
309
+ if (!version && key === 'version') {
310
+ return;
311
+ }
312
+ const flagName = config.helpFlagCasing === 'camel' ? key : camelToKebab(key);
313
+ console.log(
314
+ ` ${aliasStr.padEnd(4)}--${formatHelpText(flagName, longestOptNameLn)}${formatHelpText(option.describe || '', longestOptDescLn)} ${formatOptionType(option.type, false, option.required)}`,
315
+ );
316
+ });
317
+ });
269
318
  }
270
319
 
271
320
  /** Utility to convert kebab-case to camelCase */
package/src/interfaces.ts CHANGED
@@ -1,9 +1,9 @@
1
- export interface ArgumentOptions {
1
+ export interface FlagOption {
2
2
  /** option type */
3
3
  type?: 'string' | 'boolean' | 'number' | 'array';
4
4
 
5
- /** description of the flag option */
6
- description: string;
5
+ /** describe the flag option */
6
+ describe: string;
7
7
 
8
8
  /** defaults to undefined, provide shorter alias as command options */
9
9
  alias?: string;
@@ -11,6 +11,9 @@ export interface ArgumentOptions {
11
11
  /** default value for the option if not provided */
12
12
  default?: any;
13
13
 
14
+ /** optional group for grouping some commands */
15
+ group?: string;
16
+
14
17
  /** defaults to false, is the option required? */
15
18
  required?: boolean;
16
19
  }
@@ -19,8 +22,8 @@ export interface PositionalArgument {
19
22
  /** positional argument name (it will be displayed in the help docs) */
20
23
  name: string;
21
24
 
22
- /** positional argument description */
23
- description: string;
25
+ /** describe positional argument */
26
+ describe: string;
24
27
 
25
28
  /** postional argument type */
26
29
  type?: 'string' | 'boolean' | 'number' | 'array';
@@ -35,30 +38,47 @@ export interface PositionalArgument {
35
38
  required?: boolean;
36
39
  }
37
40
 
38
- export interface CommandOptions {
41
+ export interface CommandOption {
39
42
  /** command name, used in the help docs */
40
43
  name: string;
41
44
 
42
- /** command description */
43
- description: string;
45
+ /** describe command */
46
+ describe: string;
47
+
48
+ /** give some example invocations of your program */
49
+ examples?: readonly ExampleOption[];
44
50
 
45
51
  /** list of positional arguments */
46
52
  positionals?: readonly PositionalArgument[];
47
53
  }
48
54
 
55
+ export interface ExampleOption {
56
+ /** script example command, the string `$0` will get interpolated to the current script name or node command */
57
+ cmd: string;
58
+
59
+ /** describe what the script command does */
60
+ describe: string;
61
+ }
62
+
49
63
  /** CLI options */
50
64
  export interface Config {
51
65
  /** CLI definition */
52
- command: CommandOptions;
66
+ command: CommandOption;
67
+
68
+ /**
69
+ * Show flag option in which casing (camelCase or kebab-case) in the help (defaults to 'kebab').
70
+ * Note: this is only for the help print, the parsing will always support both camel/kebab casing
71
+ */
72
+ helpFlagCasing?: 'camel' | 'kebab';
53
73
 
54
- /** option name length (w/wo alias) shown in the help (defaults to 20) */
55
- helpOptLength?: number;
74
+ /** min description length shown in the help (defaults to 50) */
75
+ helpDescMinLength?: number;
56
76
 
57
- /** description length shown in the help (defaults to 65) */
58
- helpDescLength?: number;
77
+ /** max description length shown in the help (defaults to 100) */
78
+ helpDescMaxLength?: number;
59
79
 
60
80
  /** CLI list of flag options */
61
- options: Record<string, ArgumentOptions>;
81
+ options: Record<string, FlagOption>;
62
82
 
63
83
  /** CLI or package version */
64
84
  version?: string;