zod-args-parser 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +215 -0
- package/lib/commonjs/help.js +1 -0
- package/lib/commonjs/help.js.map +1 -0
- package/lib/commonjs/index.js +1 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/parser.js +1 -0
- package/lib/commonjs/parser.js.map +1 -0
- package/lib/commonjs/types.js +1 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/utils.js +1 -0
- package/lib/commonjs/utils.js.map +1 -0
- package/lib/module/help.js +1 -0
- package/lib/module/help.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/parser.js +1 -0
- package/lib/module/parser.js.map +1 -0
- package/lib/module/types.js +1 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils.js +1 -0
- package/lib/module/utils.js.map +1 -0
- package/lib/typescript/help.d.ts +9 -0
- package/lib/typescript/help.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +8 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/parser.d.ts +6 -0
- package/lib/typescript/parser.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +256 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/lib/typescript/utils.d.ts +38 -0
- package/lib/typescript/utils.d.ts.map +1 -0
- package/package.json +50 -0
- package/src/help.ts +340 -0
- package/src/index.ts +22 -0
- package/src/parser.ts +299 -0
- package/src/types.ts +286 -0
- package/src/utils.ts +154 -0
package/src/help.ts
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { concat, getDefaultValueFromSchema, indent, ln, print, println, transformOptionToArg } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
import type { Argument, Cli, Option, PrintHelpOpt, Subcommand } from "./types.js";
|
|
5
|
+
|
|
6
|
+
/** Colors */
|
|
7
|
+
const colors: NonNullable<Required<PrintHelpOpt["customColors"]>> = {
|
|
8
|
+
title: chalk.bold.blue,
|
|
9
|
+
description: chalk.white,
|
|
10
|
+
default: chalk.dim.italic,
|
|
11
|
+
optional: chalk.dim.italic,
|
|
12
|
+
exampleTitle: chalk.yellow,
|
|
13
|
+
example: chalk.dim.italic,
|
|
14
|
+
command: chalk.yellow,
|
|
15
|
+
option: chalk.cyan,
|
|
16
|
+
argument: chalk.green,
|
|
17
|
+
placeholder: chalk.hex("#FF9800"),
|
|
18
|
+
punctuation: chalk.white.dim,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type PreparedToPrint = {
|
|
22
|
+
names: string;
|
|
23
|
+
description: string;
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
example?: string;
|
|
26
|
+
default?: string;
|
|
27
|
+
optional?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function printCliHelp(params: [Cli, ...Subcommand[]], printOptions: PrintHelpOpt = {}) {
|
|
31
|
+
printOptions.colors ??= true;
|
|
32
|
+
|
|
33
|
+
const noColors = new Proxy(colors, {
|
|
34
|
+
get: () => {
|
|
35
|
+
return (...str: string[]) => str.join(" ");
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const c = printOptions.colors ? colors : noColors;
|
|
40
|
+
|
|
41
|
+
if (printOptions.customColors) {
|
|
42
|
+
Object.assign(c, printOptions.customColors);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const isFirstParamCli = "cliName" in params[0];
|
|
46
|
+
const cliOptions = (isFirstParamCli ? params.shift() : {}) as Cli;
|
|
47
|
+
const subcommands = params as Subcommand[];
|
|
48
|
+
|
|
49
|
+
/** Print a styled title */
|
|
50
|
+
const printTitle = (title: string) => {
|
|
51
|
+
print(c.title(` ${title.toUpperCase()} `));
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Print CLI usage
|
|
55
|
+
const cliName = cliOptions.cliName ?? "";
|
|
56
|
+
const usage =
|
|
57
|
+
cliOptions.usage ??
|
|
58
|
+
concat(
|
|
59
|
+
c.punctuation("$"),
|
|
60
|
+
cliName,
|
|
61
|
+
subcommands.length ? c.command("[command]") : "",
|
|
62
|
+
cliOptions.options?.length ? c.option("[options]") : "",
|
|
63
|
+
cliOptions.arguments?.length || cliOptions.allowPositional ? c.argument("<arguments>") : "",
|
|
64
|
+
);
|
|
65
|
+
printTitle("Usage");
|
|
66
|
+
println();
|
|
67
|
+
println(indent(2), usage, ln(1));
|
|
68
|
+
|
|
69
|
+
// Print CLI description
|
|
70
|
+
if (cliOptions.description) {
|
|
71
|
+
printTitle("Description");
|
|
72
|
+
println();
|
|
73
|
+
println(indent(2), c.description(cliOptions.description), ln(1));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let longest = 0;
|
|
77
|
+
|
|
78
|
+
// Prepare CLI options
|
|
79
|
+
const [optionsToPrint, longestOptionIndent] = prepareOptionsToPrint(cliOptions.options);
|
|
80
|
+
if (longestOptionIndent > longest) longest = longestOptionIndent;
|
|
81
|
+
|
|
82
|
+
// Prepare CLI commands
|
|
83
|
+
const [commandsToPrint, longestSubcommandIndent] = prepareCommandsToPrint(subcommands);
|
|
84
|
+
if (longestSubcommandIndent > longest) longest = longestSubcommandIndent;
|
|
85
|
+
|
|
86
|
+
// Prepare CLI arguments
|
|
87
|
+
const [argsToPrint, longestArgIndent] = prepareArgumentsToPrint(cliOptions.arguments);
|
|
88
|
+
if (longestArgIndent > longest) longest = longestArgIndent;
|
|
89
|
+
|
|
90
|
+
// Print CLI options
|
|
91
|
+
printPreparedOptions(optionsToPrint, c, longest);
|
|
92
|
+
|
|
93
|
+
// Print CLI commands
|
|
94
|
+
printPreparedCommands(commandsToPrint, c, longest);
|
|
95
|
+
|
|
96
|
+
// Print CLI arguments
|
|
97
|
+
printPreparedArguments(argsToPrint, c, longest);
|
|
98
|
+
|
|
99
|
+
// Print CLI example
|
|
100
|
+
if (cliOptions.example) {
|
|
101
|
+
printTitle("Example");
|
|
102
|
+
println();
|
|
103
|
+
const normalizeExample = cliOptions.example.replace(/\n/g, "\n" + indent(3));
|
|
104
|
+
println(indent(2), c.example(normalizeExample), ln(1));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function printSubcommandHelp(subcommand: Subcommand, printOptions: PrintHelpOpt = {}, cliName = "") {
|
|
109
|
+
printOptions.colors ??= true;
|
|
110
|
+
|
|
111
|
+
const noColors = new Proxy(colors, {
|
|
112
|
+
get: () => {
|
|
113
|
+
return (...str: string[]) => str.join(" ");
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const c = printOptions.colors ? colors : noColors;
|
|
118
|
+
|
|
119
|
+
if (printOptions.customColors) {
|
|
120
|
+
Object.assign(c, printOptions.customColors);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Print a styled title */
|
|
124
|
+
const printTitle = (title: string) => {
|
|
125
|
+
print(c.title(` ${title.toUpperCase()} `));
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Print command usage
|
|
129
|
+
const usage = concat(
|
|
130
|
+
c.punctuation("$"),
|
|
131
|
+
cliName,
|
|
132
|
+
c.command(subcommand.name),
|
|
133
|
+
subcommand.options?.length ? c.option("[options]") : "",
|
|
134
|
+
subcommand.arguments?.length || subcommand.allowPositional ? c.argument("<arguments>") : "",
|
|
135
|
+
);
|
|
136
|
+
printTitle("Usage");
|
|
137
|
+
println();
|
|
138
|
+
println(indent(2), usage, ln(1));
|
|
139
|
+
|
|
140
|
+
// Print command description
|
|
141
|
+
if (subcommand.description) {
|
|
142
|
+
printTitle("Description");
|
|
143
|
+
println();
|
|
144
|
+
const normalizeDesc = subcommand.description.replace(/\n/g, "\n" + indent(3));
|
|
145
|
+
println(indent(2), c.description(normalizeDesc), ln(1));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let longest = 0;
|
|
149
|
+
|
|
150
|
+
// Prepare command options
|
|
151
|
+
const [optionsToPrint, longestOptionIndent] = prepareOptionsToPrint(subcommand.options);
|
|
152
|
+
if (longestOptionIndent > longest) longest = longestOptionIndent;
|
|
153
|
+
|
|
154
|
+
// Prepare command arguments
|
|
155
|
+
const [argsToPrint, longestArgIndent] = prepareArgumentsToPrint(subcommand.arguments);
|
|
156
|
+
if (longestArgIndent > longest) longest = longestArgIndent;
|
|
157
|
+
|
|
158
|
+
// Print command options
|
|
159
|
+
printPreparedOptions(optionsToPrint, c, longest);
|
|
160
|
+
|
|
161
|
+
// Print command arguments
|
|
162
|
+
printPreparedArguments(argsToPrint, c, longest);
|
|
163
|
+
|
|
164
|
+
// Print command example
|
|
165
|
+
if (subcommand.example) {
|
|
166
|
+
printTitle("Example");
|
|
167
|
+
println();
|
|
168
|
+
const normalizeExample = subcommand.example.replace(/\n/g, "\n" + indent(3));
|
|
169
|
+
println(indent(2), c.example(normalizeExample), ln(1));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// * Prepare
|
|
174
|
+
function prepareOptionsToPrint(options: Option[] | undefined): [PreparedToPrint[], number] {
|
|
175
|
+
if (!options || !options.length) return [[], 0];
|
|
176
|
+
|
|
177
|
+
const optionsToPrint: PreparedToPrint[] = [];
|
|
178
|
+
let longest = 0;
|
|
179
|
+
|
|
180
|
+
for (const option of options) {
|
|
181
|
+
const nameWithAliases = option.aliases ? [...option.aliases, option.name] : [option.name];
|
|
182
|
+
const names = Array.from(new Set(nameWithAliases.map(name => transformOptionToArg(name)))).join(", ");
|
|
183
|
+
|
|
184
|
+
const defaultValue = getDefaultValueFromSchema(option.type);
|
|
185
|
+
|
|
186
|
+
const placeholder = option.placeholder ?? " ";
|
|
187
|
+
optionsToPrint.push({
|
|
188
|
+
names,
|
|
189
|
+
placeholder,
|
|
190
|
+
description: option.description ?? option.type.description ?? "",
|
|
191
|
+
default: typeof defaultValue !== "undefined" ? `(default: ${JSON.stringify(defaultValue)})` : "",
|
|
192
|
+
optional: option.type.isOptional() ? "[optional]" : "",
|
|
193
|
+
example: option.example ?? "",
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const optLength = names.length + placeholder.length;
|
|
197
|
+
|
|
198
|
+
if (optLength > longest) longest = optLength;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return [optionsToPrint, longest];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function prepareCommandsToPrint(subcommands: Subcommand[] | undefined): [PreparedToPrint[], number] {
|
|
205
|
+
if (!subcommands || !subcommands.length) return [[], 0];
|
|
206
|
+
|
|
207
|
+
const commandsToPrint: PreparedToPrint[] = [];
|
|
208
|
+
let longest = 0;
|
|
209
|
+
|
|
210
|
+
for (const subcommand of subcommands) {
|
|
211
|
+
const { name, aliases, description } = subcommand;
|
|
212
|
+
const names = Array.from(new Set([...(aliases ?? []), name])).join(", ");
|
|
213
|
+
|
|
214
|
+
const placeholder =
|
|
215
|
+
subcommand.placeholder ?? (subcommand.options ? "[options]" : subcommand.allowPositional ? "<args>" : " ");
|
|
216
|
+
|
|
217
|
+
commandsToPrint.push({ names, placeholder, description: description ?? "" });
|
|
218
|
+
|
|
219
|
+
const cmdLength = names.length + placeholder.length;
|
|
220
|
+
if (cmdLength > longest) longest = cmdLength;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return [commandsToPrint, longest];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function prepareArgumentsToPrint(args: Argument[] | undefined): [PreparedToPrint[], number] {
|
|
227
|
+
if (!args || !args.length) return [[], 0];
|
|
228
|
+
|
|
229
|
+
const argsToPrint: PreparedToPrint[] = [];
|
|
230
|
+
let longest = 0;
|
|
231
|
+
|
|
232
|
+
for (const arg of args) {
|
|
233
|
+
const defaultValue = getDefaultValueFromSchema(arg.type);
|
|
234
|
+
|
|
235
|
+
argsToPrint.push({
|
|
236
|
+
names: arg.name,
|
|
237
|
+
description: arg.description ?? "",
|
|
238
|
+
default: typeof defaultValue !== "undefined" ? `(default: ${JSON.stringify(defaultValue)})` : "",
|
|
239
|
+
optional: arg.type.isOptional() ? "[optional]" : "",
|
|
240
|
+
example: arg.example ?? "",
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const cmdLength = arg.name.length;
|
|
244
|
+
if (cmdLength > longest) longest = cmdLength;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return [argsToPrint, longest];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// * Print
|
|
251
|
+
function printPreparedOptions(optionsToPrint: PreparedToPrint[], c: typeof colors, longest: number) {
|
|
252
|
+
if (!optionsToPrint.length) return;
|
|
253
|
+
|
|
254
|
+
print(c.title(" OPTIONS "));
|
|
255
|
+
|
|
256
|
+
println();
|
|
257
|
+
|
|
258
|
+
for (const { names, placeholder, description, example, optional, default: def } of optionsToPrint) {
|
|
259
|
+
const optLength = names.length + (placeholder?.length ?? 0);
|
|
260
|
+
const spacing = longest + 1 - optLength;
|
|
261
|
+
const normalizeDesc = description.replace(/\n/g, "\n" + indent(longest + 7) + c.punctuation("└"));
|
|
262
|
+
|
|
263
|
+
const coloredNames = names
|
|
264
|
+
.split(/(,)/)
|
|
265
|
+
.map(name => (name === "," ? c.punctuation(name) : c.option(name)))
|
|
266
|
+
.join("");
|
|
267
|
+
|
|
268
|
+
println(
|
|
269
|
+
indent(2),
|
|
270
|
+
coloredNames,
|
|
271
|
+
c.placeholder(placeholder),
|
|
272
|
+
indent(spacing),
|
|
273
|
+
c.description(normalizeDesc),
|
|
274
|
+
def ? c.default(def) : c.optional(optional),
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
if (example) {
|
|
278
|
+
const normalizeExample = example.replace(/\n/g, "\n" + indent(longest + 17));
|
|
279
|
+
println(indent(longest + 6), c.punctuation("└") + c.exampleTitle("Example:"), c.example(normalizeExample));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
println();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function printPreparedCommands(commandsToPrint: PreparedToPrint[], c: typeof colors, longest: number) {
|
|
287
|
+
if (!commandsToPrint.length) return;
|
|
288
|
+
|
|
289
|
+
print(c.title(" COMMANDS "));
|
|
290
|
+
|
|
291
|
+
println();
|
|
292
|
+
|
|
293
|
+
for (const { names, placeholder, description } of commandsToPrint) {
|
|
294
|
+
const optLength = names.length + (placeholder?.length ?? 0);
|
|
295
|
+
const spacing = longest + 1 - optLength;
|
|
296
|
+
const normalizeDesc = description.replace(/\n/g, "\n" + indent(longest + 7));
|
|
297
|
+
|
|
298
|
+
const coloredNames = names
|
|
299
|
+
.split(/(,)/)
|
|
300
|
+
.map(name => (name === "," ? c.punctuation(name) : c.command(name)))
|
|
301
|
+
.join("");
|
|
302
|
+
|
|
303
|
+
println(indent(2), coloredNames, c.placeholder(placeholder), indent(spacing), c.description(normalizeDesc));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
println();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function printPreparedArguments(argsToPrint: PreparedToPrint[], c: typeof colors, longest: number) {
|
|
310
|
+
if (!argsToPrint.length) return;
|
|
311
|
+
|
|
312
|
+
print(c.title(" ARGUMENTS "));
|
|
313
|
+
|
|
314
|
+
println();
|
|
315
|
+
|
|
316
|
+
for (const { names, description, example, optional, default: def } of argsToPrint) {
|
|
317
|
+
const spacing = longest + 2 - names.length;
|
|
318
|
+
const normalizeDesc = description.replace(/\n/g, "\n" + indent(longest + 6) + c.punctuation("└"));
|
|
319
|
+
|
|
320
|
+
println(
|
|
321
|
+
indent(2),
|
|
322
|
+
c.argument(names),
|
|
323
|
+
indent(spacing),
|
|
324
|
+
c.description(normalizeDesc),
|
|
325
|
+
def ? c.default(def) : c.optional(optional),
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
if (example) {
|
|
329
|
+
const normalizeExample = example.replace(/\n/g, "\n" + indent(longest + 16));
|
|
330
|
+
println(indent(longest + 5), c.punctuation("└") + c.exampleTitle("Example:"), c.example(normalizeExample));
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
println();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export const help = {
|
|
338
|
+
printCliHelp,
|
|
339
|
+
printSubcommandHelp,
|
|
340
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { help } from "./help.js";
|
|
2
|
+
import { parse, safeParse } from "./parser.js";
|
|
3
|
+
|
|
4
|
+
import type { ActionFn, Cli, Prettify, Subcommand, UnSafeParseResult } from "./types.js";
|
|
5
|
+
|
|
6
|
+
function createSubcommand<const T extends Subcommand>(input: T & Subcommand): Prettify<T & ActionFn<T>> {
|
|
7
|
+
return Object.assign(input, {
|
|
8
|
+
setAction: (action: (res: UnSafeParseResult<[T]>) => void) => (input.action = action),
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function createCli<const T extends Cli>(input: T & Cli): Prettify<T & ActionFn<T>> {
|
|
13
|
+
return Object.assign(input, {
|
|
14
|
+
setAction: (action: (res: UnSafeParseResult<[T]>) => void) => (input.action = action),
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { printCliHelp, printSubcommandHelp } = help;
|
|
19
|
+
|
|
20
|
+
export { createCli, createSubcommand, parse, printCliHelp, printSubcommandHelp, safeParse };
|
|
21
|
+
|
|
22
|
+
export type * from "./types.js";
|
package/src/parser.ts
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { help } from "./help.js";
|
|
2
|
+
import {
|
|
3
|
+
decoupleFlags,
|
|
4
|
+
getOrdinalPlacement,
|
|
5
|
+
isBooleanSchema,
|
|
6
|
+
isFlagArg,
|
|
7
|
+
isOptionArg,
|
|
8
|
+
noName,
|
|
9
|
+
stringToBoolean,
|
|
10
|
+
transformArg,
|
|
11
|
+
transformOptionToArg,
|
|
12
|
+
} from "./utils.js";
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
Cli,
|
|
16
|
+
NoSubcommand,
|
|
17
|
+
Option,
|
|
18
|
+
PrintHelpOpt,
|
|
19
|
+
SafeParseResult,
|
|
20
|
+
Subcommand,
|
|
21
|
+
UnSafeParseResult,
|
|
22
|
+
} from "./types.js";
|
|
23
|
+
|
|
24
|
+
export function parse<T extends Subcommand[]>(argsv: string[], ...params: T): UnSafeParseResult<T>;
|
|
25
|
+
export function parse<T extends Subcommand[], U extends Cli>(
|
|
26
|
+
argsv: string[],
|
|
27
|
+
...params: [U, ...T]
|
|
28
|
+
): UnSafeParseResult<[...T, NoSubcommand & U]>;
|
|
29
|
+
|
|
30
|
+
export function parse<T extends Subcommand[], U extends Cli>(argsv: string[], ...params: [U, ...T]) {
|
|
31
|
+
const cliOptions = ("cliName" in params[0] ? params[0] : {}) as U;
|
|
32
|
+
const subcommandArr = params as unknown as T;
|
|
33
|
+
const allSubcommands = new Set<string>(subcommandArr.flatMap(c => [c.name, ...(c.aliases || [])]));
|
|
34
|
+
|
|
35
|
+
// decouple flags E.g. `-rf` -> `-r, -f`
|
|
36
|
+
argsv = decoupleFlags(argsv);
|
|
37
|
+
|
|
38
|
+
type ResultObj = Record<string, unknown> & {
|
|
39
|
+
subcommand: string | undefined;
|
|
40
|
+
positional?: string[];
|
|
41
|
+
arguments?: unknown[];
|
|
42
|
+
_info?: Record<string, { rawArg?: string; rawValue?: string; source: "cli" | "default" }>;
|
|
43
|
+
printCliHelp: (options?: PrintHelpOpt) => void;
|
|
44
|
+
printSubcommandHelp: (subcommand: any, options?: PrintHelpOpt) => void;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const results: ResultObj = {
|
|
48
|
+
subcommand: undefined,
|
|
49
|
+
printCliHelp(opt) {
|
|
50
|
+
help.printCliHelp(params, opt);
|
|
51
|
+
},
|
|
52
|
+
printSubcommandHelp(subcommandStr, opt) {
|
|
53
|
+
const subcommand = subcommandArr.find(c => c.name === subcommandStr);
|
|
54
|
+
if (!subcommand) return console.error(`Cannot print help for subcommand "${subcommandStr}" as it does not exist`);
|
|
55
|
+
help.printSubcommandHelp(subcommand, opt, cliOptions.cliName);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const addRawArg = (optionName: string, rawArg: string) => {
|
|
60
|
+
if (!results._info) results._info = {};
|
|
61
|
+
if (!results._info[optionName]) results._info[optionName] = Object.create({});
|
|
62
|
+
results._info[optionName].rawArg = rawArg;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const addRawValue = (optionName: string, rawValue: string) => {
|
|
66
|
+
if (!results._info) results._info = {};
|
|
67
|
+
if (!results._info[optionName]) results._info[optionName] = Object.create({});
|
|
68
|
+
results._info[optionName].rawValue = rawValue;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const addSource = (optionName: string, source: "cli" | "default") => {
|
|
72
|
+
if (!results._info) results._info = {};
|
|
73
|
+
if (!results._info[optionName]) results._info[optionName] = Object.create({});
|
|
74
|
+
results._info[optionName].source = source;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const fillOption = (optionName: string, value: Option) => {
|
|
78
|
+
if (!results._info) results._info = {};
|
|
79
|
+
if (!results._info[optionName]) results._info[optionName] = Object.create({});
|
|
80
|
+
Object.assign(results._info[optionName], value);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
for (let i = 0; i < argsv.length; i++) {
|
|
84
|
+
const arg = argsv[i];
|
|
85
|
+
|
|
86
|
+
// * subcommand
|
|
87
|
+
if (i === 0) {
|
|
88
|
+
results.subcommand = allSubcommands.has(arg) ? arg : undefined;
|
|
89
|
+
|
|
90
|
+
// add positional and arguments array
|
|
91
|
+
const subcommandProps = subcommandArr.find(c => c.name === results.subcommand);
|
|
92
|
+
if (subcommandProps?.allowPositional) results.positional = [];
|
|
93
|
+
if (subcommandProps?.arguments?.length) results.arguments = [];
|
|
94
|
+
|
|
95
|
+
if (results.subcommand) continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// * option
|
|
99
|
+
const argAndValue = arg.split("=").filter(Boolean);
|
|
100
|
+
const argWithEquals = arg.includes("=");
|
|
101
|
+
const argument = argAndValue[0];
|
|
102
|
+
const argValue: string | undefined = argAndValue[1];
|
|
103
|
+
|
|
104
|
+
if (isOptionArg(argument)) {
|
|
105
|
+
if (isFlagArg(argument) && argWithEquals) {
|
|
106
|
+
throw new Error(`Flag arguments cannot be assigned using "=": "${arg}"`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const subcommandProps = subcommandArr.find(c => c.name === results.subcommand);
|
|
110
|
+
if (!subcommandProps) throw new Error(`Unknown subcommand: "${results.subcommand}"`);
|
|
111
|
+
|
|
112
|
+
if (!subcommandProps.options) {
|
|
113
|
+
const msg = !results.subcommand
|
|
114
|
+
? "options are not allowed here"
|
|
115
|
+
: `subcommand "${results.subcommand}" does not allow options`;
|
|
116
|
+
throw new Error(`Error: ${msg}: "${argument}"`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const optionName = transformArg(argument);
|
|
120
|
+
if (optionName in results) throw new Error(`Duplicate option: "${argument}"`);
|
|
121
|
+
|
|
122
|
+
const isNegative = argument.startsWith("--no-");
|
|
123
|
+
|
|
124
|
+
const option = subcommandProps.options.find(o => {
|
|
125
|
+
if (o.name === optionName) return true;
|
|
126
|
+
if (isNegative && noName(o.name) === optionName) return true;
|
|
127
|
+
|
|
128
|
+
if (!o.aliases) return false;
|
|
129
|
+
if (o.aliases.includes(optionName)) return true;
|
|
130
|
+
if (isNegative && o.aliases.map(noName).includes(optionName)) return true;
|
|
131
|
+
|
|
132
|
+
return false;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (!option) {
|
|
136
|
+
throw new Error(`Unknown option: "${argument}"`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const isTypeBoolean = isBooleanSchema(option.type);
|
|
140
|
+
const nextArg = argsv[i + 1];
|
|
141
|
+
|
|
142
|
+
let optionValue: string | boolean = argWithEquals ? argValue : nextArg;
|
|
143
|
+
|
|
144
|
+
if (isTypeBoolean) {
|
|
145
|
+
if (argWithEquals) {
|
|
146
|
+
const parsedBoolean = stringToBoolean(argValue);
|
|
147
|
+
optionValue = isNegative ? !parsedBoolean : parsedBoolean;
|
|
148
|
+
} else {
|
|
149
|
+
optionValue = !isNegative;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (typeof optionValue === "undefined") {
|
|
154
|
+
throw new Error(`Expected a value for "${argument}" but got nothing`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!argWithEquals && isOptionArg(optionValue)) {
|
|
158
|
+
throw new Error(`Expected a value for "${argument}" but got an argument "${nextArg}"`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const res = option.type.safeParse(optionValue);
|
|
162
|
+
if (!res.success) {
|
|
163
|
+
throw new Error(`Invalid value "${optionValue}" for "${argument}": ${res.error.errors[0].message}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
results[option.name] = res.data;
|
|
167
|
+
addRawArg(option.name, argument);
|
|
168
|
+
const rawVal = argWithEquals ? argValue : isTypeBoolean ? "" : nextArg;
|
|
169
|
+
addRawValue(option.name, rawVal);
|
|
170
|
+
fillOption(option.name, option);
|
|
171
|
+
|
|
172
|
+
if (!argWithEquals && !isTypeBoolean) i++;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const subcommandProps = subcommandArr.find(c => c.name === results.subcommand);
|
|
177
|
+
|
|
178
|
+
// * arguments
|
|
179
|
+
if (subcommandProps?.arguments?.length) {
|
|
180
|
+
if (!results.arguments) results.arguments = [];
|
|
181
|
+
|
|
182
|
+
const currentArgCount = results.arguments.length;
|
|
183
|
+
|
|
184
|
+
if (currentArgCount < subcommandProps.arguments.length) {
|
|
185
|
+
const argType = subcommandProps.arguments[currentArgCount].type;
|
|
186
|
+
|
|
187
|
+
let argValue: string | boolean = arg;
|
|
188
|
+
const isTypeBoolean = isBooleanSchema(argType);
|
|
189
|
+
if (isTypeBoolean) argValue = stringToBoolean(argValue);
|
|
190
|
+
|
|
191
|
+
const res = argType.safeParse(argValue);
|
|
192
|
+
if (!res.success) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
`The ${getOrdinalPlacement(currentArgCount)} argument "${arg}" is invalid: ${res.error.errors[0].message}`,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
results.arguments.push(res.data);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// * positional
|
|
204
|
+
if (subcommandProps?.allowPositional) {
|
|
205
|
+
if (!results.positional) results.positional = [];
|
|
206
|
+
results.positional.push(arg);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const msg = !results.subcommand ? "here" : `for subcommand "${results.subcommand}"`;
|
|
211
|
+
throw new Error(`Unexpected argument "${arg}": positional arguments are not allowed ${msg}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// check for missing options - set defaults - add _source
|
|
215
|
+
const subcommandProps = subcommandArr.find(c => c.name === results.subcommand);
|
|
216
|
+
if (subcommandProps?.options?.length) {
|
|
217
|
+
for (const option of subcommandProps.options) {
|
|
218
|
+
if (option.name in results) {
|
|
219
|
+
addSource(option.name, "cli");
|
|
220
|
+
fillOption(option.name, option);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (option.type.isOptional()) {
|
|
225
|
+
const hasDefault = typeof option.type._def.defaultValue === "function";
|
|
226
|
+
if (!hasDefault) continue;
|
|
227
|
+
results[option.name] = option.type._def.defaultValue();
|
|
228
|
+
addSource(option.name, "default");
|
|
229
|
+
fillOption(option.name, option);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
throw new Error(`Missing required option: ${transformOptionToArg(option.name)}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// check for arguments - set defaults
|
|
238
|
+
if (subcommandProps?.arguments?.length) {
|
|
239
|
+
const currentArgCount = results.arguments?.length ?? 0;
|
|
240
|
+
const subcommandArgCount = subcommandProps.arguments.length;
|
|
241
|
+
|
|
242
|
+
// missing arguments
|
|
243
|
+
if (currentArgCount < subcommandArgCount) {
|
|
244
|
+
for (let i = currentArgCount; i < subcommandArgCount; i++) {
|
|
245
|
+
const argumentType = subcommandProps.arguments[i].type;
|
|
246
|
+
const hasDefault = typeof argumentType._def.defaultValue === "function";
|
|
247
|
+
if (hasDefault && results.arguments) {
|
|
248
|
+
results.arguments.push(argumentType._def.defaultValue());
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (argumentType.isOptional()) continue;
|
|
253
|
+
|
|
254
|
+
throw new Error(`the ${getOrdinalPlacement(i)} argument is required: "${subcommandProps.arguments[i].name}"`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (subcommandProps?.action) {
|
|
260
|
+
subcommandProps.action(results);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return results as UnSafeParseResult<[...T, NoSubcommand & U]>;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function safeParse<T extends Subcommand[]>(argsv: string[], ...params: T): SafeParseResult<T>;
|
|
267
|
+
export function safeParse<T extends Subcommand[], U extends Cli>(
|
|
268
|
+
argsv: string[],
|
|
269
|
+
...params: [U, ...T]
|
|
270
|
+
): SafeParseResult<[...T, NoSubcommand & U]>;
|
|
271
|
+
|
|
272
|
+
export function safeParse<T extends Subcommand[], U extends Cli>(argsv: string[], ...params: [U, ...T]) {
|
|
273
|
+
const cliOptions = ("cliName" in params[0] ? params[0] : {}) as U;
|
|
274
|
+
const subcommandArr = params as Subcommand[];
|
|
275
|
+
|
|
276
|
+
const printCliHelp = (opt?: PrintHelpOpt) => help.printCliHelp(params, opt);
|
|
277
|
+
const printSubcommandHelp = (subcommandStr: NonNullable<T[number]["name"]>, opt?: PrintHelpOpt) => {
|
|
278
|
+
const subcommand = subcommandArr.find(c => c.name === subcommandStr);
|
|
279
|
+
if (!subcommand) return console.error(`Cannot print help for subcommand "${subcommandStr}" as it does not exist`);
|
|
280
|
+
help.printSubcommandHelp(subcommand, opt, cliOptions.cliName);
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const data = parse(argsv, ...params);
|
|
285
|
+
// @ts-expect-error error
|
|
286
|
+
delete data.printCliHelp;
|
|
287
|
+
// @ts-expect-error error
|
|
288
|
+
delete data.printSubcommandHelp;
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
success: true,
|
|
292
|
+
data: data as Omit<typeof data, "printCliHelp" | "printSubcommandHelp">,
|
|
293
|
+
printCliHelp,
|
|
294
|
+
printSubcommandHelp,
|
|
295
|
+
};
|
|
296
|
+
} catch (e) {
|
|
297
|
+
return { success: false, error: e as Error, printCliHelp, printSubcommandHelp };
|
|
298
|
+
}
|
|
299
|
+
}
|