jackspeak 4.0.1 → 4.0.3

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/dist/esm/index.js CHANGED
@@ -1,19 +1,54 @@
1
- import { inspect } from 'node:util';
2
- import { parseArgs } from './parse-args.js';
1
+ import { inspect, parseArgs, } from 'node:util';
3
2
  // it's a tiny API, just cast it inline, it's fine
4
3
  //@ts-ignore
5
4
  import cliui from '@isaacs/cliui';
6
5
  import { basename } from 'node:path';
7
- const width = Math.min((process && process.stdout && process.stdout.columns) || 80, 80);
6
+ export const isConfigType = (t) => typeof t === 'string' &&
7
+ (t === 'string' || t === 'number' || t === 'boolean');
8
+ const isValidValue = (v, type, multi) => {
9
+ if (multi) {
10
+ if (!Array.isArray(v))
11
+ return false;
12
+ return !v.some((v) => !isValidValue(v, type, false));
13
+ }
14
+ if (Array.isArray(v))
15
+ return false;
16
+ return typeof v === type;
17
+ };
18
+ const isValidOption = (v, vo) => !!vo &&
19
+ (Array.isArray(v) ? v.every(x => isValidOption(x, vo)) : vo.includes(v));
20
+ /**
21
+ * Determine whether an unknown object is a {@link ConfigOption} based only
22
+ * on its `type` and `multiple` property
23
+ */
24
+ export const isConfigOptionOfType = (o, type, multi) => !!o &&
25
+ typeof o === 'object' &&
26
+ isConfigType(o.type) &&
27
+ o.type === type &&
28
+ !!o.multiple === multi;
29
+ /**
30
+ * Determine whether an unknown object is a {@link ConfigOption} based on
31
+ * it having all valid properties
32
+ */
33
+ export const isConfigOption = (o, type, multi) => isConfigOptionOfType(o, type, multi) &&
34
+ undefOrType(o.short, 'string') &&
35
+ undefOrType(o.description, 'string') &&
36
+ undefOrType(o.hint, 'string') &&
37
+ undefOrType(o.validate, 'function') &&
38
+ (o.type === 'boolean' ?
39
+ o.validOptions === undefined
40
+ : undefOrTypeArray(o.validOptions, o.type)) &&
41
+ (o.default === undefined || isValidValue(o.default, type, multi));
42
+ const isHeading = (r) => r.type === 'heading';
43
+ const isDescription = (r) => r.type === 'description';
44
+ const width = Math.min(process?.stdout?.columns ?? 80, 80);
8
45
  // indentation spaces from heading level
9
46
  const indent = (n) => (n - 1) * 2;
10
- const toEnvKey = (pref, key) => {
11
- return [pref, key.replace(/[^a-zA-Z0-9]+/g, ' ')]
12
- .join(' ')
13
- .trim()
14
- .toUpperCase()
15
- .replace(/ /g, '_');
16
- };
47
+ const toEnvKey = (pref, key) => [pref, key.replace(/[^a-zA-Z0-9]+/g, ' ')]
48
+ .join(' ')
49
+ .trim()
50
+ .toUpperCase()
51
+ .replace(/ /g, '_');
17
52
  const toEnvVal = (value, delim = '\n') => {
18
53
  const str = typeof value === 'string' ? value
19
54
  : typeof value === 'boolean' ?
@@ -24,7 +59,7 @@ const toEnvVal = (value, delim = '\n') => {
24
59
  value.map((v) => toEnvVal(v)).join(delim)
25
60
  : /* c8 ignore start */ undefined;
26
61
  if (typeof str !== 'string') {
27
- throw new Error(`could not serialize value to environment: ${JSON.stringify(value)}`);
62
+ throw new Error(`could not serialize value to environment: ${JSON.stringify(value)}`, { cause: { code: 'JACKSPEAK' } });
28
63
  }
29
64
  /* c8 ignore stop */
30
65
  return str;
@@ -35,254 +70,144 @@ const fromEnvVal = (env, type, multiple, delim = '\n') => (multiple ?
35
70
  : type === 'string' ? env
36
71
  : type === 'boolean' ? env === '1'
37
72
  : +env.trim());
38
- export const isConfigType = (t) => typeof t === 'string' &&
39
- (t === 'string' || t === 'number' || t === 'boolean');
40
73
  const undefOrType = (v, t) => v === undefined || typeof v === t;
41
74
  const undefOrTypeArray = (v, t) => v === undefined || (Array.isArray(v) && v.every(x => typeof x === t));
42
- const isValidOption = (v, vo) => Array.isArray(v) ? v.every(x => isValidOption(x, vo)) : vo.includes(v);
43
75
  // print the value type, for error message reporting
44
76
  const valueType = (v) => typeof v === 'string' ? 'string'
45
77
  : typeof v === 'boolean' ? 'boolean'
46
78
  : typeof v === 'number' ? 'number'
47
79
  : Array.isArray(v) ?
48
- joinTypes([...new Set(v.map(v => valueType(v)))]) + '[]'
80
+ `${joinTypes([...new Set(v.map(v => valueType(v)))])}[]`
49
81
  : `${v.type}${v.multiple ? '[]' : ''}`;
50
82
  const joinTypes = (types) => types.length === 1 && typeof types[0] === 'string' ?
51
83
  types[0]
52
84
  : `(${types.join('|')})`;
53
- const isValidValue = (v, type, multi) => {
54
- if (multi) {
55
- if (!Array.isArray(v))
56
- return false;
57
- return !v.some((v) => !isValidValue(v, type, false));
58
- }
59
- if (Array.isArray(v))
60
- return false;
61
- return typeof v === type;
62
- };
63
- export const isConfigOption = (o, type, multi) => !!o &&
64
- typeof o === 'object' &&
65
- isConfigType(o.type) &&
66
- o.type === type &&
67
- undefOrType(o.short, 'string') &&
68
- undefOrType(o.description, 'string') &&
69
- undefOrType(o.hint, 'string') &&
70
- undefOrType(o.validate, 'function') &&
71
- (o.type === 'boolean' ?
72
- o.validOptions === undefined
73
- : undefOrTypeArray(o.validOptions, o.type)) &&
74
- (o.default === undefined || isValidValue(o.default, type, multi)) &&
75
- !!o.multiple === multi;
76
- function num(o = {}) {
77
- const { default: def, validate: val, validOptions, ...rest } = o;
78
- if (def !== undefined && !isValidValue(def, 'number', false)) {
79
- throw new TypeError('invalid default value', {
80
- cause: {
81
- found: def,
82
- wanted: 'number',
83
- },
84
- });
85
- }
86
- if (!undefOrTypeArray(validOptions, 'number')) {
87
- throw new TypeError('invalid validOptions', {
88
- cause: {
89
- found: validOptions,
90
- wanted: 'number[]',
91
- },
92
- });
93
- }
94
- const validate = val ?
95
- val
96
- : undefined;
97
- return {
98
- ...rest,
99
- default: def,
100
- validate,
101
- validOptions,
102
- type: 'number',
103
- multiple: false,
104
- };
105
- }
106
- function numList(o = {}) {
107
- const { default: def, validate: val, validOptions, ...rest } = o;
108
- if (def !== undefined && !isValidValue(def, 'number', true)) {
109
- throw new TypeError('invalid default value', {
110
- cause: {
111
- found: def,
112
- wanted: 'number[]',
113
- },
114
- });
85
+ const validateFieldMeta = (field, fieldMeta) => {
86
+ if (fieldMeta) {
87
+ if (field.type !== undefined && field.type !== fieldMeta.type) {
88
+ throw new TypeError(`invalid type`, {
89
+ cause: {
90
+ found: field.type,
91
+ wanted: [fieldMeta.type, undefined],
92
+ },
93
+ });
94
+ }
95
+ if (field.multiple !== undefined &&
96
+ !!field.multiple !== fieldMeta.multiple) {
97
+ throw new TypeError(`invalid multiple`, {
98
+ cause: {
99
+ found: field.multiple,
100
+ wanted: [fieldMeta.multiple, undefined],
101
+ },
102
+ });
103
+ }
104
+ return fieldMeta;
115
105
  }
116
- if (!undefOrTypeArray(validOptions, 'number')) {
117
- throw new TypeError('invalid validOptions', {
106
+ if (!isConfigType(field.type)) {
107
+ throw new TypeError(`invalid type`, {
118
108
  cause: {
119
- found: validOptions,
120
- wanted: 'number[]',
109
+ found: field.type,
110
+ wanted: ['string', 'number', 'boolean'],
121
111
  },
122
112
  });
123
113
  }
124
- const validate = val ?
125
- val
126
- : undefined;
127
114
  return {
128
- ...rest,
129
- default: def,
130
- validate,
131
- validOptions,
132
- type: 'number',
133
- multiple: true,
115
+ type: field.type,
116
+ multiple: !!field.multiple,
134
117
  };
135
- }
136
- function opt(o = {}) {
137
- const { default: def, validate: val, validOptions, ...rest } = o;
138
- if (def !== undefined && !isValidValue(def, 'string', false)) {
139
- throw new TypeError('invalid default value', {
140
- cause: {
141
- found: def,
142
- wanted: 'string',
143
- },
144
- });
145
- }
146
- if (!undefOrTypeArray(validOptions, 'string')) {
147
- throw new TypeError('invalid validOptions', {
148
- cause: {
149
- found: validOptions,
150
- wanted: 'string[]',
151
- },
152
- });
153
- }
154
- const validate = val ?
155
- val
156
- : undefined;
157
- return {
158
- ...rest,
159
- default: def,
160
- validate,
161
- validOptions,
162
- type: 'string',
163
- multiple: false,
118
+ };
119
+ const validateField = (o, type, multiple) => {
120
+ const validateValidOptions = (def, validOptions) => {
121
+ if (!undefOrTypeArray(validOptions, type)) {
122
+ throw new TypeError('invalid validOptions', {
123
+ cause: {
124
+ found: validOptions,
125
+ wanted: valueType({ type, multiple: true }),
126
+ },
127
+ });
128
+ }
129
+ if (def !== undefined && validOptions !== undefined) {
130
+ const valid = Array.isArray(def) ?
131
+ def.every(v => validOptions.includes(v))
132
+ : validOptions.includes(def);
133
+ if (!valid) {
134
+ throw new TypeError('invalid default value not in validOptions', {
135
+ cause: {
136
+ found: def,
137
+ wanted: validOptions,
138
+ },
139
+ });
140
+ }
141
+ }
164
142
  };
165
- }
166
- function optList(o = {}) {
167
- const { default: def, validate: val, validOptions, ...rest } = o;
168
- if (def !== undefined && !isValidValue(def, 'string', true)) {
143
+ if (o.default !== undefined &&
144
+ !isValidValue(o.default, type, multiple)) {
169
145
  throw new TypeError('invalid default value', {
170
146
  cause: {
171
- found: def,
172
- wanted: 'string[]',
147
+ found: o.default,
148
+ wanted: valueType({ type, multiple }),
173
149
  },
174
150
  });
175
151
  }
176
- if (!undefOrTypeArray(validOptions, 'string')) {
177
- throw new TypeError('invalid validOptions', {
178
- cause: {
179
- found: validOptions,
180
- wanted: 'string[]',
181
- },
182
- });
152
+ if (isConfigOptionOfType(o, 'number', false) ||
153
+ isConfigOptionOfType(o, 'number', true)) {
154
+ validateValidOptions(o.default, o.validOptions);
183
155
  }
184
- const validate = val ?
185
- val
186
- : undefined;
187
- return {
188
- ...rest,
189
- default: def,
190
- validate,
191
- validOptions,
192
- type: 'string',
193
- multiple: true,
194
- };
195
- }
196
- function flag(o = {}) {
197
- const { hint, default: def, validate: val, ...rest } = o;
198
- delete rest.validOptions;
199
- if (def !== undefined && !isValidValue(def, 'boolean', false)) {
200
- throw new TypeError('invalid default value');
201
- }
202
- const validate = val ?
203
- val
204
- : undefined;
205
- if (hint !== undefined) {
206
- throw new TypeError('cannot provide hint for flag');
156
+ else if (isConfigOptionOfType(o, 'string', false) ||
157
+ isConfigOptionOfType(o, 'string', true)) {
158
+ validateValidOptions(o.default, o.validOptions);
207
159
  }
208
- return {
209
- ...rest,
210
- default: def,
211
- validate,
212
- type: 'boolean',
213
- multiple: false,
214
- };
215
- }
216
- function flagList(o = {}) {
217
- const { hint, default: def, validate: val, ...rest } = o;
218
- delete rest.validOptions;
219
- if (def !== undefined && !isValidValue(def, 'boolean', true)) {
220
- throw new TypeError('invalid default value');
221
- }
222
- const validate = val ?
223
- val
224
- : undefined;
225
- if (hint !== undefined) {
226
- throw new TypeError('cannot provide hint for flag list');
160
+ else if (isConfigOptionOfType(o, 'boolean', false) ||
161
+ isConfigOptionOfType(o, 'boolean', true)) {
162
+ if (o.hint !== undefined) {
163
+ throw new TypeError('cannot provide hint for flag');
164
+ }
165
+ if (o.validOptions !== undefined) {
166
+ throw new TypeError('cannot provide validOptions for flag');
167
+ }
227
168
  }
228
- return {
229
- ...rest,
230
- default: def,
231
- validate,
232
- type: 'boolean',
233
- multiple: true,
234
- };
235
- }
169
+ return o;
170
+ };
236
171
  const toParseArgsOptionsConfig = (options) => {
237
- const c = {};
238
- for (const longOption in options) {
239
- const config = options[longOption];
240
- /* c8 ignore start */
241
- if (!config) {
242
- throw new Error('config must be an object: ' + longOption);
243
- }
244
- /* c8 ignore start */
245
- if (isConfigOption(config, 'number', true)) {
246
- c[longOption] = {
247
- type: 'string',
248
- multiple: true,
249
- default: config.default?.map(c => String(c)),
250
- };
251
- }
252
- else if (isConfigOption(config, 'number', false)) {
253
- c[longOption] = {
254
- type: 'string',
255
- multiple: false,
256
- default: config.default === undefined ?
257
- undefined
258
- : String(config.default),
259
- };
172
+ return Object.entries(options).reduce((acc, [longOption, o]) => {
173
+ const p = {
174
+ type: 'string',
175
+ multiple: !!o.multiple,
176
+ ...(typeof o.short === 'string' ? { short: o.short } : undefined),
177
+ };
178
+ const setNoBool = () => {
179
+ if (!longOption.startsWith('no-') && !options[`no-${longOption}`]) {
180
+ acc[`no-${longOption}`] = {
181
+ type: 'boolean',
182
+ multiple: !!o.multiple,
183
+ };
184
+ }
185
+ };
186
+ const setDefault = (def, fn) => {
187
+ if (def !== undefined) {
188
+ p.default = fn(def);
189
+ }
190
+ };
191
+ if (isConfigOption(o, 'number', false)) {
192
+ setDefault(o.default, String);
260
193
  }
261
- else {
262
- const conf = config;
263
- c[longOption] = {
264
- type: conf.type,
265
- multiple: !!conf.multiple,
266
- default: conf.default,
267
- };
268
- }
269
- const clo = c[longOption];
270
- if (typeof config.short === 'string') {
271
- clo.short = config.short;
272
- }
273
- if (config.type === 'boolean' &&
274
- !longOption.startsWith('no-') &&
275
- !options[`no-${longOption}`]) {
276
- c[`no-${longOption}`] = {
277
- type: 'boolean',
278
- multiple: config.multiple,
279
- };
280
- }
281
- }
282
- return c;
194
+ else if (isConfigOption(o, 'number', true)) {
195
+ setDefault(o.default, d => d.map(v => String(v)));
196
+ }
197
+ else if (isConfigOption(o, 'string', false) ||
198
+ isConfigOption(o, 'string', true)) {
199
+ setDefault(o.default, v => v);
200
+ }
201
+ else if (isConfigOption(o, 'boolean', false) ||
202
+ isConfigOption(o, 'boolean', true)) {
203
+ p.type = 'boolean';
204
+ setDefault(o.default, v => v);
205
+ setNoBool();
206
+ }
207
+ acc[longOption] = p;
208
+ return acc;
209
+ }, {});
283
210
  };
284
- const isHeading = (r) => r.type === 'heading';
285
- const isDescription = (r) => r.type === 'description';
286
211
  /**
287
212
  * Class returned by the {@link jack} function and all configuration
288
213
  * definition methods. This is what gets chained together.
@@ -320,16 +245,12 @@ export class Jack {
320
245
  this.validate(values);
321
246
  }
322
247
  catch (er) {
323
- const e = er;
324
- if (source && e && typeof e === 'object') {
325
- if (e.cause && typeof e.cause === 'object') {
326
- Object.assign(e.cause, { path: source });
327
- }
328
- else {
329
- e.cause = { path: source };
330
- }
248
+ if (source && er instanceof Error) {
249
+ /* c8 ignore next */
250
+ const cause = typeof er.cause === 'object' ? er.cause : {};
251
+ er.cause = { ...cause, path: source };
331
252
  }
332
- throw e;
253
+ throw er;
333
254
  }
334
255
  for (const [field, value] of Object.entries(values)) {
335
256
  const my = this.#configSet[field];
@@ -337,7 +258,10 @@ export class Jack {
337
258
  /* c8 ignore start */
338
259
  if (!my) {
339
260
  throw new Error('unexpected field in config set: ' + field, {
340
- cause: { found: field },
261
+ cause: {
262
+ code: 'JACKSPEAK',
263
+ found: field,
264
+ },
341
265
  });
342
266
  }
343
267
  /* c8 ignore stop */
@@ -392,10 +316,9 @@ export class Jack {
392
316
  if (args === process.argv) {
393
317
  args = args.slice(process._eval !== undefined ? 1 : 2);
394
318
  }
395
- const options = toParseArgsOptionsConfig(this.#configSet);
396
319
  const result = parseArgs({
397
320
  args,
398
- options,
321
+ options: toParseArgsOptionsConfig(this.#configSet),
399
322
  // always strict, but using our own logic
400
323
  strict: false,
401
324
  allowPositionals: this.#allowPositionals,
@@ -435,6 +358,7 @@ export class Jack {
435
358
  `place it at the end of the command after '--', as in ` +
436
359
  `'-- ${token.rawName}'`, {
437
360
  cause: {
361
+ code: 'JACKSPEAK',
438
362
  found: token.rawName + (token.value ? `=${token.value}` : ''),
439
363
  },
440
364
  });
@@ -444,6 +368,7 @@ export class Jack {
444
368
  if (my.type !== 'boolean') {
445
369
  throw new Error(`No value provided for ${token.rawName}, expected ${my.type}`, {
446
370
  cause: {
371
+ code: 'JACKSPEAK',
447
372
  name: token.rawName,
448
373
  wanted: valueType(my),
449
374
  },
@@ -453,7 +378,7 @@ export class Jack {
453
378
  }
454
379
  else {
455
380
  if (my.type === 'boolean') {
456
- throw new Error(`Flag ${token.rawName} does not take a value, received '${token.value}'`, { cause: { found: token } });
381
+ throw new Error(`Flag ${token.rawName} does not take a value, received '${token.value}'`, { cause: { code: 'JACKSPEAK', found: token } });
457
382
  }
458
383
  if (my.type === 'string') {
459
384
  value = token.value;
@@ -464,6 +389,7 @@ export class Jack {
464
389
  throw new Error(`Invalid value '${token.value}' provided for ` +
465
390
  `'${token.rawName}' option, expected number`, {
466
391
  cause: {
392
+ code: 'JACKSPEAK',
467
393
  name: token.rawName,
468
394
  found: token.value,
469
395
  wanted: 'number',
@@ -488,15 +414,12 @@ export class Jack {
488
414
  for (const [field, value] of Object.entries(p.values)) {
489
415
  const valid = this.#configSet[field]?.validate;
490
416
  const validOptions = this.#configSet[field]?.validOptions;
491
- let cause;
492
- if (validOptions && !isValidOption(value, validOptions)) {
493
- cause = { name: field, found: value, validOptions: validOptions };
494
- }
495
- if (valid && !valid(value)) {
496
- cause ??= { name: field, found: value };
497
- }
417
+ const cause = validOptions && !isValidOption(value, validOptions) ?
418
+ { name: field, found: value, validOptions: validOptions }
419
+ : valid && !valid(value) ? { name: field, found: value }
420
+ : undefined;
498
421
  if (cause) {
499
- throw new Error(`Invalid value provided for --${field}: ${JSON.stringify(value)}`, { cause });
422
+ throw new Error(`Invalid value provided for --${field}: ${JSON.stringify(value)}`, { cause: { ...cause, code: 'JACKSPEAK' } });
500
423
  }
501
424
  }
502
425
  return p;
@@ -512,7 +435,7 @@ export class Jack {
512
435
  // recurse so we get the core config key we care about.
513
436
  this.#noNoFields(yes, val, s);
514
437
  if (this.#configSet[yes]?.type === 'boolean') {
515
- throw new Error(`do not set '${s}', instead set '${yes}' as desired.`, { cause: { found: s, wanted: yes } });
438
+ throw new Error(`do not set '${s}', instead set '${yes}' as desired.`, { cause: { code: 'JACKSPEAK', found: s, wanted: yes } });
516
439
  }
517
440
  }
518
441
  /**
@@ -522,7 +445,7 @@ export class Jack {
522
445
  validate(o) {
523
446
  if (!o || typeof o !== 'object') {
524
447
  throw new Error('Invalid config: not an object', {
525
- cause: { found: o },
448
+ cause: { code: 'JACKSPEAK', found: o },
526
449
  });
527
450
  }
528
451
  const opts = o;
@@ -535,33 +458,27 @@ export class Jack {
535
458
  const config = this.#configSet[field];
536
459
  if (!config) {
537
460
  throw new Error(`Unknown config option: ${field}`, {
538
- cause: { found: field },
461
+ cause: { code: 'JACKSPEAK', found: field },
539
462
  });
540
463
  }
541
464
  if (!isValidValue(value, config.type, !!config.multiple)) {
542
465
  throw new Error(`Invalid value ${valueType(value)} for ${field}, expected ${valueType(config)}`, {
543
466
  cause: {
467
+ code: 'JACKSPEAK',
544
468
  name: field,
545
469
  found: value,
546
470
  wanted: valueType(config),
547
471
  },
548
472
  });
549
473
  }
550
- let cause;
551
- if (config.validOptions &&
552
- !isValidOption(value, config.validOptions)) {
553
- cause = {
554
- name: field,
555
- found: value,
556
- validOptions: config.validOptions,
557
- };
558
- }
559
- if (config.validate && !config.validate(value)) {
560
- cause ??= { name: field, found: value };
561
- }
474
+ const cause = config.validOptions && !isValidOption(value, config.validOptions) ?
475
+ { name: field, found: value, validOptions: config.validOptions }
476
+ : config.validate && !config.validate(value) ?
477
+ { name: field, found: value }
478
+ : undefined;
562
479
  if (cause) {
563
480
  throw new Error(`Invalid config value for ${field}: ${value}`, {
564
- cause,
481
+ cause: { ...cause, code: 'JACKSPEAK' },
565
482
  });
566
483
  }
567
484
  }
@@ -595,37 +512,37 @@ export class Jack {
595
512
  * Add one or more number fields.
596
513
  */
597
514
  num(fields) {
598
- return this.#addFields(fields, num);
515
+ return this.#addFieldsWith(fields, 'number', false);
599
516
  }
600
517
  /**
601
518
  * Add one or more multiple number fields.
602
519
  */
603
520
  numList(fields) {
604
- return this.#addFields(fields, numList);
521
+ return this.#addFieldsWith(fields, 'number', true);
605
522
  }
606
523
  /**
607
524
  * Add one or more string option fields.
608
525
  */
609
526
  opt(fields) {
610
- return this.#addFields(fields, opt);
527
+ return this.#addFieldsWith(fields, 'string', false);
611
528
  }
612
529
  /**
613
530
  * Add one or more multiple string option fields.
614
531
  */
615
532
  optList(fields) {
616
- return this.#addFields(fields, optList);
533
+ return this.#addFieldsWith(fields, 'string', true);
617
534
  }
618
535
  /**
619
536
  * Add one or more flag fields.
620
537
  */
621
538
  flag(fields) {
622
- return this.#addFields(fields, flag);
539
+ return this.#addFieldsWith(fields, 'boolean', false);
623
540
  }
624
541
  /**
625
542
  * Add one or more multiple flag fields.
626
543
  */
627
544
  flagList(fields) {
628
- return this.#addFields(fields, flagList);
545
+ return this.#addFieldsWith(fields, 'boolean', true);
629
546
  }
630
547
  /**
631
548
  * Generic field definition method. Similar to flag/flagList/number/etc,
@@ -633,29 +550,22 @@ export class Jack {
633
550
  * fields on each one, or Jack won't know how to define them.
634
551
  */
635
552
  addFields(fields) {
636
- const next = this;
637
- for (const [name, field] of Object.entries(fields)) {
638
- this.#validateName(name, field);
639
- next.#fields.push({
640
- type: 'config',
641
- name,
642
- value: field,
643
- });
644
- }
645
- Object.assign(next.#configSet, fields);
646
- return next;
553
+ return this.#addFields(this, fields);
554
+ }
555
+ #addFieldsWith(fields, type, multiple) {
556
+ return this.#addFields(this, fields, {
557
+ type,
558
+ multiple,
559
+ });
647
560
  }
648
- #addFields(fields, fn) {
649
- const next = this;
561
+ #addFields(next, fields, opt) {
650
562
  Object.assign(next.#configSet, Object.fromEntries(Object.entries(fields).map(([name, field]) => {
651
563
  this.#validateName(name, field);
652
- const option = fn(field);
653
- next.#fields.push({
654
- type: 'config',
655
- name,
656
- value: option,
657
- });
658
- return [name, option];
564
+ const { type, multiple } = validateFieldMeta(field, opt);
565
+ const value = { ...field, type, multiple };
566
+ validateField(value, type, multiple);
567
+ next.#fields.push({ type: 'config', name, value });
568
+ return [name, value];
659
569
  })));
660
570
  return next;
661
571
  }
@@ -691,6 +601,7 @@ export class Jack {
691
601
  if (this.#usage)
692
602
  return this.#usage;
693
603
  let headingLevel = 1;
604
+ //@ts-ignore
694
605
  const ui = cliui({ width });
695
606
  const first = this.#fields[0];
696
607
  let start = first?.type === 'heading' ? 1 : 0;
@@ -932,6 +843,10 @@ export class Jack {
932
843
  return `Jack ${inspect(this.toJSON(), options)}`;
933
844
  }
934
845
  }
846
+ /**
847
+ * Main entry point. Create and return a {@link Jack} object.
848
+ */
849
+ export const jack = (options = {}) => new Jack(options);
935
850
  // Unwrap and un-indent, so we can wrap description
936
851
  // strings however makes them look nice in the code.
937
852
  const normalize = (s, pre = false) => {
@@ -993,8 +908,4 @@ const normalizeOneLine = (s, pre = false) => {
993
908
  .trim();
994
909
  return pre ? `\`${n}\`` : n;
995
910
  };
996
- /**
997
- * Main entry point. Create and return a {@link Jack} object.
998
- */
999
- export const jack = (options = {}) => new Jack(options);
1000
911
  //# sourceMappingURL=index.js.map