jackspeak 2.3.5 → 3.0.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/dist/esm/index.js CHANGED
@@ -15,50 +15,38 @@ const toEnvKey = (pref, key) => {
15
15
  .replace(/ /g, '_');
16
16
  };
17
17
  const toEnvVal = (value, delim = '\n') => {
18
- const str = typeof value === 'string'
19
- ? value
20
- : typeof value === 'boolean'
21
- ? value
22
- ? '1'
18
+ const str = typeof value === 'string' ? value
19
+ : typeof value === 'boolean' ?
20
+ value ? '1'
23
21
  : '0'
24
- : typeof value === 'number'
25
- ? String(value)
26
- : Array.isArray(value)
27
- ? value
28
- .map((v) => toEnvVal(v))
29
- .join(delim)
30
- : /* c8 ignore start */
31
- undefined;
22
+ : typeof value === 'number' ? String(value)
23
+ : Array.isArray(value) ?
24
+ value.map((v) => toEnvVal(v)).join(delim)
25
+ : /* c8 ignore start */ undefined;
32
26
  if (typeof str !== 'string') {
33
27
  throw new Error(`could not serialize value to environment: ${JSON.stringify(value)}`);
34
28
  }
35
29
  /* c8 ignore stop */
36
30
  return str;
37
31
  };
38
- const fromEnvVal = (env, type, multiple, delim = '\n') => (multiple
39
- ? env
40
- ? env.split(delim).map(v => fromEnvVal(v, type, false))
32
+ const fromEnvVal = (env, type, multiple, delim = '\n') => (multiple ?
33
+ env ? env.split(delim).map(v => fromEnvVal(v, type, false))
41
34
  : []
42
- : type === 'string'
43
- ? env
44
- : type === 'boolean'
45
- ? env === '1'
35
+ : type === 'string' ? env
36
+ : type === 'boolean' ? env === '1'
46
37
  : +env.trim());
47
38
  export const isConfigType = (t) => typeof t === 'string' &&
48
39
  (t === 'string' || t === 'number' || t === 'boolean');
49
40
  const undefOrType = (v, t) => v === undefined || typeof v === t;
50
41
  // print the value type, for error message reporting
51
- const valueType = (v) => typeof v === 'string'
52
- ? 'string'
53
- : typeof v === 'boolean'
54
- ? 'boolean'
55
- : typeof v === 'number'
56
- ? 'number'
57
- : Array.isArray(v)
58
- ? joinTypes([...new Set(v.map(v => valueType(v)))]) + '[]'
42
+ const valueType = (v) => typeof v === 'string' ? 'string'
43
+ : typeof v === 'boolean' ? 'boolean'
44
+ : typeof v === 'number' ? 'number'
45
+ : Array.isArray(v) ?
46
+ joinTypes([...new Set(v.map(v => valueType(v)))]) + '[]'
59
47
  : `${v.type}${v.multiple ? '[]' : ''}`;
60
- const joinTypes = (types) => types.length === 1 && typeof types[0] === 'string'
61
- ? types[0]
48
+ const joinTypes = (types) => types.length === 1 && typeof types[0] === 'string' ?
49
+ types[0]
62
50
  : `(${types.join('|')})`;
63
51
  const isValidValue = (v, type, multi) => {
64
52
  if (multi) {
@@ -85,9 +73,7 @@ function num(o = {}) {
85
73
  if (def !== undefined && !isValidValue(def, 'number', false)) {
86
74
  throw new TypeError('invalid default value');
87
75
  }
88
- const validate = val
89
- ? val
90
- : undefined;
76
+ const validate = val ? val : undefined;
91
77
  return {
92
78
  ...rest,
93
79
  default: def,
@@ -101,9 +87,7 @@ function numList(o = {}) {
101
87
  if (def !== undefined && !isValidValue(def, 'number', true)) {
102
88
  throw new TypeError('invalid default value');
103
89
  }
104
- const validate = val
105
- ? val
106
- : undefined;
90
+ const validate = val ? val : undefined;
107
91
  return {
108
92
  ...rest,
109
93
  default: def,
@@ -117,9 +101,7 @@ function opt(o = {}) {
117
101
  if (def !== undefined && !isValidValue(def, 'string', false)) {
118
102
  throw new TypeError('invalid default value');
119
103
  }
120
- const validate = val
121
- ? val
122
- : undefined;
104
+ const validate = val ? val : undefined;
123
105
  return {
124
106
  ...rest,
125
107
  default: def,
@@ -133,9 +115,7 @@ function optList(o = {}) {
133
115
  if (def !== undefined && !isValidValue(def, 'string', true)) {
134
116
  throw new TypeError('invalid default value');
135
117
  }
136
- const validate = val
137
- ? val
138
- : undefined;
118
+ const validate = val ? val : undefined;
139
119
  return {
140
120
  ...rest,
141
121
  default: def,
@@ -149,8 +129,8 @@ function flag(o = {}) {
149
129
  if (def !== undefined && !isValidValue(def, 'boolean', false)) {
150
130
  throw new TypeError('invalid default value');
151
131
  }
152
- const validate = val
153
- ? val
132
+ const validate = val ?
133
+ val
154
134
  : undefined;
155
135
  if (hint !== undefined) {
156
136
  throw new TypeError('cannot provide hint for flag');
@@ -168,9 +148,7 @@ function flagList(o = {}) {
168
148
  if (def !== undefined && !isValidValue(def, 'boolean', true)) {
169
149
  throw new TypeError('invalid default value');
170
150
  }
171
- const validate = val
172
- ? val
173
- : undefined;
151
+ const validate = val ? val : undefined;
174
152
  if (hint !== undefined) {
175
153
  throw new TypeError('cannot provide hint for flag list');
176
154
  }
@@ -202,8 +180,8 @@ const toParseArgsOptionsConfig = (options) => {
202
180
  c[longOption] = {
203
181
  type: 'string',
204
182
  multiple: false,
205
- default: config.default === undefined
206
- ? undefined
183
+ default: config.default === undefined ?
184
+ undefined
207
185
  : String(config.default),
208
186
  };
209
187
  }
@@ -269,14 +247,25 @@ export class Jack {
269
247
  this.validate(values);
270
248
  }
271
249
  catch (er) {
272
- throw Object.assign(er, source ? { source } : {});
250
+ const e = er;
251
+ if (source && e && typeof e === 'object') {
252
+ if (e.cause && typeof e.cause === 'object') {
253
+ Object.assign(e.cause, { path: source });
254
+ }
255
+ else {
256
+ e.cause = { path: source };
257
+ }
258
+ }
259
+ throw e;
273
260
  }
274
261
  for (const [field, value] of Object.entries(values)) {
275
262
  const my = this.#configSet[field];
276
263
  // already validated, just for TS's benefit
277
264
  /* c8 ignore start */
278
265
  if (!my) {
279
- throw new Error('unexpected field in config set: ' + field);
266
+ throw new Error('unexpected field in config set: ' + field, {
267
+ cause: { found: field },
268
+ });
280
269
  }
281
270
  /* c8 ignore stop */
282
271
  my.default = value;
@@ -347,18 +336,27 @@ export class Jack {
347
336
  throw new Error(`Unknown option '${token.rawName}'. ` +
348
337
  `To specify a positional argument starting with a '-', ` +
349
338
  `place it at the end of the command after '--', as in ` +
350
- `'-- ${token.rawName}'`);
339
+ `'-- ${token.rawName}'`, {
340
+ cause: {
341
+ found: token.rawName + (token.value ? `=${token.value}` : ''),
342
+ },
343
+ });
351
344
  }
352
345
  if (value === undefined) {
353
346
  if (token.value === undefined) {
354
347
  if (my.type !== 'boolean') {
355
- throw new Error(`No value provided for ${token.rawName}, expected ${my.type}`);
348
+ throw new Error(`No value provided for ${token.rawName}, expected ${my.type}`, {
349
+ cause: {
350
+ name: token.rawName,
351
+ wanted: valueType(my),
352
+ },
353
+ });
356
354
  }
357
355
  value = true;
358
356
  }
359
357
  else {
360
358
  if (my.type === 'boolean') {
361
- throw new Error(`Flag ${token.rawName} does not take a value, received '${token.value}'`);
359
+ throw new Error(`Flag ${token.rawName} does not take a value, received '${token.value}'`, { cause: { found: token } });
362
360
  }
363
361
  if (my.type === 'string') {
364
362
  value = token.value;
@@ -367,7 +365,13 @@ export class Jack {
367
365
  value = +token.value;
368
366
  if (value !== value) {
369
367
  throw new Error(`Invalid value '${token.value}' provided for ` +
370
- `'${token.rawName}' option, expected number`);
368
+ `'${token.rawName}' option, expected number`, {
369
+ cause: {
370
+ name: token.rawName,
371
+ found: token.value,
372
+ wanted: 'number',
373
+ },
374
+ });
371
375
  }
372
376
  }
373
377
  }
@@ -393,7 +397,7 @@ export class Jack {
393
397
  for (const [field, value] of Object.entries(p.values)) {
394
398
  const valid = this.#configSet[field]?.validate;
395
399
  if (valid && !valid(value)) {
396
- throw new Error(`Invalid value provided for --${field}: ${JSON.stringify(value)}`);
400
+ throw new Error(`Invalid value provided for --${field}: ${JSON.stringify(value)}`, { cause: { name: field, found: value } });
397
401
  }
398
402
  }
399
403
  this.#writeEnv(p);
@@ -410,7 +414,7 @@ export class Jack {
410
414
  // recurse so we get the core config key we care about.
411
415
  this.#noNoFields(yes, val, s);
412
416
  if (this.#configSet[yes]?.type === 'boolean') {
413
- throw new Error(`do not set '${s}', instead set '${yes}' as desired.`);
417
+ throw new Error(`do not set '${s}', instead set '${yes}' as desired.`, { cause: { found: s, wanted: yes } });
414
418
  }
415
419
  }
416
420
  /**
@@ -419,22 +423,31 @@ export class Jack {
419
423
  */
420
424
  validate(o) {
421
425
  if (!o || typeof o !== 'object') {
422
- throw new Error('Invalid config: not an object');
426
+ throw new Error('Invalid config: not an object', {
427
+ cause: { found: o },
428
+ });
423
429
  }
424
430
  for (const field in o) {
425
431
  this.#noNoFields(field, o[field]);
426
432
  const config = this.#configSet[field];
427
433
  if (!config) {
428
- throw new Error(`Unknown config option: ${field}`);
434
+ throw new Error(`Unknown config option: ${field}`, {
435
+ cause: { found: field },
436
+ });
429
437
  }
430
438
  if (!isValidValue(o[field], config.type, !!config.multiple)) {
431
- throw Object.assign(new Error(`Invalid value ${valueType(o[field])} for ${field}, expected ${valueType(config)}`), {
432
- field,
433
- value: o[field],
439
+ throw new Error(`Invalid value ${valueType(o[field])} for ${field}, expected ${valueType(config)}`, {
440
+ cause: {
441
+ name: field,
442
+ found: o[field],
443
+ wanted: valueType(config),
444
+ },
434
445
  });
435
446
  }
436
447
  if (config.validate && !config.validate(o[field])) {
437
- throw new Error(`Invalid config value for ${field}: ${o[field]}`);
448
+ throw new Error(`Invalid config value for ${field}: ${o[field]}`, {
449
+ cause: { name: field, found: o[field] },
450
+ });
438
451
  }
439
452
  }
440
453
  }
@@ -748,18 +761,14 @@ export class Jack {
748
761
  const dmDelim = mult && (desc.includes('\n') ? '\n\n' : '\n');
749
762
  const text = normalize(desc + dmDelim + mult);
750
763
  const hint = value.hint ||
751
- (value.type === 'number'
752
- ? 'n'
753
- : value.type === 'string'
754
- ? field.name
764
+ (value.type === 'number' ? 'n'
765
+ : value.type === 'string' ? field.name
755
766
  : undefined);
756
- const short = !value.short
757
- ? ''
758
- : value.type === 'boolean'
759
- ? `-${value.short} `
767
+ const short = !value.short ? ''
768
+ : value.type === 'boolean' ? `-${value.short} `
760
769
  : `-${value.short}<${hint}> `;
761
- const left = value.type === 'boolean'
762
- ? `${short}--${field.name}`
770
+ const left = value.type === 'boolean' ?
771
+ `${short}--${field.name}`
763
772
  : `${short}--${field.name}=<${hint}>`;
764
773
  const row = { text, left, type: 'config' };
765
774
  if (text.length > width - maxMax) {
@@ -787,8 +796,8 @@ export class Jack {
787
796
  ...(def.multiple ? { multiple: true } : {}),
788
797
  ...(def.delim ? { delim: def.delim } : {}),
789
798
  ...(def.short ? { short: def.short } : {}),
790
- ...(def.description
791
- ? { description: normalize(def.description) }
799
+ ...(def.description ?
800
+ { description: normalize(def.description) }
792
801
  : {}),
793
802
  ...(def.validate ? { validate: def.validate } : {}),
794
803
  ...(def.default !== undefined ? { default: def.default } : {}),
@@ -804,12 +813,12 @@ export class Jack {
804
813
  }
805
814
  // Unwrap and un-indent, so we can wrap description
806
815
  // strings however makes them look nice in the code.
807
- const normalize = (s, pre = false) => pre
808
- ? // prepend a ZWSP to each line so cliui doesn't strip it.
809
- s
810
- .split('\n')
811
- .map(l => `\u200b${l}`)
812
- .join('\n')
816
+ const normalize = (s, pre = false) => pre ?
817
+ // prepend a ZWSP to each line so cliui doesn't strip it.
818
+ s
819
+ .split('\n')
820
+ .map(l => `\u200b${l}`)
821
+ .join('\n')
813
822
  : s
814
823
  // remove single line breaks, except for lists
815
824
  .replace(/([^\n])\n[ \t]*([^\n])/g, (_, $1, $2) => !/^[-*]/.test($2) ? `${$1} ${$2}` : `${$1}\n${$2}`)
@@ -823,8 +832,8 @@ const normalize = (s, pre = false) => pre
823
832
  // normalize for markdown printing, remove leading spaces on lines
824
833
  const normalizeMarkdown = (s, pre = false) => {
825
834
  const n = normalize(s, pre).replace(/\\/g, '\\\\');
826
- return pre
827
- ? `\`\`\`\n${n.replace(/\u200b/g, '')}\n\`\`\``
835
+ return pre ?
836
+ `\`\`\`\n${n.replace(/\u200b/g, '')}\n\`\`\``
828
837
  : n.replace(/\n +/g, '\n').trim();
829
838
  };
830
839
  const normalizeOneLine = (s, pre = false) => {