@crustjs/core 0.0.3 → 0.0.4
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 +2 -2
- package/dist/index.d.ts +176 -94
- package/dist/index.js +7 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,9 +17,9 @@ import { defineCommand, runMain } from "@crustjs/core";
|
|
|
17
17
|
|
|
18
18
|
const main = defineCommand({
|
|
19
19
|
meta: { name: "greet", description: "Say hello" },
|
|
20
|
-
args: [{ name: "name", type:
|
|
20
|
+
args: [{ name: "name", type: "string", default: "world" }],
|
|
21
21
|
flags: {
|
|
22
|
-
loud: { type:
|
|
22
|
+
loud: { type: "boolean", description: "Shout it", alias: "l" },
|
|
23
23
|
},
|
|
24
24
|
run({ args, flags }) {
|
|
25
25
|
const msg = `Hello, ${args.name}!`;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,79 +1,131 @@
|
|
|
1
|
-
/**
|
|
2
|
-
type
|
|
1
|
+
/** Supported type literals for args and flags */
|
|
2
|
+
type ValueType = "string" | "number" | "boolean";
|
|
3
3
|
/**
|
|
4
|
-
* Resolves a
|
|
4
|
+
* Resolves a type literal to its corresponding TypeScript primitive type.
|
|
5
5
|
*
|
|
6
|
-
* - `
|
|
7
|
-
* - `
|
|
8
|
-
* - `
|
|
6
|
+
* - `"string"` → `string`
|
|
7
|
+
* - `"number"` → `number`
|
|
8
|
+
* - `"boolean"` → `boolean`
|
|
9
9
|
*/
|
|
10
|
-
type ResolvePrimitive<T extends
|
|
10
|
+
type ResolvePrimitive<T extends ValueType> = T extends "string" ? string : T extends "number" ? number : T extends "boolean" ? boolean : never;
|
|
11
|
+
/** Shared fields present on every positional argument definition */
|
|
12
|
+
interface ArgDefBase {
|
|
13
|
+
/** The argument name (used as the key in the parsed result and in help text) */
|
|
14
|
+
name: string;
|
|
15
|
+
/** Human-readable description for help text */
|
|
16
|
+
description?: string;
|
|
17
|
+
/** When `true`, the parser throws if the argument is not provided */
|
|
18
|
+
required?: true;
|
|
19
|
+
/** When `true`, collects all remaining positional values into an array */
|
|
20
|
+
variadic?: true;
|
|
21
|
+
}
|
|
22
|
+
/** A positional argument whose value is a string */
|
|
23
|
+
interface StringArgDef extends ArgDefBase {
|
|
24
|
+
type: "string";
|
|
25
|
+
/** Default string value when the argument is not provided */
|
|
26
|
+
default?: string;
|
|
27
|
+
}
|
|
28
|
+
/** A positional argument whose value is a number */
|
|
29
|
+
interface NumberArgDef extends ArgDefBase {
|
|
30
|
+
type: "number";
|
|
31
|
+
/** Default number value when the argument is not provided */
|
|
32
|
+
default?: number;
|
|
33
|
+
}
|
|
34
|
+
/** A positional argument whose value is a boolean */
|
|
35
|
+
interface BooleanArgDef extends ArgDefBase {
|
|
36
|
+
type: "boolean";
|
|
37
|
+
/** Default boolean value when the argument is not provided */
|
|
38
|
+
default?: boolean;
|
|
39
|
+
}
|
|
11
40
|
/**
|
|
12
41
|
* Defines a single positional argument for a CLI command.
|
|
13
42
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
43
|
+
* Discriminated by `type` for type-safe `default` values. Boolean toggle
|
|
44
|
+
* fields (`required`, `variadic`) only accept `true`.
|
|
16
45
|
*
|
|
17
46
|
* @example
|
|
18
47
|
* ```ts
|
|
19
48
|
* const args = [
|
|
20
|
-
* { name: "port", type:
|
|
21
|
-
* { name: "name", type:
|
|
22
|
-
* { name: "files", type:
|
|
49
|
+
* { name: "port", type: "number", description: "Port number", default: 3000 },
|
|
50
|
+
* { name: "name", type: "string", required: true },
|
|
51
|
+
* { name: "files", type: "string", variadic: true },
|
|
23
52
|
* ] as const satisfies ArgsDef;
|
|
24
53
|
* ```
|
|
25
54
|
*/
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
V extends boolean = boolean
|
|
32
|
-
> {
|
|
33
|
-
/** The argument name (used as the key in the parsed result and in help text) */
|
|
34
|
-
name: N;
|
|
35
|
-
/** Constructor function indicating the argument's type: `String`, `Number`, or `Boolean` */
|
|
36
|
-
type: T;
|
|
55
|
+
type ArgDef = StringArgDef | NumberArgDef | BooleanArgDef;
|
|
56
|
+
/** Ordered tuple of positional argument definitions */
|
|
57
|
+
type ArgsDef = readonly ArgDef[];
|
|
58
|
+
/** Shared fields present on every flag definition */
|
|
59
|
+
interface FlagDefBase {
|
|
37
60
|
/** Human-readable description for help text */
|
|
38
61
|
description?: string;
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
required?:
|
|
43
|
-
|
|
44
|
-
|
|
62
|
+
/** Short alias or array of aliases (e.g. `"v"` or `["v", "V"]`) */
|
|
63
|
+
alias?: string | string[];
|
|
64
|
+
/** When `true`, the parser throws if the flag is not provided */
|
|
65
|
+
required?: true;
|
|
66
|
+
}
|
|
67
|
+
/** Base for single-value flags — `multiple` must be omitted */
|
|
68
|
+
interface SingleFlagBase extends FlagDefBase {
|
|
69
|
+
/** Must be omitted for single-value flags — set to `true` for multi-value */
|
|
70
|
+
multiple?: never;
|
|
71
|
+
}
|
|
72
|
+
/** A single-value string flag */
|
|
73
|
+
interface StringFlagDef extends SingleFlagBase {
|
|
74
|
+
type: "string";
|
|
75
|
+
/** Default string value */
|
|
76
|
+
default?: string;
|
|
77
|
+
}
|
|
78
|
+
/** A single-value number flag */
|
|
79
|
+
interface NumberFlagDef extends SingleFlagBase {
|
|
80
|
+
type: "number";
|
|
81
|
+
/** Default number value */
|
|
82
|
+
default?: number;
|
|
83
|
+
}
|
|
84
|
+
/** A single-value boolean flag */
|
|
85
|
+
interface BooleanFlagDef extends SingleFlagBase {
|
|
86
|
+
type: "boolean";
|
|
87
|
+
/** Default boolean value */
|
|
88
|
+
default?: boolean;
|
|
89
|
+
}
|
|
90
|
+
/** Base for multi-value flags — `multiple` is required as `true` */
|
|
91
|
+
interface MultiFlagBase extends FlagDefBase {
|
|
92
|
+
/** Collect repeated values into an array */
|
|
93
|
+
multiple: true;
|
|
94
|
+
}
|
|
95
|
+
/** A multi-value string flag (collects repeated values into an array) */
|
|
96
|
+
interface StringMultiFlagDef extends MultiFlagBase {
|
|
97
|
+
type: "string";
|
|
98
|
+
/** Default string array value */
|
|
99
|
+
default?: string[];
|
|
100
|
+
}
|
|
101
|
+
/** A multi-value number flag (collects repeated values into an array) */
|
|
102
|
+
interface NumberMultiFlagDef extends MultiFlagBase {
|
|
103
|
+
type: "number";
|
|
104
|
+
/** Default number array value */
|
|
105
|
+
default?: number[];
|
|
106
|
+
}
|
|
107
|
+
/** A multi-value boolean flag (collects repeated values into an array) */
|
|
108
|
+
interface BooleanMultiFlagDef extends MultiFlagBase {
|
|
109
|
+
type: "boolean";
|
|
110
|
+
/** Default boolean array value */
|
|
111
|
+
default?: boolean[];
|
|
45
112
|
}
|
|
46
|
-
/** Ordered tuple of positional argument definitions */
|
|
47
|
-
type ArgsDef = readonly ArgDef[];
|
|
48
113
|
/**
|
|
49
114
|
* Defines a single named flag for a CLI command.
|
|
50
115
|
*
|
|
116
|
+
* Discriminated by `type` and `multiple` for type-safe `default` values.
|
|
117
|
+
* Boolean toggle fields (`required`, `multiple`) only accept `true`.
|
|
118
|
+
*
|
|
51
119
|
* @example
|
|
52
120
|
* ```ts
|
|
53
121
|
* const flags = {
|
|
54
|
-
* verbose: { type:
|
|
55
|
-
* port: { type:
|
|
122
|
+
* verbose: { type: "boolean", description: "Enable verbose logging", alias: "v" },
|
|
123
|
+
* port: { type: "number", description: "Port number", default: 3000 },
|
|
124
|
+
* files: { type: "string", multiple: true, default: ["index.ts"] },
|
|
56
125
|
* } satisfies FlagsDef;
|
|
57
126
|
* ```
|
|
58
127
|
*/
|
|
59
|
-
|
|
60
|
-
T extends TypeConstructor = TypeConstructor,
|
|
61
|
-
R extends boolean = boolean,
|
|
62
|
-
M extends boolean = boolean
|
|
63
|
-
> {
|
|
64
|
-
/** Constructor function indicating the flag's type: `String`, `Number`, or `Boolean` */
|
|
65
|
-
type: T;
|
|
66
|
-
/** Human-readable description for help text */
|
|
67
|
-
description?: string;
|
|
68
|
-
/** Default value when the flag is not provided. Must be an array when `multiple: true`. */
|
|
69
|
-
default?: M extends true ? ResolvePrimitive<T>[] : ResolvePrimitive<T>;
|
|
70
|
-
/** Whether this flag must be provided (errors if missing) */
|
|
71
|
-
required?: R;
|
|
72
|
-
/** Short alias or array of aliases (e.g. `"v"` or `["v", "V"]`) */
|
|
73
|
-
alias?: string | string[];
|
|
74
|
-
/** Whether this flag can be provided multiple times, collecting values into an array */
|
|
75
|
-
multiple?: M;
|
|
76
|
-
}
|
|
128
|
+
type FlagDef = StringFlagDef | NumberFlagDef | BooleanFlagDef | StringMultiFlagDef | NumberMultiFlagDef | BooleanMultiFlagDef;
|
|
77
129
|
/** Record mapping flag names to their definitions */
|
|
78
130
|
type FlagsDef = Record<string, FlagDef>;
|
|
79
131
|
/** Extract alias string literals from a single FlagDef */
|
|
@@ -81,12 +133,6 @@ type ExtractAliases<F extends FlagDef> = F extends {
|
|
|
81
133
|
alias: infer A;
|
|
82
134
|
} ? A extends string ? A : A extends readonly string[] ? A[number] : never : never;
|
|
83
135
|
/**
|
|
84
|
-
* Collects all alias literals across a FlagsDef, then intersects with `keyof F`.
|
|
85
|
-
* Resolves to `never` when no alias matches a flag name, or the colliding
|
|
86
|
-
* name(s) when a match exists.
|
|
87
|
-
*/
|
|
88
|
-
type FlagAliasNameCollision<F extends FlagsDef> = { [K in keyof F & string] : ExtractAliases<F[K]> }[keyof F & string] & keyof F;
|
|
89
|
-
/**
|
|
90
136
|
* Collects aliases from every flag *except* flag K.
|
|
91
137
|
* Used to detect alias→alias duplicates across different flags.
|
|
92
138
|
*/
|
|
@@ -95,38 +141,44 @@ type AliasesExcluding<
|
|
|
95
141
|
K extends keyof F & string
|
|
96
142
|
> = { [J in Exclude<keyof F & string, K>] : ExtractAliases<F[J]> }[Exclude<keyof F & string, K>];
|
|
97
143
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
144
|
+
* Per-flag collision detection: resolves to the alias literal(s) of flag K
|
|
145
|
+
* that collide with another flag's name or another flag's alias,
|
|
146
|
+
* or `never` when K's aliases are all unique.
|
|
100
147
|
*/
|
|
101
|
-
type
|
|
148
|
+
type CollidingAliases<
|
|
149
|
+
F extends FlagsDef,
|
|
150
|
+
K extends keyof F & string
|
|
151
|
+
> = (ExtractAliases<F[K]> & Exclude<keyof F & string, K>) | (ExtractAliases<F[K]> & AliasesExcluding<F, K>);
|
|
102
152
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
153
|
+
* Per-flag validation mapped type. Resolves to `F` when no collisions exist.
|
|
154
|
+
* For flags with colliding aliases, adds a branded error property to the
|
|
155
|
+
* specific flag definition, causing a type error on that flag's value:
|
|
105
156
|
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Checks whether any element in `Init` (all elements except the last) has
|
|
113
|
-
* `variadic: true`. Used by {@link CheckVariadicArgs} to ensure only the
|
|
114
|
-
* last positional arg can be variadic.
|
|
157
|
+
* ```
|
|
158
|
+
* Property 'FIX_ALIAS_COLLISION' is missing in type '{ type: "string"; alias: "minify" }'
|
|
159
|
+
* but required in type
|
|
160
|
+
* '{ readonly FIX_ALIAS_COLLISION: "Alias \"minify\" collides with another flag name or alias" }'.
|
|
161
|
+
* ```
|
|
115
162
|
*/
|
|
116
|
-
type
|
|
117
|
-
|
|
118
|
-
}
|
|
163
|
+
type ValidateFlagAliases<F extends FlagsDef> = { [K in keyof F & string] : CollidingAliases<F, K> extends never ? F[K] : F[K] & {
|
|
164
|
+
readonly FIX_ALIAS_COLLISION: `Alias "${CollidingAliases<F, K>}" collides with another flag name or alias`;
|
|
165
|
+
} };
|
|
119
166
|
/**
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* and verify that no element in `Init` is variadic.
|
|
167
|
+
* Per-arg validation tuple type. Resolves to `A` when the constraint is
|
|
168
|
+
* satisfied (only the last arg is variadic). For non-last args that have
|
|
169
|
+
* `variadic: true`, adds a branded error property to the specific arg:
|
|
124
170
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
171
|
+
* ```
|
|
172
|
+
* Property 'FIX_VARIADIC_POSITION' is missing in type '{ name: "files"; ... variadic: true }'
|
|
173
|
+
* but required in type
|
|
174
|
+
* '{ readonly FIX_VARIADIC_POSITION: "Only the last positional argument can be variadic" }'.
|
|
175
|
+
* ```
|
|
128
176
|
*/
|
|
129
|
-
type
|
|
177
|
+
type ValidateVariadicArgs<A extends ArgsDef> = A extends readonly [infer Head extends ArgDef, ...infer Tail extends readonly ArgDef[]] ? Tail extends readonly [ArgDef, ...ArgDef[]] ? Head extends {
|
|
178
|
+
variadic: true;
|
|
179
|
+
} ? readonly [Head & {
|
|
180
|
+
readonly FIX_VARIADIC_POSITION: "Only the last positional argument can be variadic";
|
|
181
|
+
}, ...ValidateVariadicArgs<readonly [...Tail]>] : readonly [Head, ...ValidateVariadicArgs<readonly [...Tail]>] : readonly [Head] : A;
|
|
130
182
|
/**
|
|
131
183
|
* Infer the resolved type for a single ArgDef:
|
|
132
184
|
*
|
|
@@ -156,9 +208,9 @@ type Simplify<T> = { [K in keyof T] : T[K] };
|
|
|
156
208
|
* @example
|
|
157
209
|
* ```ts
|
|
158
210
|
* type Result = InferArgs<readonly [
|
|
159
|
-
* { name: "port"; type:
|
|
160
|
-
* { name: "name"; type:
|
|
161
|
-
* { name: "files"; type:
|
|
211
|
+
* { name: "port"; type: "number"; default: 3000 },
|
|
212
|
+
* { name: "name"; type: "string"; required: true },
|
|
213
|
+
* { name: "files"; type: "string"; variadic: true },
|
|
162
214
|
* ]>;
|
|
163
215
|
* // Result = { port: number; name: string; files: string[] }
|
|
164
216
|
* ```
|
|
@@ -188,8 +240,8 @@ type InferFlagValue<F extends FlagDef> = F extends {
|
|
|
188
240
|
* @example
|
|
189
241
|
* ```ts
|
|
190
242
|
* type Result = InferFlags<{
|
|
191
|
-
* verbose: { type:
|
|
192
|
-
* port: { type:
|
|
243
|
+
* verbose: { type: "boolean" };
|
|
244
|
+
* port: { type: "number", default: 3000 };
|
|
193
245
|
* }>;
|
|
194
246
|
* // Result = { verbose: boolean | undefined; port: number }
|
|
195
247
|
* ```
|
|
@@ -233,7 +285,37 @@ interface CommandContext<
|
|
|
233
285
|
/** The resolved command that is being executed */
|
|
234
286
|
command: AnyCommand;
|
|
235
287
|
}
|
|
236
|
-
/**
|
|
288
|
+
/**
|
|
289
|
+
* Configuration object accepted by `defineCommand()`.
|
|
290
|
+
*
|
|
291
|
+
* Identical shape to {@link Command} but uses `NoInfer` on lifecycle-hook
|
|
292
|
+
* parameters so TypeScript infers `A` and `F` solely from the `args` / `flags`
|
|
293
|
+
* data properties — not from callbacks. This ensures full contextual typing
|
|
294
|
+
* (e.g. `description` is `string`, not `any`) when writing command definitions.
|
|
295
|
+
*
|
|
296
|
+
* Compile-time validation for variadic args and flag alias collisions is
|
|
297
|
+
* enforced via parameter-level intersection in `defineCommand()`.
|
|
298
|
+
*/
|
|
299
|
+
interface CommandDef<
|
|
300
|
+
A extends ArgsDef = ArgsDef,
|
|
301
|
+
F extends FlagsDef = FlagsDef
|
|
302
|
+
> {
|
|
303
|
+
/** Command metadata (name, description, usage) */
|
|
304
|
+
meta: CommandMeta;
|
|
305
|
+
/** Positional argument definitions */
|
|
306
|
+
args?: A;
|
|
307
|
+
/** Flag definitions */
|
|
308
|
+
flags?: F;
|
|
309
|
+
/** Named subcommands */
|
|
310
|
+
subCommands?: Record<string, AnyCommand>;
|
|
311
|
+
/** Called before `run()` — useful for initialization */
|
|
312
|
+
preRun?(context: CommandContext<NoInfer<A>, NoInfer<F>>): void | Promise<void>;
|
|
313
|
+
/** The main command handler */
|
|
314
|
+
run?(context: CommandContext<NoInfer<A>, NoInfer<F>>): void | Promise<void>;
|
|
315
|
+
/** Called after `run()` (even if it throws) — useful for teardown */
|
|
316
|
+
postRun?(context: CommandContext<NoInfer<A>, NoInfer<F>>): void | Promise<void>;
|
|
317
|
+
}
|
|
318
|
+
/** Frozen command object returned by `defineCommand()` and used at runtime. */
|
|
237
319
|
interface Command<
|
|
238
320
|
A extends ArgsDef = ArgsDef,
|
|
239
321
|
F extends FlagsDef = FlagsDef
|
|
@@ -270,10 +352,10 @@ type AnyCommand = Command<any, any>;
|
|
|
270
352
|
* const cmd = defineCommand({
|
|
271
353
|
* meta: { name: "serve", description: "Start dev server" },
|
|
272
354
|
* args: [
|
|
273
|
-
* { name: "port", type:
|
|
355
|
+
* { name: "port", type: "number", description: "Port number", default: 3000 },
|
|
274
356
|
* ],
|
|
275
357
|
* flags: {
|
|
276
|
-
* verbose: { type:
|
|
358
|
+
* verbose: { type: "boolean", description: "Enable verbose logging", alias: "v" },
|
|
277
359
|
* },
|
|
278
360
|
* run({ args, flags }) {
|
|
279
361
|
* // args.port is typed as number, flags.verbose is typed as boolean | undefined
|
|
@@ -285,9 +367,9 @@ type AnyCommand = Command<any, any>;
|
|
|
285
367
|
declare function defineCommand<
|
|
286
368
|
const A extends ArgsDef = ArgsDef,
|
|
287
369
|
const F extends FlagsDef = FlagsDef
|
|
288
|
-
>(config:
|
|
289
|
-
args?:
|
|
290
|
-
flags?:
|
|
370
|
+
>(config: CommandDef<A, F> & {
|
|
371
|
+
args?: ValidateVariadicArgs<A>;
|
|
372
|
+
flags?: ValidateFlagAliases<F>;
|
|
291
373
|
}): Command<A, F>;
|
|
292
374
|
interface CommandNotFoundErrorDetails {
|
|
293
375
|
input: string;
|
|
@@ -457,4 +539,4 @@ interface RunOptions {
|
|
|
457
539
|
}
|
|
458
540
|
declare function runCommand(command: AnyCommand, options?: RunOptions): Promise<void>;
|
|
459
541
|
declare function runMain(command: AnyCommand, options?: RunOptions): Promise<void>;
|
|
460
|
-
export { runMain, runCommand, resolveCommand, parseArgs, defineCommand, SetupContext, RunOptions, PluginMiddleware, ParseResult, MiddlewareContext, InferFlags, InferArgs, FlagsDef, FlagDef, CrustPlugin, CrustErrorCode, CrustError, CommandRoute, CommandNotFoundErrorDetails, CommandMeta, CommandContext, Command, ArgsDef, ArgDef, AnyCommand };
|
|
542
|
+
export { runMain, runCommand, resolveCommand, parseArgs, defineCommand, SetupContext, RunOptions, PluginMiddleware, ParseResult, MiddlewareContext, InferFlags, InferArgs, FlagsDef, FlagDef, CrustPlugin, CrustErrorCode, CrustError, CommandRoute, CommandNotFoundErrorDetails, CommandMeta, CommandDef, CommandContext, Command, ArgsDef, ArgDef, AnyCommand };
|
package/dist/index.js
CHANGED
|
@@ -51,7 +51,7 @@ function buildParseArgsOptionDescriptor(flagsDef) {
|
|
|
51
51
|
aliasRegistry.set(name, name);
|
|
52
52
|
}
|
|
53
53
|
for (const [name, def] of Object.entries(flagsDef)) {
|
|
54
|
-
const parseType = def.type ===
|
|
54
|
+
const parseType = def.type === "boolean" ? "boolean" : "string";
|
|
55
55
|
const opt = { type: parseType };
|
|
56
56
|
if (def.multiple) {
|
|
57
57
|
opt.multiple = true;
|
|
@@ -61,7 +61,7 @@ function buildParseArgsOptionDescriptor(flagsDef) {
|
|
|
61
61
|
for (const alias of aliases) {
|
|
62
62
|
const existing = aliasRegistry.get(alias);
|
|
63
63
|
if (existing) {
|
|
64
|
-
throw new CrustError("DEFINITION", `Alias collision: "
|
|
64
|
+
throw new CrustError("DEFINITION", `Alias collision: "${alias.length === 1 ? "-" : "--"}${alias}" is used by both "--${existing}" and "--${name}"`);
|
|
65
65
|
}
|
|
66
66
|
aliasRegistry.set(alias, name);
|
|
67
67
|
aliasToName[alias] = name;
|
|
@@ -81,14 +81,14 @@ function buildParseArgsOptionDescriptor(flagsDef) {
|
|
|
81
81
|
return { options, aliasToName };
|
|
82
82
|
}
|
|
83
83
|
function coerceValue(value, type, label) {
|
|
84
|
-
if (type ===
|
|
84
|
+
if (type === "number") {
|
|
85
85
|
const num = Number(value);
|
|
86
86
|
if (Number.isNaN(num)) {
|
|
87
87
|
throw new CrustError("PARSE", `Expected number for ${label}, got "${value}"`);
|
|
88
88
|
}
|
|
89
89
|
return num;
|
|
90
90
|
}
|
|
91
|
-
if (type ===
|
|
91
|
+
if (type === "boolean") {
|
|
92
92
|
return value === "true" || value === "1";
|
|
93
93
|
}
|
|
94
94
|
return value;
|
|
@@ -104,9 +104,9 @@ function applyDefaultOrThrow(def, label) {
|
|
|
104
104
|
function coerceFlagValue(name, def, parsedValue) {
|
|
105
105
|
const label = `--${name}`;
|
|
106
106
|
if (def.multiple && Array.isArray(parsedValue)) {
|
|
107
|
-
return def.type ===
|
|
107
|
+
return def.type === "boolean" ? parsedValue.map((v) => typeof v === "boolean" ? v : Boolean(v)) : parsedValue.map((v) => coerceValue(v, def.type, label));
|
|
108
108
|
}
|
|
109
|
-
if (def.type ===
|
|
109
|
+
if (def.type === "boolean") {
|
|
110
110
|
return typeof parsedValue === "boolean" ? parsedValue : Boolean(parsedValue);
|
|
111
111
|
}
|
|
112
112
|
if (typeof parsedValue === "string") {
|
|
@@ -172,7 +172,7 @@ function resolveArgs(argsDef, positionals) {
|
|
|
172
172
|
if (def.required === true && remaining.length === 0) {
|
|
173
173
|
throw new CrustError("VALIDATION", `Missing required ${label}`);
|
|
174
174
|
}
|
|
175
|
-
resolved[name] = def.type ===
|
|
175
|
+
resolved[name] = def.type === "string" ? remaining : remaining.map((v) => coerceValue(v, def.type, `<${name}>`));
|
|
176
176
|
index = positionals.length;
|
|
177
177
|
} else if (index < positionals.length) {
|
|
178
178
|
resolved[name] = coerceValue(positionals[index], def.type, `<${name}>`);
|