@thi.ng/args 2.2.46 → 2.3.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/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2023-12-11T10:07:09Z
3
+ - **Last updated**: 2023-12-18T13:41:19Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
@@ -9,6 +9,24 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
9
9
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
10
10
  and/or version bumps of transitive dependencies.
11
11
 
12
+ ## [2.3.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/args@2.3.0) (2023-12-18)
13
+
14
+ #### 🚀 Features
15
+
16
+ - add cliApp() runner ([b2248fa](https://github.com/thi-ng/umbrella/commit/b2248fa))
17
+ - update lifecycle hooks, add NO_COLOR support, add docs ([4a0ebda](https://github.com/thi-ng/umbrella/commit/4a0ebda))
18
+ - add CLIAppConfig pre/post lifecycle hooks
19
+ - update UsageOpts.color handling
20
+ - add `NO_COLOR` env var support in cliApp()
21
+ - add doc strings
22
+ - update deps
23
+ - update cliApp() to support command context extensions ([61d9fb8](https://github.com/thi-ng/umbrella/commit/61d9fb8))
24
+ - update cliApp() error handling ([019e5a1](https://github.com/thi-ng/umbrella/commit/019e5a1))
25
+ - update argv handling in cliApp() ([b1ed768](https://github.com/thi-ng/umbrella/commit/b1ed768))
26
+ - add NO_COLOR aware formatters to CommandCtx ([0e7ddda](https://github.com/thi-ng/umbrella/commit/0e7ddda))
27
+ - update deps
28
+ - update cliApp() to use StreamLogger (target: process.stderr) ([b249295](https://github.com/thi-ng/umbrella/commit/b249295))
29
+
12
30
  ### [2.2.28](https://github.com/thi-ng/umbrella/tree/@thi.ng/args@2.2.28) (2023-08-04)
13
31
 
14
32
  #### ♻️ Refactoring
package/README.md CHANGED
@@ -69,14 +69,16 @@ For Node.js REPL:
69
69
  const args = await import("@thi.ng/args");
70
70
  ```
71
71
 
72
- Package sizes (brotli'd, pre-treeshake): ESM: 2.31 KB
72
+ Package sizes (brotli'd, pre-treeshake): ESM: 2.75 KB
73
73
 
74
74
  ## Dependencies
75
75
 
76
76
  - [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/develop/packages/api)
77
77
  - [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks)
78
78
  - [@thi.ng/errors](https://github.com/thi-ng/umbrella/tree/develop/packages/errors)
79
+ - [@thi.ng/logger](https://github.com/thi-ng/umbrella/tree/develop/packages/logger)
79
80
  - [@thi.ng/strings](https://github.com/thi-ng/umbrella/tree/develop/packages/strings)
81
+ - [@thi.ng/text-format](https://github.com/thi-ng/umbrella/tree/develop/packages/text-format)
80
82
 
81
83
  ## API
82
84
 
package/api.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- import type { Fn, IDeref, IObjectOf } from "@thi.ng/api";
1
+ import type { Fn, Fn2, IDeref, IObjectOf } from "@thi.ng/api";
2
+ import type { ILogger } from "@thi.ng/logger";
3
+ import type { FormatPresets } from "@thi.ng/text-format";
2
4
  export interface ArgSpecBase {
3
5
  /**
4
6
  * Shorthand for given arg/option
@@ -112,9 +114,13 @@ export interface UsageOpts {
112
114
  /**
113
115
  * If false, ANSI colors will be stripped from output.
114
116
  *
117
+ * @remarks
118
+ * When using {@link cliApp}, the default for this value will depend on the
119
+ * `NO_COLOR` env var being set. See https://no-color.org/ for details.
120
+ *
115
121
  * @defaultValue true
116
122
  */
117
- color: Partial<ColorTheme> | false;
123
+ color: Partial<ColorTheme> | boolean;
118
124
  /**
119
125
  * If true (default), display argument default values. Nullish or false
120
126
  * default values will never be shown.
@@ -172,4 +178,95 @@ export declare class Tuple<T> implements IDeref<T[]> {
172
178
  constructor(value: T[]);
173
179
  deref(): T[];
174
180
  }
181
+ export interface CLIAppConfig<OPTS extends object, CTX extends CommandCtx<OPTS, OPTS> = CommandCtx<OPTS, OPTS>> {
182
+ /**
183
+ * App (CLI command) short name.
184
+ */
185
+ name: string;
186
+ /**
187
+ * Shared args for all commands
188
+ */
189
+ opts: Args<OPTS>;
190
+ /**
191
+ * Command spec registry
192
+ */
193
+ commands: IObjectOf<Command<any, OPTS, CTX>>;
194
+ /**
195
+ * If true, the app will only use the single command entry in
196
+ * {@link CLIAppConfig.commands} and not expect the first CLI args to be a
197
+ * command name.
198
+ */
199
+ single?: boolean;
200
+ /**
201
+ * Usage options, same as {@link UsageOpts}. Usage will be shown
202
+ * automatically in case of arg parse errors.
203
+ */
204
+ usage: Partial<UsageOpts>;
205
+ /**
206
+ * Arguments vector to use for arg parsing. If omitted, uses `process.argv`
207
+ */
208
+ argv?: string[];
209
+ /**
210
+ * {@link CLIAppConfig.argv} index to start parsing from.
211
+ *
212
+ * @defaultValue 2
213
+ */
214
+ start?: number;
215
+ /**
216
+ * {@link CommandCtx} augmentation handler, i.e. an async function which
217
+ * will be called just before the actual command for additional setup/config
218
+ * purposes. The context object returned will be the one passed to the
219
+ * command.
220
+ */
221
+ ctx: Fn2<CommandCtx<OPTS, OPTS>, Command<any, OPTS, CTX>, Promise<CTX>>;
222
+ /**
223
+ * Lifecycle hook. Function which will be called just after the actual
224
+ * command handler, e.g. for teardown purposes.
225
+ */
226
+ post?: Fn2<CTX, Command<any, OPTS, CTX>, Promise<void>>;
227
+ }
228
+ export interface Command<OPTS extends BASE, BASE extends object, CTX extends CommandCtx<OPTS, BASE> = CommandCtx<OPTS, BASE>> {
229
+ /**
230
+ * Command description (short, single line)
231
+ */
232
+ desc: string;
233
+ /**
234
+ * Command specific CLI arg specs
235
+ */
236
+ opts: Args<Omit<OPTS, keyof BASE>>;
237
+ /**
238
+ * Number of required rest input value (after all parsed options). Leave
239
+ * unset to allow any number.
240
+ */
241
+ inputs?: number;
242
+ /**
243
+ * Actual command function/implementation.
244
+ */
245
+ fn: Fn<CTX, Promise<void>>;
246
+ }
247
+ export interface CommandCtx<OPTS extends BASE, BASE extends object> {
248
+ /**
249
+ * Logger to be used by all commands. By default uses a console logger with
250
+ * log level INFO. Can be customized via {@link CLIAppConfig.pre}.
251
+ */
252
+ logger: ILogger;
253
+ /**
254
+ * `NO_COLOR`-aware text formatting presets. If color output is NOT disabled
255
+ * via the `NO_COLOR` env var, this defaults to
256
+ * [`PRESET_ANSI16`](https://github.com/thi-ng/umbrella/blob/develop/packages/text-format/README.md),
257
+ * otherwise `PRESET_NONE` (i.e. same API, but ignoring any color requests).
258
+ *
259
+ * See https://no-color.org for context.
260
+ */
261
+ format: FormatPresets;
262
+ /**
263
+ * Parsed CLI args (according to provided command spec)
264
+ */
265
+ opts: OPTS;
266
+ /**
267
+ * Array of remaining CLI args (after parsed options). Individual commands
268
+ * can specify the number of items required via {@link Command.inputs}.
269
+ */
270
+ inputs: string[];
271
+ }
175
272
  //# sourceMappingURL=api.d.ts.map
package/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { CLIAppConfig, CommandCtx } from "./api.js";
2
+ export declare const cliApp: <OPTS extends object, CTX extends CommandCtx<OPTS, OPTS>>(config: CLIAppConfig<OPTS, CTX>) => Promise<void>;
3
+ //# sourceMappingURL=cli.d.ts.map
package/cli.js ADDED
@@ -0,0 +1,78 @@
1
+ import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
2
+ import { StreamLogger } from "@thi.ng/logger/stream";
3
+ import { padRight } from "@thi.ng/strings/pad-right";
4
+ import { PRESET_ANSI16, PRESET_NONE } from "@thi.ng/text-format/presets";
5
+ import { parse } from "./parse.js";
6
+ import { usage } from "./usage.js";
7
+ const cliApp = async (config) => {
8
+ const argv = config.argv || process.argv;
9
+ const isColor = !process.env.NO_COLOR;
10
+ const usageOpts = {
11
+ prefix: "",
12
+ color: isColor,
13
+ ...config.usage
14
+ };
15
+ try {
16
+ let cmdID;
17
+ let cmd;
18
+ let start = config.start ?? 2;
19
+ if (config.single) {
20
+ cmdID = Object.keys(config.commands)[0];
21
+ if (!cmdID)
22
+ illegalArgs("no command provided");
23
+ cmd = config.commands[cmdID];
24
+ } else {
25
+ cmdID = argv[start];
26
+ cmd = config.commands[cmdID];
27
+ usageOpts.prefix += __descriptions(config.commands);
28
+ if (!cmd)
29
+ __usageAndExit(config, usageOpts);
30
+ start++;
31
+ }
32
+ let parsed;
33
+ try {
34
+ parsed = parse({ ...config.opts, ...cmd.opts }, argv, {
35
+ showUsage: true,
36
+ usageOpts,
37
+ start
38
+ });
39
+ } catch (_) {
40
+ }
41
+ if (!parsed)
42
+ process.exit(1);
43
+ if (cmd.inputs !== void 0 && cmd.inputs !== parsed.rest.length) {
44
+ process.stderr.write(`expected ${cmd.inputs || 0} input(s)
45
+ `);
46
+ __usageAndExit(config, usageOpts);
47
+ }
48
+ const ctx = await config.ctx(
49
+ {
50
+ logger: new StreamLogger(process.stderr, config.name, "INFO"),
51
+ format: isColor ? PRESET_ANSI16 : PRESET_NONE,
52
+ opts: parsed.result,
53
+ inputs: parsed.rest
54
+ },
55
+ cmd
56
+ );
57
+ await cmd.fn(ctx);
58
+ if (config.post)
59
+ await config.post(ctx, cmd);
60
+ } catch (e) {
61
+ process.stderr.write(e.message + "\n\n");
62
+ process.exit(1);
63
+ }
64
+ };
65
+ const __usageAndExit = (config, usageOpts) => {
66
+ process.stderr.write(usage(config.opts, usageOpts));
67
+ process.exit(1);
68
+ };
69
+ const __descriptions = (commands) => [
70
+ "\nAvailable commands:\n",
71
+ ...Object.keys(commands).map(
72
+ (x) => `${padRight(16)(x)}: ${commands[x].desc}`
73
+ ),
74
+ "\n"
75
+ ].join("\n");
76
+ export {
77
+ cliApp
78
+ };
package/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./api.js";
2
2
  export * from "./args.js";
3
+ export * from "./cli.js";
3
4
  export * from "./coerce.js";
4
5
  export * from "./parse.js";
5
6
  export * from "./usage.js";
package/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./api.js";
2
2
  export * from "./args.js";
3
+ export * from "./cli.js";
3
4
  export * from "./coerce.js";
4
5
  export * from "./parse.js";
5
6
  export * from "./usage.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/args",
3
- "version": "2.2.46",
3
+ "version": "2.3.0",
4
4
  "description": "Declarative, functional & typechecked CLI argument/options parser, value coercions etc.",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -35,10 +35,12 @@
35
35
  "test": "bun test"
36
36
  },
37
37
  "dependencies": {
38
- "@thi.ng/api": "^8.9.12",
39
- "@thi.ng/checks": "^3.4.12",
40
- "@thi.ng/errors": "^2.4.6",
41
- "@thi.ng/strings": "^3.7.3"
38
+ "@thi.ng/api": "^8.9.13",
39
+ "@thi.ng/checks": "^3.4.13",
40
+ "@thi.ng/errors": "^2.4.7",
41
+ "@thi.ng/logger": "^2.1.0",
42
+ "@thi.ng/strings": "^3.7.4",
43
+ "@thi.ng/text-format": "^2.0.0"
42
44
  },
43
45
  "devDependencies": {
44
46
  "@microsoft/api-extractor": "^7.38.3",
@@ -58,6 +60,7 @@
58
60
  "declarative",
59
61
  "functional",
60
62
  "hex",
63
+ "logger",
61
64
  "nodejs",
62
65
  "parser",
63
66
  "tuple",
@@ -68,7 +71,7 @@
68
71
  "access": "public"
69
72
  },
70
73
  "engines": {
71
- "node": ">=12.7"
74
+ "node": ">=18"
72
75
  },
73
76
  "files": [
74
77
  "./*.js",
@@ -84,6 +87,9 @@
84
87
  "./args": {
85
88
  "default": "./args.js"
86
89
  },
90
+ "./cli": {
91
+ "default": "./cli.js"
92
+ },
87
93
  "./coerce": {
88
94
  "default": "./coerce.js"
89
95
  },
@@ -97,5 +103,5 @@
97
103
  "thi.ng": {
98
104
  "year": 2018
99
105
  },
100
- "gitHead": "5e7bafedfc3d53bc131469a28de31dd8e5b4a3ff\n"
106
+ "gitHead": "25a42a81fac8603a1e440a7aa8bc343276211ff4\n"
101
107
  }
package/usage.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { isPlainObject } from "@thi.ng/checks/is-plain-object";
1
2
  import { lengthAnsi } from "@thi.ng/strings/ansi";
2
3
  import { capitalize, kebab } from "@thi.ng/strings/case";
3
4
  import { padRight } from "@thi.ng/strings/pad-right";
@@ -17,7 +18,7 @@ const usage = (specs, opts = {}) => {
17
18
  groups: ["flags", "main"],
18
19
  ...opts
19
20
  };
20
- const theme = opts.color !== false ? { ...DEFAULT_THEME, ...opts.color } : {};
21
+ const theme = isPlainObject(opts.color) ? { ...DEFAULT_THEME, ...opts.color } : opts.color ? DEFAULT_THEME : {};
21
22
  const indent = repeat(" ", opts.paramWidth);
22
23
  const format = (ids) => ids.map((id) => argUsage(id, specs[id], opts, theme, indent));
23
24
  const sortedIDs = Object.keys(specs).sort();