v1.0 with SW PWA enabled

This commit is contained in:
Blomios
2026-01-01 17:40:53 +01:00
parent 1c0e22aac1
commit 3c8bebb2ad
29775 changed files with 2197201 additions and 119080 deletions

View File

@ -209,6 +209,38 @@ Define one or more fields of any type. Note that `type` and
`multiple` must be set explicitly on each definition when using
this method.
## Informative Getters
Once you've defined several fields with the various methods
described above, you can get at the definitions and such with
these methods.
This are primarily just informative, but can be useful in some
advanced scenarios, such as providing "Did you mean?" type
suggestions when someone misspells an option name.
### `Jack.definitions`
The set of config field definitions in no particular order. This
is a data object suitable to passing to `util.parseArgs`, but
with the addition of `short` and `description` fields, where
appropriate.
### `Jack.jackOptions`
The options passed into the initial `jack()` function (or `new
Jack()` constructor).
### `Jack.shorts`
The `{ <short>: <long> }` name record for all short options
defined.
### `Jack.usageFields`
The array of fields that are used to generate `Jack.usage()` and
`Jack.usageMarkdown()` content.
## Actions
Use these methods on a Jack object that's already had its config

View File

@ -1,10 +1,16 @@
/// <reference types="node" />
import { inspect, InspectOptions, ParseArgsConfig } from 'node:util';
export type ParseArgsOptions = Exclude<ParseArgsConfig['options'], undefined>;
export type ParseArgsOption = ParseArgsOptions[string];
export type ParseArgsDefault = Exclude<ConfigValue, number | number[]>;
export type ConfigType = 'number' | 'string' | 'boolean';
export declare const isConfigType: (t: unknown) => t is ConfigType;
export type ConfigValuePrimitive = string | boolean | number;
export type ConfigValueArray = string[] | boolean[] | number[];
export type ConfigValue = ConfigValuePrimitive | ConfigValueArray;
/**
* Given a Jack object, get the typeof its ConfigSet
*/
export type Unwrap<J> = J extends Jack<infer C> ? C : never;
import { inspect, InspectOptions } from 'node:util';
/**
* Defines the type of value that is valid, given a config definition's
* {@link ConfigType} and boolean multiple setting
@ -12,81 +18,69 @@ import { inspect, InspectOptions } from 'node:util';
export type ValidValue<T extends ConfigType = ConfigType, M extends boolean = boolean> = [
T,
M
] extends ['number', true] ? number[] : [T, M] extends ['string', true] ? string[] : [T, M] extends ['boolean', true] ? boolean[] : [T, M] extends ['number', false] ? number : [T, M] extends ['string', false] ? string : [T, M] extends ['boolean', false] ? boolean : [T, M] extends ['string', boolean] ? string | string[] : [T, M] extends ['boolean', boolean] ? boolean | boolean[] : [T, M] extends ['number', boolean] ? number | number[] : [T, M] extends [ConfigType, false] ? string | number | boolean : [T, M] extends [ConfigType, true] ? string[] | number[] | boolean[] : string | number | boolean | string[] | number[] | boolean[];
] extends ['number', true] ? number[] : [T, M] extends ['string', true] ? string[] : [T, M] extends ['boolean', true] ? boolean[] : [T, M] extends ['number', false] ? number : [T, M] extends ['string', false] ? string : [T, M] extends ['boolean', false] ? boolean : [T, M] extends ['string', boolean] ? string | string[] : [T, M] extends ['boolean', boolean] ? boolean | boolean[] : [T, M] extends ['number', boolean] ? number | number[] : [T, M] extends [ConfigType, false] ? ConfigValuePrimitive : [T, M] extends [ConfigType, true] ? ConfigValueArray : ConfigValue;
export type ReadonlyArrays = readonly number[] | readonly string[];
/**
* Defines the type of validOptions that are valid, given a config definition's
* {@link ConfigType}
*/
export type ValidOptions<T extends ConfigType> = T extends 'boolean' ? undefined : T extends 'string' ? readonly string[] : T extends 'number' ? readonly number[] : ReadonlyArrays;
/**
* A config field definition, in its full representation.
* This is what is passed in to addFields so `type` is required.
*/
export type ConfigOption<T extends ConfigType = ConfigType, M extends boolean = boolean, O extends undefined | ValidOptions<T> = undefined | ValidOptions<T>> = {
type: T;
short?: string;
default?: ValidValue<T, M> & (O extends ReadonlyArrays ? M extends false ? O[number] : O[number][] : unknown);
description?: string;
hint?: T extends 'boolean' ? undefined : string;
validate?: ((v: unknown) => v is ValidValue<T, M>) | ((v: unknown) => boolean);
validOptions?: O;
delim?: M extends false ? undefined : string;
multiple?: M;
};
/**
* Determine whether an unknown object is a {@link ConfigOption} based only
* on its `type` and `multiple` property
*/
export declare const isConfigOptionOfType: <T extends ConfigType, M extends boolean>(o: any, type: T, multi: M) => o is ConfigOption<T, M>;
/**
* Determine whether an unknown object is a {@link ConfigOption} based on
* it having all valid properties
*/
export declare const isConfigOption: <T extends ConfigType, M extends boolean>(o: any, type: T, multi: M) => o is ConfigOption<T, M>;
/**
* The meta information for a config option definition, when the
* type and multiple values can be inferred by the method being used
*/
export type ConfigOptionMeta<T extends ConfigType, M extends boolean = boolean, O extends undefined | (T extends 'boolean' ? never : T extends 'string' ? readonly string[] : T extends 'number' ? readonly number[] : readonly number[] | readonly string[]) = undefined | (T extends 'boolean' ? never : T extends 'string' ? readonly string[] : T extends 'number' ? readonly number[] : readonly number[] | readonly string[])> = {
default?: undefined | (ValidValue<T, M> & (O extends number[] | string[] ? M extends false ? O[number] : O[number][] : unknown));
validOptions?: O;
description?: string;
validate?: ((v: unknown) => v is ValidValue<T, M>) | ((v: unknown) => boolean);
short?: string | undefined;
type?: T;
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;
});
export type ConfigOptionMeta<T extends ConfigType, M extends boolean, O extends ConfigOption<T, M> = ConfigOption<T, M>> = Pick<Partial<O>, 'type'> & Omit<O, 'type'>;
/**
* A set of {@link ConfigOption} objects, referenced by their longOption
* string values.
*/
export type ConfigSet = {
[longOption: string]: ConfigOption;
};
/**
* A set of {@link ConfigOptionMeta} fields, referenced by their longOption
* string values.
*/
export type ConfigMetaSet<T extends ConfigType, M extends boolean = boolean> = {
export type ConfigMetaSet<T extends ConfigType, M extends boolean> = {
[longOption: string]: ConfigOptionMeta<T, M>;
};
/**
* Infer {@link ConfigSet} fields from a given {@link ConfigMetaSet}
*/
export type ConfigSetFromMetaSet<T extends ConfigType, M extends boolean, S extends ConfigMetaSet<T, M>> = {
[longOption in keyof S]: ConfigOptionBase<T, M>;
export type ConfigSetFromMetaSet<T extends ConfigType, M extends boolean, S extends ConfigMetaSet<T, M>> = S & {
[longOption in keyof S]: ConfigOption<T, M>;
};
/**
* Fields that can be set on a {@link ConfigOptionBase} or
* {@link ConfigOptionMeta} based on whether or not the field is known to be
* multiple.
*/
export type MultiType<M extends boolean> = M extends true ? {
multiple: true;
delim?: string | undefined;
} : M extends false ? {
multiple?: false | undefined;
delim?: undefined;
} : {
multiple?: boolean | undefined;
delim?: string | undefined;
};
/**
* A config field definition, in its full representation.
*/
export type ConfigOptionBase<T extends ConfigType, M extends boolean = boolean> = {
type: T;
short?: string | undefined;
default?: ValidValue<T, M> | undefined;
description?: string;
hint?: T extends 'boolean' ? undefined : string | undefined;
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>;
export declare const isConfigType: (t: string) => t is ConfigType;
export declare const isConfigOption: <T extends ConfigType, M extends boolean>(o: any, type: T, multi: M) => o is ConfigOptionBase<T, M>;
/**
* A set of {@link ConfigOptionBase} objects, referenced by their longOption
* string values.
*/
export type ConfigSet = {
[longOption: string]: ConfigOptionBase<ConfigType>;
};
/**
* The 'values' field returned by {@link Jack#parse}
* The 'values' field returned by {@link Jack#parse}. If a value has
* a default field it will be required on the object otherwise it is optional.
*/
export type OptionsResults<T extends ConfigSet> = {
[k in keyof T]?: T[k]['validOptions'] extends (readonly string[] | readonly 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;
[K in keyof T]: (T[K]['validOptions'] extends ReadonlyArrays ? T[K] extends ConfigOption<'string' | 'number', false> ? T[K]['validOptions'][number] : T[K] extends ConfigOption<'string' | 'number', true> ? T[K]['validOptions'][number][] : never : T[K] extends ConfigOption<'string', false> ? string : T[K] extends ConfigOption<'string', true> ? string[] : T[K] extends ConfigOption<'number', false> ? number : T[K] extends ConfigOption<'number', true> ? number[] : T[K] extends ConfigOption<'boolean', false> ? boolean : T[K] extends ConfigOption<'boolean', true> ? boolean[] : never) | (T[K]['default'] extends ConfigValue ? never : undefined);
};
/**
* The object retured by {@link Jack#parse}
@ -140,12 +134,12 @@ export interface Description extends Row {
*/
export type TextRow = Heading | Description;
/**
* Either a {@link TextRow} or a reference to a {@link ConfigOptionBase}
* Either a {@link TextRow} or a reference to a {@link ConfigOption}
*/
export type UsageField = TextRow | {
type: 'config';
name: string;
value: ConfigOptionBase<ConfigType>;
value: ConfigOption;
};
/**
* Options provided to the {@link Jack} constructor
@ -167,9 +161,7 @@ export interface JackOptions {
* Environment object to read/write. Defaults `process.env`.
* No effect if `envPrefix` is not set.
*/
env?: {
[k: string]: string | undefined;
};
env?: Record<string, string | undefined>;
/**
* A short usage string. If not provided, will be generated from the
* options provided, but that can of course be rather verbose if
@ -203,13 +195,29 @@ export interface JackOptions {
export declare class Jack<C extends ConfigSet = {}> {
#private;
constructor(options?: JackOptions);
/**
* Resulting definitions, suitable to be passed to Node's `util.parseArgs`,
* but also including `description` and `short` fields, if set.
*/
get definitions(): C;
/** map of `{ <short>: <long> }` strings for each short name defined */
get shorts(): Record<string, string>;
/**
* options passed to the {@link Jack} constructor
*/
get jackOptions(): JackOptions;
/**
* the data used to generate {@link Jack#usage} and
* {@link Jack#usageMarkdown} content.
*/
get usageFields(): UsageField[];
/**
* Set the default value (which will still be overridden by env or cli)
* as if from a parsed config file. The optional `source` param, if
* provided, will be included in error messages if a value is invalid or
* unknown.
*/
setConfigValues(values: OptionsResults<C>, source?: string): this;
setConfigValues(values: Partial<OptionsResults<C>>, source?: string): this;
/**
* Parse a string of arguments, and return the resulting
* `{ values, positionals }` object.
@ -256,7 +264,7 @@ export declare class Jack<C extends ConfigSet = {}> {
/**
* Add one or more multiple number fields.
*/
numList<F extends ConfigMetaSet<'number'>>(fields: F): Jack<C & ConfigSetFromMetaSet<'number', true, F>>;
numList<F extends ConfigMetaSet<'number', true>>(fields: F): Jack<C & ConfigSetFromMetaSet<'number', true, F>>;
/**
* Add one or more string option fields.
*/
@ -264,7 +272,7 @@ export declare class Jack<C extends ConfigSet = {}> {
/**
* Add one or more multiple string option fields.
*/
optList<F extends ConfigMetaSet<'string'>>(fields: F): Jack<C & ConfigSetFromMetaSet<'string', true, F>>;
optList<F extends ConfigMetaSet<'string', true>>(fields: F): Jack<C & ConfigSetFromMetaSet<'string', true, F>>;
/**
* Add one or more flag fields.
*/
@ -272,7 +280,7 @@ export declare class Jack<C extends ConfigSet = {}> {
/**
* Add one or more multiple flag fields.
*/
flagList<F extends ConfigMetaSet<'boolean'>>(fields: F): Jack<C & ConfigSetFromMetaSet<'boolean', true, F>>;
flagList<F extends ConfigMetaSet<'boolean', true>>(fields: F): Jack<C & ConfigSetFromMetaSet<'boolean', true, F>>;
/**
* Generic field definition method. Similar to flag/flagList/number/etc,
* but you must specify the `type` (and optionally `multiple` and `delim`)
@ -293,9 +301,9 @@ export declare class Jack<C extends ConfigSet = {}> {
toJSON(): {
[k: string]: {
hint?: string | undefined;
default?: string | number | boolean | string[] | number[] | boolean[] | undefined;
default?: ConfigValue | undefined;
validOptions?: readonly number[] | readonly string[] | undefined;
validate?: ((v: unknown) => v is string | number | boolean | string[] | number[] | boolean[]) | undefined;
validate?: ((v: unknown) => boolean) | ((v: unknown) => v is ValidValue<ConfigType, boolean>) | undefined;
description?: string | undefined;
short?: string | undefined;
delim?: string | undefined;

File diff suppressed because one or more lines are too long

View File

@ -3,60 +3,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.jack = exports.Jack = exports.isConfigOption = exports.isConfigType = void 0;
exports.jack = exports.Jack = exports.isConfigOption = exports.isConfigOptionOfType = exports.isConfigType = void 0;
const node_util_1 = require("node:util");
const parse_args_js_1 = require("./parse-args.js");
// it's a tiny API, just cast it inline, it's fine
//@ts-ignore
const cliui_1 = __importDefault(require("@isaacs/cliui"));
const node_path_1 = require("node:path");
const width = Math.min((process && process.stdout && process.stdout.columns) || 80, 80);
// indentation spaces from heading level
const indent = (n) => (n - 1) * 2;
const toEnvKey = (pref, key) => {
return [pref, key.replace(/[^a-zA-Z0-9]+/g, ' ')]
.join(' ')
.trim()
.toUpperCase()
.replace(/ /g, '_');
};
const toEnvVal = (value, delim = '\n') => {
const str = typeof value === 'string' ? value
: typeof value === 'boolean' ?
value ? '1'
: '0'
: typeof value === 'number' ? String(value)
: Array.isArray(value) ?
value.map((v) => toEnvVal(v)).join(delim)
: /* c8 ignore start */ undefined;
if (typeof str !== 'string') {
throw new Error(`could not serialize value to environment: ${JSON.stringify(value)}`);
}
/* c8 ignore stop */
return str;
};
const fromEnvVal = (env, type, multiple, delim = '\n') => (multiple ?
env ? env.split(delim).map(v => fromEnvVal(v, type, false))
: []
: type === 'string' ? env
: type === 'boolean' ? env === '1'
: +env.trim());
const isConfigType = (t) => typeof t === 'string' &&
(t === 'string' || t === 'number' || t === 'boolean');
exports.isConfigType = isConfigType;
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
const valueType = (v) => typeof v === 'string' ? 'string'
: typeof v === 'boolean' ? 'boolean'
: typeof v === 'number' ? 'number'
: Array.isArray(v) ?
joinTypes([...new Set(v.map(v => valueType(v)))]) + '[]'
: `${v.type}${v.multiple ? '[]' : ''}`;
const joinTypes = (types) => types.length === 1 && typeof types[0] === 'string' ?
types[0]
: `(${types.join('|')})`;
const isValidValue = (v, type, multi) => {
if (multi) {
if (!Array.isArray(v))
@ -67,10 +22,23 @@ const isValidValue = (v, type, multi) => {
return false;
return typeof v === type;
};
const isConfigOption = (o, type, multi) => !!o &&
const isValidOption = (v, vo) => !!vo &&
(Array.isArray(v) ? v.every(x => isValidOption(x, vo)) : vo.includes(v));
/**
* Determine whether an unknown object is a {@link ConfigOption} based only
* on its `type` and `multiple` property
*/
const isConfigOptionOfType = (o, type, multi) => !!o &&
typeof o === 'object' &&
(0, exports.isConfigType)(o.type) &&
o.type === type &&
!!o.multiple === multi;
exports.isConfigOptionOfType = isConfigOptionOfType;
/**
* Determine whether an unknown object is a {@link ConfigOption} based on
* it having all valid properties
*/
const isConfigOption = (o, type, multi) => (0, exports.isConfigOptionOfType)(o, type, multi) &&
undefOrType(o.short, 'string') &&
undefOrType(o.description, 'string') &&
undefOrType(o.hint, 'string') &&
@ -78,219 +46,177 @@ const isConfigOption = (o, type, multi) => !!o &&
(o.type === 'boolean' ?
o.validOptions === undefined
: undefOrTypeArray(o.validOptions, o.type)) &&
(o.default === undefined || isValidValue(o.default, type, multi)) &&
!!o.multiple === multi;
(o.default === undefined || isValidValue(o.default, type, multi));
exports.isConfigOption = isConfigOption;
function num(o = {}) {
const { default: def, validate: val, validOptions, ...rest } = o;
if (def !== undefined && !isValidValue(def, 'number', false)) {
throw new TypeError('invalid default value', {
cause: {
found: def,
wanted: 'number',
},
});
}
if (!undefOrTypeArray(validOptions, 'number')) {
throw new TypeError('invalid validOptions', {
cause: {
found: validOptions,
wanted: 'number[]',
},
});
}
const validate = val ?
val
: undefined;
return {
...rest,
default: def,
validate,
validOptions,
type: 'number',
multiple: false,
};
}
function numList(o = {}) {
const { default: def, validate: val, validOptions, ...rest } = o;
if (def !== undefined && !isValidValue(def, 'number', true)) {
throw new TypeError('invalid default value', {
cause: {
found: def,
wanted: 'number[]',
},
});
}
if (!undefOrTypeArray(validOptions, 'number')) {
throw new TypeError('invalid validOptions', {
cause: {
found: validOptions,
wanted: 'number[]',
},
});
}
const validate = val ?
val
: undefined;
return {
...rest,
default: def,
validate,
validOptions,
type: 'number',
multiple: true,
};
}
function opt(o = {}) {
const { default: def, validate: val, validOptions, ...rest } = o;
if (def !== undefined && !isValidValue(def, 'string', false)) {
throw new TypeError('invalid default value', {
cause: {
found: def,
wanted: 'string',
},
});
}
if (!undefOrTypeArray(validOptions, 'string')) {
throw new TypeError('invalid validOptions', {
cause: {
found: validOptions,
wanted: 'string[]',
},
});
}
const validate = val ?
val
: undefined;
return {
...rest,
default: def,
validate,
validOptions,
type: 'string',
multiple: false,
};
}
function optList(o = {}) {
const { default: def, validate: val, validOptions, ...rest } = o;
if (def !== undefined && !isValidValue(def, 'string', true)) {
throw new TypeError('invalid default value', {
cause: {
found: def,
wanted: 'string[]',
},
});
}
if (!undefOrTypeArray(validOptions, 'string')) {
throw new TypeError('invalid validOptions', {
cause: {
found: validOptions,
wanted: 'string[]',
},
});
}
const validate = val ?
val
: undefined;
return {
...rest,
default: def,
validate,
validOptions,
type: 'string',
multiple: true,
};
}
function flag(o = {}) {
const { hint, default: def, validate: val, ...rest } = o;
delete rest.validOptions;
if (def !== undefined && !isValidValue(def, 'boolean', false)) {
throw new TypeError('invalid default value');
}
const validate = val ?
val
: undefined;
if (hint !== undefined) {
throw new TypeError('cannot provide hint for flag');
}
return {
...rest,
default: def,
validate,
type: 'boolean',
multiple: false,
};
}
function flagList(o = {}) {
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;
if (hint !== undefined) {
throw new TypeError('cannot provide hint for flag list');
}
return {
...rest,
default: def,
validate,
type: 'boolean',
multiple: true,
};
}
const toParseArgsOptionsConfig = (options) => {
const c = {};
for (const longOption in options) {
const config = options[longOption];
/* c8 ignore start */
if (!config) {
throw new Error('config must be an object: ' + longOption);
}
/* c8 ignore start */
if ((0, exports.isConfigOption)(config, 'number', true)) {
c[longOption] = {
type: 'string',
multiple: true,
default: config.default?.map(c => String(c)),
};
}
else if ((0, exports.isConfigOption)(config, 'number', false)) {
c[longOption] = {
type: 'string',
multiple: false,
default: config.default === undefined ?
undefined
: String(config.default),
};
}
else {
const conf = config;
c[longOption] = {
type: conf.type,
multiple: !!conf.multiple,
default: conf.default,
};
}
const clo = c[longOption];
if (typeof config.short === 'string') {
clo.short = config.short;
}
if (config.type === 'boolean' &&
!longOption.startsWith('no-') &&
!options[`no-${longOption}`]) {
c[`no-${longOption}`] = {
type: 'boolean',
multiple: config.multiple,
};
}
}
return c;
};
const isHeading = (r) => r.type === 'heading';
const isDescription = (r) => r.type === 'description';
const width = Math.min(process?.stdout?.columns ?? 80, 80);
// indentation spaces from heading level
const indent = (n) => (n - 1) * 2;
const toEnvKey = (pref, key) => [pref, key.replace(/[^a-zA-Z0-9]+/g, ' ')]
.join(' ')
.trim()
.toUpperCase()
.replace(/ /g, '_');
const toEnvVal = (value, delim = '\n') => {
const str = typeof value === 'string' ? value
: typeof value === 'boolean' ?
value ? '1'
: '0'
: typeof value === 'number' ? String(value)
: Array.isArray(value) ?
value.map((v) => toEnvVal(v)).join(delim)
: /* c8 ignore start */ undefined;
if (typeof str !== 'string') {
throw new Error(`could not serialize value to environment: ${JSON.stringify(value)}`, { cause: { code: 'JACKSPEAK' } });
}
/* c8 ignore stop */
return str;
};
const fromEnvVal = (env, type, multiple, delim = '\n') => (multiple ?
env ? env.split(delim).map(v => fromEnvVal(v, type, false))
: []
: type === 'string' ? env
: type === 'boolean' ? env === '1'
: +env.trim());
const undefOrType = (v, t) => v === undefined || typeof v === t;
const undefOrTypeArray = (v, t) => v === undefined || (Array.isArray(v) && v.every(x => typeof x === t));
// print the value type, for error message reporting
const valueType = (v) => typeof v === 'string' ? 'string'
: typeof v === 'boolean' ? 'boolean'
: typeof v === 'number' ? 'number'
: Array.isArray(v) ?
`${joinTypes([...new Set(v.map(v => valueType(v)))])}[]`
: `${v.type}${v.multiple ? '[]' : ''}`;
const joinTypes = (types) => types.length === 1 && typeof types[0] === 'string' ?
types[0]
: `(${types.join('|')})`;
const validateFieldMeta = (field, fieldMeta) => {
if (fieldMeta) {
if (field.type !== undefined && field.type !== fieldMeta.type) {
throw new TypeError(`invalid type`, {
cause: {
found: field.type,
wanted: [fieldMeta.type, undefined],
},
});
}
if (field.multiple !== undefined &&
!!field.multiple !== fieldMeta.multiple) {
throw new TypeError(`invalid multiple`, {
cause: {
found: field.multiple,
wanted: [fieldMeta.multiple, undefined],
},
});
}
return fieldMeta;
}
if (!(0, exports.isConfigType)(field.type)) {
throw new TypeError(`invalid type`, {
cause: {
found: field.type,
wanted: ['string', 'number', 'boolean'],
},
});
}
return {
type: field.type,
multiple: !!field.multiple,
};
};
const validateField = (o, type, multiple) => {
const validateValidOptions = (def, validOptions) => {
if (!undefOrTypeArray(validOptions, type)) {
throw new TypeError('invalid validOptions', {
cause: {
found: validOptions,
wanted: valueType({ type, multiple: true }),
},
});
}
if (def !== undefined && validOptions !== undefined) {
const valid = Array.isArray(def) ?
def.every(v => validOptions.includes(v))
: validOptions.includes(def);
if (!valid) {
throw new TypeError('invalid default value not in validOptions', {
cause: {
found: def,
wanted: validOptions,
},
});
}
}
};
if (o.default !== undefined &&
!isValidValue(o.default, type, multiple)) {
throw new TypeError('invalid default value', {
cause: {
found: o.default,
wanted: valueType({ type, multiple }),
},
});
}
if ((0, exports.isConfigOptionOfType)(o, 'number', false) ||
(0, exports.isConfigOptionOfType)(o, 'number', true)) {
validateValidOptions(o.default, o.validOptions);
}
else if ((0, exports.isConfigOptionOfType)(o, 'string', false) ||
(0, exports.isConfigOptionOfType)(o, 'string', true)) {
validateValidOptions(o.default, o.validOptions);
}
else if ((0, exports.isConfigOptionOfType)(o, 'boolean', false) ||
(0, exports.isConfigOptionOfType)(o, 'boolean', true)) {
if (o.hint !== undefined) {
throw new TypeError('cannot provide hint for flag');
}
if (o.validOptions !== undefined) {
throw new TypeError('cannot provide validOptions for flag');
}
}
return o;
};
const toParseArgsOptionsConfig = (options) => {
return Object.entries(options).reduce((acc, [longOption, o]) => {
const p = {
type: 'string',
multiple: !!o.multiple,
...(typeof o.short === 'string' ? { short: o.short } : undefined),
};
const setNoBool = () => {
if (!longOption.startsWith('no-') && !options[`no-${longOption}`]) {
acc[`no-${longOption}`] = {
type: 'boolean',
multiple: !!o.multiple,
};
}
};
const setDefault = (def, fn) => {
if (def !== undefined) {
p.default = fn(def);
}
};
if ((0, exports.isConfigOption)(o, 'number', false)) {
setDefault(o.default, String);
}
else if ((0, exports.isConfigOption)(o, 'number', true)) {
setDefault(o.default, d => d.map(v => String(v)));
}
else if ((0, exports.isConfigOption)(o, 'string', false) ||
(0, exports.isConfigOption)(o, 'string', true)) {
setDefault(o.default, v => v);
}
else if ((0, exports.isConfigOption)(o, 'boolean', false) ||
(0, exports.isConfigOption)(o, 'boolean', true)) {
p.type = 'boolean';
setDefault(o.default, v => v);
setNoBool();
}
acc[longOption] = p;
return acc;
}, {});
};
/**
* Class returned by the {@link jack} function and all configuration
* definition methods. This is what gets chained together.
@ -317,6 +243,30 @@ class Jack {
this.#configSet = Object.create(null);
this.#shorts = Object.create(null);
}
/**
* Resulting definitions, suitable to be passed to Node's `util.parseArgs`,
* but also including `description` and `short` fields, if set.
*/
get definitions() {
return this.#configSet;
}
/** map of `{ <short>: <long> }` strings for each short name defined */
get shorts() {
return this.#shorts;
}
/**
* options passed to the {@link Jack} constructor
*/
get jackOptions() {
return this.#options;
}
/**
* the data used to generate {@link Jack#usage} and
* {@link Jack#usageMarkdown} content.
*/
get usageFields() {
return this.#fields;
}
/**
* Set the default value (which will still be overridden by env or cli)
* as if from a parsed config file. The optional `source` param, if
@ -328,16 +278,13 @@ class Jack {
this.validate(values);
}
catch (er) {
const e = er;
if (source && e && typeof e === 'object') {
if (e.cause && typeof e.cause === 'object') {
Object.assign(e.cause, { path: source });
}
else {
e.cause = { path: source };
}
if (source && er instanceof Error) {
/* c8 ignore next */
const cause = typeof er.cause === 'object' ? er.cause : {};
er.cause = { ...cause, path: source };
Error.captureStackTrace(er, this.setConfigValues);
}
throw e;
throw er;
}
for (const [field, value] of Object.entries(values)) {
const my = this.#configSet[field];
@ -345,7 +292,10 @@ class Jack {
/* c8 ignore start */
if (!my) {
throw new Error('unexpected field in config set: ' + field, {
cause: { found: field },
cause: {
code: 'JACKSPEAK',
found: field,
},
});
}
/* c8 ignore stop */
@ -400,10 +350,9 @@ class Jack {
if (args === process.argv) {
args = args.slice(process._eval !== undefined ? 1 : 2);
}
const options = toParseArgsOptionsConfig(this.#configSet);
const result = (0, parse_args_js_1.parseArgs)({
const result = (0, node_util_1.parseArgs)({
args,
options,
options: toParseArgsOptionsConfig(this.#configSet),
// always strict, but using our own logic
strict: false,
allowPositionals: this.#allowPositionals,
@ -443,6 +392,7 @@ class Jack {
`place it at the end of the command after '--', as in ` +
`'-- ${token.rawName}'`, {
cause: {
code: 'JACKSPEAK',
found: token.rawName + (token.value ? `=${token.value}` : ''),
},
});
@ -452,6 +402,7 @@ class Jack {
if (my.type !== 'boolean') {
throw new Error(`No value provided for ${token.rawName}, expected ${my.type}`, {
cause: {
code: 'JACKSPEAK',
name: token.rawName,
wanted: valueType(my),
},
@ -461,7 +412,7 @@ class Jack {
}
else {
if (my.type === 'boolean') {
throw new Error(`Flag ${token.rawName} does not take a value, received '${token.value}'`, { cause: { found: token } });
throw new Error(`Flag ${token.rawName} does not take a value, received '${token.value}'`, { cause: { code: 'JACKSPEAK', found: token } });
}
if (my.type === 'string') {
value = token.value;
@ -472,6 +423,7 @@ class Jack {
throw new Error(`Invalid value '${token.value}' provided for ` +
`'${token.rawName}' option, expected number`, {
cause: {
code: 'JACKSPEAK',
name: token.rawName,
found: token.value,
wanted: 'number',
@ -496,15 +448,12 @@ class Jack {
for (const [field, value] of Object.entries(p.values)) {
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)) {
cause = cause || { name: field, found: value };
}
const cause = validOptions && !isValidOption(value, validOptions) ?
{ name: field, found: value, validOptions }
: valid && !valid(value) ? { name: field, found: value }
: undefined;
if (cause) {
throw new Error(`Invalid value provided for --${field}: ${JSON.stringify(value)}`, { cause });
throw new Error(`Invalid value provided for --${field}: ${JSON.stringify(value)}`, { cause: { ...cause, code: 'JACKSPEAK' } });
}
}
return p;
@ -520,7 +469,7 @@ class Jack {
// recurse so we get the core config key we care about.
this.#noNoFields(yes, val, s);
if (this.#configSet[yes]?.type === 'boolean') {
throw new Error(`do not set '${s}', instead set '${yes}' as desired.`, { cause: { found: s, wanted: yes } });
throw new Error(`do not set '${s}', instead set '${yes}' as desired.`, { cause: { code: 'JACKSPEAK', found: s, wanted: yes } });
}
}
/**
@ -530,7 +479,7 @@ class Jack {
validate(o) {
if (!o || typeof o !== 'object') {
throw new Error('Invalid config: not an object', {
cause: { found: o },
cause: { code: 'JACKSPEAK', found: o },
});
}
const opts = o;
@ -543,33 +492,27 @@ class Jack {
const config = this.#configSet[field];
if (!config) {
throw new Error(`Unknown config option: ${field}`, {
cause: { found: field },
cause: { code: 'JACKSPEAK', found: field },
});
}
if (!isValidValue(value, config.type, !!config.multiple)) {
throw new Error(`Invalid value ${valueType(value)} for ${field}, expected ${valueType(config)}`, {
cause: {
code: 'JACKSPEAK',
name: field,
found: value,
wanted: valueType(config),
},
});
}
let cause;
if (config.validOptions &&
!isValidOption(value, config.validOptions)) {
cause = {
name: field,
found: value,
validOptions: config.validOptions,
};
}
if (config.validate && !config.validate(value)) {
cause = cause || { name: field, found: value };
}
const cause = config.validOptions && !isValidOption(value, config.validOptions) ?
{ name: field, found: value, validOptions: config.validOptions }
: config.validate && !config.validate(value) ?
{ name: field, found: value }
: undefined;
if (cause) {
throw new Error(`Invalid config value for ${field}: ${value}`, {
cause,
cause: { ...cause, code: 'JACKSPEAK' },
});
}
}
@ -603,37 +546,37 @@ class Jack {
* Add one or more number fields.
*/
num(fields) {
return this.#addFields(fields, num);
return this.#addFieldsWith(fields, 'number', false);
}
/**
* Add one or more multiple number fields.
*/
numList(fields) {
return this.#addFields(fields, numList);
return this.#addFieldsWith(fields, 'number', true);
}
/**
* Add one or more string option fields.
*/
opt(fields) {
return this.#addFields(fields, opt);
return this.#addFieldsWith(fields, 'string', false);
}
/**
* Add one or more multiple string option fields.
*/
optList(fields) {
return this.#addFields(fields, optList);
return this.#addFieldsWith(fields, 'string', true);
}
/**
* Add one or more flag fields.
*/
flag(fields) {
return this.#addFields(fields, flag);
return this.#addFieldsWith(fields, 'boolean', false);
}
/**
* Add one or more multiple flag fields.
*/
flagList(fields) {
return this.#addFields(fields, flagList);
return this.#addFieldsWith(fields, 'boolean', true);
}
/**
* Generic field definition method. Similar to flag/flagList/number/etc,
@ -641,29 +584,22 @@ class Jack {
* fields on each one, or Jack won't know how to define them.
*/
addFields(fields) {
const next = this;
for (const [name, field] of Object.entries(fields)) {
this.#validateName(name, field);
next.#fields.push({
type: 'config',
name,
value: field,
});
}
Object.assign(next.#configSet, fields);
return next;
return this.#addFields(this, fields);
}
#addFields(fields, fn) {
const next = this;
#addFieldsWith(fields, type, multiple) {
return this.#addFields(this, fields, {
type,
multiple,
});
}
#addFields(next, fields, opt) {
Object.assign(next.#configSet, Object.fromEntries(Object.entries(fields).map(([name, field]) => {
this.#validateName(name, field);
const option = fn(field);
next.#fields.push({
type: 'config',
name,
value: option,
});
return [name, option];
const { type, multiple } = validateFieldMeta(field, opt);
const value = { ...field, type, multiple };
validateField(value, type, multiple);
next.#fields.push({ type: 'config', name, value });
return [name, value];
})));
return next;
}
@ -699,6 +635,7 @@ class Jack {
if (this.#usage)
return this.#usage;
let headingLevel = 1;
//@ts-ignore
const ui = (0, cliui_1.default)({ width });
const first = this.#fields[0];
let start = first?.type === 'heading' ? 1 : 0;
@ -941,6 +878,11 @@ class Jack {
}
}
exports.Jack = Jack;
/**
* Main entry point. Create and return a {@link Jack} object.
*/
const jack = (options = {}) => new Jack(options);
exports.jack = jack;
// Unwrap and un-indent, so we can wrap description
// strings however makes them look nice in the code.
const normalize = (s, pre = false) => {
@ -1002,9 +944,4 @@ const normalizeOneLine = (s, pre = false) => {
.trim();
return pre ? `\`${n}\`` : n;
};
/**
* Main entry point. Create and return a {@link Jack} object.
*/
const jack = (options = {}) => new Jack(options);
exports.jack = jack;
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"version":3,"file":"parse-args-cjs.cjs","sourceRoot":"","sources":["../../src/parse-args-cjs.cts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAA4B;AAE5B,MAAM,EAAE,GACN,CACE,OAAO,OAAO,KAAK,QAAQ;IAC3B,CAAC,CAAC,OAAO;IACT,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,CACpC,CAAC,CAAC;IACD,OAAO,CAAC,OAAO;IACjB,CAAC,CAAC,QAAQ,CAAA;AACZ,MAAM,GAAG,GAAG,EAAE;KACX,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;KACjB,KAAK,CAAC,GAAG,CAAC;KACV,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;AAE5B,qBAAqB;AACrB,MAAM,CAAC,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,CAAA;AAClC,oBAAoB;AAEpB,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,IAAI,CAAA;AAC5B,qBAAqB;AACrB,IACE,CAAC,EAAE;IACH,KAAK,GAAG,EAAE;IACV,CAAC,KAAK,KAAK,EAAE,IAAI,KAAK,GAAG,EAAE,CAAC;IAC5B,CAAC,KAAK,KAAK,EAAE,IAAI,KAAK,GAAG,EAAE,CAAC,EAC5B,CAAC;IACD,oBAAoB;IACpB,EAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC,SAAS,CAAA;AAC5C,CAAC;AAEY,QAAA,SAAS,GAAG,EAAE,CAAA","sourcesContent":["import * as util from 'util'\n\nconst pv =\n (\n typeof process === 'object' &&\n !!process &&\n typeof process.version === 'string'\n ) ?\n process.version\n : 'v0.0.0'\nconst pvs = pv\n .replace(/^v/, '')\n .split('.')\n .map(s => parseInt(s, 10))\n\n/* c8 ignore start */\nconst [major = 0, minor = 0] = pvs\n/* c8 ignore stop */\n\nlet { parseArgs: pa } = util\n/* c8 ignore start */\nif (\n !pa ||\n major < 16 ||\n (major === 18 && minor < 11) ||\n (major === 16 && minor < 19)\n) {\n /* c8 ignore stop */\n pa = require('@pkgjs/parseargs').parseArgs\n}\n\nexport const parseArgs = pa\n"]}

View File

@ -1 +0,0 @@
{"version":3,"file":"parse-args-cjs.d.cts","sourceRoot":"","sources":["../../src/parse-args-cjs.cts"],"names":[],"mappings":";AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AA+B5B,eAAO,MAAM,SAAS,uBAAK,CAAA"}

View File

@ -1,4 +0,0 @@
/// <reference types="node" />
import * as util from 'util';
export declare const parseArgs: typeof util.parseArgs;
//# sourceMappingURL=parse-args-cjs.d.cts.map

View File

@ -1,50 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseArgs = void 0;
const util = __importStar(require("util"));
const pv = (typeof process === 'object' &&
!!process &&
typeof process.version === 'string') ?
process.version
: 'v0.0.0';
const pvs = pv
.replace(/^v/, '')
.split('.')
.map(s => parseInt(s, 10));
/* c8 ignore start */
const [major = 0, minor = 0] = pvs;
/* c8 ignore stop */
let { parseArgs: pa } = util;
/* c8 ignore start */
if (!pa ||
major < 16 ||
(major === 18 && minor < 11) ||
(major === 16 && minor < 19)) {
/* c8 ignore stop */
pa = require('@pkgjs/parseargs').parseArgs;
}
exports.parseArgs = pa;
//# sourceMappingURL=parse-args-cjs.cjs.map

View File

@ -1,10 +1,16 @@
/// <reference types="node" resolution-mode="require"/>
import { inspect, InspectOptions, ParseArgsConfig } from 'node:util';
export type ParseArgsOptions = Exclude<ParseArgsConfig['options'], undefined>;
export type ParseArgsOption = ParseArgsOptions[string];
export type ParseArgsDefault = Exclude<ConfigValue, number | number[]>;
export type ConfigType = 'number' | 'string' | 'boolean';
export declare const isConfigType: (t: unknown) => t is ConfigType;
export type ConfigValuePrimitive = string | boolean | number;
export type ConfigValueArray = string[] | boolean[] | number[];
export type ConfigValue = ConfigValuePrimitive | ConfigValueArray;
/**
* Given a Jack object, get the typeof its ConfigSet
*/
export type Unwrap<J> = J extends Jack<infer C> ? C : never;
import { inspect, InspectOptions } from 'node:util';
/**
* Defines the type of value that is valid, given a config definition's
* {@link ConfigType} and boolean multiple setting
@ -12,81 +18,69 @@ import { inspect, InspectOptions } from 'node:util';
export type ValidValue<T extends ConfigType = ConfigType, M extends boolean = boolean> = [
T,
M
] extends ['number', true] ? number[] : [T, M] extends ['string', true] ? string[] : [T, M] extends ['boolean', true] ? boolean[] : [T, M] extends ['number', false] ? number : [T, M] extends ['string', false] ? string : [T, M] extends ['boolean', false] ? boolean : [T, M] extends ['string', boolean] ? string | string[] : [T, M] extends ['boolean', boolean] ? boolean | boolean[] : [T, M] extends ['number', boolean] ? number | number[] : [T, M] extends [ConfigType, false] ? string | number | boolean : [T, M] extends [ConfigType, true] ? string[] | number[] | boolean[] : string | number | boolean | string[] | number[] | boolean[];
] extends ['number', true] ? number[] : [T, M] extends ['string', true] ? string[] : [T, M] extends ['boolean', true] ? boolean[] : [T, M] extends ['number', false] ? number : [T, M] extends ['string', false] ? string : [T, M] extends ['boolean', false] ? boolean : [T, M] extends ['string', boolean] ? string | string[] : [T, M] extends ['boolean', boolean] ? boolean | boolean[] : [T, M] extends ['number', boolean] ? number | number[] : [T, M] extends [ConfigType, false] ? ConfigValuePrimitive : [T, M] extends [ConfigType, true] ? ConfigValueArray : ConfigValue;
export type ReadonlyArrays = readonly number[] | readonly string[];
/**
* Defines the type of validOptions that are valid, given a config definition's
* {@link ConfigType}
*/
export type ValidOptions<T extends ConfigType> = T extends 'boolean' ? undefined : T extends 'string' ? readonly string[] : T extends 'number' ? readonly number[] : ReadonlyArrays;
/**
* A config field definition, in its full representation.
* This is what is passed in to addFields so `type` is required.
*/
export type ConfigOption<T extends ConfigType = ConfigType, M extends boolean = boolean, O extends undefined | ValidOptions<T> = undefined | ValidOptions<T>> = {
type: T;
short?: string;
default?: ValidValue<T, M> & (O extends ReadonlyArrays ? M extends false ? O[number] : O[number][] : unknown);
description?: string;
hint?: T extends 'boolean' ? undefined : string;
validate?: ((v: unknown) => v is ValidValue<T, M>) | ((v: unknown) => boolean);
validOptions?: O;
delim?: M extends false ? undefined : string;
multiple?: M;
};
/**
* Determine whether an unknown object is a {@link ConfigOption} based only
* on its `type` and `multiple` property
*/
export declare const isConfigOptionOfType: <T extends ConfigType, M extends boolean>(o: any, type: T, multi: M) => o is ConfigOption<T, M>;
/**
* Determine whether an unknown object is a {@link ConfigOption} based on
* it having all valid properties
*/
export declare const isConfigOption: <T extends ConfigType, M extends boolean>(o: any, type: T, multi: M) => o is ConfigOption<T, M>;
/**
* The meta information for a config option definition, when the
* type and multiple values can be inferred by the method being used
*/
export type ConfigOptionMeta<T extends ConfigType, M extends boolean = boolean, O extends undefined | (T extends 'boolean' ? never : T extends 'string' ? readonly string[] : T extends 'number' ? readonly number[] : readonly number[] | readonly string[]) = undefined | (T extends 'boolean' ? never : T extends 'string' ? readonly string[] : T extends 'number' ? readonly number[] : readonly number[] | readonly string[])> = {
default?: undefined | (ValidValue<T, M> & (O extends number[] | string[] ? M extends false ? O[number] : O[number][] : unknown));
validOptions?: O;
description?: string;
validate?: ((v: unknown) => v is ValidValue<T, M>) | ((v: unknown) => boolean);
short?: string | undefined;
type?: T;
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;
});
export type ConfigOptionMeta<T extends ConfigType, M extends boolean, O extends ConfigOption<T, M> = ConfigOption<T, M>> = Pick<Partial<O>, 'type'> & Omit<O, 'type'>;
/**
* A set of {@link ConfigOption} objects, referenced by their longOption
* string values.
*/
export type ConfigSet = {
[longOption: string]: ConfigOption;
};
/**
* A set of {@link ConfigOptionMeta} fields, referenced by their longOption
* string values.
*/
export type ConfigMetaSet<T extends ConfigType, M extends boolean = boolean> = {
export type ConfigMetaSet<T extends ConfigType, M extends boolean> = {
[longOption: string]: ConfigOptionMeta<T, M>;
};
/**
* Infer {@link ConfigSet} fields from a given {@link ConfigMetaSet}
*/
export type ConfigSetFromMetaSet<T extends ConfigType, M extends boolean, S extends ConfigMetaSet<T, M>> = {
[longOption in keyof S]: ConfigOptionBase<T, M>;
export type ConfigSetFromMetaSet<T extends ConfigType, M extends boolean, S extends ConfigMetaSet<T, M>> = S & {
[longOption in keyof S]: ConfigOption<T, M>;
};
/**
* Fields that can be set on a {@link ConfigOptionBase} or
* {@link ConfigOptionMeta} based on whether or not the field is known to be
* multiple.
*/
export type MultiType<M extends boolean> = M extends true ? {
multiple: true;
delim?: string | undefined;
} : M extends false ? {
multiple?: false | undefined;
delim?: undefined;
} : {
multiple?: boolean | undefined;
delim?: string | undefined;
};
/**
* A config field definition, in its full representation.
*/
export type ConfigOptionBase<T extends ConfigType, M extends boolean = boolean> = {
type: T;
short?: string | undefined;
default?: ValidValue<T, M> | undefined;
description?: string;
hint?: T extends 'boolean' ? undefined : string | undefined;
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>;
export declare const isConfigType: (t: string) => t is ConfigType;
export declare const isConfigOption: <T extends ConfigType, M extends boolean>(o: any, type: T, multi: M) => o is ConfigOptionBase<T, M>;
/**
* A set of {@link ConfigOptionBase} objects, referenced by their longOption
* string values.
*/
export type ConfigSet = {
[longOption: string]: ConfigOptionBase<ConfigType>;
};
/**
* The 'values' field returned by {@link Jack#parse}
* The 'values' field returned by {@link Jack#parse}. If a value has
* a default field it will be required on the object otherwise it is optional.
*/
export type OptionsResults<T extends ConfigSet> = {
[k in keyof T]?: T[k]['validOptions'] extends (readonly string[] | readonly 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;
[K in keyof T]: (T[K]['validOptions'] extends ReadonlyArrays ? T[K] extends ConfigOption<'string' | 'number', false> ? T[K]['validOptions'][number] : T[K] extends ConfigOption<'string' | 'number', true> ? T[K]['validOptions'][number][] : never : T[K] extends ConfigOption<'string', false> ? string : T[K] extends ConfigOption<'string', true> ? string[] : T[K] extends ConfigOption<'number', false> ? number : T[K] extends ConfigOption<'number', true> ? number[] : T[K] extends ConfigOption<'boolean', false> ? boolean : T[K] extends ConfigOption<'boolean', true> ? boolean[] : never) | (T[K]['default'] extends ConfigValue ? never : undefined);
};
/**
* The object retured by {@link Jack#parse}
@ -140,12 +134,12 @@ export interface Description extends Row {
*/
export type TextRow = Heading | Description;
/**
* Either a {@link TextRow} or a reference to a {@link ConfigOptionBase}
* Either a {@link TextRow} or a reference to a {@link ConfigOption}
*/
export type UsageField = TextRow | {
type: 'config';
name: string;
value: ConfigOptionBase<ConfigType>;
value: ConfigOption;
};
/**
* Options provided to the {@link Jack} constructor
@ -167,9 +161,7 @@ export interface JackOptions {
* Environment object to read/write. Defaults `process.env`.
* No effect if `envPrefix` is not set.
*/
env?: {
[k: string]: string | undefined;
};
env?: Record<string, string | undefined>;
/**
* A short usage string. If not provided, will be generated from the
* options provided, but that can of course be rather verbose if
@ -203,13 +195,29 @@ export interface JackOptions {
export declare class Jack<C extends ConfigSet = {}> {
#private;
constructor(options?: JackOptions);
/**
* Resulting definitions, suitable to be passed to Node's `util.parseArgs`,
* but also including `description` and `short` fields, if set.
*/
get definitions(): C;
/** map of `{ <short>: <long> }` strings for each short name defined */
get shorts(): Record<string, string>;
/**
* options passed to the {@link Jack} constructor
*/
get jackOptions(): JackOptions;
/**
* the data used to generate {@link Jack#usage} and
* {@link Jack#usageMarkdown} content.
*/
get usageFields(): UsageField[];
/**
* Set the default value (which will still be overridden by env or cli)
* as if from a parsed config file. The optional `source` param, if
* provided, will be included in error messages if a value is invalid or
* unknown.
*/
setConfigValues(values: OptionsResults<C>, source?: string): this;
setConfigValues(values: Partial<OptionsResults<C>>, source?: string): this;
/**
* Parse a string of arguments, and return the resulting
* `{ values, positionals }` object.
@ -256,7 +264,7 @@ export declare class Jack<C extends ConfigSet = {}> {
/**
* Add one or more multiple number fields.
*/
numList<F extends ConfigMetaSet<'number'>>(fields: F): Jack<C & ConfigSetFromMetaSet<'number', true, F>>;
numList<F extends ConfigMetaSet<'number', true>>(fields: F): Jack<C & ConfigSetFromMetaSet<'number', true, F>>;
/**
* Add one or more string option fields.
*/
@ -264,7 +272,7 @@ export declare class Jack<C extends ConfigSet = {}> {
/**
* Add one or more multiple string option fields.
*/
optList<F extends ConfigMetaSet<'string'>>(fields: F): Jack<C & ConfigSetFromMetaSet<'string', true, F>>;
optList<F extends ConfigMetaSet<'string', true>>(fields: F): Jack<C & ConfigSetFromMetaSet<'string', true, F>>;
/**
* Add one or more flag fields.
*/
@ -272,7 +280,7 @@ export declare class Jack<C extends ConfigSet = {}> {
/**
* Add one or more multiple flag fields.
*/
flagList<F extends ConfigMetaSet<'boolean'>>(fields: F): Jack<C & ConfigSetFromMetaSet<'boolean', true, F>>;
flagList<F extends ConfigMetaSet<'boolean', true>>(fields: F): Jack<C & ConfigSetFromMetaSet<'boolean', true, F>>;
/**
* Generic field definition method. Similar to flag/flagList/number/etc,
* but you must specify the `type` (and optionally `multiple` and `delim`)
@ -293,9 +301,9 @@ export declare class Jack<C extends ConfigSet = {}> {
toJSON(): {
[k: string]: {
hint?: string | undefined;
default?: string | number | boolean | string[] | number[] | boolean[] | undefined;
default?: ConfigValue | undefined;
validOptions?: readonly number[] | readonly string[] | undefined;
validate?: ((v: unknown) => v is string | number | boolean | string[] | number[] | boolean[]) | undefined;
validate?: ((v: unknown) => boolean) | ((v: unknown) => v is ValidValue<ConfigType, boolean>) | undefined;
description?: string | undefined;
short?: string | undefined;
delim?: string | undefined;

File diff suppressed because one or more lines are too long

View File

@ -1,55 +1,10 @@
import { inspect } from 'node:util';
import { parseArgs } from './parse-args.js';
import { inspect, parseArgs, } from 'node:util';
// it's a tiny API, just cast it inline, it's fine
//@ts-ignore
import cliui from '@isaacs/cliui';
import { basename } from 'node:path';
const width = Math.min((process && process.stdout && process.stdout.columns) || 80, 80);
// indentation spaces from heading level
const indent = (n) => (n - 1) * 2;
const toEnvKey = (pref, key) => {
return [pref, key.replace(/[^a-zA-Z0-9]+/g, ' ')]
.join(' ')
.trim()
.toUpperCase()
.replace(/ /g, '_');
};
const toEnvVal = (value, delim = '\n') => {
const str = typeof value === 'string' ? value
: typeof value === 'boolean' ?
value ? '1'
: '0'
: typeof value === 'number' ? String(value)
: Array.isArray(value) ?
value.map((v) => toEnvVal(v)).join(delim)
: /* c8 ignore start */ undefined;
if (typeof str !== 'string') {
throw new Error(`could not serialize value to environment: ${JSON.stringify(value)}`);
}
/* c8 ignore stop */
return str;
};
const fromEnvVal = (env, type, multiple, delim = '\n') => (multiple ?
env ? env.split(delim).map(v => fromEnvVal(v, type, false))
: []
: type === 'string' ? env
: type === 'boolean' ? env === '1'
: +env.trim());
export const isConfigType = (t) => typeof t === 'string' &&
(t === 'string' || t === 'number' || t === 'boolean');
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
const valueType = (v) => typeof v === 'string' ? 'string'
: typeof v === 'boolean' ? 'boolean'
: typeof v === 'number' ? 'number'
: Array.isArray(v) ?
joinTypes([...new Set(v.map(v => valueType(v)))]) + '[]'
: `${v.type}${v.multiple ? '[]' : ''}`;
const joinTypes = (types) => types.length === 1 && typeof types[0] === 'string' ?
types[0]
: `(${types.join('|')})`;
const isValidValue = (v, type, multi) => {
if (multi) {
if (!Array.isArray(v))
@ -60,10 +15,22 @@ const isValidValue = (v, type, multi) => {
return false;
return typeof v === type;
};
export const isConfigOption = (o, type, multi) => !!o &&
const isValidOption = (v, vo) => !!vo &&
(Array.isArray(v) ? v.every(x => isValidOption(x, vo)) : vo.includes(v));
/**
* Determine whether an unknown object is a {@link ConfigOption} based only
* on its `type` and `multiple` property
*/
export const isConfigOptionOfType = (o, type, multi) => !!o &&
typeof o === 'object' &&
isConfigType(o.type) &&
o.type === type &&
!!o.multiple === multi;
/**
* Determine whether an unknown object is a {@link ConfigOption} based on
* it having all valid properties
*/
export const isConfigOption = (o, type, multi) => isConfigOptionOfType(o, type, multi) &&
undefOrType(o.short, 'string') &&
undefOrType(o.description, 'string') &&
undefOrType(o.hint, 'string') &&
@ -71,218 +38,176 @@ export const isConfigOption = (o, type, multi) => !!o &&
(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, validOptions, ...rest } = o;
if (def !== undefined && !isValidValue(def, 'number', false)) {
throw new TypeError('invalid default value', {
cause: {
found: def,
wanted: 'number',
},
});
}
if (!undefOrTypeArray(validOptions, 'number')) {
throw new TypeError('invalid validOptions', {
cause: {
found: validOptions,
wanted: 'number[]',
},
});
}
const validate = val ?
val
: undefined;
return {
...rest,
default: def,
validate,
validOptions,
type: 'number',
multiple: false,
};
}
function numList(o = {}) {
const { default: def, validate: val, validOptions, ...rest } = o;
if (def !== undefined && !isValidValue(def, 'number', true)) {
throw new TypeError('invalid default value', {
cause: {
found: def,
wanted: 'number[]',
},
});
}
if (!undefOrTypeArray(validOptions, 'number')) {
throw new TypeError('invalid validOptions', {
cause: {
found: validOptions,
wanted: 'number[]',
},
});
}
const validate = val ?
val
: undefined;
return {
...rest,
default: def,
validate,
validOptions,
type: 'number',
multiple: true,
};
}
function opt(o = {}) {
const { default: def, validate: val, validOptions, ...rest } = o;
if (def !== undefined && !isValidValue(def, 'string', false)) {
throw new TypeError('invalid default value', {
cause: {
found: def,
wanted: 'string',
},
});
}
if (!undefOrTypeArray(validOptions, 'string')) {
throw new TypeError('invalid validOptions', {
cause: {
found: validOptions,
wanted: 'string[]',
},
});
}
const validate = val ?
val
: undefined;
return {
...rest,
default: def,
validate,
validOptions,
type: 'string',
multiple: false,
};
}
function optList(o = {}) {
const { default: def, validate: val, validOptions, ...rest } = o;
if (def !== undefined && !isValidValue(def, 'string', true)) {
throw new TypeError('invalid default value', {
cause: {
found: def,
wanted: 'string[]',
},
});
}
if (!undefOrTypeArray(validOptions, 'string')) {
throw new TypeError('invalid validOptions', {
cause: {
found: validOptions,
wanted: 'string[]',
},
});
}
const validate = val ?
val
: undefined;
return {
...rest,
default: def,
validate,
validOptions,
type: 'string',
multiple: true,
};
}
function flag(o = {}) {
const { hint, default: def, validate: val, ...rest } = o;
delete rest.validOptions;
if (def !== undefined && !isValidValue(def, 'boolean', false)) {
throw new TypeError('invalid default value');
}
const validate = val ?
val
: undefined;
if (hint !== undefined) {
throw new TypeError('cannot provide hint for flag');
}
return {
...rest,
default: def,
validate,
type: 'boolean',
multiple: false,
};
}
function flagList(o = {}) {
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;
if (hint !== undefined) {
throw new TypeError('cannot provide hint for flag list');
}
return {
...rest,
default: def,
validate,
type: 'boolean',
multiple: true,
};
}
const toParseArgsOptionsConfig = (options) => {
const c = {};
for (const longOption in options) {
const config = options[longOption];
/* c8 ignore start */
if (!config) {
throw new Error('config must be an object: ' + longOption);
}
/* c8 ignore start */
if (isConfigOption(config, 'number', true)) {
c[longOption] = {
type: 'string',
multiple: true,
default: config.default?.map(c => String(c)),
};
}
else if (isConfigOption(config, 'number', false)) {
c[longOption] = {
type: 'string',
multiple: false,
default: config.default === undefined ?
undefined
: String(config.default),
};
}
else {
const conf = config;
c[longOption] = {
type: conf.type,
multiple: !!conf.multiple,
default: conf.default,
};
}
const clo = c[longOption];
if (typeof config.short === 'string') {
clo.short = config.short;
}
if (config.type === 'boolean' &&
!longOption.startsWith('no-') &&
!options[`no-${longOption}`]) {
c[`no-${longOption}`] = {
type: 'boolean',
multiple: config.multiple,
};
}
}
return c;
};
(o.default === undefined || isValidValue(o.default, type, multi));
const isHeading = (r) => r.type === 'heading';
const isDescription = (r) => r.type === 'description';
const width = Math.min(process?.stdout?.columns ?? 80, 80);
// indentation spaces from heading level
const indent = (n) => (n - 1) * 2;
const toEnvKey = (pref, key) => [pref, key.replace(/[^a-zA-Z0-9]+/g, ' ')]
.join(' ')
.trim()
.toUpperCase()
.replace(/ /g, '_');
const toEnvVal = (value, delim = '\n') => {
const str = typeof value === 'string' ? value
: typeof value === 'boolean' ?
value ? '1'
: '0'
: typeof value === 'number' ? String(value)
: Array.isArray(value) ?
value.map((v) => toEnvVal(v)).join(delim)
: /* c8 ignore start */ undefined;
if (typeof str !== 'string') {
throw new Error(`could not serialize value to environment: ${JSON.stringify(value)}`, { cause: { code: 'JACKSPEAK' } });
}
/* c8 ignore stop */
return str;
};
const fromEnvVal = (env, type, multiple, delim = '\n') => (multiple ?
env ? env.split(delim).map(v => fromEnvVal(v, type, false))
: []
: type === 'string' ? env
: type === 'boolean' ? env === '1'
: +env.trim());
const undefOrType = (v, t) => v === undefined || typeof v === t;
const undefOrTypeArray = (v, t) => v === undefined || (Array.isArray(v) && v.every(x => typeof x === t));
// print the value type, for error message reporting
const valueType = (v) => typeof v === 'string' ? 'string'
: typeof v === 'boolean' ? 'boolean'
: typeof v === 'number' ? 'number'
: Array.isArray(v) ?
`${joinTypes([...new Set(v.map(v => valueType(v)))])}[]`
: `${v.type}${v.multiple ? '[]' : ''}`;
const joinTypes = (types) => types.length === 1 && typeof types[0] === 'string' ?
types[0]
: `(${types.join('|')})`;
const validateFieldMeta = (field, fieldMeta) => {
if (fieldMeta) {
if (field.type !== undefined && field.type !== fieldMeta.type) {
throw new TypeError(`invalid type`, {
cause: {
found: field.type,
wanted: [fieldMeta.type, undefined],
},
});
}
if (field.multiple !== undefined &&
!!field.multiple !== fieldMeta.multiple) {
throw new TypeError(`invalid multiple`, {
cause: {
found: field.multiple,
wanted: [fieldMeta.multiple, undefined],
},
});
}
return fieldMeta;
}
if (!isConfigType(field.type)) {
throw new TypeError(`invalid type`, {
cause: {
found: field.type,
wanted: ['string', 'number', 'boolean'],
},
});
}
return {
type: field.type,
multiple: !!field.multiple,
};
};
const validateField = (o, type, multiple) => {
const validateValidOptions = (def, validOptions) => {
if (!undefOrTypeArray(validOptions, type)) {
throw new TypeError('invalid validOptions', {
cause: {
found: validOptions,
wanted: valueType({ type, multiple: true }),
},
});
}
if (def !== undefined && validOptions !== undefined) {
const valid = Array.isArray(def) ?
def.every(v => validOptions.includes(v))
: validOptions.includes(def);
if (!valid) {
throw new TypeError('invalid default value not in validOptions', {
cause: {
found: def,
wanted: validOptions,
},
});
}
}
};
if (o.default !== undefined &&
!isValidValue(o.default, type, multiple)) {
throw new TypeError('invalid default value', {
cause: {
found: o.default,
wanted: valueType({ type, multiple }),
},
});
}
if (isConfigOptionOfType(o, 'number', false) ||
isConfigOptionOfType(o, 'number', true)) {
validateValidOptions(o.default, o.validOptions);
}
else if (isConfigOptionOfType(o, 'string', false) ||
isConfigOptionOfType(o, 'string', true)) {
validateValidOptions(o.default, o.validOptions);
}
else if (isConfigOptionOfType(o, 'boolean', false) ||
isConfigOptionOfType(o, 'boolean', true)) {
if (o.hint !== undefined) {
throw new TypeError('cannot provide hint for flag');
}
if (o.validOptions !== undefined) {
throw new TypeError('cannot provide validOptions for flag');
}
}
return o;
};
const toParseArgsOptionsConfig = (options) => {
return Object.entries(options).reduce((acc, [longOption, o]) => {
const p = {
type: 'string',
multiple: !!o.multiple,
...(typeof o.short === 'string' ? { short: o.short } : undefined),
};
const setNoBool = () => {
if (!longOption.startsWith('no-') && !options[`no-${longOption}`]) {
acc[`no-${longOption}`] = {
type: 'boolean',
multiple: !!o.multiple,
};
}
};
const setDefault = (def, fn) => {
if (def !== undefined) {
p.default = fn(def);
}
};
if (isConfigOption(o, 'number', false)) {
setDefault(o.default, String);
}
else if (isConfigOption(o, 'number', true)) {
setDefault(o.default, d => d.map(v => String(v)));
}
else if (isConfigOption(o, 'string', false) ||
isConfigOption(o, 'string', true)) {
setDefault(o.default, v => v);
}
else if (isConfigOption(o, 'boolean', false) ||
isConfigOption(o, 'boolean', true)) {
p.type = 'boolean';
setDefault(o.default, v => v);
setNoBool();
}
acc[longOption] = p;
return acc;
}, {});
};
/**
* Class returned by the {@link jack} function and all configuration
* definition methods. This is what gets chained together.
@ -309,6 +234,30 @@ export class Jack {
this.#configSet = Object.create(null);
this.#shorts = Object.create(null);
}
/**
* Resulting definitions, suitable to be passed to Node's `util.parseArgs`,
* but also including `description` and `short` fields, if set.
*/
get definitions() {
return this.#configSet;
}
/** map of `{ <short>: <long> }` strings for each short name defined */
get shorts() {
return this.#shorts;
}
/**
* options passed to the {@link Jack} constructor
*/
get jackOptions() {
return this.#options;
}
/**
* the data used to generate {@link Jack#usage} and
* {@link Jack#usageMarkdown} content.
*/
get usageFields() {
return this.#fields;
}
/**
* Set the default value (which will still be overridden by env or cli)
* as if from a parsed config file. The optional `source` param, if
@ -320,16 +269,13 @@ export class Jack {
this.validate(values);
}
catch (er) {
const e = er;
if (source && e && typeof e === 'object') {
if (e.cause && typeof e.cause === 'object') {
Object.assign(e.cause, { path: source });
}
else {
e.cause = { path: source };
}
if (source && er instanceof Error) {
/* c8 ignore next */
const cause = typeof er.cause === 'object' ? er.cause : {};
er.cause = { ...cause, path: source };
Error.captureStackTrace(er, this.setConfigValues);
}
throw e;
throw er;
}
for (const [field, value] of Object.entries(values)) {
const my = this.#configSet[field];
@ -337,7 +283,10 @@ export class Jack {
/* c8 ignore start */
if (!my) {
throw new Error('unexpected field in config set: ' + field, {
cause: { found: field },
cause: {
code: 'JACKSPEAK',
found: field,
},
});
}
/* c8 ignore stop */
@ -392,10 +341,9 @@ export class Jack {
if (args === process.argv) {
args = args.slice(process._eval !== undefined ? 1 : 2);
}
const options = toParseArgsOptionsConfig(this.#configSet);
const result = parseArgs({
args,
options,
options: toParseArgsOptionsConfig(this.#configSet),
// always strict, but using our own logic
strict: false,
allowPositionals: this.#allowPositionals,
@ -435,6 +383,7 @@ export class Jack {
`place it at the end of the command after '--', as in ` +
`'-- ${token.rawName}'`, {
cause: {
code: 'JACKSPEAK',
found: token.rawName + (token.value ? `=${token.value}` : ''),
},
});
@ -444,6 +393,7 @@ export class Jack {
if (my.type !== 'boolean') {
throw new Error(`No value provided for ${token.rawName}, expected ${my.type}`, {
cause: {
code: 'JACKSPEAK',
name: token.rawName,
wanted: valueType(my),
},
@ -453,7 +403,7 @@ export class Jack {
}
else {
if (my.type === 'boolean') {
throw new Error(`Flag ${token.rawName} does not take a value, received '${token.value}'`, { cause: { found: token } });
throw new Error(`Flag ${token.rawName} does not take a value, received '${token.value}'`, { cause: { code: 'JACKSPEAK', found: token } });
}
if (my.type === 'string') {
value = token.value;
@ -464,6 +414,7 @@ export class Jack {
throw new Error(`Invalid value '${token.value}' provided for ` +
`'${token.rawName}' option, expected number`, {
cause: {
code: 'JACKSPEAK',
name: token.rawName,
found: token.value,
wanted: 'number',
@ -488,15 +439,12 @@ export class Jack {
for (const [field, value] of Object.entries(p.values)) {
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)) {
cause = cause || { name: field, found: value };
}
const cause = validOptions && !isValidOption(value, validOptions) ?
{ name: field, found: value, validOptions }
: valid && !valid(value) ? { name: field, found: value }
: undefined;
if (cause) {
throw new Error(`Invalid value provided for --${field}: ${JSON.stringify(value)}`, { cause });
throw new Error(`Invalid value provided for --${field}: ${JSON.stringify(value)}`, { cause: { ...cause, code: 'JACKSPEAK' } });
}
}
return p;
@ -512,7 +460,7 @@ export class Jack {
// recurse so we get the core config key we care about.
this.#noNoFields(yes, val, s);
if (this.#configSet[yes]?.type === 'boolean') {
throw new Error(`do not set '${s}', instead set '${yes}' as desired.`, { cause: { found: s, wanted: yes } });
throw new Error(`do not set '${s}', instead set '${yes}' as desired.`, { cause: { code: 'JACKSPEAK', found: s, wanted: yes } });
}
}
/**
@ -522,7 +470,7 @@ export class Jack {
validate(o) {
if (!o || typeof o !== 'object') {
throw new Error('Invalid config: not an object', {
cause: { found: o },
cause: { code: 'JACKSPEAK', found: o },
});
}
const opts = o;
@ -535,33 +483,27 @@ export class Jack {
const config = this.#configSet[field];
if (!config) {
throw new Error(`Unknown config option: ${field}`, {
cause: { found: field },
cause: { code: 'JACKSPEAK', found: field },
});
}
if (!isValidValue(value, config.type, !!config.multiple)) {
throw new Error(`Invalid value ${valueType(value)} for ${field}, expected ${valueType(config)}`, {
cause: {
code: 'JACKSPEAK',
name: field,
found: value,
wanted: valueType(config),
},
});
}
let cause;
if (config.validOptions &&
!isValidOption(value, config.validOptions)) {
cause = {
name: field,
found: value,
validOptions: config.validOptions,
};
}
if (config.validate && !config.validate(value)) {
cause = cause || { name: field, found: value };
}
const cause = config.validOptions && !isValidOption(value, config.validOptions) ?
{ name: field, found: value, validOptions: config.validOptions }
: config.validate && !config.validate(value) ?
{ name: field, found: value }
: undefined;
if (cause) {
throw new Error(`Invalid config value for ${field}: ${value}`, {
cause,
cause: { ...cause, code: 'JACKSPEAK' },
});
}
}
@ -595,37 +537,37 @@ export class Jack {
* Add one or more number fields.
*/
num(fields) {
return this.#addFields(fields, num);
return this.#addFieldsWith(fields, 'number', false);
}
/**
* Add one or more multiple number fields.
*/
numList(fields) {
return this.#addFields(fields, numList);
return this.#addFieldsWith(fields, 'number', true);
}
/**
* Add one or more string option fields.
*/
opt(fields) {
return this.#addFields(fields, opt);
return this.#addFieldsWith(fields, 'string', false);
}
/**
* Add one or more multiple string option fields.
*/
optList(fields) {
return this.#addFields(fields, optList);
return this.#addFieldsWith(fields, 'string', true);
}
/**
* Add one or more flag fields.
*/
flag(fields) {
return this.#addFields(fields, flag);
return this.#addFieldsWith(fields, 'boolean', false);
}
/**
* Add one or more multiple flag fields.
*/
flagList(fields) {
return this.#addFields(fields, flagList);
return this.#addFieldsWith(fields, 'boolean', true);
}
/**
* Generic field definition method. Similar to flag/flagList/number/etc,
@ -633,29 +575,22 @@ export class Jack {
* fields on each one, or Jack won't know how to define them.
*/
addFields(fields) {
const next = this;
for (const [name, field] of Object.entries(fields)) {
this.#validateName(name, field);
next.#fields.push({
type: 'config',
name,
value: field,
});
}
Object.assign(next.#configSet, fields);
return next;
return this.#addFields(this, fields);
}
#addFields(fields, fn) {
const next = this;
#addFieldsWith(fields, type, multiple) {
return this.#addFields(this, fields, {
type,
multiple,
});
}
#addFields(next, fields, opt) {
Object.assign(next.#configSet, Object.fromEntries(Object.entries(fields).map(([name, field]) => {
this.#validateName(name, field);
const option = fn(field);
next.#fields.push({
type: 'config',
name,
value: option,
});
return [name, option];
const { type, multiple } = validateFieldMeta(field, opt);
const value = { ...field, type, multiple };
validateField(value, type, multiple);
next.#fields.push({ type: 'config', name, value });
return [name, value];
})));
return next;
}
@ -691,6 +626,7 @@ export class Jack {
if (this.#usage)
return this.#usage;
let headingLevel = 1;
//@ts-ignore
const ui = cliui({ width });
const first = this.#fields[0];
let start = first?.type === 'heading' ? 1 : 0;
@ -932,6 +868,10 @@ export class Jack {
return `Jack ${inspect(this.toJSON(), options)}`;
}
}
/**
* Main entry point. Create and return a {@link Jack} object.
*/
export const jack = (options = {}) => new Jack(options);
// Unwrap and un-indent, so we can wrap description
// strings however makes them look nice in the code.
const normalize = (s, pre = false) => {
@ -993,8 +933,4 @@ const normalizeOneLine = (s, pre = false) => {
.trim();
return pre ? `\`${n}\`` : n;
};
/**
* Main entry point. Create and return a {@link Jack} object.
*/
export const jack = (options = {}) => new Jack(options);
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,4 +0,0 @@
/// <reference types="node" resolution-mode="require"/>
import * as util from 'util';
export declare const parseArgs: typeof util.parseArgs;
//# sourceMappingURL=parse-args.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"parse-args.d.ts","sourceRoot":"","sources":["../../src/parse-args.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAoC5B,eAAO,MAAM,SAAS,uBAA6C,CAAA"}

View File

@ -1,26 +0,0 @@
import * as util from 'util';
const pv = (typeof process === 'object' &&
!!process &&
typeof process.version === 'string') ?
process.version
: 'v0.0.0';
const pvs = pv
.replace(/^v/, '')
.split('.')
.map(s => parseInt(s, 10));
/* c8 ignore start */
const [major = 0, minor = 0] = pvs;
/* c8 ignore stop */
let { parseArgs: pa, } = util;
/* c8 ignore start - version specific */
if (!pa ||
major < 16 ||
(major === 18 && minor < 11) ||
(major === 16 && minor < 19)) {
// Ignore because we will clobber it for commonjs
//@ts-ignore
pa = (await import('@pkgjs/parseargs')).parseArgs;
}
/* c8 ignore stop */
export const parseArgs = pa;
//# sourceMappingURL=parse-args.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"parse-args.js","sourceRoot":"","sources":["../../src/parse-args.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAE5B,MAAM,EAAE,GACN,CACE,OAAO,OAAO,KAAK,QAAQ;IAC3B,CAAC,CAAC,OAAO;IACT,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,CACpC,CAAC,CAAC;IACD,OAAO,CAAC,OAAO;IACjB,CAAC,CAAC,QAAQ,CAAA;AACZ,MAAM,GAAG,GAAG,EAAE;KACX,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;KACjB,KAAK,CAAC,GAAG,CAAC;KACV,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;AAE5B,qBAAqB;AACrB,MAAM,CAAC,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,CAAA;AAClC,oBAAoB;AAEpB,IAAI,EACF,SAAS,EAAE,EAAE,GACd,GAA8D,IAAI,CAAA;AAEnE,wCAAwC;AACxC,IACE,CAAC,EAAE;IACH,KAAK,GAAG,EAAE;IACV,CAAC,KAAK,KAAK,EAAE,IAAI,KAAK,GAAG,EAAE,CAAC;IAC5B,CAAC,KAAK,KAAK,EAAE,IAAI,KAAK,GAAG,EAAE,CAAC,EAC5B,CAAC;IACD,iDAAiD;IACjD,YAAY;IACZ,EAAE,GAAG,CAAC,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAA;AACnD,CAAC;AACD,oBAAoB;AAEpB,MAAM,CAAC,MAAM,SAAS,GAAG,EAA0C,CAAA","sourcesContent":["import * as util from 'util'\n\nconst pv =\n (\n typeof process === 'object' &&\n !!process &&\n typeof process.version === 'string'\n ) ?\n process.version\n : 'v0.0.0'\nconst pvs = pv\n .replace(/^v/, '')\n .split('.')\n .map(s => parseInt(s, 10))\n\n/* c8 ignore start */\nconst [major = 0, minor = 0] = pvs\n/* c8 ignore stop */\n\nlet {\n parseArgs: pa,\n}: typeof import('util') | typeof import('@pkgjs/parseargs') = util\n\n/* c8 ignore start - version specific */\nif (\n !pa ||\n major < 16 ||\n (major === 18 && minor < 11) ||\n (major === 16 && minor < 19)\n) {\n // Ignore because we will clobber it for commonjs\n //@ts-ignore\n pa = (await import('@pkgjs/parseargs')).parseArgs\n}\n/* c8 ignore stop */\n\nexport const parseArgs = pa as (typeof import('util'))['parseArgs']\n"]}

View File

@ -1,9 +1,6 @@
{
"name": "jackspeak",
"publishConfig": {
"tag": "v3-legacy"
},
"version": "3.4.3",
"version": "4.1.1",
"description": "A very strict and proper argument parser.",
"tshy": {
"main": true,
@ -58,17 +55,18 @@
"endOfLine": "lf"
},
"devDependencies": {
"@types/node": "^20.7.0",
"@types/pkgjs__parseargs": "^0.10.1",
"prettier": "^3.2.5",
"tap": "^18.8.0",
"tshy": "^1.14.0",
"typedoc": "^0.25.1",
"typescript": "^5.2.2"
"@types/node": "^22.6.0",
"prettier": "^3.3.3",
"tap": "^21.0.1",
"tshy": "^3.0.2",
"typedoc": "^0.26.7"
},
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
@ -89,7 +87,8 @@
"parsing"
],
"author": "Isaac Z. Schlueter <i@izs.me>",
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
"tap": {
"typecheck": true
},
"module": "./dist/esm/index.js"
}