jackspeak 4.0.0 → 4.0.2

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.
@@ -3,23 +3,61 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.jack = exports.Jack = exports.isConfigOption = exports.isConfigType = void 0;
6
+ exports.jack = exports.Jack = exports.isConfigOption = exports.isConfigOptionOfType = exports.isConfigType = void 0;
7
7
  const node_util_1 = require("node:util");
8
- const parse_args_js_1 = require("./parse-args.js");
9
8
  // it's a tiny API, just cast it inline, it's fine
10
9
  //@ts-ignore
11
10
  const cliui_1 = __importDefault(require("@isaacs/cliui"));
12
11
  const node_path_1 = require("node:path");
13
- const width = Math.min((process && process.stdout && process.stdout.columns) || 80, 80);
12
+ const isConfigType = (t) => typeof t === 'string' &&
13
+ (t === 'string' || t === 'number' || t === 'boolean');
14
+ exports.isConfigType = isConfigType;
15
+ const isValidValue = (v, type, multi) => {
16
+ if (multi) {
17
+ if (!Array.isArray(v))
18
+ return false;
19
+ return !v.some((v) => !isValidValue(v, type, false));
20
+ }
21
+ if (Array.isArray(v))
22
+ return false;
23
+ return typeof v === type;
24
+ };
25
+ const isValidOption = (v, vo) => !!vo &&
26
+ (Array.isArray(v) ? v.every(x => isValidOption(x, vo)) : vo.includes(v));
27
+ /**
28
+ * Determine whether an unknown object is a {@link ConfigOption} based only
29
+ * on its `type` and `multiple` property
30
+ */
31
+ const isConfigOptionOfType = (o, type, multi) => !!o &&
32
+ typeof o === 'object' &&
33
+ (0, exports.isConfigType)(o.type) &&
34
+ o.type === type &&
35
+ !!o.multiple === multi;
36
+ exports.isConfigOptionOfType = isConfigOptionOfType;
37
+ /**
38
+ * Determine whether an unknown object is a {@link ConfigOption} based on
39
+ * it having all valid properties
40
+ */
41
+ const isConfigOption = (o, type, multi) => (0, exports.isConfigOptionOfType)(o, type, multi) &&
42
+ undefOrType(o.short, 'string') &&
43
+ undefOrType(o.description, 'string') &&
44
+ undefOrType(o.hint, 'string') &&
45
+ undefOrType(o.validate, 'function') &&
46
+ (o.type === 'boolean' ?
47
+ o.validOptions === undefined
48
+ : undefOrTypeArray(o.validOptions, o.type)) &&
49
+ (o.default === undefined || isValidValue(o.default, type, multi));
50
+ exports.isConfigOption = isConfigOption;
51
+ const isHeading = (r) => r.type === 'heading';
52
+ const isDescription = (r) => r.type === 'description';
53
+ const width = Math.min(process?.stdout?.columns ?? 80, 80);
14
54
  // indentation spaces from heading level
15
55
  const indent = (n) => (n - 1) * 2;
16
- const toEnvKey = (pref, key) => {
17
- return [pref, key.replace(/[^a-zA-Z0-9]+/g, ' ')]
18
- .join(' ')
19
- .trim()
20
- .toUpperCase()
21
- .replace(/ /g, '_');
22
- };
56
+ const toEnvKey = (pref, key) => [pref, key.replace(/[^a-zA-Z0-9]+/g, ' ')]
57
+ .join(' ')
58
+ .trim()
59
+ .toUpperCase()
60
+ .replace(/ /g, '_');
23
61
  const toEnvVal = (value, delim = '\n') => {
24
62
  const str = typeof value === 'string' ? value
25
63
  : typeof value === 'boolean' ?
@@ -41,256 +79,144 @@ const fromEnvVal = (env, type, multiple, delim = '\n') => (multiple ?
41
79
  : type === 'string' ? env
42
80
  : type === 'boolean' ? env === '1'
43
81
  : +env.trim());
44
- const isConfigType = (t) => typeof t === 'string' &&
45
- (t === 'string' || t === 'number' || t === 'boolean');
46
- exports.isConfigType = isConfigType;
47
82
  const undefOrType = (v, t) => v === undefined || typeof v === t;
48
83
  const undefOrTypeArray = (v, t) => v === undefined || (Array.isArray(v) && v.every(x => typeof x === t));
49
- const isValidOption = (v, vo) => Array.isArray(v) ? v.every(x => isValidOption(x, vo)) : vo.includes(v);
50
84
  // print the value type, for error message reporting
51
85
  const valueType = (v) => typeof v === 'string' ? 'string'
52
86
  : typeof v === 'boolean' ? 'boolean'
53
87
  : typeof v === 'number' ? 'number'
54
88
  : Array.isArray(v) ?
55
- joinTypes([...new Set(v.map(v => valueType(v)))]) + '[]'
89
+ `${joinTypes([...new Set(v.map(v => valueType(v)))])}[]`
56
90
  : `${v.type}${v.multiple ? '[]' : ''}`;
57
91
  const joinTypes = (types) => types.length === 1 && typeof types[0] === 'string' ?
58
92
  types[0]
59
93
  : `(${types.join('|')})`;
60
- const isValidValue = (v, type, multi) => {
61
- if (multi) {
62
- if (!Array.isArray(v))
63
- return false;
64
- return !v.some((v) => !isValidValue(v, type, false));
65
- }
66
- if (Array.isArray(v))
67
- return false;
68
- return typeof v === type;
69
- };
70
- const isConfigOption = (o, type, multi) => !!o &&
71
- typeof o === 'object' &&
72
- (0, exports.isConfigType)(o.type) &&
73
- o.type === type &&
74
- undefOrType(o.short, 'string') &&
75
- undefOrType(o.description, 'string') &&
76
- undefOrType(o.hint, 'string') &&
77
- undefOrType(o.validate, 'function') &&
78
- (o.type === 'boolean' ?
79
- o.validOptions === undefined
80
- : undefOrTypeArray(o.validOptions, o.type)) &&
81
- (o.default === undefined || isValidValue(o.default, type, multi)) &&
82
- !!o.multiple === multi;
83
- exports.isConfigOption = isConfigOption;
84
- function num(o = {}) {
85
- const { default: def, validate: val, validOptions, ...rest } = o;
86
- if (def !== undefined && !isValidValue(def, 'number', false)) {
87
- throw new TypeError('invalid default value', {
88
- cause: {
89
- found: def,
90
- wanted: 'number',
91
- },
92
- });
93
- }
94
- if (!undefOrTypeArray(validOptions, 'number')) {
95
- throw new TypeError('invalid validOptions', {
96
- cause: {
97
- found: validOptions,
98
- wanted: 'number[]',
99
- },
100
- });
101
- }
102
- const validate = val ?
103
- val
104
- : undefined;
105
- return {
106
- ...rest,
107
- default: def,
108
- validate,
109
- validOptions,
110
- type: 'number',
111
- multiple: false,
112
- };
113
- }
114
- function numList(o = {}) {
115
- const { default: def, validate: val, validOptions, ...rest } = o;
116
- if (def !== undefined && !isValidValue(def, 'number', true)) {
117
- throw new TypeError('invalid default value', {
118
- cause: {
119
- found: def,
120
- wanted: 'number[]',
121
- },
122
- });
94
+ const validateFieldMeta = (field, fieldMeta) => {
95
+ if (fieldMeta) {
96
+ if (field.type !== undefined && field.type !== fieldMeta.type) {
97
+ throw new TypeError(`invalid type`, {
98
+ cause: {
99
+ found: field.type,
100
+ wanted: [fieldMeta.type, undefined],
101
+ },
102
+ });
103
+ }
104
+ if (field.multiple !== undefined &&
105
+ !!field.multiple !== fieldMeta.multiple) {
106
+ throw new TypeError(`invalid multiple`, {
107
+ cause: {
108
+ found: field.multiple,
109
+ wanted: [fieldMeta.multiple, undefined],
110
+ },
111
+ });
112
+ }
113
+ return fieldMeta;
123
114
  }
124
- if (!undefOrTypeArray(validOptions, 'number')) {
125
- throw new TypeError('invalid validOptions', {
115
+ if (!(0, exports.isConfigType)(field.type)) {
116
+ throw new TypeError(`invalid type`, {
126
117
  cause: {
127
- found: validOptions,
128
- wanted: 'number[]',
118
+ found: field.type,
119
+ wanted: ['string', 'number', 'boolean'],
129
120
  },
130
121
  });
131
122
  }
132
- const validate = val ?
133
- val
134
- : undefined;
135
123
  return {
136
- ...rest,
137
- default: def,
138
- validate,
139
- validOptions,
140
- type: 'number',
141
- multiple: true,
124
+ type: field.type,
125
+ multiple: !!field.multiple,
142
126
  };
143
- }
144
- function opt(o = {}) {
145
- const { default: def, validate: val, validOptions, ...rest } = o;
146
- if (def !== undefined && !isValidValue(def, 'string', false)) {
147
- throw new TypeError('invalid default value', {
148
- cause: {
149
- found: def,
150
- wanted: 'string',
151
- },
152
- });
153
- }
154
- if (!undefOrTypeArray(validOptions, 'string')) {
155
- throw new TypeError('invalid validOptions', {
156
- cause: {
157
- found: validOptions,
158
- wanted: 'string[]',
159
- },
160
- });
161
- }
162
- const validate = val ?
163
- val
164
- : undefined;
165
- return {
166
- ...rest,
167
- default: def,
168
- validate,
169
- validOptions,
170
- type: 'string',
171
- multiple: false,
127
+ };
128
+ const validateField = (o, type, multiple) => {
129
+ const validateValidOptions = (def, validOptions) => {
130
+ if (!undefOrTypeArray(validOptions, type)) {
131
+ throw new TypeError('invalid validOptions', {
132
+ cause: {
133
+ found: validOptions,
134
+ wanted: valueType({ type, multiple: true }),
135
+ },
136
+ });
137
+ }
138
+ if (def !== undefined && validOptions !== undefined) {
139
+ const valid = Array.isArray(def) ?
140
+ def.every(v => validOptions.includes(v))
141
+ : validOptions.includes(def);
142
+ if (!valid) {
143
+ throw new TypeError('invalid default value not in validOptions', {
144
+ cause: {
145
+ found: def,
146
+ wanted: validOptions,
147
+ },
148
+ });
149
+ }
150
+ }
172
151
  };
173
- }
174
- function optList(o = {}) {
175
- const { default: def, validate: val, validOptions, ...rest } = o;
176
- if (def !== undefined && !isValidValue(def, 'string', true)) {
152
+ if (o.default !== undefined &&
153
+ !isValidValue(o.default, type, multiple)) {
177
154
  throw new TypeError('invalid default value', {
178
155
  cause: {
179
- found: def,
180
- wanted: 'string[]',
156
+ found: o.default,
157
+ wanted: valueType({ type, multiple }),
181
158
  },
182
159
  });
183
160
  }
184
- if (!undefOrTypeArray(validOptions, 'string')) {
185
- throw new TypeError('invalid validOptions', {
186
- cause: {
187
- found: validOptions,
188
- wanted: 'string[]',
189
- },
190
- });
161
+ if ((0, exports.isConfigOptionOfType)(o, 'number', false) ||
162
+ (0, exports.isConfigOptionOfType)(o, 'number', true)) {
163
+ validateValidOptions(o.default, o.validOptions);
191
164
  }
192
- const validate = val ?
193
- val
194
- : undefined;
195
- return {
196
- ...rest,
197
- default: def,
198
- validate,
199
- validOptions,
200
- type: 'string',
201
- multiple: true,
202
- };
203
- }
204
- function flag(o = {}) {
205
- const { hint, default: def, validate: val, ...rest } = o;
206
- delete rest.validOptions;
207
- if (def !== undefined && !isValidValue(def, 'boolean', false)) {
208
- throw new TypeError('invalid default value');
209
- }
210
- const validate = val ?
211
- val
212
- : undefined;
213
- if (hint !== undefined) {
214
- throw new TypeError('cannot provide hint for flag');
165
+ else if ((0, exports.isConfigOptionOfType)(o, 'string', false) ||
166
+ (0, exports.isConfigOptionOfType)(o, 'string', true)) {
167
+ validateValidOptions(o.default, o.validOptions);
215
168
  }
216
- return {
217
- ...rest,
218
- default: def,
219
- validate,
220
- type: 'boolean',
221
- multiple: false,
222
- };
223
- }
224
- function flagList(o = {}) {
225
- const { hint, default: def, validate: val, ...rest } = o;
226
- delete rest.validOptions;
227
- if (def !== undefined && !isValidValue(def, 'boolean', true)) {
228
- throw new TypeError('invalid default value');
229
- }
230
- const validate = val ?
231
- val
232
- : undefined;
233
- if (hint !== undefined) {
234
- throw new TypeError('cannot provide hint for flag list');
169
+ else if ((0, exports.isConfigOptionOfType)(o, 'boolean', false) ||
170
+ (0, exports.isConfigOptionOfType)(o, 'boolean', true)) {
171
+ if (o.hint !== undefined) {
172
+ throw new TypeError('cannot provide hint for flag');
173
+ }
174
+ if (o.validOptions !== undefined) {
175
+ throw new TypeError('cannot provide validOptions for flag');
176
+ }
235
177
  }
236
- return {
237
- ...rest,
238
- default: def,
239
- validate,
240
- type: 'boolean',
241
- multiple: true,
242
- };
243
- }
178
+ return o;
179
+ };
244
180
  const toParseArgsOptionsConfig = (options) => {
245
- const c = {};
246
- for (const longOption in options) {
247
- const config = options[longOption];
248
- /* c8 ignore start */
249
- if (!config) {
250
- throw new Error('config must be an object: ' + longOption);
251
- }
252
- /* c8 ignore start */
253
- if ((0, exports.isConfigOption)(config, 'number', true)) {
254
- c[longOption] = {
255
- type: 'string',
256
- multiple: true,
257
- default: config.default?.map(c => String(c)),
258
- };
259
- }
260
- else if ((0, exports.isConfigOption)(config, 'number', false)) {
261
- c[longOption] = {
262
- type: 'string',
263
- multiple: false,
264
- default: config.default === undefined ?
265
- undefined
266
- : String(config.default),
267
- };
181
+ return Object.entries(options).reduce((acc, [longOption, o]) => {
182
+ const p = {
183
+ type: 'string',
184
+ multiple: !!o.multiple,
185
+ ...(typeof o.short === 'string' ? { short: o.short } : undefined),
186
+ };
187
+ const setNoBool = () => {
188
+ if (!longOption.startsWith('no-') && !options[`no-${longOption}`]) {
189
+ acc[`no-${longOption}`] = {
190
+ type: 'boolean',
191
+ multiple: !!o.multiple,
192
+ };
193
+ }
194
+ };
195
+ const setDefault = (def, fn) => {
196
+ if (def !== undefined) {
197
+ p.default = fn(def);
198
+ }
199
+ };
200
+ if ((0, exports.isConfigOption)(o, 'number', false)) {
201
+ setDefault(o.default, String);
268
202
  }
269
- else {
270
- const conf = config;
271
- c[longOption] = {
272
- type: conf.type,
273
- multiple: !!conf.multiple,
274
- default: conf.default,
275
- };
276
- }
277
- const clo = c[longOption];
278
- if (typeof config.short === 'string') {
279
- clo.short = config.short;
280
- }
281
- if (config.type === 'boolean' &&
282
- !longOption.startsWith('no-') &&
283
- !options[`no-${longOption}`]) {
284
- c[`no-${longOption}`] = {
285
- type: 'boolean',
286
- multiple: config.multiple,
287
- };
288
- }
289
- }
290
- return c;
203
+ else if ((0, exports.isConfigOption)(o, 'number', true)) {
204
+ setDefault(o.default, d => d.map(v => String(v)));
205
+ }
206
+ else if ((0, exports.isConfigOption)(o, 'string', false) ||
207
+ (0, exports.isConfigOption)(o, 'string', true)) {
208
+ setDefault(o.default, v => v);
209
+ }
210
+ else if ((0, exports.isConfigOption)(o, 'boolean', false) ||
211
+ (0, exports.isConfigOption)(o, 'boolean', true)) {
212
+ p.type = 'boolean';
213
+ setDefault(o.default, v => v);
214
+ setNoBool();
215
+ }
216
+ acc[longOption] = p;
217
+ return acc;
218
+ }, {});
291
219
  };
292
- const isHeading = (r) => r.type === 'heading';
293
- const isDescription = (r) => r.type === 'description';
294
220
  /**
295
221
  * Class returned by the {@link jack} function and all configuration
296
222
  * definition methods. This is what gets chained together.
@@ -328,16 +254,12 @@ class Jack {
328
254
  this.validate(values);
329
255
  }
330
256
  catch (er) {
331
- const e = er;
332
- if (source && e && typeof e === 'object') {
333
- if (e.cause && typeof e.cause === 'object') {
334
- Object.assign(e.cause, { path: source });
335
- }
336
- else {
337
- e.cause = { path: source };
338
- }
257
+ if (source && er instanceof Error) {
258
+ /* c8 ignore next */
259
+ const cause = typeof er.cause === 'object' ? er.cause : {};
260
+ er.cause = { ...cause, path: source };
339
261
  }
340
- throw e;
262
+ throw er;
341
263
  }
342
264
  for (const [field, value] of Object.entries(values)) {
343
265
  const my = this.#configSet[field];
@@ -400,10 +322,9 @@ class Jack {
400
322
  if (args === process.argv) {
401
323
  args = args.slice(process._eval !== undefined ? 1 : 2);
402
324
  }
403
- const options = toParseArgsOptionsConfig(this.#configSet);
404
- const result = (0, parse_args_js_1.parseArgs)({
325
+ const result = (0, node_util_1.parseArgs)({
405
326
  args,
406
- options,
327
+ options: toParseArgsOptionsConfig(this.#configSet),
407
328
  // always strict, but using our own logic
408
329
  strict: false,
409
330
  allowPositionals: this.#allowPositionals,
@@ -496,13 +417,10 @@ class Jack {
496
417
  for (const [field, value] of Object.entries(p.values)) {
497
418
  const valid = this.#configSet[field]?.validate;
498
419
  const validOptions = this.#configSet[field]?.validOptions;
499
- let cause;
500
- if (validOptions && !isValidOption(value, validOptions)) {
501
- cause = { name: field, found: value, validOptions: validOptions };
502
- }
503
- if (valid && !valid(value)) {
504
- cause = cause || { name: field, found: value };
505
- }
420
+ const cause = validOptions && !isValidOption(value, validOptions) ?
421
+ { name: field, found: value, validOptions: validOptions }
422
+ : valid && !valid(value) ? { name: field, found: value }
423
+ : undefined;
506
424
  if (cause) {
507
425
  throw new Error(`Invalid value provided for --${field}: ${JSON.stringify(value)}`, { cause });
508
426
  }
@@ -555,18 +473,11 @@ class Jack {
555
473
  },
556
474
  });
557
475
  }
558
- let cause;
559
- if (config.validOptions &&
560
- !isValidOption(value, config.validOptions)) {
561
- cause = {
562
- name: field,
563
- found: value,
564
- validOptions: config.validOptions,
565
- };
566
- }
567
- if (config.validate && !config.validate(value)) {
568
- cause = cause || { name: field, found: value };
569
- }
476
+ const cause = config.validOptions && !isValidOption(value, config.validOptions) ?
477
+ { name: field, found: value, validOptions: config.validOptions }
478
+ : config.validate && !config.validate(value) ?
479
+ { name: field, found: value }
480
+ : undefined;
570
481
  if (cause) {
571
482
  throw new Error(`Invalid config value for ${field}: ${value}`, {
572
483
  cause,
@@ -603,37 +514,37 @@ class Jack {
603
514
  * Add one or more number fields.
604
515
  */
605
516
  num(fields) {
606
- return this.#addFields(fields, num);
517
+ return this.#addFieldsWith(fields, 'number', false);
607
518
  }
608
519
  /**
609
520
  * Add one or more multiple number fields.
610
521
  */
611
522
  numList(fields) {
612
- return this.#addFields(fields, numList);
523
+ return this.#addFieldsWith(fields, 'number', true);
613
524
  }
614
525
  /**
615
526
  * Add one or more string option fields.
616
527
  */
617
528
  opt(fields) {
618
- return this.#addFields(fields, opt);
529
+ return this.#addFieldsWith(fields, 'string', false);
619
530
  }
620
531
  /**
621
532
  * Add one or more multiple string option fields.
622
533
  */
623
534
  optList(fields) {
624
- return this.#addFields(fields, optList);
535
+ return this.#addFieldsWith(fields, 'string', true);
625
536
  }
626
537
  /**
627
538
  * Add one or more flag fields.
628
539
  */
629
540
  flag(fields) {
630
- return this.#addFields(fields, flag);
541
+ return this.#addFieldsWith(fields, 'boolean', false);
631
542
  }
632
543
  /**
633
544
  * Add one or more multiple flag fields.
634
545
  */
635
546
  flagList(fields) {
636
- return this.#addFields(fields, flagList);
547
+ return this.#addFieldsWith(fields, 'boolean', true);
637
548
  }
638
549
  /**
639
550
  * Generic field definition method. Similar to flag/flagList/number/etc,
@@ -641,29 +552,22 @@ class Jack {
641
552
  * fields on each one, or Jack won't know how to define them.
642
553
  */
643
554
  addFields(fields) {
644
- const next = this;
645
- for (const [name, field] of Object.entries(fields)) {
646
- this.#validateName(name, field);
647
- next.#fields.push({
648
- type: 'config',
649
- name,
650
- value: field,
651
- });
652
- }
653
- Object.assign(next.#configSet, fields);
654
- return next;
555
+ return this.#addFields(this, fields);
655
556
  }
656
- #addFields(fields, fn) {
657
- const next = this;
557
+ #addFieldsWith(fields, type, multiple) {
558
+ return this.#addFields(this, fields, {
559
+ type,
560
+ multiple,
561
+ });
562
+ }
563
+ #addFields(next, fields, opt) {
658
564
  Object.assign(next.#configSet, Object.fromEntries(Object.entries(fields).map(([name, field]) => {
659
565
  this.#validateName(name, field);
660
- const option = fn(field);
661
- next.#fields.push({
662
- type: 'config',
663
- name,
664
- value: option,
665
- });
666
- return [name, option];
566
+ const { type, multiple } = validateFieldMeta(field, opt);
567
+ const value = { ...field, type, multiple };
568
+ validateField(value, type, multiple);
569
+ next.#fields.push({ type: 'config', name, value });
570
+ return [name, value];
667
571
  })));
668
572
  return next;
669
573
  }
@@ -699,6 +603,7 @@ class Jack {
699
603
  if (this.#usage)
700
604
  return this.#usage;
701
605
  let headingLevel = 1;
606
+ //@ts-ignore
702
607
  const ui = (0, cliui_1.default)({ width });
703
608
  const first = this.#fields[0];
704
609
  let start = first?.type === 'heading' ? 1 : 0;
@@ -941,6 +846,11 @@ class Jack {
941
846
  }
942
847
  }
943
848
  exports.Jack = Jack;
849
+ /**
850
+ * Main entry point. Create and return a {@link Jack} object.
851
+ */
852
+ const jack = (options = {}) => new Jack(options);
853
+ exports.jack = jack;
944
854
  // Unwrap and un-indent, so we can wrap description
945
855
  // strings however makes them look nice in the code.
946
856
  const normalize = (s, pre = false) => {
@@ -1002,9 +912,4 @@ const normalizeOneLine = (s, pre = false) => {
1002
912
  .trim();
1003
913
  return pre ? `\`${n}\`` : n;
1004
914
  };
1005
- /**
1006
- * Main entry point. Create and return a {@link Jack} object.
1007
- */
1008
- const jack = (options = {}) => new Jack(options);
1009
- exports.jack = jack;
1010
915
  //# sourceMappingURL=index.js.map