jackspeak
Advanced tools
Comparing version 3.0.0 to 3.1.0
@@ -12,3 +12,3 @@ /// <reference types="node" /> | ||
*/ | ||
export type ValidValue<T extends ConfigType, M extends boolean> = [ | ||
export type ValidValue<T extends ConfigType = ConfigType, M extends boolean = boolean> = [ | ||
T, | ||
@@ -21,13 +21,17 @@ M | ||
*/ | ||
export type ConfigOptionMeta<T extends ConfigType, M extends boolean> = { | ||
default?: ValidValue<T, M> | undefined; | ||
export type ConfigOptionMeta<T extends ConfigType, M extends boolean = boolean, O extends undefined | (T extends 'boolean' ? never : T extends 'string' ? string[] : T extends 'number' ? number[] : number[] | string[]) = undefined | (T extends 'boolean' ? never : T extends 'string' ? string[] : T extends 'number' ? number[] : number[] | string[])> = { | ||
default?: undefined | (ValidValue<T, M> & (O extends number[] | string[] ? M extends false ? O[number] : O[number][] : unknown)); | ||
validOptions?: O; | ||
description?: string; | ||
validate?: ((v: any) => v is ValidValue<T, M>) | ((v: any) => boolean); | ||
validate?: ((v: unknown) => v is ValidValue<T, M>) | ((v: unknown) => boolean); | ||
short?: string | undefined; | ||
type?: T; | ||
} & (T extends 'boolean' ? {} : { | ||
hint?: string | undefined; | ||
}) & (M extends false ? {} : { | ||
multiple?: M | undefined; | ||
delim?: string | undefined; | ||
hint?: T extends 'boolean' ? never : string; | ||
delim?: M extends true ? string : never; | ||
} & (M extends false ? { | ||
multiple?: false | undefined; | ||
} : M extends true ? { | ||
multiple: true; | ||
} : { | ||
multiple?: boolean; | ||
}); | ||
@@ -65,3 +69,3 @@ /** | ||
*/ | ||
export type ConfigOptionBase<T extends ConfigType, M extends boolean> = { | ||
export type ConfigOptionBase<T extends ConfigType, M extends boolean = boolean> = { | ||
type: T; | ||
@@ -72,3 +76,4 @@ short?: string | undefined; | ||
hint?: T extends 'boolean' ? undefined : string | undefined; | ||
validate?: (v: any) => v is ValidValue<T, M>; | ||
validate?: (v: unknown) => v is ValidValue<T, M>; | ||
validOptions?: T extends 'boolean' ? undefined : T extends 'string' ? readonly string[] : T extends 'number' ? readonly number[] : readonly number[] | readonly string[]; | ||
} & MultiType<M>; | ||
@@ -82,3 +87,3 @@ export declare const isConfigType: (t: string) => t is ConfigType; | ||
export type ConfigSet = { | ||
[longOption: string]: ConfigOptionBase<ConfigType, boolean>; | ||
[longOption: string]: ConfigOptionBase<ConfigType>; | ||
}; | ||
@@ -89,3 +94,3 @@ /** | ||
export type OptionsResults<T extends ConfigSet> = { | ||
[k in keyof T]?: T[k] extends ConfigOptionBase<'string', false> ? string : T[k] extends ConfigOptionBase<'string', true> ? string[] : T[k] extends ConfigOptionBase<'number', false> ? number : T[k] extends ConfigOptionBase<'number', true> ? number[] : T[k] extends ConfigOptionBase<'boolean', false> ? boolean : T[k] extends ConfigOptionBase<'boolean', true> ? boolean[] : never; | ||
[k in keyof T]?: T[k]['validOptions'] extends string[] | number[] ? T[k] extends ConfigOptionBase<'string' | 'number', false> ? T[k]['validOptions'][number] : T[k] extends ConfigOptionBase<'string' | 'number', true> ? T[k]['validOptions'][number][] : never : T[k] extends ConfigOptionBase<'string', false> ? string : T[k] extends ConfigOptionBase<'string', true> ? string[] : T[k] extends ConfigOptionBase<'number', false> ? number : T[k] extends ConfigOptionBase<'number', true> ? number[] : T[k] extends ConfigOptionBase<'boolean', false> ? boolean : T[k] extends ConfigOptionBase<'boolean', true> ? boolean[] : never; | ||
}; | ||
@@ -149,3 +154,3 @@ /** | ||
name: string; | ||
value: ConfigOptionBase<ConfigType, boolean>; | ||
value: ConfigOptionBase<ConfigType>; | ||
}; | ||
@@ -225,3 +230,3 @@ /** | ||
*/ | ||
validate(o: any): asserts o is Parsed<C>['values']; | ||
validate(o: unknown): asserts o is Parsed<C>['values']; | ||
/** | ||
@@ -283,3 +288,4 @@ * Add a heading to the usage output banner | ||
default?: string | number | boolean | string[] | number[] | boolean[] | undefined; | ||
validate?: ((v: any) => v is string | number | boolean | string[] | number[] | boolean[]) | undefined; | ||
validOptions?: readonly number[] | readonly string[] | undefined; | ||
validate?: ((v: unknown) => v is string | number | boolean | string[] | number[] | boolean[]) | undefined; | ||
description?: string | undefined; | ||
@@ -286,0 +292,0 @@ short?: string | undefined; |
@@ -48,2 +48,4 @@ "use strict"; | ||
const undefOrType = (v, t) => v === undefined || typeof v === t; | ||
const undefOrTypeArray = (v, t) => v === undefined || (Array.isArray(v) && v.every(x => typeof x === t)); | ||
const isValidOption = (v, vo) => Array.isArray(v) ? v.every(x => isValidOption(x, vo)) : vo.includes(v); | ||
// print the value type, for error message reporting | ||
@@ -77,2 +79,5 @@ const valueType = (v) => typeof v === 'string' ? 'string' | ||
undefOrType(o.validate, 'function') && | ||
(o.type === 'boolean' ? | ||
o.validOptions === undefined | ||
: undefOrTypeArray(o.validOptions, o.type)) && | ||
(o.default === undefined || isValidValue(o.default, type, multi)) && | ||
@@ -82,7 +87,22 @@ !!o.multiple === multi; | ||
function num(o = {}) { | ||
const { default: def, validate: val, ...rest } = o; | ||
const { default: def, validate: val, validOptions, ...rest } = o; | ||
if (def !== undefined && !isValidValue(def, 'number', false)) { | ||
throw new TypeError('invalid default value'); | ||
throw new TypeError('invalid default value', { | ||
cause: { | ||
found: def, | ||
wanted: 'number', | ||
}, | ||
}); | ||
} | ||
const validate = val ? val : undefined; | ||
if (!undefOrTypeArray(validOptions, 'number')) { | ||
throw new TypeError('invalid validOptions', { | ||
cause: { | ||
found: validOptions, | ||
wanted: 'number[]', | ||
}, | ||
}); | ||
} | ||
const validate = val ? | ||
val | ||
: undefined; | ||
return { | ||
@@ -92,2 +112,3 @@ ...rest, | ||
validate, | ||
validOptions, | ||
type: 'number', | ||
@@ -98,7 +119,22 @@ multiple: false, | ||
function numList(o = {}) { | ||
const { default: def, validate: val, ...rest } = o; | ||
const { default: def, validate: val, validOptions, ...rest } = o; | ||
if (def !== undefined && !isValidValue(def, 'number', true)) { | ||
throw new TypeError('invalid default value'); | ||
throw new TypeError('invalid default value', { | ||
cause: { | ||
found: def, | ||
wanted: 'number[]', | ||
}, | ||
}); | ||
} | ||
const validate = val ? val : undefined; | ||
if (!undefOrTypeArray(validOptions, 'number')) { | ||
throw new TypeError('invalid validOptions', { | ||
cause: { | ||
found: validOptions, | ||
wanted: 'number[]', | ||
}, | ||
}); | ||
} | ||
const validate = val ? | ||
val | ||
: undefined; | ||
return { | ||
@@ -108,2 +144,3 @@ ...rest, | ||
validate, | ||
validOptions, | ||
type: 'number', | ||
@@ -114,7 +151,22 @@ multiple: true, | ||
function opt(o = {}) { | ||
const { default: def, validate: val, ...rest } = o; | ||
const { default: def, validate: val, validOptions, ...rest } = o; | ||
if (def !== undefined && !isValidValue(def, 'string', false)) { | ||
throw new TypeError('invalid default value'); | ||
throw new TypeError('invalid default value', { | ||
cause: { | ||
found: def, | ||
wanted: 'string', | ||
}, | ||
}); | ||
} | ||
const validate = val ? val : undefined; | ||
if (!undefOrTypeArray(validOptions, 'string')) { | ||
throw new TypeError('invalid validOptions', { | ||
cause: { | ||
found: validOptions, | ||
wanted: 'string[]', | ||
}, | ||
}); | ||
} | ||
const validate = val ? | ||
val | ||
: undefined; | ||
return { | ||
@@ -124,2 +176,3 @@ ...rest, | ||
validate, | ||
validOptions, | ||
type: 'string', | ||
@@ -130,7 +183,22 @@ multiple: false, | ||
function optList(o = {}) { | ||
const { default: def, validate: val, ...rest } = o; | ||
const { default: def, validate: val, validOptions, ...rest } = o; | ||
if (def !== undefined && !isValidValue(def, 'string', true)) { | ||
throw new TypeError('invalid default value'); | ||
throw new TypeError('invalid default value', { | ||
cause: { | ||
found: def, | ||
wanted: 'string[]', | ||
}, | ||
}); | ||
} | ||
const validate = val ? val : undefined; | ||
if (!undefOrTypeArray(validOptions, 'string')) { | ||
throw new TypeError('invalid validOptions', { | ||
cause: { | ||
found: validOptions, | ||
wanted: 'string[]', | ||
}, | ||
}); | ||
} | ||
const validate = val ? | ||
val | ||
: undefined; | ||
return { | ||
@@ -140,2 +208,3 @@ ...rest, | ||
validate, | ||
validOptions, | ||
type: 'string', | ||
@@ -147,2 +216,3 @@ multiple: true, | ||
const { hint, default: def, validate: val, ...rest } = o; | ||
delete rest.validOptions; | ||
if (def !== undefined && !isValidValue(def, 'boolean', false)) { | ||
@@ -167,6 +237,9 @@ throw new TypeError('invalid default value'); | ||
const { hint, default: def, validate: val, ...rest } = o; | ||
delete rest.validOptions; | ||
if (def !== undefined && !isValidValue(def, 'boolean', true)) { | ||
throw new TypeError('invalid default value'); | ||
} | ||
const validate = val ? val : undefined; | ||
const validate = val ? | ||
val | ||
: undefined; | ||
if (hint !== undefined) { | ||
@@ -212,3 +285,3 @@ throw new TypeError('cannot provide hint for flag list'); | ||
type: conf.type, | ||
multiple: conf.multiple, | ||
multiple: !!conf.multiple, | ||
default: conf.default, | ||
@@ -420,5 +493,13 @@ }; | ||
const valid = this.#configSet[field]?.validate; | ||
const validOptions = this.#configSet[field]?.validOptions; | ||
let cause; | ||
if (validOptions && !isValidOption(value, validOptions)) { | ||
cause = { name: field, found: value, validOptions: validOptions }; | ||
} | ||
if (valid && !valid(value)) { | ||
throw new Error(`Invalid value provided for --${field}: ${JSON.stringify(value)}`, { cause: { name: field, found: value } }); | ||
cause ??= { name: field, found: value }; | ||
} | ||
if (cause) { | ||
throw new Error(`Invalid value provided for --${field}: ${JSON.stringify(value)}`, { cause }); | ||
} | ||
} | ||
@@ -452,4 +533,9 @@ this.#writeEnv(p); | ||
} | ||
const opts = o; | ||
for (const field in o) { | ||
this.#noNoFields(field, o[field]); | ||
const value = opts[field]; | ||
/* c8 ignore next - for TS */ | ||
if (value === undefined) | ||
continue; | ||
this.#noNoFields(field, value); | ||
const config = this.#configSet[field]; | ||
@@ -461,7 +547,7 @@ if (!config) { | ||
} | ||
if (!isValidValue(o[field], config.type, !!config.multiple)) { | ||
throw new Error(`Invalid value ${valueType(o[field])} for ${field}, expected ${valueType(config)}`, { | ||
if (!isValidValue(value, config.type, !!config.multiple)) { | ||
throw new Error(`Invalid value ${valueType(value)} for ${field}, expected ${valueType(config)}`, { | ||
cause: { | ||
name: field, | ||
found: o[field], | ||
found: value, | ||
wanted: valueType(config), | ||
@@ -471,5 +557,17 @@ }, | ||
} | ||
if (config.validate && !config.validate(o[field])) { | ||
throw new Error(`Invalid config value for ${field}: ${o[field]}`, { | ||
cause: { name: field, found: o[field] }, | ||
let cause; | ||
if (config.validOptions && | ||
!isValidOption(value, config.validOptions)) { | ||
cause = { | ||
name: field, | ||
found: value, | ||
validOptions: config.validOptions, | ||
}; | ||
} | ||
if (config.validate && !config.validate(value)) { | ||
cause ??= { name: field, found: value }; | ||
} | ||
if (cause) { | ||
throw new Error(`Invalid config value for ${field}: ${value}`, { | ||
cause, | ||
}); | ||
@@ -786,4 +884,8 @@ } | ||
const mult = value.multiple ? 'Can be set multiple times' : ''; | ||
const dmDelim = mult && (desc.includes('\n') ? '\n\n' : '\n'); | ||
const text = normalize(desc + dmDelim + mult); | ||
const opts = value.validOptions?.length ? | ||
`Valid options:${value.validOptions.map(v => ` ${JSON.stringify(v)}`)}` | ||
: ''; | ||
const dmDelim = desc.includes('\n') ? '\n\n' : '\n'; | ||
const extra = [opts, mult].join(dmDelim).trim(); | ||
const text = (normalize(desc) + dmDelim + extra).trim(); | ||
const hint = value.hint || | ||
@@ -829,2 +931,3 @@ (value.type === 'number' ? 'n' | ||
...(def.validate ? { validate: def.validate } : {}), | ||
...(def.validOptions ? { validOptions: def.validOptions } : {}), | ||
...(def.default !== undefined ? { default: def.default } : {}), | ||
@@ -831,0 +934,0 @@ }, |
@@ -12,3 +12,3 @@ /// <reference types="node" resolution-mode="require"/> | ||
*/ | ||
export type ValidValue<T extends ConfigType, M extends boolean> = [ | ||
export type ValidValue<T extends ConfigType = ConfigType, M extends boolean = boolean> = [ | ||
T, | ||
@@ -21,13 +21,17 @@ M | ||
*/ | ||
export type ConfigOptionMeta<T extends ConfigType, M extends boolean> = { | ||
default?: ValidValue<T, M> | undefined; | ||
export type ConfigOptionMeta<T extends ConfigType, M extends boolean = boolean, O extends undefined | (T extends 'boolean' ? never : T extends 'string' ? string[] : T extends 'number' ? number[] : number[] | string[]) = undefined | (T extends 'boolean' ? never : T extends 'string' ? string[] : T extends 'number' ? number[] : number[] | string[])> = { | ||
default?: undefined | (ValidValue<T, M> & (O extends number[] | string[] ? M extends false ? O[number] : O[number][] : unknown)); | ||
validOptions?: O; | ||
description?: string; | ||
validate?: ((v: any) => v is ValidValue<T, M>) | ((v: any) => boolean); | ||
validate?: ((v: unknown) => v is ValidValue<T, M>) | ((v: unknown) => boolean); | ||
short?: string | undefined; | ||
type?: T; | ||
} & (T extends 'boolean' ? {} : { | ||
hint?: string | undefined; | ||
}) & (M extends false ? {} : { | ||
multiple?: M | undefined; | ||
delim?: string | undefined; | ||
hint?: T extends 'boolean' ? never : string; | ||
delim?: M extends true ? string : never; | ||
} & (M extends false ? { | ||
multiple?: false | undefined; | ||
} : M extends true ? { | ||
multiple: true; | ||
} : { | ||
multiple?: boolean; | ||
}); | ||
@@ -65,3 +69,3 @@ /** | ||
*/ | ||
export type ConfigOptionBase<T extends ConfigType, M extends boolean> = { | ||
export type ConfigOptionBase<T extends ConfigType, M extends boolean = boolean> = { | ||
type: T; | ||
@@ -72,3 +76,4 @@ short?: string | undefined; | ||
hint?: T extends 'boolean' ? undefined : string | undefined; | ||
validate?: (v: any) => v is ValidValue<T, M>; | ||
validate?: (v: unknown) => v is ValidValue<T, M>; | ||
validOptions?: T extends 'boolean' ? undefined : T extends 'string' ? readonly string[] : T extends 'number' ? readonly number[] : readonly number[] | readonly string[]; | ||
} & MultiType<M>; | ||
@@ -82,3 +87,3 @@ export declare const isConfigType: (t: string) => t is ConfigType; | ||
export type ConfigSet = { | ||
[longOption: string]: ConfigOptionBase<ConfigType, boolean>; | ||
[longOption: string]: ConfigOptionBase<ConfigType>; | ||
}; | ||
@@ -89,3 +94,3 @@ /** | ||
export type OptionsResults<T extends ConfigSet> = { | ||
[k in keyof T]?: T[k] extends ConfigOptionBase<'string', false> ? string : T[k] extends ConfigOptionBase<'string', true> ? string[] : T[k] extends ConfigOptionBase<'number', false> ? number : T[k] extends ConfigOptionBase<'number', true> ? number[] : T[k] extends ConfigOptionBase<'boolean', false> ? boolean : T[k] extends ConfigOptionBase<'boolean', true> ? boolean[] : never; | ||
[k in keyof T]?: T[k]['validOptions'] extends string[] | number[] ? T[k] extends ConfigOptionBase<'string' | 'number', false> ? T[k]['validOptions'][number] : T[k] extends ConfigOptionBase<'string' | 'number', true> ? T[k]['validOptions'][number][] : never : T[k] extends ConfigOptionBase<'string', false> ? string : T[k] extends ConfigOptionBase<'string', true> ? string[] : T[k] extends ConfigOptionBase<'number', false> ? number : T[k] extends ConfigOptionBase<'number', true> ? number[] : T[k] extends ConfigOptionBase<'boolean', false> ? boolean : T[k] extends ConfigOptionBase<'boolean', true> ? boolean[] : never; | ||
}; | ||
@@ -149,3 +154,3 @@ /** | ||
name: string; | ||
value: ConfigOptionBase<ConfigType, boolean>; | ||
value: ConfigOptionBase<ConfigType>; | ||
}; | ||
@@ -225,3 +230,3 @@ /** | ||
*/ | ||
validate(o: any): asserts o is Parsed<C>['values']; | ||
validate(o: unknown): asserts o is Parsed<C>['values']; | ||
/** | ||
@@ -283,3 +288,4 @@ * Add a heading to the usage output banner | ||
default?: string | number | boolean | string[] | number[] | boolean[] | undefined; | ||
validate?: ((v: any) => v is string | number | boolean | string[] | number[] | boolean[]) | undefined; | ||
validOptions?: readonly number[] | readonly string[] | undefined; | ||
validate?: ((v: unknown) => v is string | number | boolean | string[] | number[] | boolean[]) | undefined; | ||
description?: string | undefined; | ||
@@ -286,0 +292,0 @@ short?: string | undefined; |
@@ -41,2 +41,4 @@ import { inspect } from 'node:util'; | ||
const undefOrType = (v, t) => v === undefined || typeof v === t; | ||
const undefOrTypeArray = (v, t) => v === undefined || (Array.isArray(v) && v.every(x => typeof x === t)); | ||
const isValidOption = (v, vo) => Array.isArray(v) ? v.every(x => isValidOption(x, vo)) : vo.includes(v); | ||
// print the value type, for error message reporting | ||
@@ -70,10 +72,28 @@ const valueType = (v) => typeof v === 'string' ? 'string' | ||
undefOrType(o.validate, 'function') && | ||
(o.type === 'boolean' ? | ||
o.validOptions === undefined | ||
: undefOrTypeArray(o.validOptions, o.type)) && | ||
(o.default === undefined || isValidValue(o.default, type, multi)) && | ||
!!o.multiple === multi; | ||
function num(o = {}) { | ||
const { default: def, validate: val, ...rest } = o; | ||
const { default: def, validate: val, validOptions, ...rest } = o; | ||
if (def !== undefined && !isValidValue(def, 'number', false)) { | ||
throw new TypeError('invalid default value'); | ||
throw new TypeError('invalid default value', { | ||
cause: { | ||
found: def, | ||
wanted: 'number', | ||
}, | ||
}); | ||
} | ||
const validate = val ? val : undefined; | ||
if (!undefOrTypeArray(validOptions, 'number')) { | ||
throw new TypeError('invalid validOptions', { | ||
cause: { | ||
found: validOptions, | ||
wanted: 'number[]', | ||
}, | ||
}); | ||
} | ||
const validate = val ? | ||
val | ||
: undefined; | ||
return { | ||
@@ -83,2 +103,3 @@ ...rest, | ||
validate, | ||
validOptions, | ||
type: 'number', | ||
@@ -89,7 +110,22 @@ multiple: false, | ||
function numList(o = {}) { | ||
const { default: def, validate: val, ...rest } = o; | ||
const { default: def, validate: val, validOptions, ...rest } = o; | ||
if (def !== undefined && !isValidValue(def, 'number', true)) { | ||
throw new TypeError('invalid default value'); | ||
throw new TypeError('invalid default value', { | ||
cause: { | ||
found: def, | ||
wanted: 'number[]', | ||
}, | ||
}); | ||
} | ||
const validate = val ? val : undefined; | ||
if (!undefOrTypeArray(validOptions, 'number')) { | ||
throw new TypeError('invalid validOptions', { | ||
cause: { | ||
found: validOptions, | ||
wanted: 'number[]', | ||
}, | ||
}); | ||
} | ||
const validate = val ? | ||
val | ||
: undefined; | ||
return { | ||
@@ -99,2 +135,3 @@ ...rest, | ||
validate, | ||
validOptions, | ||
type: 'number', | ||
@@ -105,7 +142,22 @@ multiple: true, | ||
function opt(o = {}) { | ||
const { default: def, validate: val, ...rest } = o; | ||
const { default: def, validate: val, validOptions, ...rest } = o; | ||
if (def !== undefined && !isValidValue(def, 'string', false)) { | ||
throw new TypeError('invalid default value'); | ||
throw new TypeError('invalid default value', { | ||
cause: { | ||
found: def, | ||
wanted: 'string', | ||
}, | ||
}); | ||
} | ||
const validate = val ? val : undefined; | ||
if (!undefOrTypeArray(validOptions, 'string')) { | ||
throw new TypeError('invalid validOptions', { | ||
cause: { | ||
found: validOptions, | ||
wanted: 'string[]', | ||
}, | ||
}); | ||
} | ||
const validate = val ? | ||
val | ||
: undefined; | ||
return { | ||
@@ -115,2 +167,3 @@ ...rest, | ||
validate, | ||
validOptions, | ||
type: 'string', | ||
@@ -121,7 +174,22 @@ multiple: false, | ||
function optList(o = {}) { | ||
const { default: def, validate: val, ...rest } = o; | ||
const { default: def, validate: val, validOptions, ...rest } = o; | ||
if (def !== undefined && !isValidValue(def, 'string', true)) { | ||
throw new TypeError('invalid default value'); | ||
throw new TypeError('invalid default value', { | ||
cause: { | ||
found: def, | ||
wanted: 'string[]', | ||
}, | ||
}); | ||
} | ||
const validate = val ? val : undefined; | ||
if (!undefOrTypeArray(validOptions, 'string')) { | ||
throw new TypeError('invalid validOptions', { | ||
cause: { | ||
found: validOptions, | ||
wanted: 'string[]', | ||
}, | ||
}); | ||
} | ||
const validate = val ? | ||
val | ||
: undefined; | ||
return { | ||
@@ -131,2 +199,3 @@ ...rest, | ||
validate, | ||
validOptions, | ||
type: 'string', | ||
@@ -138,2 +207,3 @@ multiple: true, | ||
const { hint, default: def, validate: val, ...rest } = o; | ||
delete rest.validOptions; | ||
if (def !== undefined && !isValidValue(def, 'boolean', false)) { | ||
@@ -158,6 +228,9 @@ throw new TypeError('invalid default value'); | ||
const { hint, default: def, validate: val, ...rest } = o; | ||
delete rest.validOptions; | ||
if (def !== undefined && !isValidValue(def, 'boolean', true)) { | ||
throw new TypeError('invalid default value'); | ||
} | ||
const validate = val ? val : undefined; | ||
const validate = val ? | ||
val | ||
: undefined; | ||
if (hint !== undefined) { | ||
@@ -203,3 +276,3 @@ throw new TypeError('cannot provide hint for flag list'); | ||
type: conf.type, | ||
multiple: conf.multiple, | ||
multiple: !!conf.multiple, | ||
default: conf.default, | ||
@@ -411,5 +484,13 @@ }; | ||
const valid = this.#configSet[field]?.validate; | ||
const validOptions = this.#configSet[field]?.validOptions; | ||
let cause; | ||
if (validOptions && !isValidOption(value, validOptions)) { | ||
cause = { name: field, found: value, validOptions: validOptions }; | ||
} | ||
if (valid && !valid(value)) { | ||
throw new Error(`Invalid value provided for --${field}: ${JSON.stringify(value)}`, { cause: { name: field, found: value } }); | ||
cause ??= { name: field, found: value }; | ||
} | ||
if (cause) { | ||
throw new Error(`Invalid value provided for --${field}: ${JSON.stringify(value)}`, { cause }); | ||
} | ||
} | ||
@@ -443,4 +524,9 @@ this.#writeEnv(p); | ||
} | ||
const opts = o; | ||
for (const field in o) { | ||
this.#noNoFields(field, o[field]); | ||
const value = opts[field]; | ||
/* c8 ignore next - for TS */ | ||
if (value === undefined) | ||
continue; | ||
this.#noNoFields(field, value); | ||
const config = this.#configSet[field]; | ||
@@ -452,7 +538,7 @@ if (!config) { | ||
} | ||
if (!isValidValue(o[field], config.type, !!config.multiple)) { | ||
throw new Error(`Invalid value ${valueType(o[field])} for ${field}, expected ${valueType(config)}`, { | ||
if (!isValidValue(value, config.type, !!config.multiple)) { | ||
throw new Error(`Invalid value ${valueType(value)} for ${field}, expected ${valueType(config)}`, { | ||
cause: { | ||
name: field, | ||
found: o[field], | ||
found: value, | ||
wanted: valueType(config), | ||
@@ -462,5 +548,17 @@ }, | ||
} | ||
if (config.validate && !config.validate(o[field])) { | ||
throw new Error(`Invalid config value for ${field}: ${o[field]}`, { | ||
cause: { name: field, found: o[field] }, | ||
let cause; | ||
if (config.validOptions && | ||
!isValidOption(value, config.validOptions)) { | ||
cause = { | ||
name: field, | ||
found: value, | ||
validOptions: config.validOptions, | ||
}; | ||
} | ||
if (config.validate && !config.validate(value)) { | ||
cause ??= { name: field, found: value }; | ||
} | ||
if (cause) { | ||
throw new Error(`Invalid config value for ${field}: ${value}`, { | ||
cause, | ||
}); | ||
@@ -777,4 +875,8 @@ } | ||
const mult = value.multiple ? 'Can be set multiple times' : ''; | ||
const dmDelim = mult && (desc.includes('\n') ? '\n\n' : '\n'); | ||
const text = normalize(desc + dmDelim + mult); | ||
const opts = value.validOptions?.length ? | ||
`Valid options:${value.validOptions.map(v => ` ${JSON.stringify(v)}`)}` | ||
: ''; | ||
const dmDelim = desc.includes('\n') ? '\n\n' : '\n'; | ||
const extra = [opts, mult].join(dmDelim).trim(); | ||
const text = (normalize(desc) + dmDelim + extra).trim(); | ||
const hint = value.hint || | ||
@@ -820,2 +922,3 @@ (value.type === 'number' ? 'n' | ||
...(def.validate ? { validate: def.validate } : {}), | ||
...(def.validOptions ? { validOptions: def.validOptions } : {}), | ||
...(def.default !== undefined ? { default: def.default } : {}), | ||
@@ -822,0 +925,0 @@ }, |
@@ -15,3 +15,3 @@ import * as util from 'util'; | ||
let { parseArgs: pa, } = util; | ||
/* c8 ignore start */ | ||
/* c8 ignore start - version specific */ | ||
if (!pa || | ||
@@ -21,3 +21,2 @@ major < 16 || | ||
(major === 16 && minor < 19)) { | ||
/* c8 ignore stop */ | ||
// Ignore because we will clobber it for commonjs | ||
@@ -27,3 +26,4 @@ //@ts-ignore | ||
} | ||
/* c8 ignore stop */ | ||
export const parseArgs = pa; | ||
//# sourceMappingURL=parse-args.js.map |
{ | ||
"name": "jackspeak", | ||
"version": "3.0.0", | ||
"version": "3.1.0", | ||
"description": "A very strict and proper argument parser.", | ||
@@ -5,0 +5,0 @@ "tshy": { |
@@ -158,2 +158,5 @@ # jackspeak | ||
option value is invalid. | ||
- `validOptions` An array of strings or numbers that define the | ||
valid values that can be set. This is not allowed on `boolean` | ||
(flag) options. May be used along with a `validate()` method. | ||
- `default` A default value for the field. Note that this may be | ||
@@ -160,0 +163,0 @@ overridden by an environment variable, if present. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
284300
2596
352