Update dependencies (#20)
All checks were successful
Lint / pre-commit Linting (push) Successful in 29s

- @actions/core
- glob
- screeps-api

Reviewed-on: #20
Co-authored-by: Philipp Horstenkamp <philipp@horstenkamp.de>
Co-committed-by: Philipp Horstenkamp <philipp@horstenkamp.de>
This commit is contained in:
2025-04-12 13:43:45 +02:00
committed by Philipp Horstenkamp
parent 1aa7be2b73
commit 318515b9c4
634 changed files with 49143 additions and 4925 deletions

55
node_modules/jackspeak/LICENSE.md generated vendored Normal file
View File

@ -0,0 +1,55 @@
# Blue Oak Model License
Version 1.0.0
## Purpose
This license gives everyone as much permission to work with
this software as possible, while protecting contributors
from liability.
## Acceptance
In order to receive this license, you must agree to its
rules. The rules of this license are both obligations
under that agreement and conditions to your license.
You must not do anything with this software that triggers
a rule that you cannot or will not follow.
## Copyright
Each contributor licenses you to do everything with this
software that would otherwise infringe that contributor's
copyright in it.
## Notices
You must ensure that everyone who gets a copy of
any part of this software from you, with or without
changes, also gets the text of this license or a link to
<https://blueoakcouncil.org/license/1.0.0>.
## Excuse
If anyone notifies you in writing that you have not
complied with [Notices](#notices), you can keep your
license by taking all practical steps to comply within 30
days after the notice. If you do not do so, your license
ends immediately.
## Patent
Each contributor licenses you to do everything with this
software that would otherwise infringe any patent claims
they can license or become able to license.
## Reliability
No contributor can revoke this license.
## No Liability
**_As far as the law allows, this software comes as is,
without any warranty or condition, and no contributor
will be liable to anyone for any damages related to this
software or this license, under any kind of legal claim._**

389
node_modules/jackspeak/README.md generated vendored Normal file
View File

@ -0,0 +1,389 @@
# jackspeak
A very strict and proper argument parser.
Validate string, boolean, and number options, from the command
line and the environment.
Call the `jack` method with a config object, and then chain
methods off of it.
At the end, call the `.parse()` method, and you'll get an object
with `positionals` and `values` members.
Any unrecognized configs or invalid values will throw an error.
As long as you define configs using object literals, types will
be properly inferred and TypeScript will know what kinds of
things you got.
If you give it a prefix for environment variables, then defaults
will be read from the environment, and parsed values written back
to it, so you can easily pass configs through to child processes.
Automatically generates a `usage`/`help` banner by calling the
`.usage()` method.
Unless otherwise noted, all methods return the object itself.
## USAGE
```js
import { jack } from 'jackspeak'
// this works too:
// const { jack } = require('jackspeak')
const { positionals, values } = jack({ envPrefix: 'FOO' })
.flag({
asdf: { description: 'sets the asfd flag', short: 'a', default: true },
'no-asdf': { description: 'unsets the asdf flag', short: 'A' },
foo: { description: 'another boolean', short: 'f' },
})
.optList({
'ip-addrs': {
description: 'addresses to ip things',
delim: ',', // defaults to '\n'
default: ['127.0.0.1'],
},
})
.parse([
'some',
'positional',
'--ip-addrs',
'192.168.0.1',
'--ip-addrs',
'1.1.1.1',
'args',
'--foo', // sets the foo flag
'-A', // short for --no-asdf, sets asdf flag to false
])
console.log(process.env.FOO_ASDF) // '0'
console.log(process.env.FOO_FOO) // '1'
console.log(values) // {
// 'ip-addrs': ['192.168.0.1', '1.1.1.1'],
// foo: true,
// asdf: false,
// }
console.log(process.env.FOO_IP_ADDRS) // '192.168.0.1,1.1.1.1'
console.log(positionals) // ['some', 'positional', 'args']
```
## `jack(options: JackOptions = {}) => Jack`
Returns a `Jack` object that can be used to chain and add
field definitions. The other methods (apart from `validate()`,
`parse()`, and `usage()` obviously) return the same Jack object,
updated with the new types, so they can be chained together as
shown in the code examples.
Options:
- `allowPositionals` Defaults to true. Set to `false` to not
allow any positional arguments.
- `envPrefix` Set to a string to write configs to and read
configs from the environment. For example, if set to `MY_APP`
then the `foo-bar` config will default based on the value of
`env.MY_APP_FOO_BAR` and will write back to that when parsed.
Boolean values are written as `'1'` and `'0'`, and will be
treated as `true` if they're `'1'` or false otherwise.
Number values are written with their `toString()`
representation.
Strings are just strings.
Any value with `multiple: true` will be represented in the
environment split by a delimiter, which defaults to `\n`.
- `env` The place to read/write environment variables. Defaults
to `process.env`.
- `usage` A short usage string to print at the top of the help
banner.
- `stopAtPositional` Boolean, default false. Stop parsing opts
and flags at the first positional argument. This is useful if
you want to pass certain options to subcommands, like some
programs do, so you can stop parsing and pass the positionals
to the subcommand to parse.
- `stopAtPositionalTest` Conditional `stopAtPositional`. Provide
a function that takes a positional argument string and returns
boolean. If it returns `true`, then parsing will stop. Useful
when _some_ subcommands should parse the rest of the command
line options, and others should not.
### `Jack.heading(text: string, level?: 1 | 2 | 3 | 4 | 5 | 6)`
Define a short string heading, used in the `usage()` output.
Indentation of the heading and subsequent description/config
usage entries (up until the next heading) is set by the heading
level.
If the first usage item defined is a heading, it is always
treated as level 1, regardless of the argument provided.
Headings level 1 and 2 will have a line of padding underneath
them. Headings level 3 through 6 will not.
### `Jack.description(text: string, { pre?: boolean } = {})`
Define a long string description, used in the `usage()` output.
If the `pre` option is set to `true`, then whitespace will not be
normalized. However, if any line is too long for the width
allotted, it will still be wrapped.
## Option Definitions
Configs are defined by calling the appropriate field definition
method with an object where the keys are the long option name,
and the value defines the config.
Options:
- `type` Only needed for the `addFields` method, as the others
set it implicitly. Can be `'string'`, `'boolean'`, or
`'number'`.
- `multiple` Only needed for the `addFields` method, as the
others set it implicitly. Set to `true` to define an array
type. This means that it can be set on the CLI multiple times,
set as an array in the `values`
and it is represented in the environment as a delimited string.
- `short` A one-character shorthand for the option.
- `description` Some words to describe what this option is and
why you'd set it.
- `hint` (Only relevant for non-boolean types) The thing to show
in the usage output, like `--option=<hint>`
- `validate` A function that returns false (or throws) if an
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
overridden by an environment variable, if present.
### `Jack.flag({ [option: string]: definition, ... })`
Define one or more boolean fields.
Boolean options may be set to `false` by using a
`--no-${optionName}` argument, which will be implicitly created
if it's not defined to be something else.
If a boolean option named `no-${optionName}` with the same
`multiple` setting is in the configuration, then that will be
treated as a negating flag.
### `Jack.flagList({ [option: string]: definition, ... })`
Define one or more boolean array fields.
### `Jack.num({ [option: string]: definition, ... })`
Define one or more number fields. These will be set in the
environment as a stringified number, and included in the `values`
object as a number.
### `Jack.numList({ [option: string]: definition, ... })`
Define one or more number list fields. These will be set in the
environment as a delimited set of stringified numbers, and
included in the `values` as a number array.
### `Jack.opt({ [option: string]: definition, ... })`
Define one or more string option fields.
### `Jack.optList({ [option: string]: definition, ... })`
Define one or more string list fields.
### `Jack.addFields({ [option: string]: definition, ... })`
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
fields defined.
### `Jack.parse(args: string[] = process.argv): { positionals: string[], values: OptionsResults }`
Parse the arguments list, write to the environment if `envPrefix`
is set, and returned the parsed values and remaining positional
arguments.
### `Jack.validate(o: any): asserts o is OptionsResults`
Throws an error if the object provided is not a valid result set,
for the configurations defined thusfar.
### `Jack.usage(): string`
Returns the compiled `usage` string, with all option descriptions
and heading/description text, wrapped to the appropriate width
for the terminal.
### `Jack.setConfigValues(options: OptionsResults, src?: string)`
Validate the `options` argument, and set the default value for
each field that appears in the options.
Values provided will be overridden by environment variables or
command line arguments.
### `Jack.usageMarkdown(): string`
Returns the compiled `usage` string, with all option descriptions
and heading/description text, but as markdown instead of
formatted for a terminal, for generating HTML documentation for
your CLI.
## Some Example Code
Also see [the examples
folder](https://github.com/isaacs/jackspeak/tree/master/examples)
```js
import { jack } from 'jackspeak'
const j = jack({
// Optional
// This will be auto-generated from the descriptions if not supplied
// top level usage line, printed by -h
// will be auto-generated if not specified
usage: 'foo [options] <files>',
})
.heading('The best Foo that ever Fooed')
.description(
`
Executes all the files and interprets their output as
TAP formatted test result data.
To parse TAP data from stdin, specify "-" as a filename.
`,
)
// flags don't take a value, they're boolean on or off, and can be
// turned off by prefixing with `--no-`
// so this adds support for -b to mean --bail, or -B to mean --no-bail
.flag({
flag: {
// specify a short value if you like. this must be a single char
short: 'f',
// description is optional as well.
description: `Make the flags wave`,
// default value for flags is 'false', unless you change it
default: true,
},
'no-flag': {
// you can can always negate a flag with `--no-flag`
// specifying a negate option will let you define a short
// single-char option for negation.
short: 'F',
description: `Do not wave the flags`,
},
})
// Options that take a value are specified with `opt()`
.opt({
reporter: {
short: 'R',
description: 'the style of report to display',
},
})
// if you want a number, say so, and jackspeak will enforce it
.num({
jobs: {
short: 'j',
description: 'how many jobs to run in parallel',
default: 1,
},
})
// A list is an option that can be specified multiple times,
// to expand into an array of all the settings. Normal opts
// will just give you the last value specified.
.optList({
'node-arg': {},
})
// a flagList is an array of booleans, so `-ddd` is [true, true, true]
// count the `true` values to treat it as a counter.
.flagList({
debug: { short: 'd' },
})
// opts take a value, and is set to the string in the results
// you can combine multiple short-form flags together, but
// an opt will end the combine chain, posix-style. So,
// -bofilename would be like --bail --output-file=filename
.opt({
'output-file': {
short: 'o',
// optional: make it -o<file> in the help output insead of -o<value>
hint: 'file',
description: `Send the raw output to the specified file.`,
},
})
// now we can parse argv like this:
const { values, positionals } = j.parse(process.argv)
// or decide to show the usage banner
console.log(j.usage())
// or validate an object config we got from somewhere else
try {
j.validate(someConfig)
} catch (er) {
console.error('someConfig is not valid!', er)
}
```
## Name
The inspiration for this module is [yargs](http://npm.im/yargs), which
is pirate talk themed. Yargs has all the features, and is infinitely
flexible. "Jackspeak" is the slang of the royal navy. This module
does not have all the features. It is declarative and rigid by design.

311
node_modules/jackspeak/dist/commonjs/index.d.ts generated vendored Normal file
View File

@ -0,0 +1,311 @@
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;
/**
* Defines the type of value that is valid, given a config definition's
* {@link ConfigType} and boolean multiple setting
*/
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] ? 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, 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> = {
[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>> = S & {
[longOption in keyof S]: ConfigOption<T, M>;
};
/**
* 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 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}
*/
export type Parsed<T extends ConfigSet> = {
values: OptionsResults<T>;
positionals: string[];
};
/**
* A row used when generating the {@link Jack#usage} string
*/
export interface Row {
left?: string;
text: string;
skipLine?: boolean;
type?: string;
}
/**
* A heading for a section in the usage, created by the jack.heading()
* method.
*
* First heading is always level 1, subsequent headings default to 2.
*
* The level of the nearest heading level sets the indentation of the
* description that follows.
*/
export interface Heading extends Row {
type: 'heading';
text: string;
left?: '';
skipLine?: boolean;
level: number;
pre?: boolean;
}
/**
* An arbitrary blob of text describing some stuff, set by the
* jack.description() method.
*
* Indentation determined by level of the nearest header.
*/
export interface Description extends Row {
type: 'description';
text: string;
left?: '';
skipLine?: boolean;
pre?: boolean;
}
/**
* A heading or description row used when generating the {@link Jack#usage}
* string
*/
export type TextRow = Heading | Description;
/**
* Either a {@link TextRow} or a reference to a {@link ConfigOption}
*/
export type UsageField = TextRow | {
type: 'config';
name: string;
value: ConfigOption;
};
/**
* Options provided to the {@link Jack} constructor
*/
export interface JackOptions {
/**
* Whether to allow positional arguments
*
* @default true
*/
allowPositionals?: boolean;
/**
* Prefix to use when reading/writing the environment variables
*
* If not specified, environment behavior will not be available.
*/
envPrefix?: string;
/**
* Environment object to read/write. Defaults `process.env`.
* No effect if `envPrefix` is not set.
*/
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
* there are a lot of options.
*/
usage?: string;
/**
* Stop parsing flags and opts at the first positional argument.
* This is to support cases like `cmd [flags] <subcmd> [options]`, where
* each subcommand may have different options. This effectively treats
* any positional as a `--` argument. Only relevant if `allowPositionals`
* is true.
*
* To do subcommands, set this option, look at the first positional, and
* parse the remaining positionals as appropriate.
*
* @default false
*/
stopAtPositional?: boolean;
/**
* Conditional `stopAtPositional`. If set to a `(string)=>boolean` function,
* will be called with each positional argument encountered. If the function
* returns true, then parsing will stop at that point.
*/
stopAtPositionalTest?: (arg: string) => boolean;
}
/**
* Class returned by the {@link jack} function and all configuration
* definition methods. This is what gets chained together.
*/
export declare class Jack<C extends ConfigSet = {}> {
#private;
constructor(options?: JackOptions);
get definitions(): C;
get shorts(): Record<string, string>;
get jackOptions(): JackOptions;
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: Partial<OptionsResults<C>>, source?: string): this;
/**
* Parse a string of arguments, and return the resulting
* `{ values, positionals }` object.
*
* If an {@link JackOptions#envPrefix} is set, then it will read default
* values from the environment, and write the resulting values back
* to the environment as well.
*
* Environment values always take precedence over any other value, except
* an explicit CLI setting.
*/
parse(args?: string[]): Parsed<C>;
loadEnvDefaults(): void;
applyDefaults(p: Parsed<C>): void;
/**
* Only parse the command line arguments passed in.
* Does not strip off the `node script.js` bits, so it must be just the
* arguments you wish to have parsed.
* Does not read from or write to the environment, or set defaults.
*/
parseRaw(args: string[]): Parsed<C>;
/**
* Validate that any arbitrary object is a valid configuration `values`
* object. Useful when loading config files or other sources.
*/
validate(o: unknown): asserts o is Parsed<C>['values'];
writeEnv(p: Parsed<C>): void;
/**
* Add a heading to the usage output banner
*/
heading(text: string, level?: 1 | 2 | 3 | 4 | 5 | 6, { pre }?: {
pre?: boolean;
}): Jack<C>;
/**
* Add a long-form description to the usage output at this position.
*/
description(text: string, { pre }?: {
pre?: boolean;
}): Jack<C>;
/**
* Add one or more number fields.
*/
num<F extends ConfigMetaSet<'number', false>>(fields: F): Jack<C & ConfigSetFromMetaSet<'number', false, F>>;
/**
* Add one or more multiple number fields.
*/
numList<F extends ConfigMetaSet<'number', true>>(fields: F): Jack<C & ConfigSetFromMetaSet<'number', true, F>>;
/**
* Add one or more string option fields.
*/
opt<F extends ConfigMetaSet<'string', false>>(fields: F): Jack<C & ConfigSetFromMetaSet<'string', false, F>>;
/**
* Add one or more multiple string option fields.
*/
optList<F extends ConfigMetaSet<'string', true>>(fields: F): Jack<C & ConfigSetFromMetaSet<'string', true, F>>;
/**
* Add one or more flag fields.
*/
flag<F extends ConfigMetaSet<'boolean', false>>(fields: F): Jack<C & ConfigSetFromMetaSet<'boolean', false, F>>;
/**
* Add one or more multiple flag fields.
*/
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`)
* fields on each one, or Jack won't know how to define them.
*/
addFields<F extends ConfigSet>(fields: F): Jack<C & F>;
/**
* Return the usage banner for the given configuration
*/
usage(): string;
/**
* Return the usage banner markdown for the given configuration
*/
usageMarkdown(): string;
/**
* Return the configuration options as a plain object
*/
toJSON(): {
[k: string]: {
hint?: string | undefined;
default?: ConfigValue | undefined;
validOptions?: readonly number[] | readonly string[] | undefined;
validate?: ((v: unknown) => boolean) | ((v: unknown) => v is ValidValue<ConfigType, boolean>) | undefined;
description?: string | undefined;
short?: string | undefined;
delim?: string | undefined;
multiple?: boolean | undefined;
type: ConfigType;
};
};
/**
* Custom printer for `util.inspect`
*/
[inspect.custom](_: number, options: InspectOptions): string;
}
/**
* Main entry point. Create and return a {@link Jack} object.
*/
export declare const jack: (options?: JackOptions) => Jack<{}>;
//# sourceMappingURL=index.d.ts.map

1
node_modules/jackspeak/dist/commonjs/index.d.ts.map generated vendored Normal file

File diff suppressed because one or more lines are too long

934
node_modules/jackspeak/dist/commonjs/index.js generated vendored Normal file
View File

@ -0,0 +1,934 @@
"use strict";
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.isConfigOptionOfType = exports.isConfigType = void 0;
const node_util_1 = require("node:util");
// 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 isConfigType = (t) => typeof t === 'string' &&
(t === 'string' || t === 'number' || t === 'boolean');
exports.isConfigType = isConfigType;
const isValidValue = (v, type, multi) => {
if (multi) {
if (!Array.isArray(v))
return false;
return !v.some((v) => !isValidValue(v, type, false));
}
if (Array.isArray(v))
return false;
return typeof v === type;
};
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') &&
undefOrType(o.validate, 'function') &&
(o.type === 'boolean' ?
o.validOptions === undefined
: undefOrTypeArray(o.validOptions, o.type)) &&
(o.default === undefined || isValidValue(o.default, type, multi));
exports.isConfigOption = isConfigOption;
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.
*/
class Jack {
#configSet;
#shorts;
#options;
#fields = [];
#env;
#envPrefix;
#allowPositionals;
#usage;
#usageMarkdown;
constructor(options = {}) {
this.#options = options;
this.#allowPositionals = options.allowPositionals !== false;
this.#env =
this.#options.env === undefined ? process.env : this.#options.env;
this.#envPrefix = options.envPrefix;
// We need to fib a little, because it's always the same object, but it
// starts out as having an empty config set. Then each method that adds
// fields returns `this as Jack<C & { ...newConfigs }>`
this.#configSet = Object.create(null);
this.#shorts = Object.create(null);
}
get definitions() {
return this.#configSet;
}
get shorts() {
return this.#shorts;
}
get jackOptions() {
return this.#options;
}
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
* provided, will be included in error messages if a value is invalid or
* unknown.
*/
setConfigValues(values, source = '') {
try {
this.validate(values);
}
catch (er) {
if (source && er instanceof Error) {
/* c8 ignore next */
const cause = typeof er.cause === 'object' ? er.cause : {};
er.cause = { ...cause, path: source };
}
throw er;
}
for (const [field, value] of Object.entries(values)) {
const my = this.#configSet[field];
// already validated, just for TS's benefit
/* c8 ignore start */
if (!my) {
throw new Error('unexpected field in config set: ' + field, {
cause: {
code: 'JACKSPEAK',
found: field,
},
});
}
/* c8 ignore stop */
my.default = value;
}
return this;
}
/**
* Parse a string of arguments, and return the resulting
* `{ values, positionals }` object.
*
* If an {@link JackOptions#envPrefix} is set, then it will read default
* values from the environment, and write the resulting values back
* to the environment as well.
*
* Environment values always take precedence over any other value, except
* an explicit CLI setting.
*/
parse(args = process.argv) {
this.loadEnvDefaults();
const p = this.parseRaw(args);
this.applyDefaults(p);
this.writeEnv(p);
return p;
}
loadEnvDefaults() {
if (this.#envPrefix) {
for (const [field, my] of Object.entries(this.#configSet)) {
const ek = toEnvKey(this.#envPrefix, field);
const env = this.#env[ek];
if (env !== undefined) {
my.default = fromEnvVal(env, my.type, !!my.multiple, my.delim);
}
}
}
}
applyDefaults(p) {
for (const [field, c] of Object.entries(this.#configSet)) {
if (c.default !== undefined && !(field in p.values)) {
//@ts-ignore
p.values[field] = c.default;
}
}
}
/**
* Only parse the command line arguments passed in.
* Does not strip off the `node script.js` bits, so it must be just the
* arguments you wish to have parsed.
* Does not read from or write to the environment, or set defaults.
*/
parseRaw(args) {
if (args === process.argv) {
args = args.slice(process._eval !== undefined ? 1 : 2);
}
const result = (0, node_util_1.parseArgs)({
args,
options: toParseArgsOptionsConfig(this.#configSet),
// always strict, but using our own logic
strict: false,
allowPositionals: this.#allowPositionals,
tokens: true,
});
const p = {
values: {},
positionals: [],
};
for (const token of result.tokens) {
if (token.kind === 'positional') {
p.positionals.push(token.value);
if (this.#options.stopAtPositional ||
this.#options.stopAtPositionalTest?.(token.value)) {
p.positionals.push(...args.slice(token.index + 1));
break;
}
}
else if (token.kind === 'option') {
let value = undefined;
if (token.name.startsWith('no-')) {
const my = this.#configSet[token.name];
const pname = token.name.substring('no-'.length);
const pos = this.#configSet[pname];
if (pos &&
pos.type === 'boolean' &&
(!my ||
(my.type === 'boolean' && !!my.multiple === !!pos.multiple))) {
value = false;
token.name = pname;
}
}
const my = this.#configSet[token.name];
if (!my) {
throw new Error(`Unknown option '${token.rawName}'. ` +
`To specify a positional argument starting with a '-', ` +
`place it at the end of the command after '--', as in ` +
`'-- ${token.rawName}'`, {
cause: {
code: 'JACKSPEAK',
found: token.rawName + (token.value ? `=${token.value}` : ''),
},
});
}
if (value === undefined) {
if (token.value === undefined) {
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),
},
});
}
value = true;
}
else {
if (my.type === 'boolean') {
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;
}
else {
value = +token.value;
if (value !== value) {
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',
},
});
}
}
}
}
if (my.multiple) {
const pv = p.values;
const tn = pv[token.name] ?? [];
pv[token.name] = tn;
tn.push(value);
}
else {
const pv = p.values;
pv[token.name] = value;
}
}
}
for (const [field, value] of Object.entries(p.values)) {
const valid = this.#configSet[field]?.validate;
const validOptions = this.#configSet[field]?.validOptions;
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: { ...cause, code: 'JACKSPEAK' } });
}
}
return p;
}
/**
* do not set fields as 'no-foo' if 'foo' exists and both are bools
* just set foo.
*/
#noNoFields(f, val, s = f) {
if (!f.startsWith('no-') || typeof val !== 'boolean')
return;
const yes = f.substring('no-'.length);
// 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: { code: 'JACKSPEAK', found: s, wanted: yes } });
}
}
/**
* Validate that any arbitrary object is a valid configuration `values`
* object. Useful when loading config files or other sources.
*/
validate(o) {
if (!o || typeof o !== 'object') {
throw new Error('Invalid config: not an object', {
cause: { code: 'JACKSPEAK', found: o },
});
}
const opts = o;
for (const field in o) {
const value = opts[field];
/* c8 ignore next - for TS */
if (value === undefined)
continue;
this.#noNoFields(field, value);
const config = this.#configSet[field];
if (!config) {
throw new Error(`Unknown config option: ${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),
},
});
}
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, code: 'JACKSPEAK' },
});
}
}
}
writeEnv(p) {
if (!this.#env || !this.#envPrefix)
return;
for (const [field, value] of Object.entries(p.values)) {
const my = this.#configSet[field];
this.#env[toEnvKey(this.#envPrefix, field)] = toEnvVal(value, my?.delim);
}
}
/**
* Add a heading to the usage output banner
*/
heading(text, level, { pre = false } = {}) {
if (level === undefined) {
level = this.#fields.some(r => isHeading(r)) ? 2 : 1;
}
this.#fields.push({ type: 'heading', text, level, pre });
return this;
}
/**
* Add a long-form description to the usage output at this position.
*/
description(text, { pre } = {}) {
this.#fields.push({ type: 'description', text, pre });
return this;
}
/**
* Add one or more number fields.
*/
num(fields) {
return this.#addFieldsWith(fields, 'number', false);
}
/**
* Add one or more multiple number fields.
*/
numList(fields) {
return this.#addFieldsWith(fields, 'number', true);
}
/**
* Add one or more string option fields.
*/
opt(fields) {
return this.#addFieldsWith(fields, 'string', false);
}
/**
* Add one or more multiple string option fields.
*/
optList(fields) {
return this.#addFieldsWith(fields, 'string', true);
}
/**
* Add one or more flag fields.
*/
flag(fields) {
return this.#addFieldsWith(fields, 'boolean', false);
}
/**
* Add one or more multiple flag fields.
*/
flagList(fields) {
return this.#addFieldsWith(fields, 'boolean', true);
}
/**
* Generic field definition method. Similar to flag/flagList/number/etc,
* but you must specify the `type` (and optionally `multiple` and `delim`)
* fields on each one, or Jack won't know how to define them.
*/
addFields(fields) {
return this.#addFields(this, fields);
}
#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 { 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;
}
#validateName(name, field) {
if (!/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(name)) {
throw new TypeError(`Invalid option name: ${name}, ` +
`must be '-' delimited ASCII alphanumeric`);
}
if (this.#configSet[name]) {
throw new TypeError(`Cannot redefine option ${field}`);
}
if (this.#shorts[name]) {
throw new TypeError(`Cannot redefine option ${name}, already ` +
`in use for ${this.#shorts[name]}`);
}
if (field.short) {
if (!/^[a-zA-Z0-9]$/.test(field.short)) {
throw new TypeError(`Invalid ${name} short option: ${field.short}, ` +
'must be 1 ASCII alphanumeric character');
}
if (this.#shorts[field.short]) {
throw new TypeError(`Invalid ${name} short option: ${field.short}, ` +
`already in use for ${this.#shorts[field.short]}`);
}
this.#shorts[field.short] = name;
this.#shorts[name] = name;
}
}
/**
* Return the usage banner for the given configuration
*/
usage() {
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;
if (first?.type === 'heading') {
ui.div({
padding: [0, 0, 0, 0],
text: normalize(first.text),
});
}
ui.div({ padding: [0, 0, 0, 0], text: 'Usage:' });
if (this.#options.usage) {
ui.div({
text: this.#options.usage,
padding: [0, 0, 0, 2],
});
}
else {
const cmd = (0, node_path_1.basename)(String(process.argv[1]));
const shortFlags = [];
const shorts = [];
const flags = [];
const opts = [];
for (const [field, config] of Object.entries(this.#configSet)) {
if (config.short) {
if (config.type === 'boolean')
shortFlags.push(config.short);
else
shorts.push([config.short, config.hint || field]);
}
else {
if (config.type === 'boolean')
flags.push(field);
else
opts.push([field, config.hint || field]);
}
}
const sf = shortFlags.length ? ' -' + shortFlags.join('') : '';
const so = shorts.map(([k, v]) => ` --${k}=<${v}>`).join('');
const lf = flags.map(k => ` --${k}`).join('');
const lo = opts.map(([k, v]) => ` --${k}=<${v}>`).join('');
const usage = `${cmd}${sf}${so}${lf}${lo}`.trim();
ui.div({
text: usage,
padding: [0, 0, 0, 2],
});
}
ui.div({ padding: [0, 0, 0, 0], text: '' });
const maybeDesc = this.#fields[start];
if (maybeDesc && isDescription(maybeDesc)) {
const print = normalize(maybeDesc.text, maybeDesc.pre);
start++;
ui.div({ padding: [0, 0, 0, 0], text: print });
ui.div({ padding: [0, 0, 0, 0], text: '' });
}
const { rows, maxWidth } = this.#usageRows(start);
// every heading/description after the first gets indented by 2
// extra spaces.
for (const row of rows) {
if (row.left) {
// If the row is too long, don't wrap it
// Bump the right-hand side down a line to make room
const configIndent = indent(Math.max(headingLevel, 2));
if (row.left.length > maxWidth - 3) {
ui.div({ text: row.left, padding: [0, 0, 0, configIndent] });
ui.div({ text: row.text, padding: [0, 0, 0, maxWidth] });
}
else {
ui.div({
text: row.left,
padding: [0, 1, 0, configIndent],
width: maxWidth,
}, { padding: [0, 0, 0, 0], text: row.text });
}
if (row.skipLine) {
ui.div({ padding: [0, 0, 0, 0], text: '' });
}
}
else {
if (isHeading(row)) {
const { level } = row;
headingLevel = level;
// only h1 and h2 have bottom padding
// h3-h6 do not
const b = level <= 2 ? 1 : 0;
ui.div({ ...row, padding: [0, 0, b, indent(level)] });
}
else {
ui.div({ ...row, padding: [0, 0, 1, indent(headingLevel + 1)] });
}
}
}
return (this.#usage = ui.toString());
}
/**
* Return the usage banner markdown for the given configuration
*/
usageMarkdown() {
if (this.#usageMarkdown)
return this.#usageMarkdown;
const out = [];
let headingLevel = 1;
const first = this.#fields[0];
let start = first?.type === 'heading' ? 1 : 0;
if (first?.type === 'heading') {
out.push(`# ${normalizeOneLine(first.text)}`);
}
out.push('Usage:');
if (this.#options.usage) {
out.push(normalizeMarkdown(this.#options.usage, true));
}
else {
const cmd = (0, node_path_1.basename)(String(process.argv[1]));
const shortFlags = [];
const shorts = [];
const flags = [];
const opts = [];
for (const [field, config] of Object.entries(this.#configSet)) {
if (config.short) {
if (config.type === 'boolean')
shortFlags.push(config.short);
else
shorts.push([config.short, config.hint || field]);
}
else {
if (config.type === 'boolean')
flags.push(field);
else
opts.push([field, config.hint || field]);
}
}
const sf = shortFlags.length ? ' -' + shortFlags.join('') : '';
const so = shorts.map(([k, v]) => ` --${k}=<${v}>`).join('');
const lf = flags.map(k => ` --${k}`).join('');
const lo = opts.map(([k, v]) => ` --${k}=<${v}>`).join('');
const usage = `${cmd}${sf}${so}${lf}${lo}`.trim();
out.push(normalizeMarkdown(usage, true));
}
const maybeDesc = this.#fields[start];
if (maybeDesc && isDescription(maybeDesc)) {
out.push(normalizeMarkdown(maybeDesc.text, maybeDesc.pre));
start++;
}
const { rows } = this.#usageRows(start);
// heading level in markdown is number of # ahead of text
for (const row of rows) {
if (row.left) {
out.push('#'.repeat(headingLevel + 1) +
' ' +
normalizeOneLine(row.left, true));
if (row.text)
out.push(normalizeMarkdown(row.text));
}
else if (isHeading(row)) {
const { level } = row;
headingLevel = level;
out.push(`${'#'.repeat(headingLevel)} ${normalizeOneLine(row.text, row.pre)}`);
}
else {
out.push(normalizeMarkdown(row.text, !!row.pre));
}
}
return (this.#usageMarkdown = out.join('\n\n') + '\n');
}
#usageRows(start) {
// turn each config type into a row, and figure out the width of the
// left hand indentation for the option descriptions.
let maxMax = Math.max(12, Math.min(26, Math.floor(width / 3)));
let maxWidth = 8;
let prev = undefined;
const rows = [];
for (const field of this.#fields.slice(start)) {
if (field.type !== 'config') {
if (prev?.type === 'config')
prev.skipLine = true;
prev = undefined;
field.text = normalize(field.text, !!field.pre);
rows.push(field);
continue;
}
const { value } = field;
const desc = value.description || '';
const mult = value.multiple ? 'Can be set multiple times' : '';
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 ||
(value.type === 'number' ? 'n'
: value.type === 'string' ? field.name
: undefined);
const short = !value.short ? ''
: value.type === 'boolean' ? `-${value.short} `
: `-${value.short}<${hint}> `;
const left = value.type === 'boolean' ?
`${short}--${field.name}`
: `${short}--${field.name}=<${hint}>`;
const row = { text, left, type: 'config' };
if (text.length > width - maxMax) {
row.skipLine = true;
}
if (prev && left.length > maxMax)
prev.skipLine = true;
prev = row;
const len = left.length + 4;
if (len > maxWidth && len < maxMax) {
maxWidth = len;
}
rows.push(row);
}
return { rows, maxWidth };
}
/**
* Return the configuration options as a plain object
*/
toJSON() {
return Object.fromEntries(Object.entries(this.#configSet).map(([field, def]) => [
field,
{
type: def.type,
...(def.multiple ? { multiple: true } : {}),
...(def.delim ? { delim: def.delim } : {}),
...(def.short ? { short: def.short } : {}),
...(def.description ?
{ description: normalize(def.description) }
: {}),
...(def.validate ? { validate: def.validate } : {}),
...(def.validOptions ? { validOptions: def.validOptions } : {}),
...(def.default !== undefined ? { default: def.default } : {}),
...(def.hint ? { hint: def.hint } : {}),
},
]));
}
/**
* Custom printer for `util.inspect`
*/
[node_util_1.inspect.custom](_, options) {
return `Jack ${(0, node_util_1.inspect)(this.toJSON(), options)}`;
}
}
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) => {
if (pre)
// prepend a ZWSP to each line so cliui doesn't strip it.
return s
.split('\n')
.map(l => `\u200b${l}`)
.join('\n');
return s
.split(/^\s*```\s*$/gm)
.map((s, i) => {
if (i % 2 === 1) {
if (!s.trim()) {
return `\`\`\`\n\`\`\`\n`;
}
// outdent the ``` blocks, but preserve whitespace otherwise.
const split = s.split('\n');
// throw out the \n at the start and end
split.pop();
split.shift();
const si = split.reduce((shortest, l) => {
/* c8 ignore next */
const ind = l.match(/^\s*/)?.[0] ?? '';
if (ind.length)
return Math.min(ind.length, shortest);
else
return shortest;
}, Infinity);
/* c8 ignore next */
const i = isFinite(si) ? si : 0;
return ('\n```\n' +
split.map(s => `\u200b${s.substring(i)}`).join('\n') +
'\n```\n');
}
return (s
// remove single line breaks, except for lists
.replace(/([^\n])\n[ \t]*([^\n])/g, (_, $1, $2) => !/^[-*]/.test($2) ? `${$1} ${$2}` : `${$1}\n${$2}`)
// normalize mid-line whitespace
.replace(/([^\n])[ \t]+([^\n])/g, '$1 $2')
// two line breaks are enough
.replace(/\n{3,}/g, '\n\n')
// remove any spaces at the start of a line
.replace(/\n[ \t]+/g, '\n')
.trim());
})
.join('\n');
};
// normalize for markdown printing, remove leading spaces on lines
const normalizeMarkdown = (s, pre = false) => {
const n = normalize(s, pre).replace(/\\/g, '\\\\');
return pre ?
`\`\`\`\n${n.replace(/\u200b/g, '')}\n\`\`\``
: n.replace(/\n +/g, '\n').trim();
};
const normalizeOneLine = (s, pre = false) => {
const n = normalize(s, pre)
.replace(/[\s\u200b]+/g, ' ')
.trim();
return pre ? `\`${n}\`` : n;
};
//# sourceMappingURL=index.js.map

1
node_modules/jackspeak/dist/commonjs/index.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

3
node_modules/jackspeak/dist/commonjs/package.json generated vendored Normal file
View File

@ -0,0 +1,3 @@
{
"type": "commonjs"
}

311
node_modules/jackspeak/dist/esm/index.d.ts generated vendored Normal file
View File

@ -0,0 +1,311 @@
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;
/**
* Defines the type of value that is valid, given a config definition's
* {@link ConfigType} and boolean multiple setting
*/
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] ? 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, 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> = {
[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>> = S & {
[longOption in keyof S]: ConfigOption<T, M>;
};
/**
* 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 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}
*/
export type Parsed<T extends ConfigSet> = {
values: OptionsResults<T>;
positionals: string[];
};
/**
* A row used when generating the {@link Jack#usage} string
*/
export interface Row {
left?: string;
text: string;
skipLine?: boolean;
type?: string;
}
/**
* A heading for a section in the usage, created by the jack.heading()
* method.
*
* First heading is always level 1, subsequent headings default to 2.
*
* The level of the nearest heading level sets the indentation of the
* description that follows.
*/
export interface Heading extends Row {
type: 'heading';
text: string;
left?: '';
skipLine?: boolean;
level: number;
pre?: boolean;
}
/**
* An arbitrary blob of text describing some stuff, set by the
* jack.description() method.
*
* Indentation determined by level of the nearest header.
*/
export interface Description extends Row {
type: 'description';
text: string;
left?: '';
skipLine?: boolean;
pre?: boolean;
}
/**
* A heading or description row used when generating the {@link Jack#usage}
* string
*/
export type TextRow = Heading | Description;
/**
* Either a {@link TextRow} or a reference to a {@link ConfigOption}
*/
export type UsageField = TextRow | {
type: 'config';
name: string;
value: ConfigOption;
};
/**
* Options provided to the {@link Jack} constructor
*/
export interface JackOptions {
/**
* Whether to allow positional arguments
*
* @default true
*/
allowPositionals?: boolean;
/**
* Prefix to use when reading/writing the environment variables
*
* If not specified, environment behavior will not be available.
*/
envPrefix?: string;
/**
* Environment object to read/write. Defaults `process.env`.
* No effect if `envPrefix` is not set.
*/
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
* there are a lot of options.
*/
usage?: string;
/**
* Stop parsing flags and opts at the first positional argument.
* This is to support cases like `cmd [flags] <subcmd> [options]`, where
* each subcommand may have different options. This effectively treats
* any positional as a `--` argument. Only relevant if `allowPositionals`
* is true.
*
* To do subcommands, set this option, look at the first positional, and
* parse the remaining positionals as appropriate.
*
* @default false
*/
stopAtPositional?: boolean;
/**
* Conditional `stopAtPositional`. If set to a `(string)=>boolean` function,
* will be called with each positional argument encountered. If the function
* returns true, then parsing will stop at that point.
*/
stopAtPositionalTest?: (arg: string) => boolean;
}
/**
* Class returned by the {@link jack} function and all configuration
* definition methods. This is what gets chained together.
*/
export declare class Jack<C extends ConfigSet = {}> {
#private;
constructor(options?: JackOptions);
get definitions(): C;
get shorts(): Record<string, string>;
get jackOptions(): JackOptions;
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: Partial<OptionsResults<C>>, source?: string): this;
/**
* Parse a string of arguments, and return the resulting
* `{ values, positionals }` object.
*
* If an {@link JackOptions#envPrefix} is set, then it will read default
* values from the environment, and write the resulting values back
* to the environment as well.
*
* Environment values always take precedence over any other value, except
* an explicit CLI setting.
*/
parse(args?: string[]): Parsed<C>;
loadEnvDefaults(): void;
applyDefaults(p: Parsed<C>): void;
/**
* Only parse the command line arguments passed in.
* Does not strip off the `node script.js` bits, so it must be just the
* arguments you wish to have parsed.
* Does not read from or write to the environment, or set defaults.
*/
parseRaw(args: string[]): Parsed<C>;
/**
* Validate that any arbitrary object is a valid configuration `values`
* object. Useful when loading config files or other sources.
*/
validate(o: unknown): asserts o is Parsed<C>['values'];
writeEnv(p: Parsed<C>): void;
/**
* Add a heading to the usage output banner
*/
heading(text: string, level?: 1 | 2 | 3 | 4 | 5 | 6, { pre }?: {
pre?: boolean;
}): Jack<C>;
/**
* Add a long-form description to the usage output at this position.
*/
description(text: string, { pre }?: {
pre?: boolean;
}): Jack<C>;
/**
* Add one or more number fields.
*/
num<F extends ConfigMetaSet<'number', false>>(fields: F): Jack<C & ConfigSetFromMetaSet<'number', false, F>>;
/**
* Add one or more multiple number fields.
*/
numList<F extends ConfigMetaSet<'number', true>>(fields: F): Jack<C & ConfigSetFromMetaSet<'number', true, F>>;
/**
* Add one or more string option fields.
*/
opt<F extends ConfigMetaSet<'string', false>>(fields: F): Jack<C & ConfigSetFromMetaSet<'string', false, F>>;
/**
* Add one or more multiple string option fields.
*/
optList<F extends ConfigMetaSet<'string', true>>(fields: F): Jack<C & ConfigSetFromMetaSet<'string', true, F>>;
/**
* Add one or more flag fields.
*/
flag<F extends ConfigMetaSet<'boolean', false>>(fields: F): Jack<C & ConfigSetFromMetaSet<'boolean', false, F>>;
/**
* Add one or more multiple flag fields.
*/
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`)
* fields on each one, or Jack won't know how to define them.
*/
addFields<F extends ConfigSet>(fields: F): Jack<C & F>;
/**
* Return the usage banner for the given configuration
*/
usage(): string;
/**
* Return the usage banner markdown for the given configuration
*/
usageMarkdown(): string;
/**
* Return the configuration options as a plain object
*/
toJSON(): {
[k: string]: {
hint?: string | undefined;
default?: ConfigValue | undefined;
validOptions?: readonly number[] | readonly string[] | undefined;
validate?: ((v: unknown) => boolean) | ((v: unknown) => v is ValidValue<ConfigType, boolean>) | undefined;
description?: string | undefined;
short?: string | undefined;
delim?: string | undefined;
multiple?: boolean | undefined;
type: ConfigType;
};
};
/**
* Custom printer for `util.inspect`
*/
[inspect.custom](_: number, options: InspectOptions): string;
}
/**
* Main entry point. Create and return a {@link Jack} object.
*/
export declare const jack: (options?: JackOptions) => Jack<{}>;
//# sourceMappingURL=index.d.ts.map

1
node_modules/jackspeak/dist/esm/index.d.ts.map generated vendored Normal file

File diff suppressed because one or more lines are too long

923
node_modules/jackspeak/dist/esm/index.js generated vendored Normal file
View File

@ -0,0 +1,923 @@
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';
export const isConfigType = (t) => typeof t === 'string' &&
(t === 'string' || t === 'number' || t === 'boolean');
const isValidValue = (v, type, multi) => {
if (multi) {
if (!Array.isArray(v))
return false;
return !v.some((v) => !isValidValue(v, type, false));
}
if (Array.isArray(v))
return false;
return typeof v === type;
};
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') &&
undefOrType(o.validate, 'function') &&
(o.type === 'boolean' ?
o.validOptions === undefined
: undefOrTypeArray(o.validOptions, o.type)) &&
(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.
*/
export class Jack {
#configSet;
#shorts;
#options;
#fields = [];
#env;
#envPrefix;
#allowPositionals;
#usage;
#usageMarkdown;
constructor(options = {}) {
this.#options = options;
this.#allowPositionals = options.allowPositionals !== false;
this.#env =
this.#options.env === undefined ? process.env : this.#options.env;
this.#envPrefix = options.envPrefix;
// We need to fib a little, because it's always the same object, but it
// starts out as having an empty config set. Then each method that adds
// fields returns `this as Jack<C & { ...newConfigs }>`
this.#configSet = Object.create(null);
this.#shorts = Object.create(null);
}
get definitions() {
return this.#configSet;
}
get shorts() {
return this.#shorts;
}
get jackOptions() {
return this.#options;
}
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
* provided, will be included in error messages if a value is invalid or
* unknown.
*/
setConfigValues(values, source = '') {
try {
this.validate(values);
}
catch (er) {
if (source && er instanceof Error) {
/* c8 ignore next */
const cause = typeof er.cause === 'object' ? er.cause : {};
er.cause = { ...cause, path: source };
}
throw er;
}
for (const [field, value] of Object.entries(values)) {
const my = this.#configSet[field];
// already validated, just for TS's benefit
/* c8 ignore start */
if (!my) {
throw new Error('unexpected field in config set: ' + field, {
cause: {
code: 'JACKSPEAK',
found: field,
},
});
}
/* c8 ignore stop */
my.default = value;
}
return this;
}
/**
* Parse a string of arguments, and return the resulting
* `{ values, positionals }` object.
*
* If an {@link JackOptions#envPrefix} is set, then it will read default
* values from the environment, and write the resulting values back
* to the environment as well.
*
* Environment values always take precedence over any other value, except
* an explicit CLI setting.
*/
parse(args = process.argv) {
this.loadEnvDefaults();
const p = this.parseRaw(args);
this.applyDefaults(p);
this.writeEnv(p);
return p;
}
loadEnvDefaults() {
if (this.#envPrefix) {
for (const [field, my] of Object.entries(this.#configSet)) {
const ek = toEnvKey(this.#envPrefix, field);
const env = this.#env[ek];
if (env !== undefined) {
my.default = fromEnvVal(env, my.type, !!my.multiple, my.delim);
}
}
}
}
applyDefaults(p) {
for (const [field, c] of Object.entries(this.#configSet)) {
if (c.default !== undefined && !(field in p.values)) {
//@ts-ignore
p.values[field] = c.default;
}
}
}
/**
* Only parse the command line arguments passed in.
* Does not strip off the `node script.js` bits, so it must be just the
* arguments you wish to have parsed.
* Does not read from or write to the environment, or set defaults.
*/
parseRaw(args) {
if (args === process.argv) {
args = args.slice(process._eval !== undefined ? 1 : 2);
}
const result = parseArgs({
args,
options: toParseArgsOptionsConfig(this.#configSet),
// always strict, but using our own logic
strict: false,
allowPositionals: this.#allowPositionals,
tokens: true,
});
const p = {
values: {},
positionals: [],
};
for (const token of result.tokens) {
if (token.kind === 'positional') {
p.positionals.push(token.value);
if (this.#options.stopAtPositional ||
this.#options.stopAtPositionalTest?.(token.value)) {
p.positionals.push(...args.slice(token.index + 1));
break;
}
}
else if (token.kind === 'option') {
let value = undefined;
if (token.name.startsWith('no-')) {
const my = this.#configSet[token.name];
const pname = token.name.substring('no-'.length);
const pos = this.#configSet[pname];
if (pos &&
pos.type === 'boolean' &&
(!my ||
(my.type === 'boolean' && !!my.multiple === !!pos.multiple))) {
value = false;
token.name = pname;
}
}
const my = this.#configSet[token.name];
if (!my) {
throw new Error(`Unknown option '${token.rawName}'. ` +
`To specify a positional argument starting with a '-', ` +
`place it at the end of the command after '--', as in ` +
`'-- ${token.rawName}'`, {
cause: {
code: 'JACKSPEAK',
found: token.rawName + (token.value ? `=${token.value}` : ''),
},
});
}
if (value === undefined) {
if (token.value === undefined) {
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),
},
});
}
value = true;
}
else {
if (my.type === 'boolean') {
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;
}
else {
value = +token.value;
if (value !== value) {
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',
},
});
}
}
}
}
if (my.multiple) {
const pv = p.values;
const tn = pv[token.name] ?? [];
pv[token.name] = tn;
tn.push(value);
}
else {
const pv = p.values;
pv[token.name] = value;
}
}
}
for (const [field, value] of Object.entries(p.values)) {
const valid = this.#configSet[field]?.validate;
const validOptions = this.#configSet[field]?.validOptions;
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: { ...cause, code: 'JACKSPEAK' } });
}
}
return p;
}
/**
* do not set fields as 'no-foo' if 'foo' exists and both are bools
* just set foo.
*/
#noNoFields(f, val, s = f) {
if (!f.startsWith('no-') || typeof val !== 'boolean')
return;
const yes = f.substring('no-'.length);
// 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: { code: 'JACKSPEAK', found: s, wanted: yes } });
}
}
/**
* Validate that any arbitrary object is a valid configuration `values`
* object. Useful when loading config files or other sources.
*/
validate(o) {
if (!o || typeof o !== 'object') {
throw new Error('Invalid config: not an object', {
cause: { code: 'JACKSPEAK', found: o },
});
}
const opts = o;
for (const field in o) {
const value = opts[field];
/* c8 ignore next - for TS */
if (value === undefined)
continue;
this.#noNoFields(field, value);
const config = this.#configSet[field];
if (!config) {
throw new Error(`Unknown config option: ${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),
},
});
}
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, code: 'JACKSPEAK' },
});
}
}
}
writeEnv(p) {
if (!this.#env || !this.#envPrefix)
return;
for (const [field, value] of Object.entries(p.values)) {
const my = this.#configSet[field];
this.#env[toEnvKey(this.#envPrefix, field)] = toEnvVal(value, my?.delim);
}
}
/**
* Add a heading to the usage output banner
*/
heading(text, level, { pre = false } = {}) {
if (level === undefined) {
level = this.#fields.some(r => isHeading(r)) ? 2 : 1;
}
this.#fields.push({ type: 'heading', text, level, pre });
return this;
}
/**
* Add a long-form description to the usage output at this position.
*/
description(text, { pre } = {}) {
this.#fields.push({ type: 'description', text, pre });
return this;
}
/**
* Add one or more number fields.
*/
num(fields) {
return this.#addFieldsWith(fields, 'number', false);
}
/**
* Add one or more multiple number fields.
*/
numList(fields) {
return this.#addFieldsWith(fields, 'number', true);
}
/**
* Add one or more string option fields.
*/
opt(fields) {
return this.#addFieldsWith(fields, 'string', false);
}
/**
* Add one or more multiple string option fields.
*/
optList(fields) {
return this.#addFieldsWith(fields, 'string', true);
}
/**
* Add one or more flag fields.
*/
flag(fields) {
return this.#addFieldsWith(fields, 'boolean', false);
}
/**
* Add one or more multiple flag fields.
*/
flagList(fields) {
return this.#addFieldsWith(fields, 'boolean', true);
}
/**
* Generic field definition method. Similar to flag/flagList/number/etc,
* but you must specify the `type` (and optionally `multiple` and `delim`)
* fields on each one, or Jack won't know how to define them.
*/
addFields(fields) {
return this.#addFields(this, fields);
}
#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 { 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;
}
#validateName(name, field) {
if (!/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(name)) {
throw new TypeError(`Invalid option name: ${name}, ` +
`must be '-' delimited ASCII alphanumeric`);
}
if (this.#configSet[name]) {
throw new TypeError(`Cannot redefine option ${field}`);
}
if (this.#shorts[name]) {
throw new TypeError(`Cannot redefine option ${name}, already ` +
`in use for ${this.#shorts[name]}`);
}
if (field.short) {
if (!/^[a-zA-Z0-9]$/.test(field.short)) {
throw new TypeError(`Invalid ${name} short option: ${field.short}, ` +
'must be 1 ASCII alphanumeric character');
}
if (this.#shorts[field.short]) {
throw new TypeError(`Invalid ${name} short option: ${field.short}, ` +
`already in use for ${this.#shorts[field.short]}`);
}
this.#shorts[field.short] = name;
this.#shorts[name] = name;
}
}
/**
* Return the usage banner for the given configuration
*/
usage() {
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;
if (first?.type === 'heading') {
ui.div({
padding: [0, 0, 0, 0],
text: normalize(first.text),
});
}
ui.div({ padding: [0, 0, 0, 0], text: 'Usage:' });
if (this.#options.usage) {
ui.div({
text: this.#options.usage,
padding: [0, 0, 0, 2],
});
}
else {
const cmd = basename(String(process.argv[1]));
const shortFlags = [];
const shorts = [];
const flags = [];
const opts = [];
for (const [field, config] of Object.entries(this.#configSet)) {
if (config.short) {
if (config.type === 'boolean')
shortFlags.push(config.short);
else
shorts.push([config.short, config.hint || field]);
}
else {
if (config.type === 'boolean')
flags.push(field);
else
opts.push([field, config.hint || field]);
}
}
const sf = shortFlags.length ? ' -' + shortFlags.join('') : '';
const so = shorts.map(([k, v]) => ` --${k}=<${v}>`).join('');
const lf = flags.map(k => ` --${k}`).join('');
const lo = opts.map(([k, v]) => ` --${k}=<${v}>`).join('');
const usage = `${cmd}${sf}${so}${lf}${lo}`.trim();
ui.div({
text: usage,
padding: [0, 0, 0, 2],
});
}
ui.div({ padding: [0, 0, 0, 0], text: '' });
const maybeDesc = this.#fields[start];
if (maybeDesc && isDescription(maybeDesc)) {
const print = normalize(maybeDesc.text, maybeDesc.pre);
start++;
ui.div({ padding: [0, 0, 0, 0], text: print });
ui.div({ padding: [0, 0, 0, 0], text: '' });
}
const { rows, maxWidth } = this.#usageRows(start);
// every heading/description after the first gets indented by 2
// extra spaces.
for (const row of rows) {
if (row.left) {
// If the row is too long, don't wrap it
// Bump the right-hand side down a line to make room
const configIndent = indent(Math.max(headingLevel, 2));
if (row.left.length > maxWidth - 3) {
ui.div({ text: row.left, padding: [0, 0, 0, configIndent] });
ui.div({ text: row.text, padding: [0, 0, 0, maxWidth] });
}
else {
ui.div({
text: row.left,
padding: [0, 1, 0, configIndent],
width: maxWidth,
}, { padding: [0, 0, 0, 0], text: row.text });
}
if (row.skipLine) {
ui.div({ padding: [0, 0, 0, 0], text: '' });
}
}
else {
if (isHeading(row)) {
const { level } = row;
headingLevel = level;
// only h1 and h2 have bottom padding
// h3-h6 do not
const b = level <= 2 ? 1 : 0;
ui.div({ ...row, padding: [0, 0, b, indent(level)] });
}
else {
ui.div({ ...row, padding: [0, 0, 1, indent(headingLevel + 1)] });
}
}
}
return (this.#usage = ui.toString());
}
/**
* Return the usage banner markdown for the given configuration
*/
usageMarkdown() {
if (this.#usageMarkdown)
return this.#usageMarkdown;
const out = [];
let headingLevel = 1;
const first = this.#fields[0];
let start = first?.type === 'heading' ? 1 : 0;
if (first?.type === 'heading') {
out.push(`# ${normalizeOneLine(first.text)}`);
}
out.push('Usage:');
if (this.#options.usage) {
out.push(normalizeMarkdown(this.#options.usage, true));
}
else {
const cmd = basename(String(process.argv[1]));
const shortFlags = [];
const shorts = [];
const flags = [];
const opts = [];
for (const [field, config] of Object.entries(this.#configSet)) {
if (config.short) {
if (config.type === 'boolean')
shortFlags.push(config.short);
else
shorts.push([config.short, config.hint || field]);
}
else {
if (config.type === 'boolean')
flags.push(field);
else
opts.push([field, config.hint || field]);
}
}
const sf = shortFlags.length ? ' -' + shortFlags.join('') : '';
const so = shorts.map(([k, v]) => ` --${k}=<${v}>`).join('');
const lf = flags.map(k => ` --${k}`).join('');
const lo = opts.map(([k, v]) => ` --${k}=<${v}>`).join('');
const usage = `${cmd}${sf}${so}${lf}${lo}`.trim();
out.push(normalizeMarkdown(usage, true));
}
const maybeDesc = this.#fields[start];
if (maybeDesc && isDescription(maybeDesc)) {
out.push(normalizeMarkdown(maybeDesc.text, maybeDesc.pre));
start++;
}
const { rows } = this.#usageRows(start);
// heading level in markdown is number of # ahead of text
for (const row of rows) {
if (row.left) {
out.push('#'.repeat(headingLevel + 1) +
' ' +
normalizeOneLine(row.left, true));
if (row.text)
out.push(normalizeMarkdown(row.text));
}
else if (isHeading(row)) {
const { level } = row;
headingLevel = level;
out.push(`${'#'.repeat(headingLevel)} ${normalizeOneLine(row.text, row.pre)}`);
}
else {
out.push(normalizeMarkdown(row.text, !!row.pre));
}
}
return (this.#usageMarkdown = out.join('\n\n') + '\n');
}
#usageRows(start) {
// turn each config type into a row, and figure out the width of the
// left hand indentation for the option descriptions.
let maxMax = Math.max(12, Math.min(26, Math.floor(width / 3)));
let maxWidth = 8;
let prev = undefined;
const rows = [];
for (const field of this.#fields.slice(start)) {
if (field.type !== 'config') {
if (prev?.type === 'config')
prev.skipLine = true;
prev = undefined;
field.text = normalize(field.text, !!field.pre);
rows.push(field);
continue;
}
const { value } = field;
const desc = value.description || '';
const mult = value.multiple ? 'Can be set multiple times' : '';
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 ||
(value.type === 'number' ? 'n'
: value.type === 'string' ? field.name
: undefined);
const short = !value.short ? ''
: value.type === 'boolean' ? `-${value.short} `
: `-${value.short}<${hint}> `;
const left = value.type === 'boolean' ?
`${short}--${field.name}`
: `${short}--${field.name}=<${hint}>`;
const row = { text, left, type: 'config' };
if (text.length > width - maxMax) {
row.skipLine = true;
}
if (prev && left.length > maxMax)
prev.skipLine = true;
prev = row;
const len = left.length + 4;
if (len > maxWidth && len < maxMax) {
maxWidth = len;
}
rows.push(row);
}
return { rows, maxWidth };
}
/**
* Return the configuration options as a plain object
*/
toJSON() {
return Object.fromEntries(Object.entries(this.#configSet).map(([field, def]) => [
field,
{
type: def.type,
...(def.multiple ? { multiple: true } : {}),
...(def.delim ? { delim: def.delim } : {}),
...(def.short ? { short: def.short } : {}),
...(def.description ?
{ description: normalize(def.description) }
: {}),
...(def.validate ? { validate: def.validate } : {}),
...(def.validOptions ? { validOptions: def.validOptions } : {}),
...(def.default !== undefined ? { default: def.default } : {}),
...(def.hint ? { hint: def.hint } : {}),
},
]));
}
/**
* Custom printer for `util.inspect`
*/
[inspect.custom](_, options) {
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) => {
if (pre)
// prepend a ZWSP to each line so cliui doesn't strip it.
return s
.split('\n')
.map(l => `\u200b${l}`)
.join('\n');
return s
.split(/^\s*```\s*$/gm)
.map((s, i) => {
if (i % 2 === 1) {
if (!s.trim()) {
return `\`\`\`\n\`\`\`\n`;
}
// outdent the ``` blocks, but preserve whitespace otherwise.
const split = s.split('\n');
// throw out the \n at the start and end
split.pop();
split.shift();
const si = split.reduce((shortest, l) => {
/* c8 ignore next */
const ind = l.match(/^\s*/)?.[0] ?? '';
if (ind.length)
return Math.min(ind.length, shortest);
else
return shortest;
}, Infinity);
/* c8 ignore next */
const i = isFinite(si) ? si : 0;
return ('\n```\n' +
split.map(s => `\u200b${s.substring(i)}`).join('\n') +
'\n```\n');
}
return (s
// remove single line breaks, except for lists
.replace(/([^\n])\n[ \t]*([^\n])/g, (_, $1, $2) => !/^[-*]/.test($2) ? `${$1} ${$2}` : `${$1}\n${$2}`)
// normalize mid-line whitespace
.replace(/([^\n])[ \t]+([^\n])/g, '$1 $2')
// two line breaks are enough
.replace(/\n{3,}/g, '\n\n')
// remove any spaces at the start of a line
.replace(/\n[ \t]+/g, '\n')
.trim());
})
.join('\n');
};
// normalize for markdown printing, remove leading spaces on lines
const normalizeMarkdown = (s, pre = false) => {
const n = normalize(s, pre).replace(/\\/g, '\\\\');
return pre ?
`\`\`\`\n${n.replace(/\u200b/g, '')}\n\`\`\``
: n.replace(/\n +/g, '\n').trim();
};
const normalizeOneLine = (s, pre = false) => {
const n = normalize(s, pre)
.replace(/[\s\u200b]+/g, ' ')
.trim();
return pre ? `\`${n}\`` : n;
};
//# sourceMappingURL=index.js.map

1
node_modules/jackspeak/dist/esm/index.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

3
node_modules/jackspeak/dist/esm/package.json generated vendored Normal file
View File

@ -0,0 +1,3 @@
{
"type": "module"
}

94
node_modules/jackspeak/package.json generated vendored Normal file
View File

@ -0,0 +1,94 @@
{
"name": "jackspeak",
"version": "4.1.0",
"description": "A very strict and proper argument parser.",
"tshy": {
"main": true,
"exports": {
"./package.json": "./package.json",
".": "./src/index.js"
}
},
"main": "./dist/commonjs/index.js",
"types": "./dist/commonjs/index.d.ts",
"type": "module",
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"types": "./dist/commonjs/index.d.ts",
"default": "./dist/commonjs/index.js"
}
}
},
"files": [
"dist"
],
"scripts": {
"build-examples": "for i in examples/*.js ; do node $i -h > ${i/.js/.txt}; done",
"preversion": "npm test",
"postversion": "npm publish",
"prepublishOnly": "git push origin --follow-tags",
"prepare": "tshy",
"pretest": "npm run prepare",
"presnap": "npm run prepare",
"test": "tap",
"snap": "tap",
"format": "prettier --write . --log-level warn",
"typedoc": "typedoc --tsconfig .tshy/esm.json ./src/*.ts"
},
"license": "BlueOak-1.0.0",
"prettier": {
"experimentalTernaries": true,
"semi": false,
"printWidth": 75,
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"jsxSingleQuote": false,
"bracketSameLine": true,
"arrowParens": "avoid",
"endOfLine": "lf"
},
"devDependencies": {
"@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"
},
"repository": {
"type": "git",
"url": "git+https://github.com/isaacs/jackspeak.git"
},
"keywords": [
"argument",
"parser",
"args",
"option",
"flag",
"cli",
"command",
"line",
"parse",
"parsing"
],
"author": "Isaac Z. Schlueter <i@izs.me>",
"tap": {
"typecheck": true
},
"module": "./dist/esm/index.js"
}