@naturalcycles/nodejs-lib 15.114.0 → 15.115.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/parseArgs.d.ts +30 -7
- package/dist/cli/parseArgs.js +15 -5
- package/package.json +2 -2
- package/src/cli/parseArgs.ts +42 -15
package/dist/cli/parseArgs.d.ts
CHANGED
|
@@ -7,7 +7,10 @@ export type CliOptionType = 'string' | 'number' | 'boolean';
|
|
|
7
7
|
* yargs `.options()` that we actually use.
|
|
8
8
|
*/
|
|
9
9
|
export interface CliOption {
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Value type. Defaults to `'string'` when omitted.
|
|
12
|
+
*/
|
|
13
|
+
type?: CliOptionType;
|
|
11
14
|
/**
|
|
12
15
|
* Accept the flag multiple times, collecting values into an array.
|
|
13
16
|
* `--id a --id b` => `['a', 'b']`. Replaces yargs `type: 'array'`.
|
|
@@ -36,15 +39,34 @@ export interface CliOption {
|
|
|
36
39
|
* Single-character alias, e.g. `short: 'v'` enables `-v`.
|
|
37
40
|
*/
|
|
38
41
|
short?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Transform the raw string value into the final value. The output type is
|
|
44
|
+
* inferred from the function's return type, so this is the way to produce
|
|
45
|
+
* branded types (e.g. `IsoDate`) or richer values (parsed numbers, JSON, ...).
|
|
46
|
+
*
|
|
47
|
+
* Applied per-element for `array` options. Receives the raw string token, so
|
|
48
|
+
* it fully owns conversion - built-in `number` coercion is not applied on top.
|
|
49
|
+
* NOT applied to `default` values: a `default` is taken to be in final form.
|
|
50
|
+
*
|
|
51
|
+
* @example transform: s => s as IsoDate
|
|
52
|
+
*/
|
|
53
|
+
transform?: (value: string) => unknown;
|
|
39
54
|
}
|
|
40
55
|
export type CliOptions = Record<string, CliOption>;
|
|
41
56
|
/**
|
|
42
|
-
* Element value type of a single option, derived from `
|
|
43
|
-
*
|
|
57
|
+
* Element value type of a single option, derived from `transform` (if present,
|
|
58
|
+
* its return type wins), then `choices`, then `type`. Defaults to `string` when
|
|
59
|
+
* none narrow it (so `type` is optional).
|
|
44
60
|
*/
|
|
45
61
|
type ElemType<O extends CliOption> = O extends {
|
|
62
|
+
transform: (...args: any[]) => infer R;
|
|
63
|
+
} ? R : O extends {
|
|
46
64
|
choices: readonly (infer C)[];
|
|
47
|
-
} ? C : O
|
|
65
|
+
} ? C : O extends {
|
|
66
|
+
type: 'number';
|
|
67
|
+
} ? number : O extends {
|
|
68
|
+
type: 'boolean';
|
|
69
|
+
} ? boolean : string;
|
|
48
70
|
/**
|
|
49
71
|
* Full value type of a single option, applying `array` on top of the element
|
|
50
72
|
* type.
|
|
@@ -116,11 +138,12 @@ export declare class ParseArgsError extends Error {
|
|
|
116
138
|
* top of node's `util.parseArgs`.
|
|
117
139
|
*
|
|
118
140
|
* @example
|
|
119
|
-
* const { dir, limit } = _parseArgs({
|
|
120
|
-
* dir: {
|
|
141
|
+
* const { dir, limit, date } = _parseArgs({
|
|
142
|
+
* dir: { desc: 'Output directory' }, // type defaults to 'string'
|
|
121
143
|
* limit: { type: 'number', default: 100 },
|
|
144
|
+
* date: { transform: s => s as IsoDate }, // inferred + converted via transform
|
|
122
145
|
* })
|
|
123
|
-
* // dir?: string limit: number _: string[]
|
|
146
|
+
* // dir?: string limit: number date?: IsoDate _: string[]
|
|
124
147
|
*/
|
|
125
148
|
export declare function _parseArgs<const O extends CliOptions>(options: O, opt?: ParseArgsOptions): InferCliArgs<O>;
|
|
126
149
|
export {};
|
package/dist/cli/parseArgs.js
CHANGED
|
@@ -11,11 +11,12 @@ export class ParseArgsError extends Error {
|
|
|
11
11
|
* top of node's `util.parseArgs`.
|
|
12
12
|
*
|
|
13
13
|
* @example
|
|
14
|
-
* const { dir, limit } = _parseArgs({
|
|
15
|
-
* dir: {
|
|
14
|
+
* const { dir, limit, date } = _parseArgs({
|
|
15
|
+
* dir: { desc: 'Output directory' }, // type defaults to 'string'
|
|
16
16
|
* limit: { type: 'number', default: 100 },
|
|
17
|
+
* date: { transform: s => s as IsoDate }, // inferred + converted via transform
|
|
17
18
|
* })
|
|
18
|
-
* // dir?: string limit: number _: string[]
|
|
19
|
+
* // dir?: string limit: number date?: IsoDate _: string[]
|
|
19
20
|
*/
|
|
20
21
|
export function _parseArgs(options, opt = {}) {
|
|
21
22
|
const { args, minPositionals = 0, usage, strict = false } = opt;
|
|
@@ -59,6 +60,9 @@ export function _parseArgs(options, opt = {}) {
|
|
|
59
60
|
const result = { _: parsed.positionals };
|
|
60
61
|
for (const [name, def] of Object.entries(options)) {
|
|
61
62
|
let v = values[name];
|
|
63
|
+
// `transform` is only applied to arg-sourced values; a `default` is taken
|
|
64
|
+
// to be in final form (see CliOption.transform docs).
|
|
65
|
+
const fromArgs = v !== undefined;
|
|
62
66
|
if (v === undefined) {
|
|
63
67
|
if (def.default !== undefined) {
|
|
64
68
|
v = def.default;
|
|
@@ -74,7 +78,8 @@ export function _parseArgs(options, opt = {}) {
|
|
|
74
78
|
if (def.array) {
|
|
75
79
|
v = Array.isArray(v) ? v : [v];
|
|
76
80
|
}
|
|
77
|
-
|
|
81
|
+
// `transform` owns conversion, so built-in number coercion is skipped for it
|
|
82
|
+
if (def.type === 'number' && !def.transform) {
|
|
78
83
|
v = Array.isArray(v) ? v.map(x => toNumber(x, name)) : toNumber(v, name);
|
|
79
84
|
}
|
|
80
85
|
if (def.choices) {
|
|
@@ -85,6 +90,10 @@ export function _parseArgs(options, opt = {}) {
|
|
|
85
90
|
}
|
|
86
91
|
}
|
|
87
92
|
}
|
|
93
|
+
if (def.transform && fromArgs) {
|
|
94
|
+
const { transform } = def;
|
|
95
|
+
v = Array.isArray(v) ? v.map(x => transform(x)) : transform(v);
|
|
96
|
+
}
|
|
88
97
|
result[name] = v;
|
|
89
98
|
}
|
|
90
99
|
if (parsed.positionals.length < minPositionals) {
|
|
@@ -106,7 +115,8 @@ function buildHelp(options, usage) {
|
|
|
106
115
|
lines.push('Options:');
|
|
107
116
|
for (const [name, def] of Object.entries(options)) {
|
|
108
117
|
const flag = def.short ? `-${def.short}, --${name}` : `--${name}`;
|
|
109
|
-
const
|
|
118
|
+
const type = def.type ?? 'string';
|
|
119
|
+
const meta = [`[${def.array ? `${type}[]` : type}]`];
|
|
110
120
|
if (def.demandOption)
|
|
111
121
|
meta.push('[required]');
|
|
112
122
|
if (def.default !== undefined)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturalcycles/nodejs-lib",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "15.
|
|
4
|
+
"version": "15.115.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@naturalcycles/js-lib": "^15",
|
|
7
7
|
"@standard-schema/spec": "^1",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"typescript": "rc",
|
|
19
|
-
"@naturalcycles/dev-lib": "
|
|
19
|
+
"@naturalcycles/dev-lib": "18.4.2"
|
|
20
20
|
},
|
|
21
21
|
"exports": {
|
|
22
22
|
".": "./dist/index.js",
|
package/src/cli/parseArgs.ts
CHANGED
|
@@ -10,7 +10,10 @@ export type CliOptionType = 'string' | 'number' | 'boolean'
|
|
|
10
10
|
* yargs `.options()` that we actually use.
|
|
11
11
|
*/
|
|
12
12
|
export interface CliOption {
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Value type. Defaults to `'string'` when omitted.
|
|
15
|
+
*/
|
|
16
|
+
type?: CliOptionType
|
|
14
17
|
/**
|
|
15
18
|
* Accept the flag multiple times, collecting values into an array.
|
|
16
19
|
* `--id a --id b` => `['a', 'b']`. Replaces yargs `type: 'array'`.
|
|
@@ -39,23 +42,36 @@ export interface CliOption {
|
|
|
39
42
|
* Single-character alias, e.g. `short: 'v'` enables `-v`.
|
|
40
43
|
*/
|
|
41
44
|
short?: string
|
|
45
|
+
/**
|
|
46
|
+
* Transform the raw string value into the final value. The output type is
|
|
47
|
+
* inferred from the function's return type, so this is the way to produce
|
|
48
|
+
* branded types (e.g. `IsoDate`) or richer values (parsed numbers, JSON, ...).
|
|
49
|
+
*
|
|
50
|
+
* Applied per-element for `array` options. Receives the raw string token, so
|
|
51
|
+
* it fully owns conversion - built-in `number` coercion is not applied on top.
|
|
52
|
+
* NOT applied to `default` values: a `default` is taken to be in final form.
|
|
53
|
+
*
|
|
54
|
+
* @example transform: s => s as IsoDate
|
|
55
|
+
*/
|
|
56
|
+
transform?: (value: string) => unknown
|
|
42
57
|
}
|
|
43
58
|
|
|
44
59
|
export type CliOptions = Record<string, CliOption>
|
|
45
60
|
|
|
46
61
|
/**
|
|
47
|
-
* Element value type of a single option, derived from `
|
|
48
|
-
*
|
|
62
|
+
* Element value type of a single option, derived from `transform` (if present,
|
|
63
|
+
* its return type wins), then `choices`, then `type`. Defaults to `string` when
|
|
64
|
+
* none narrow it (so `type` is optional).
|
|
49
65
|
*/
|
|
50
|
-
type ElemType<O extends CliOption> = O extends {
|
|
51
|
-
?
|
|
52
|
-
: O[
|
|
53
|
-
?
|
|
54
|
-
: O
|
|
66
|
+
type ElemType<O extends CliOption> = O extends { transform: (...args: any[]) => infer R }
|
|
67
|
+
? R
|
|
68
|
+
: O extends { choices: readonly (infer C)[] }
|
|
69
|
+
? C
|
|
70
|
+
: O extends { type: 'number' }
|
|
55
71
|
? number
|
|
56
|
-
: O
|
|
72
|
+
: O extends { type: 'boolean' }
|
|
57
73
|
? boolean
|
|
58
|
-
:
|
|
74
|
+
: string
|
|
59
75
|
|
|
60
76
|
/**
|
|
61
77
|
* Full value type of a single option, applying `array` on top of the element
|
|
@@ -132,11 +148,12 @@ export class ParseArgsError extends Error {
|
|
|
132
148
|
* top of node's `util.parseArgs`.
|
|
133
149
|
*
|
|
134
150
|
* @example
|
|
135
|
-
* const { dir, limit } = _parseArgs({
|
|
136
|
-
* dir: {
|
|
151
|
+
* const { dir, limit, date } = _parseArgs({
|
|
152
|
+
* dir: { desc: 'Output directory' }, // type defaults to 'string'
|
|
137
153
|
* limit: { type: 'number', default: 100 },
|
|
154
|
+
* date: { transform: s => s as IsoDate }, // inferred + converted via transform
|
|
138
155
|
* })
|
|
139
|
-
* // dir?: string limit: number _: string[]
|
|
156
|
+
* // dir?: string limit: number date?: IsoDate _: string[]
|
|
140
157
|
*/
|
|
141
158
|
export function _parseArgs<const O extends CliOptions>(
|
|
142
159
|
options: O,
|
|
@@ -189,6 +206,9 @@ export function _parseArgs<const O extends CliOptions>(
|
|
|
189
206
|
|
|
190
207
|
for (const [name, def] of Object.entries(options)) {
|
|
191
208
|
let v = values[name]
|
|
209
|
+
// `transform` is only applied to arg-sourced values; a `default` is taken
|
|
210
|
+
// to be in final form (see CliOption.transform docs).
|
|
211
|
+
const fromArgs = v !== undefined
|
|
192
212
|
|
|
193
213
|
if (v === undefined) {
|
|
194
214
|
if (def.default !== undefined) {
|
|
@@ -205,7 +225,8 @@ export function _parseArgs<const O extends CliOptions>(
|
|
|
205
225
|
v = Array.isArray(v) ? v : [v]
|
|
206
226
|
}
|
|
207
227
|
|
|
208
|
-
|
|
228
|
+
// `transform` owns conversion, so built-in number coercion is skipped for it
|
|
229
|
+
if (def.type === 'number' && !def.transform) {
|
|
209
230
|
v = Array.isArray(v) ? v.map(x => toNumber(x, name)) : toNumber(v, name)
|
|
210
231
|
}
|
|
211
232
|
|
|
@@ -220,6 +241,11 @@ export function _parseArgs<const O extends CliOptions>(
|
|
|
220
241
|
}
|
|
221
242
|
}
|
|
222
243
|
|
|
244
|
+
if (def.transform && fromArgs) {
|
|
245
|
+
const { transform } = def
|
|
246
|
+
v = Array.isArray(v) ? v.map(x => transform(x as string)) : transform(v as string)
|
|
247
|
+
}
|
|
248
|
+
|
|
223
249
|
result[name] = v
|
|
224
250
|
}
|
|
225
251
|
|
|
@@ -246,7 +272,8 @@ function buildHelp(options: CliOptions, usage?: string): string {
|
|
|
246
272
|
lines.push('Options:')
|
|
247
273
|
for (const [name, def] of Object.entries(options)) {
|
|
248
274
|
const flag = def.short ? `-${def.short}, --${name}` : `--${name}`
|
|
249
|
-
const
|
|
275
|
+
const type = def.type ?? 'string'
|
|
276
|
+
const meta = [`[${def.array ? `${type}[]` : type}]`]
|
|
250
277
|
if (def.demandOption) meta.push('[required]')
|
|
251
278
|
if (def.default !== undefined) meta.push(`[default: ${JSON.stringify(def.default)}]`)
|
|
252
279
|
if (def.choices) meta.push(`[choices: ${def.choices.join(', ')}]`)
|