@crustjs/core 0.0.16 → 0.0.18
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 +23 -1
- package/dist/index.d.ts +347 -31
- package/dist/index.js +1 -1
- package/dist/shared/chunk-1670njz2.js +2 -0
- package/dist/shared/chunk-q8y07jw2.js +3 -0
- package/package.json +5 -2
- package/dist/shared/chunk-5apf3vnv.js +0 -2
- package/dist/shared/chunk-vt64gs69.js +0 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The core library for the [Crust](https://crustjs.com) CLI framework.
|
|
4
4
|
|
|
5
|
-
Provides command definition, argument/flag parsing, subcommand routing, lifecycle hooks, and a plugin system
|
|
5
|
+
Provides command definition, argument/flag parsing, subcommand routing, lifecycle hooks, and a plugin system.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -29,6 +29,28 @@ const app = new Crust("greet")
|
|
|
29
29
|
app.execute();
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
## Built-in value types
|
|
33
|
+
|
|
34
|
+
Flags and positional arguments support six built-in `type` literals:
|
|
35
|
+
|
|
36
|
+
- `"string"` — raw string token
|
|
37
|
+
- `"number"` — coerced via `Number(raw)`
|
|
38
|
+
- `"boolean"` — toggle (`--flag` / `--no-flag`)
|
|
39
|
+
- `"url"` — `new URL(raw)` → `URL` instance
|
|
40
|
+
- `"path"` — `~` expanded, resolved to an absolute `string`
|
|
41
|
+
- `"json"` — `JSON.parse(raw)` → `unknown`
|
|
42
|
+
|
|
43
|
+
For formats that aren't built in, attach a synchronous `parse` to a `type: "string"` flag or arg. The inferred type flows from `ReturnType<parse>`:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
flags: {
|
|
47
|
+
port: { type: "string", parse: (s) => Number(s), default: "3000" },
|
|
48
|
+
}
|
|
49
|
+
// flags.port: number
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
See the [Types](https://crustjs.com/docs/guide/types) guide for the full contract, error modes, and copy-paste recipes.
|
|
53
|
+
|
|
32
54
|
## Documentation
|
|
33
55
|
|
|
34
56
|
See the full docs at [crustjs.com](https://crustjs.com).
|
package/dist/index.d.ts
CHANGED
|
@@ -21,10 +21,19 @@ interface CommandRoute {
|
|
|
21
21
|
*
|
|
22
22
|
* Resolution rules:
|
|
23
23
|
* 1. If `argv[0]` matches a subcommand key, recurse into that subcommand
|
|
24
|
-
* 2. If
|
|
25
|
-
*
|
|
24
|
+
* 2. If `argv[0]` matches a sibling's `meta.aliases` entry, recurse into
|
|
25
|
+
* that sibling and record the **canonical** name in `commandPath`
|
|
26
|
+
* 3. If no match and the current command has `run()`, return it (args passed to parser)
|
|
27
|
+
* 4. If no match and the current command has NO `run()`, it signals the caller
|
|
26
28
|
* should show help (the `showHelp` flag is set in the result)
|
|
27
|
-
*
|
|
29
|
+
* 5. Unknown subcommands produce a structured COMMAND_NOT_FOUND error whose
|
|
30
|
+
* `details.available` lists the canonical sibling names (aliases are
|
|
31
|
+
* discoverable via `details.parentCommand.subCommands[name].meta.aliases`)
|
|
32
|
+
*
|
|
33
|
+
* Implementation: linear scan over siblings on miss. Command trees are small
|
|
34
|
+
* and resolution runs once per invocation, so the cost is negligible compared
|
|
35
|
+
* to building/freezing a parallel alias→canonical map. The scan does NOT
|
|
36
|
+
* mutate `CommandNode`.
|
|
28
37
|
*
|
|
29
38
|
* @param command - The root command to resolve from
|
|
30
39
|
* @param argv - The argv array to resolve against
|
|
@@ -32,25 +41,60 @@ interface CommandRoute {
|
|
|
32
41
|
* @throws {CrustError} COMMAND_NOT_FOUND when an unknown subcommand is given and the parent has no run()
|
|
33
42
|
*/
|
|
34
43
|
declare function resolveCommand(command: CommandNode, argv: string[]): CommandRoute;
|
|
35
|
-
|
|
36
|
-
|
|
44
|
+
import { BaseValueType, ResolvePrimitive } from "@crustjs/utils";
|
|
45
|
+
/**
|
|
46
|
+
* Supported type literals for args and flags.
|
|
47
|
+
*
|
|
48
|
+
* Extends `BaseValueType` (`"string" | "number" | "boolean"`) with three
|
|
49
|
+
* formatted built-ins:
|
|
50
|
+
*
|
|
51
|
+
* - `"url"` — the raw value is parsed via `new URL()` into a {@link URL}
|
|
52
|
+
* - `"path"` — the raw value is expanded (`~`) and resolved against
|
|
53
|
+
* `process.cwd()` into an absolute `string`
|
|
54
|
+
* - `"json"` — the raw value is parsed via `JSON.parse()` into `unknown`
|
|
55
|
+
*/
|
|
56
|
+
type ValueType = BaseValueType | "url" | "path" | "json";
|
|
37
57
|
/**
|
|
38
|
-
*
|
|
58
|
+
* Resolve a {@link ValueType} literal to its runtime TypeScript type.
|
|
39
59
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
60
|
+
* Delegates to `ResolvePrimitive<T>` for the three base types; maps the
|
|
61
|
+
* three formatted types (`"url"`, `"path"`, `"json"`) to `URL`, `string`,
|
|
62
|
+
* and `unknown` respectively.
|
|
43
63
|
*/
|
|
44
|
-
type
|
|
64
|
+
type Resolve<T extends ValueType> = T extends BaseValueType ? ResolvePrimitive<T> : T extends "url" ? URL : T extends "path" ? string : T extends "json" ? unknown : never;
|
|
65
|
+
/**
|
|
66
|
+
* Resolve the inferred runtime type for a flag/arg definition.
|
|
67
|
+
*
|
|
68
|
+
* When the def declares a `parse` escape hatch (only allowed on `"string"`
|
|
69
|
+
* variants), the inferred type is `ReturnType<typeof parse>`. Otherwise it
|
|
70
|
+
* delegates to {@link Resolve} on the declared `type`.
|
|
71
|
+
*/
|
|
72
|
+
type ResolveBaseType<F> = F extends {
|
|
73
|
+
parse: (raw: string) => infer R;
|
|
74
|
+
} ? R : F extends {
|
|
75
|
+
type: infer T extends ValueType;
|
|
76
|
+
} ? Resolve<T> : never;
|
|
45
77
|
/** Shared fields present on every positional argument definition */
|
|
46
78
|
interface ArgDefBase {
|
|
47
79
|
/** The argument name (used as the key in the parsed result and in help text) */
|
|
48
80
|
name: string;
|
|
49
81
|
/** Human-readable description for help text */
|
|
50
82
|
description?: string;
|
|
51
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* When `true`, the parser throws if the argument is not provided.
|
|
85
|
+
*
|
|
86
|
+
* For variadic args, this means the array cannot be empty — the runtime
|
|
87
|
+
* value is still `T[]`, just rejected when it has length 0.
|
|
88
|
+
*/
|
|
52
89
|
required?: true;
|
|
53
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* When `true`, collects all remaining positional values into an array.
|
|
92
|
+
*
|
|
93
|
+
* The inferred TypeScript type is always `T[]` — never `T[] | undefined` —
|
|
94
|
+
* regardless of `required` or `default`. `required` only controls whether
|
|
95
|
+
* an empty array fails validation; it does not change the runtime shape
|
|
96
|
+
* or the inferred type.
|
|
97
|
+
*/
|
|
54
98
|
variadic?: true;
|
|
55
99
|
}
|
|
56
100
|
/** A positional argument whose value is a string */
|
|
@@ -58,18 +102,68 @@ interface StringArgDef extends ArgDefBase {
|
|
|
58
102
|
type: "string";
|
|
59
103
|
/** Default string value when the argument is not provided */
|
|
60
104
|
default?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Static enum of valid values for this argument.
|
|
107
|
+
*
|
|
108
|
+
* Validated at parse time before `parse` runs. Passing a value outside
|
|
109
|
+
* `choices` throws `CrustError("PARSE", …)` before any `parse` transform
|
|
110
|
+
* is applied. Also consumed by shell-completion plugins
|
|
111
|
+
* (e.g. `@crustjs/plugins/completion`) to emit value candidates.
|
|
112
|
+
*
|
|
113
|
+
* Only available on string-typed args; not supported on number/boolean.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* { name: "target", type: "string", choices: ["browser", "bun", "node"] }
|
|
117
|
+
*/
|
|
118
|
+
choices?: readonly string[];
|
|
119
|
+
/**
|
|
120
|
+
* Custom synchronous parser for the raw argv string. Runs per element
|
|
121
|
+
* for variadic args. See {@link StringFlagDef.parse} for full semantics.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* { name: "port", type: "string", parse: (s) => Number(s) }
|
|
125
|
+
*/
|
|
126
|
+
parse?: (raw: string) => unknown;
|
|
61
127
|
}
|
|
62
128
|
/** A positional argument whose value is a number */
|
|
63
129
|
interface NumberArgDef extends ArgDefBase {
|
|
64
130
|
type: "number";
|
|
65
131
|
/** Default number value when the argument is not provided */
|
|
66
132
|
default?: number;
|
|
133
|
+
/** Not supported on number args — use `type: "string"` with `parse`. */
|
|
134
|
+
parse?: never;
|
|
67
135
|
}
|
|
68
136
|
/** A positional argument whose value is a boolean */
|
|
69
137
|
interface BooleanArgDef extends ArgDefBase {
|
|
70
138
|
type: "boolean";
|
|
71
139
|
/** Default boolean value when the argument is not provided */
|
|
72
140
|
default?: boolean;
|
|
141
|
+
/** Not supported on boolean args — use `type: "string"` with `parse`. */
|
|
142
|
+
parse?: never;
|
|
143
|
+
}
|
|
144
|
+
/** A positional argument whose value is a {@link URL} */
|
|
145
|
+
interface UrlArgDef extends ArgDefBase {
|
|
146
|
+
type: "url";
|
|
147
|
+
/** Default URL value when the argument is not provided */
|
|
148
|
+
default?: URL;
|
|
149
|
+
/** Not supported on url args — use `type: "string"` with `parse`. */
|
|
150
|
+
parse?: never;
|
|
151
|
+
}
|
|
152
|
+
/** A positional argument whose value is an absolute filesystem path */
|
|
153
|
+
interface PathArgDef extends ArgDefBase {
|
|
154
|
+
type: "path";
|
|
155
|
+
/** Default path string when the argument is not provided */
|
|
156
|
+
default?: string;
|
|
157
|
+
/** Not supported on path args — use `type: "string"` with `parse`. */
|
|
158
|
+
parse?: never;
|
|
159
|
+
}
|
|
160
|
+
/** A positional argument whose value is JSON parsed to `unknown` */
|
|
161
|
+
interface JsonArgDef extends ArgDefBase {
|
|
162
|
+
type: "json";
|
|
163
|
+
/** Default parsed JSON value when the argument is not provided */
|
|
164
|
+
default?: unknown;
|
|
165
|
+
/** Not supported on json args — use `type: "string"` with `parse`. */
|
|
166
|
+
parse?: never;
|
|
73
167
|
}
|
|
74
168
|
/**
|
|
75
169
|
* Defines a single positional argument for a CLI command.
|
|
@@ -86,7 +180,16 @@ interface BooleanArgDef extends ArgDefBase {
|
|
|
86
180
|
* ] as const satisfies ArgsDef;
|
|
87
181
|
* ```
|
|
88
182
|
*/
|
|
89
|
-
|
|
183
|
+
interface RawArgDef extends ArgDefBase {
|
|
184
|
+
/** Optional parser hint. Omit for raw schema-backed validation. */
|
|
185
|
+
type?: never;
|
|
186
|
+
/** Raw default value when the argument is not provided */
|
|
187
|
+
default?: unknown;
|
|
188
|
+
choices?: readonly string[];
|
|
189
|
+
/** Not supported on raw args — schema validators own the transform. */
|
|
190
|
+
parse?: never;
|
|
191
|
+
}
|
|
192
|
+
type ArgDef = StringArgDef | NumberArgDef | BooleanArgDef | UrlArgDef | PathArgDef | JsonArgDef | RawArgDef;
|
|
90
193
|
/** Ordered tuple of positional argument definitions */
|
|
91
194
|
type ArgsDef = readonly ArgDef[];
|
|
92
195
|
/** Shared fields present on every flag definition */
|
|
@@ -112,12 +215,49 @@ interface StringFlagDef extends SingleFlagBase {
|
|
|
112
215
|
type: "string";
|
|
113
216
|
/** Default string value */
|
|
114
217
|
default?: string;
|
|
218
|
+
/**
|
|
219
|
+
* Static enum of valid values for this flag.
|
|
220
|
+
*
|
|
221
|
+
* Validated at parse time before `parse` runs. Passing a value outside
|
|
222
|
+
* `choices` throws `CrustError("PARSE", …)` before any `parse` transform
|
|
223
|
+
* is applied. Also consumed by shell-completion plugins
|
|
224
|
+
* (e.g. `@crustjs/plugins/completion`) to emit value candidates.
|
|
225
|
+
*
|
|
226
|
+
* Only available on string-typed flags; not supported on number/boolean.
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* { type: "string", choices: ["browser", "bun", "node"] }
|
|
230
|
+
*/
|
|
231
|
+
choices?: readonly string[];
|
|
232
|
+
/**
|
|
233
|
+
* Custom synchronous parser for the raw argv string.
|
|
234
|
+
*
|
|
235
|
+
* Receives the raw token as it appeared on the command line (after
|
|
236
|
+
* `choices` validation, when present) and returns the resolved value
|
|
237
|
+
* that flows to the `run` handler. The return type is inferred and
|
|
238
|
+
* becomes the flag's runtime type.
|
|
239
|
+
*
|
|
240
|
+
* Constraints:
|
|
241
|
+
* - Synchronous only. `async` parsers are rejected at command setup
|
|
242
|
+
* with `CrustError("CONFIG", …)`.
|
|
243
|
+
* - Only allowed on `type: "string"` (single + multi) and string args.
|
|
244
|
+
* `parse?: never` on every non-string variant prevents misuse at
|
|
245
|
+
* compile time.
|
|
246
|
+
* - When `default` is set and argv is absent, `parse(String(default))`
|
|
247
|
+
* runs so the runtime value matches the inferred type.
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* { type: "string", parse: (s) => Number(s) }
|
|
251
|
+
*/
|
|
252
|
+
parse?: (raw: string) => unknown;
|
|
115
253
|
}
|
|
116
254
|
/** A single-value number flag */
|
|
117
255
|
interface NumberFlagDef extends SingleFlagBase {
|
|
118
256
|
type: "number";
|
|
119
257
|
/** Default number value */
|
|
120
258
|
default?: number;
|
|
259
|
+
/** Not supported on number flags — use `type: "string"` with `parse`. */
|
|
260
|
+
parse?: never;
|
|
121
261
|
}
|
|
122
262
|
/** A single-value boolean flag */
|
|
123
263
|
interface BooleanFlagDef extends SingleFlagBase {
|
|
@@ -126,6 +266,32 @@ interface BooleanFlagDef extends SingleFlagBase {
|
|
|
126
266
|
default?: boolean;
|
|
127
267
|
/** When `true`, hide the generated `--no-{name}` help label */
|
|
128
268
|
noNegate?: true;
|
|
269
|
+
/** Not supported on boolean flags — use `type: "string"` with `parse`. */
|
|
270
|
+
parse?: never;
|
|
271
|
+
}
|
|
272
|
+
/** A single-value URL flag (parsed via `new URL()`) */
|
|
273
|
+
interface UrlFlagDef extends SingleFlagBase {
|
|
274
|
+
type: "url";
|
|
275
|
+
/** Default URL value */
|
|
276
|
+
default?: URL;
|
|
277
|
+
/** Not supported on url flags — use `type: "string"` with `parse`. */
|
|
278
|
+
parse?: never;
|
|
279
|
+
}
|
|
280
|
+
/** A single-value path flag (expanded `~` + resolved against `process.cwd()`) */
|
|
281
|
+
interface PathFlagDef extends SingleFlagBase {
|
|
282
|
+
type: "path";
|
|
283
|
+
/** Default path string value */
|
|
284
|
+
default?: string;
|
|
285
|
+
/** Not supported on path flags — use `type: "string"` with `parse`. */
|
|
286
|
+
parse?: never;
|
|
287
|
+
}
|
|
288
|
+
/** A single-value JSON flag (parsed via `JSON.parse()` to `unknown`) */
|
|
289
|
+
interface JsonFlagDef extends SingleFlagBase {
|
|
290
|
+
type: "json";
|
|
291
|
+
/** Default parsed JSON value */
|
|
292
|
+
default?: unknown;
|
|
293
|
+
/** Not supported on json flags — use `type: "string"` with `parse`. */
|
|
294
|
+
parse?: never;
|
|
129
295
|
}
|
|
130
296
|
/** Base for multi-value flags — `multiple` is required as `true` */
|
|
131
297
|
interface MultiFlagBase extends FlagDefBase {
|
|
@@ -137,12 +303,34 @@ interface StringMultiFlagDef extends MultiFlagBase {
|
|
|
137
303
|
type: "string";
|
|
138
304
|
/** Default string array value */
|
|
139
305
|
default?: string[];
|
|
306
|
+
/**
|
|
307
|
+
* Static enum of valid values for each occurrence of this flag.
|
|
308
|
+
*
|
|
309
|
+
* Each element is validated at parse time before `parse` runs. Passing
|
|
310
|
+
* a value outside `choices` throws `CrustError("PARSE", …)` before any
|
|
311
|
+
* `parse` transform is applied. Also consumed by shell-completion
|
|
312
|
+
* plugins (e.g. `@crustjs/plugins/completion`) to emit value candidates.
|
|
313
|
+
*
|
|
314
|
+
* Only available on string-typed multi-flags; not supported on number/boolean.
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* { type: "string", multiple: true, choices: ["unit", "integration"] }
|
|
318
|
+
*/
|
|
319
|
+
choices?: readonly string[];
|
|
320
|
+
/**
|
|
321
|
+
* Custom synchronous per-element parser for each raw argv string.
|
|
322
|
+
* See {@link StringFlagDef.parse} for full semantics. Runs once per
|
|
323
|
+
* occurrence; the resolved value is `ReturnType<typeof parse>[]`.
|
|
324
|
+
*/
|
|
325
|
+
parse?: (raw: string) => unknown;
|
|
140
326
|
}
|
|
141
327
|
/** A multi-value number flag (collects repeated values into an array) */
|
|
142
328
|
interface NumberMultiFlagDef extends MultiFlagBase {
|
|
143
329
|
type: "number";
|
|
144
330
|
/** Default number array value */
|
|
145
331
|
default?: number[];
|
|
332
|
+
/** Not supported — use `type: "string"`, `multiple: true`, with `parse`. */
|
|
333
|
+
parse?: never;
|
|
146
334
|
}
|
|
147
335
|
/** A multi-value boolean flag (collects repeated values into an array) */
|
|
148
336
|
interface BooleanMultiFlagDef extends MultiFlagBase {
|
|
@@ -151,6 +339,32 @@ interface BooleanMultiFlagDef extends MultiFlagBase {
|
|
|
151
339
|
default?: boolean[];
|
|
152
340
|
/** When `true`, hide the generated `--no-{name}` help label */
|
|
153
341
|
noNegate?: true;
|
|
342
|
+
/** Not supported — use `type: "string"`, `multiple: true`, with `parse`. */
|
|
343
|
+
parse?: never;
|
|
344
|
+
}
|
|
345
|
+
/** A multi-value URL flag (collects repeated URL values into an array) */
|
|
346
|
+
interface UrlMultiFlagDef extends MultiFlagBase {
|
|
347
|
+
type: "url";
|
|
348
|
+
/** Default URL array value */
|
|
349
|
+
default?: URL[];
|
|
350
|
+
/** Not supported — use `type: "string"`, `multiple: true`, with `parse`. */
|
|
351
|
+
parse?: never;
|
|
352
|
+
}
|
|
353
|
+
/** A multi-value path flag (collects repeated path strings into an array) */
|
|
354
|
+
interface PathMultiFlagDef extends MultiFlagBase {
|
|
355
|
+
type: "path";
|
|
356
|
+
/** Default path array value */
|
|
357
|
+
default?: string[];
|
|
358
|
+
/** Not supported — use `type: "string"`, `multiple: true`, with `parse`. */
|
|
359
|
+
parse?: never;
|
|
360
|
+
}
|
|
361
|
+
/** A multi-value JSON flag (collects repeated parsed JSON values) */
|
|
362
|
+
interface JsonMultiFlagDef extends MultiFlagBase {
|
|
363
|
+
type: "json";
|
|
364
|
+
/** Default parsed JSON array value */
|
|
365
|
+
default?: unknown[];
|
|
366
|
+
/** Not supported — use `type: "string"`, `multiple: true`, with `parse`. */
|
|
367
|
+
parse?: never;
|
|
154
368
|
}
|
|
155
369
|
/**
|
|
156
370
|
* Defines a single named flag for a CLI command.
|
|
@@ -167,7 +381,7 @@ interface BooleanMultiFlagDef extends MultiFlagBase {
|
|
|
167
381
|
* } satisfies FlagsDef;
|
|
168
382
|
* ```
|
|
169
383
|
*/
|
|
170
|
-
type FlagDef = StringFlagDef | NumberFlagDef | BooleanFlagDef | StringMultiFlagDef | NumberMultiFlagDef | BooleanMultiFlagDef;
|
|
384
|
+
type FlagDef = StringFlagDef | NumberFlagDef | BooleanFlagDef | UrlFlagDef | PathFlagDef | JsonFlagDef | StringMultiFlagDef | NumberMultiFlagDef | BooleanMultiFlagDef | UrlMultiFlagDef | PathMultiFlagDef | JsonMultiFlagDef;
|
|
171
385
|
/** Record mapping flag names to their definitions */
|
|
172
386
|
type FlagsDef = Record<string, FlagDef>;
|
|
173
387
|
/**
|
|
@@ -385,17 +599,30 @@ type EffectiveFlags<
|
|
|
385
599
|
/**
|
|
386
600
|
* Infer the resolved type for a single ArgDef:
|
|
387
601
|
*
|
|
388
|
-
* - **variadic** → `primitive[]`
|
|
602
|
+
* - **variadic** → `primitive[]` (always an array, never `undefined`,
|
|
603
|
+
* regardless of `required` or `default`)
|
|
389
604
|
* - **required** or **has default** → `primitive` (non-optional)
|
|
390
605
|
* - otherwise → `primitive | undefined`
|
|
606
|
+
*
|
|
607
|
+
* The variadic branch is checked first and takes precedence. Combining
|
|
608
|
+
* `variadic: true` with `required: true` keeps the inferred type as `T[]`;
|
|
609
|
+
* `required` only gates empty-array validation, not the type.
|
|
391
610
|
*/
|
|
392
611
|
type InferArgValue<A extends ArgDef> = A extends {
|
|
612
|
+
type: infer _T extends ValueType;
|
|
613
|
+
} ? A extends {
|
|
393
614
|
variadic: true;
|
|
394
|
-
} ?
|
|
615
|
+
} ? ResolveBaseType<A>[] : A extends {
|
|
395
616
|
required: true;
|
|
396
|
-
} ?
|
|
397
|
-
default:
|
|
398
|
-
} ?
|
|
617
|
+
} ? ResolveBaseType<A> : A extends {
|
|
618
|
+
default: unknown;
|
|
619
|
+
} ? ResolveBaseType<A> : ResolveBaseType<A> | undefined : A extends {
|
|
620
|
+
variadic: true;
|
|
621
|
+
} ? unknown[] : A extends {
|
|
622
|
+
required: true;
|
|
623
|
+
} | {
|
|
624
|
+
default: unknown;
|
|
625
|
+
} ? unknown : unknown;
|
|
399
626
|
/**
|
|
400
627
|
* Recursively converts an ArgsDef tuple into a named object type.
|
|
401
628
|
*
|
|
@@ -427,16 +654,18 @@ type InferArgs<A> = A extends ArgsDef ? Simplify<InferArgsTuple<A>> : Record<str
|
|
|
427
654
|
* - otherwise → `primitive | undefined`
|
|
428
655
|
*/
|
|
429
656
|
type InferFlagValue<F extends FlagDef> = F extends {
|
|
657
|
+
type: infer _T extends ValueType;
|
|
658
|
+
} ? F extends {
|
|
430
659
|
multiple: true;
|
|
431
660
|
} ? F extends {
|
|
432
661
|
required: true;
|
|
433
|
-
} ?
|
|
434
|
-
default:
|
|
435
|
-
} ?
|
|
662
|
+
} ? ResolveBaseType<F>[] : F extends {
|
|
663
|
+
default: readonly unknown[];
|
|
664
|
+
} ? ResolveBaseType<F>[] : ResolveBaseType<F>[] | undefined : F extends {
|
|
436
665
|
required: true;
|
|
437
|
-
} ?
|
|
438
|
-
default:
|
|
439
|
-
} ?
|
|
666
|
+
} ? ResolveBaseType<F> : F extends {
|
|
667
|
+
default: unknown;
|
|
668
|
+
} ? ResolveBaseType<F> : ResolveBaseType<F> | undefined : never;
|
|
440
669
|
/**
|
|
441
670
|
* Maps a full FlagsDef record to resolved flag types.
|
|
442
671
|
*
|
|
@@ -458,6 +687,65 @@ interface CommandMeta {
|
|
|
458
687
|
description?: string;
|
|
459
688
|
/** Custom usage string (overrides auto-generated usage) */
|
|
460
689
|
usage?: string;
|
|
690
|
+
/**
|
|
691
|
+
* Alternative names that resolve to the same command.
|
|
692
|
+
*
|
|
693
|
+
* Each entry is a sibling-level alternative for `name`. For example,
|
|
694
|
+
* `meta: { name: "issue", aliases: ["issues", "i"] }` makes `cli issue`,
|
|
695
|
+
* `cli issues`, and `cli i` all route to the same command node.
|
|
696
|
+
*
|
|
697
|
+
* **Conflict policy.** Alias strings must not collide with this command's
|
|
698
|
+
* own canonical `name`, with any sibling's `name`, or with any sibling's
|
|
699
|
+
* own alias. Collisions throw a `CrustError("DEFINITION", …)` at
|
|
700
|
+
* registration time (or during `validateCommandTree` for plugin-installed
|
|
701
|
+
* subcommands). Each alias must also be a non-empty string with no
|
|
702
|
+
* whitespace and must not start with `-`.
|
|
703
|
+
*
|
|
704
|
+
* **Display contract.** Help output renders the canonical name with
|
|
705
|
+
* aliases inline as `name (a, b, c)`. The canonical `name` is what
|
|
706
|
+
* appears in `commandPath`, error messages, and suggestions from
|
|
707
|
+
* `didYouMeanPlugin` — it does not depend on which alias the user typed.
|
|
708
|
+
*
|
|
709
|
+
* @example
|
|
710
|
+
* meta: { name: "issue", aliases: ["issues", "i"] }
|
|
711
|
+
*/
|
|
712
|
+
aliases?: readonly string[];
|
|
713
|
+
/**
|
|
714
|
+
* When `true`, omit this command from every tooling surface that
|
|
715
|
+
* enumerates the command tree for users:
|
|
716
|
+
*
|
|
717
|
+
* - `helpPlugin` rendered output (subcommand list + USAGE token)
|
|
718
|
+
* - `@crustjs/man` generated man pages (`SUBCOMMANDS` section)
|
|
719
|
+
* - `completionPlugin` candidate lists (recursively — hidden
|
|
720
|
+
* subcommands and their descendants never appear in generated
|
|
721
|
+
* bash/zsh/fish scripts)
|
|
722
|
+
* - `didYouMeanPlugin` typo suggestions and "Available commands"
|
|
723
|
+
* list (so internal names never surface in error UX)
|
|
724
|
+
* - `skillPlugin` manifests
|
|
725
|
+
*
|
|
726
|
+
* The command is **only hidden from listings**: routing in
|
|
727
|
+
* `@crustjs/core` does not consult `meta.hidden`, so it stays fully
|
|
728
|
+
* invocable by direct name (or alias). The intended use case is
|
|
729
|
+
* internal/runtime commands like a `__complete` shell-completion
|
|
730
|
+
* entrypoint. Marking a user-facing command `hidden` is supported but
|
|
731
|
+
* unusual.
|
|
732
|
+
*
|
|
733
|
+
* **Scope: commands only.** There is no analogous `hidden` field on
|
|
734
|
+
* `FlagDef` or `ArgDef`; flags and positional arguments always surface
|
|
735
|
+
* in help, completion, and man output. If you need a flag that does
|
|
736
|
+
* not advertise itself, the workaround is to register it through a
|
|
737
|
+
* plugin's `setup()` hook without describing it (omit `description`),
|
|
738
|
+
* which suppresses its description body but still lists the spelling
|
|
739
|
+
* — there is intentionally no full hide mechanism at the flag layer.
|
|
740
|
+
*
|
|
741
|
+
* Tooling contract: any renderer or generator that walks
|
|
742
|
+
* `subCommands` to produce a user-facing listing should skip nodes
|
|
743
|
+
* where `meta.hidden === true`.
|
|
744
|
+
*
|
|
745
|
+
* @example
|
|
746
|
+
* meta: { name: "__complete", hidden: true, description: "Internal" }
|
|
747
|
+
*/
|
|
748
|
+
hidden?: boolean;
|
|
461
749
|
}
|
|
462
750
|
/**
|
|
463
751
|
* The result of parsing argv against a command's arg/flag definitions.
|
|
@@ -587,11 +875,30 @@ interface CrustCommandContext<
|
|
|
587
875
|
* Build-time validation protocol.
|
|
588
876
|
*
|
|
589
877
|
* `crust build` spawns the user's entrypoint as a subprocess with
|
|
590
|
-
* `CRUST_INTERNAL_VALIDATE_ONLY=1
|
|
591
|
-
*
|
|
878
|
+
* `CRUST_INTERNAL_VALIDATE_ONLY=1` (and the companion
|
|
879
|
+
* {@link VALIDATION_FORCE_EXIT_ENV}=`1`). When `.execute()` detects
|
|
880
|
+
* `VALIDATION_MODE_ENV` it runs the validation pipeline and surfaces errors
|
|
881
|
+
* via stderr and `process.exitCode`.
|
|
882
|
+
*
|
|
883
|
+
* Process termination is opt-in via {@link VALIDATION_FORCE_EXIT_ENV} so
|
|
884
|
+
* that in-process callers (tests, embedders) that set only this env get
|
|
885
|
+
* the validation result without having their host process killed.
|
|
592
886
|
*/
|
|
593
887
|
declare const VALIDATION_MODE_ENV = "CRUST_INTERNAL_VALIDATE_ONLY";
|
|
594
888
|
/**
|
|
889
|
+
* Companion to {@link VALIDATION_MODE_ENV}. When set to `"1"` _alongside_
|
|
890
|
+
* `VALIDATION_MODE_ENV`, `.execute()` calls `process.exit()` after the
|
|
891
|
+
* validation pipeline completes — ensuring any code that follows
|
|
892
|
+
* `await app.execute()` in the user's entrypoint does not run during
|
|
893
|
+
* `crust build`'s pre-compile validation subprocess.
|
|
894
|
+
*
|
|
895
|
+
* Without this flag, `.execute()` only sets `process.exitCode` and returns,
|
|
896
|
+
* matching the rest of `.execute()`'s error handling. This is the path
|
|
897
|
+
* in-process callers (tests that toggle `VALIDATION_MODE_ENV`, programmatic
|
|
898
|
+
* embedders) take so the host event loop is not terminated.
|
|
899
|
+
*/
|
|
900
|
+
declare const VALIDATION_FORCE_EXIT_ENV = "CRUST_INTERNAL_VALIDATE_FORCE_EXIT";
|
|
901
|
+
/**
|
|
595
902
|
* Chainable builder for defining CLI commands with full type inference.
|
|
596
903
|
*
|
|
597
904
|
* Generic parameters:
|
|
@@ -649,14 +956,20 @@ declare class Crust<
|
|
|
649
956
|
* Set metadata (description, usage) for this command.
|
|
650
957
|
*
|
|
651
958
|
* The command name is already set by the builder source (constructor,
|
|
652
|
-
* `.sub()`, or the child builder passed into `.command(name, cb)`)
|
|
653
|
-
*
|
|
959
|
+
* `.sub()`, or the child builder passed into `.command(name, cb)`).
|
|
960
|
+
* Provide `description`, `usage`, and/or `aliases` here.
|
|
654
961
|
*
|
|
655
962
|
* Returns a new builder with updated metadata. The original builder
|
|
656
963
|
* is not mutated.
|
|
657
964
|
*
|
|
658
|
-
* @param meta - Metadata fields to set (description, usage)
|
|
965
|
+
* @param meta - Metadata fields to set (description, usage, aliases)
|
|
659
966
|
* @returns A new `Crust` instance with updated metadata
|
|
967
|
+
* @example
|
|
968
|
+
* ```ts
|
|
969
|
+
* .command("issue", (cmd) =>
|
|
970
|
+
* cmd.meta({ aliases: ["issues", "i"] }).run(() => {})
|
|
971
|
+
* )
|
|
972
|
+
* ```
|
|
660
973
|
*/
|
|
661
974
|
meta(meta: Omit<CommandMeta, "name">): Crust<Inherited, Local, A, Eff>;
|
|
662
975
|
/**
|
|
@@ -840,6 +1153,7 @@ interface CrustErrorDetailsMap {
|
|
|
840
1153
|
PARSE: undefined;
|
|
841
1154
|
EXECUTION: undefined;
|
|
842
1155
|
COMMAND_NOT_FOUND: CommandNotFoundErrorDetails;
|
|
1156
|
+
CONFIG: undefined;
|
|
843
1157
|
}
|
|
844
1158
|
/**
|
|
845
1159
|
* All possible error codes emitted by Crust.
|
|
@@ -848,6 +1162,8 @@ interface CrustErrorDetailsMap {
|
|
|
848
1162
|
* - `VALIDATION` — Missing required arguments or flags
|
|
849
1163
|
* - `PARSE` — Argv parsing failures (unknown flags, type coercion)
|
|
850
1164
|
* - `EXECUTION` — Runtime command/middleware failures
|
|
1165
|
+
* - `COMMAND_NOT_FOUND` — Unrecognised subcommand at the current level
|
|
1166
|
+
* - `CONFIG` — Unsupported command/flag definition surfaced at setup time (e.g. async `parse`)
|
|
851
1167
|
*
|
|
852
1168
|
* @example
|
|
853
1169
|
* ```ts
|
|
@@ -931,4 +1247,4 @@ declare function parseArgs<
|
|
|
931
1247
|
* @throws {CrustError} On missing required args or flags
|
|
932
1248
|
*/
|
|
933
1249
|
declare function validateParsed(command: CommandNode, parsed: ParseResult): void;
|
|
934
|
-
export { validateParsed, resolveCommand, parseArgs, ValueType, ValidateVariadicArgs, ValidateNoPrefixedFlags, ValidateFlagAliases, ValidateCrossCollisions, VALIDATION_MODE_ENV, SetupContext, SetupActions, PluginMiddleware, ParseResult, MiddlewareContext, MergeFlags, InheritableFlags, InferFlags, InferArgs, FlagsDef, FlagDef, EffectiveFlags, CrustPlugin, CrustErrorCode, CrustError, CrustCommandContext, Crust, CommandRoute, CommandNode, CommandMeta, ArgsDef, ArgDef };
|
|
1250
|
+
export { validateParsed, resolveCommand, parseArgs, ValueType, ValidateVariadicArgs, ValidateNoPrefixedFlags, ValidateFlagAliases, ValidateCrossCollisions, VALIDATION_MODE_ENV, VALIDATION_FORCE_EXIT_ENV, SetupContext, SetupActions, ResolveBaseType, Resolve, PluginMiddleware, ParseResult, MiddlewareContext, MergeFlags, InheritableFlags, InferFlags, InferArgs, FlagsDef, FlagDef, EffectiveFlags, CrustPlugin, CrustErrorCode, CrustError, CrustCommandContext, Crust, CommandRoute, CommandNode, CommandMeta, ArgsDef, ArgDef };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{a as O,b as
|
|
2
|
+
import{a as O,b as K,c as T,d as I,f as B}from"./shared/chunk-q8y07jw2.js";function P(j){return{meta:{name:j},localFlags:{},effectiveFlags:{},args:void 0,subCommands:{},plugins:[],preRun:void 0,run:void 0,postRun:void 0}}function X(j,J){let q={};for(let[Q,Z]of Object.entries(j))if(Z.inherit===!0)q[Q]=Z;for(let[Q,Z]of Object.entries(J))q[Q]=Z;return q}function E(j,J){for(let[q,Q]of Object.entries(j)){let Z=Q.meta.aliases;if(!Z)continue;if(Z.includes(J))return{canonicalName:q,node:Q}}return null}function D(j,J){let q=[j.meta.name],Q=j,Z=J;while(Z.length>0){let $=Q.subCommands;if(!$||Object.keys($).length===0)break;let G=Z[0];if(!G||G.startsWith("-"))break;if(G in $&&$[G]){Q=$[G],q.push(G),Z=Z.slice(1);continue}let H=E($,G);if(H){Q=H.node,q.push(H.canonicalName),Z=Z.slice(1);continue}if(Q.run)break;throw new K("COMMAND_NOT_FOUND",`Unknown command "${G}".`,{input:G,available:Object.keys($),commandPath:[...q],parentCommand:Q})}return{command:Q,argv:Z,commandPath:q}}function f(j){for(let[J,q]of Object.entries(j)){if(J.startsWith("no-")){let Q=J.slice(3);throw new K("DEFINITION",`Flag "--${J}" must not use "no-" prefix; define "${Q}" and negate with "--no-${Q}"`)}if(q.short?.startsWith("no-"))throw new K("DEFINITION",`Short alias "-${q.short}" on "--${J}" must not use "no-" prefix (reserved for negation)`);if(q.aliases){for(let Q of q.aliases)if(Q.startsWith("no-"))throw new K("DEFINITION",`Alias "--${Q}" on "--${J}" must not use "no-" prefix (reserved for negation)`)}}}var N="CRUST_INTERNAL_VALIDATE_ONLY",b="CRUST_INTERNAL_VALIDATE_FORCE_EXIT",x=130,C="__CRUST_VALIDATE_RESULT__";function k(){let j=new Map;return{get(J){return j.get(J)},has(J){return j.has(J)},set(J,q){j.set(J,q)},delete(J){return j.delete(J)}}}function L(j){if(!(j instanceof Error))return!1;return j.name==="CancelledError"}function h(j,J){j.effectiveFlags=X(J,j.localFlags);for(let q of Object.values(j.subCommands))h(q,j.effectiveFlags)}function y(j){return{addFlag(J,q,Q){if(q in J.effectiveFlags)j?.push(`Plugin flag "--${q}" on "${J.meta.name}" overrides existing flag`);J.effectiveFlags[q]=Q},addSubCommand(J,q,Q){if(!q.trim())throw new K("DEFINITION","addSubCommand: name must be a non-empty string");if(J.subCommands[q]){j?.push(`Plugin subcommand "${q}" on "${J.meta.name}" skipped (already exists)`);return}try{B({canonicalName:q,aliases:Q.meta.aliases},J.subCommands,q)}catch(Z){let $=Z instanceof Error?Z.message:String(Z);j?.push(`Plugin subcommand "${q}" on "${J.meta.name}" skipped: ${$}`);return}h(Q,J.effectiveFlags),J.subCommands[q]=Q}}}async function F(j,J,q){for(let Q of j){if(!Q.setup)continue;await Q.setup(J,q)}}async function A(j,J,q){let Q=j.map((G)=>G.middleware).filter((G)=>Boolean(G)),Z=-1,$=async(G)=>{if(G<=Z)throw new K("DEFINITION","Plugin middleware called next() multiple times");if(Z=G,G===Q.length){await q();return}let H=Q[G];if(!H)throw new K("DEFINITION","Plugin middleware stack is invalid");await H(J,()=>$(G+1))};await $(0)}function S(j){let J=[...j.plugins];for(let q of Object.values(j.subCommands))J.push(...S(q));return J}function w(j){let J={};for(let[q,Q]of Object.entries(j))J[q]={...Q,aliases:Q.aliases?[...Q.aliases]:void 0};return J}function v(j){let J={};for(let[q,Q]of Object.entries(j.subCommands))J[q]=v(Q);return{meta:{...j.meta},localFlags:w(j.localFlags),effectiveFlags:w(j.effectiveFlags),args:j.args?j.args.map((q)=>({...q})):void 0,subCommands:J,plugins:[...j.plugins],preRun:j.preRun,run:j.run,postRun:j.postRun}}function _(j){if(Object.freeze(j),Object.freeze(j.localFlags),Object.freeze(j.effectiveFlags),Object.freeze(j.meta),Object.freeze(j.plugins),j.args)Object.freeze(j.args);for(let J of Object.values(j.subCommands))_(J);Object.freeze(j.subCommands)}class M{_node;_inheritedFlags;constructor(j){if(!j.trim())throw new K("DEFINITION","meta.name must be a non-empty string");this._node=P(j),this._inheritedFlags={}}static _createChild(j,J){let q=new M(j);return q._inheritedFlags=J,q}_clone(j){let J=Object.create(Object.getPrototypeOf(this)),q={...this._node,localFlags:{...this._node.localFlags},effectiveFlags:{...this._node.effectiveFlags},subCommands:{...this._node.subCommands},plugins:[...this._node.plugins],meta:{...this._node.meta},args:this._node.args?[...this._node.args]:void 0,...j};return J._node=q,J._inheritedFlags=this._inheritedFlags,J}meta(j){return this._clone({meta:{...this._node.meta,...j}})}flags(j){f(j);let J={};for(let[q,Q]of Object.entries(j))J[q]={...Q};return this._clone({localFlags:J,effectiveFlags:X(this._inheritedFlags,J)})}args(j){let J=j.map((q)=>({...q}));return this._clone({args:J})}run(j){return this._clone({run:j})}preRun(j){return this._clone({preRun:j})}postRun(j){return this._clone({postRun:j})}use(j){return this._clone({plugins:[...this._node.plugins,j]})}sub(j){if(!j.trim())throw new K("DEFINITION","Subcommand name must be a non-empty string");let J=X(this._inheritedFlags,this._node.localFlags);return M._createChild(j,J)}command(j,J){if(typeof j==="string"){let $=j;if(!J)throw new K("DEFINITION","command(name, cb) requires a callback");if(!$.trim())throw new K("DEFINITION","Subcommand name must be a non-empty string");if(this._node.subCommands[$])throw new K("DEFINITION",`Subcommand "${$}" is already registered`);let G=X(this._inheritedFlags,this._node.localFlags),H=M._createChild($,G),z=J(H);B({canonicalName:$,aliases:z._node.meta.aliases},this._node.subCommands,$);let W={...z._node,effectiveFlags:X(z._inheritedFlags,z._node.localFlags)};return this._clone({subCommands:{...this._node.subCommands,[$]:W}})}let q=j,Q=q._node.meta.name;if(!Q.trim())throw new K("DEFINITION","Subcommand name must be a non-empty string");if(this._node.subCommands[Q])throw new K("DEFINITION",`Subcommand "${Q}" is already registered`);B({canonicalName:Q,aliases:q._node.meta.aliases},this._node.subCommands,Q);let Z={...q._node,effectiveFlags:X(q._inheritedFlags,q._node.localFlags)};return this._clone({subCommands:{...this._node.subCommands,[Q]:Z}})}async prepareCommandTree(j){let J=j?.argv??[],q=v(this._node),Q=S(q),Z=[],$=k(),G={argv:[...J],rootCommand:q,state:$},H=y(Z);try{await F(Q,G,H)}catch(W){if(L(W))throw W;if(W instanceof K)throw W;if(W instanceof Error)throw W;throw new K("DEFINITION",String(W))}_(q);let{validateCommandTree:z}=await import("./shared/chunk-1670njz2.js");return z(q),{root:q,warnings:Z}}async execute(j){let J=j?.argv??process.argv.slice(2),q=this._node,Q=S(q),Z=[],$=k(),G={argv:[...J],rootCommand:q,state:$},H=y(Z);try{await F(Q,G,H)}catch(W){if(L(W)){process.exitCode=x;return}if(W instanceof K){console.error(`Error: ${W.message}`),process.exitCode=1;return}let U=W instanceof Error?W.message:String(W);console.error(`Error: ${U}`),process.exitCode=1;return}if(_(q),process.env[N]==="1"){let W=(async()=>{try{let{validateCommandTree:U}=await import("./shared/chunk-1670njz2.js");U(q);for(let Y of Z)console.warn(`Warning: ${Y}`);return{ok:!0}}catch(U){let Y=U instanceof Error?U.message:String(U);return console.error(Y),process.exitCode=1,{ok:!1,error:U}}})();if(globalThis[C]=W,await W,process.env[b]==="1")return process.exit(process.exitCode??0);return}for(let W of Z)console.warn(`Warning: ${W}`);let z={argv:[...J],rootCommand:q,state:$,route:null,input:null};try{let W,U;try{let Y=D(q,[...J]);z.route=Y,W=Y.command,U=T(W,Y.argv),z.input=U}catch(Y){await A(Q,z,async()=>{throw Y});return}await A(Q,z,async()=>{if(I(W,U),!W.run)return;let Y={args:U.args,flags:U.flags,rawArgs:U.rawArgs,command:W},R;try{if(W.preRun)await W.preRun(Y);await W.run(Y)}catch(V){R=V}if(W.postRun)try{await W.postRun(Y)}catch(V){if(!R)R=V;else console.error(`Error in postRun: ${V instanceof Error?V.message:String(V)}`)}if(R)throw R})}catch(W){if(L(W)){process.exitCode=x;return}if(W instanceof K){console.error(`Error: ${W.message}`),process.exitCode=1;return}if(W instanceof Error){let U=new K("EXECUTION",W.message).withCause(W);console.error(`Error: ${U.message}`),process.exitCode=1;return}console.error(`Error: ${String(W)}`),process.exitCode=1}}}export{I as validateParsed,D as resolveCommand,T as parseArgs,N as VALIDATION_MODE_ENV,b as VALIDATION_FORCE_EXIT_ENV,K as CrustError,M as Crust};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var f=import.meta.require;class Y extends Error{code;details;cause;constructor(H,K,...z){super(K);this.name="CrustError",this.code=H,this.details=z[0]}is(H){return this.code===H}withCause(H){return this.cause=H,this}}import{parseArgs as L}from"util";import{coerceBooleanString as x,tryCoerceNumber as y}from"@crustjs/utils";import{homedir as k}from"os";import{resolve as T}from"path";function R(H){try{return new URL(H)}catch{let z=/^[a-z][a-z0-9+.-]*:/i.test(H)?"":" (missing protocol \u2014 e.g. https://example.com)";throw new Y("PARSE",`Invalid URL "${H}"${z}`)}}function J(H){if(H==="")throw new Y("PARSE","Path cannot be empty");let K=H.replace(/^~(?=\/|$)/,k());return T(process.cwd(),K)}function O(H){try{return JSON.parse(H)}catch(K){let z=K instanceof Error?K.message:String(K);throw new Y("PARSE",`Invalid JSON: ${z}. Tip: wrap JSON in single quotes on the command line, e.g. --flag '{"k":1}'`)}}function w(H){let K={},z={};if(!H)return{options:K,aliasToName:z};let $=new Map;for(let Q of Object.keys(H))$.set(Q,Q);for(let[Q,Z]of Object.entries(H)){if(Q.startsWith("no-")){let X=Q.slice(3);throw new Y("DEFINITION",`Flag "--${Q}" must not use "no-" prefix; define "${X}" and negate with "--no-${X}"`)}if(!E.has(Z.type))throw new Y("DEFINITION",`Flag "--${Q}" must declare a parser type ("string", "number", "boolean", "url", "path", or "json")`);let B=Z.type==="boolean"?"boolean":"string",W={type:B};if(Z.multiple)W.multiple=!0;if(Z.short){if(Z.short.startsWith("no-"))throw new Y("DEFINITION",`Short alias "-${Z.short}" on "--${Q}" must not use "no-" prefix (reserved for negation)`);let X=$.get(Z.short);if(X)throw new Y("DEFINITION",`Alias collision: "-${Z.short}" is used by both "--${X}" and "--${Q}"`);$.set(Z.short,Q),z[Z.short]=Q,W.short=Z.short}if(Z.aliases)for(let X of Z.aliases){if(X.startsWith("no-"))throw new Y("DEFINITION",`Alias "--${X}" on "--${Q}" must not use "no-" prefix (reserved for negation)`);let G=$.get(X);if(G)throw new Y("DEFINITION",`Alias collision: "${X.length===1?"-":"--"}${X}" is used by both "--${G}" and "--${Q}"`);$.set(X,Q),z[X]=Q;let M={type:B};if(Z.multiple)M.multiple=!0;K[X]=M}K[Q]=W}return{options:K,aliasToName:z}}var E=new Set(["string","number","boolean","url","path","json"]);function F(H,K,z){if(K==="number"){let $=y(H);if($===void 0)throw new Y("PARSE",`Expected number for ${z}, got "${H}"`);return $}if(K==="boolean")return x(H);if(K==="url")return R(H);if(K==="path")return J(H);if(K==="json")return O(H);return H}function I(H,K,z){if(!K.includes(H))throw new Y("PARSE",`Invalid value "${H}" for ${z}. Expected one of: ${K.join(", ")}`)}function _(H,K,z,$){try{return H(K)}catch(Q){let Z=$===void 0?z:`${z} element [${$}]`,B=Q instanceof Error?Q.message:String(Q);throw new Y("PARSE",`Failed to parse ${Z}: ${B}`).withCause(Q)}}function P(H,K){let{default:z,choices:$,parse:Q}=H;if(z===void 0)return;if($)if(Array.isArray(z))for(let Z of z)I(String(Z),$,K);else I(String(z),$,K);if(Q){if(Array.isArray(z))return z.map((Z,B)=>_(Q,String(Z),K,B));return _(Q,String(z),K)}if(H.type==="path"){if(Array.isArray(z))return z.map((Z)=>J(String(Z)));return J(String(z))}return z}function N(H,K){if(H)for(let[z,$]of Object.entries(H)){let Q=$.parse;if(Q&&Q.constructor.name==="AsyncFunction")throw new Y("CONFIG",`Async parse not supported for flag --${z}. Use a sync parser; do async work in run().`)}if(K)for(let z of K){let $=z.parse;if($&&$.constructor.name==="AsyncFunction")throw new Y("CONFIG",`Async parse not supported for argument <${z.name}>. Use a sync parser; do async work in run().`)}}function h(H,K,z){let $=`--${H}`,Q=K.choices,Z=K.parse;if(K.multiple&&Array.isArray(z)){if(K.type==="boolean")return z.filter((B)=>typeof B==="boolean");return z.map((B,W)=>{if(Q)I(B,Q,$);if(Z)return _(Z,B,$,W);return F(B,K.type,$)})}if(K.type==="boolean"){if(typeof z==="boolean")return z;throw new Y("PARSE",`Expected boolean value for flag "${$}", got ${typeof z}`)}if(typeof z==="string"){if(Q)I(z,Q,$);if(Z)return _(Z,z,$);return F(z,K.type,$)}throw new Y("PARSE",`Internal: unexpected value shape for flag "${$}" (got ${typeof z})`)}function C(H,K,z){let $={};for(let Q in H){let Z=K[Q]??Q;if(!(Z in z))continue;let B=H[Q],W=$[Z];if(W!==void 0&&Array.isArray(W)&&Array.isArray(B))W.push(...B);else $[Z]=B}return $}function V(H,K,z){if(!H)return{};let $=C(K,z,H),Q={};for(let[Z,B]of Object.entries(H)){let W=$[Z];if(W!==void 0){Q[Z]=h(Z,B,W);continue}Q[Z]=P(B,`--${Z}`)}return Q}function D(H,K){if(!H)return;for(let[z,$]of Object.entries(H))if($.required===!0&&$.default===void 0){if(K[z]===void 0)throw new Y("VALIDATION",`Missing required flag "--${z}"`)}}function b(H,K){if(!H)return{};let z={},$=0;for(let Q of H){let{name:Z}=Q,B=`<${Z}>`,W=Q.choices,X=Q.parse,G=(M,U)=>{if(W)I(M,W,B);if(X)return _(X,M,B,U);return Q.type===void 0?M:F(M,Q.type,B)};if(Q.variadic){let M=K.slice($);z[Z]=M.map((U,q)=>G(U,q)),$=K.length}else if($<K.length)z[Z]=G(K[$]),$++;else z[Z]=P(Q,B)}return z}function v(H,K,z){if(!K)return;for(let $ of H){if($==="--")return;if(!$.startsWith("--no-"))continue;let Q=$.indexOf("="),Z=Q===-1?$.slice(5):$.slice(5,Q);if(!Z)continue;let B=z[Z];if(!B)continue;if(B===Z)continue;if(K[B]?.type!=="boolean")continue;throw new Y("PARSE",`Cannot negate alias "--no-${Z}"; use "--no-${B}" instead`)}}function S(H,K){let{args:z,effectiveFlags:$}=H;N($,z);let{options:Q,aliasToName:Z}=w($);v(K,$,Z);let B;try{B=L({args:K,options:Q,strict:!0,allowPositionals:!0,allowNegative:!0,tokens:!0})}catch(U){if(U instanceof Error){let q=U.message.match(/Unknown option '(.+?)'/);if(q)throw new Y("PARSE",`Unknown flag "${q[1]}"`).withCause(U)}throw new Y("PARSE","Failed to parse command arguments").withCause(U)}let W=[],X=[];if(B.tokens){let U=!1;for(let q of B.tokens){if(q.kind==="option-terminator"){U=!0;continue}if(q.kind==="positional")(U?W:X).push(q.value??"")}}else X.push(...B.positionals);let G=V($,B.values,Z);return{args:b(z,X),flags:G,rawArgs:W}}function A(H,K){let{args:z,effectiveFlags:$}=H,Q=K.args,Z=K.flags;if(z)for(let B of z){let{name:W}=B,X=`argument "<${W}>"`,G=Q[W];if(B.required===!0&&B.default===void 0){if(B.variadic){if(!Array.isArray(G)||G.length===0)throw new Y("VALIDATION",`Missing required ${X}`)}else if(G===void 0)throw new Y("VALIDATION",`Missing required ${X}`)}}D($,Z)}function p(H,K,z){if(typeof H!=="string"||H.length===0)throw new Y("DEFINITION",`Subcommand "${z}" has an invalid alias: must be a non-empty string`);if(/\s/.test(H))throw new Y("DEFINITION",`Subcommand "${z}" alias "${H}" must not contain whitespace`);if(H.startsWith("-"))throw new Y("DEFINITION",`Subcommand "${z}" alias "${H}" must not start with "-" (reserved for flags)`);if(H===K)throw new Y("DEFINITION",`Subcommand "${z}" alias "${H}" must not equal its own canonical name`)}function g(H,K,z){let{canonicalName:$,aliases:Q}=H;if(Q){let Z=new Set;for(let B of Q){if(p(B,$,z),Z.has(B))throw new Y("DEFINITION",`Subcommand "${z}" lists alias "${B}" more than once`);Z.add(B)}}for(let[Z,B]of Object.entries(K)){let W=B.meta.aliases;if(W?.includes($))throw new Y("DEFINITION",`Subcommand "${z}" canonical name "${$}" collides with alias of sibling "${Z}"`);if(!Q)continue;for(let X of Q){if(X===Z)throw new Y("DEFINITION",`Subcommand "${z}" alias "${X}" collides with sibling canonical name "${Z}"`);if(W?.includes(X))throw new Y("DEFINITION",`Subcommand "${z}" alias "${X}" collides with alias of sibling "${Z}"`)}}}function j(H){let K=H.choices;if(K!==void 0&&K.length>0)return K[0];switch(H.type){case"number":return"1";case"boolean":return"true";case"url":return"https://example.com";case"json":return"null";default:return"sample"}}function m(H){let K=[],z=H.effectiveFlags;for(let[Q,Z]of Object.entries(z)){if(Z.required!==!0||Z.default!==void 0)continue;if(K.push(`--${Q}`),Z.type!=="boolean")K.push(j(Z))}let $=H.args;if($)for(let Q of $){if(Q.required!==!0||Q.default!==void 0)continue;K.push(j(Q))}return K}function Hz(H){let K=[{command:H,path:[H.meta.name]}],z=new Set;while(K.length>0){let $=K.pop();if(!$)break;let{command:Q,path:Z}=$;if(z.has(Q))continue;z.add(Q);try{let W=S(Q,m(Q));A(Q,W)}catch(W){let X=W instanceof Error?W.message:"Unknown validation error";throw new Y("DEFINITION",`Command "${Z.join(" ")}" failed runtime validation: ${X}`).withCause(W)}let B={};for(let[W,X]of Object.entries(Q.subCommands))g({canonicalName:W,aliases:X.meta.aliases},B,[...Z,W].join(" ")),B[W]=X;for(let[W,X]of Object.entries(Q.subCommands))K.push({command:X,path:[...Z,W]})}}
|
|
3
|
+
export{f as a,Y as b,S as c,A as d,p as e,g as f,Hz as g};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crustjs/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"description": "Core library for the Crust CLI framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,11 +41,14 @@
|
|
|
41
41
|
"test": "bun test",
|
|
42
42
|
"publish": "bun publish --no-git-checks || true"
|
|
43
43
|
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@crustjs/utils": "0.0.2"
|
|
46
|
+
},
|
|
44
47
|
"devDependencies": {
|
|
45
48
|
"@crustjs/config": "0.0.0",
|
|
46
49
|
"bunup": "^0.16.31"
|
|
47
50
|
},
|
|
48
51
|
"peerDependencies": {
|
|
49
|
-
"typescript": "^6.0.
|
|
52
|
+
"typescript": "^6.0.3"
|
|
50
53
|
}
|
|
51
54
|
}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import{b as Q,c as S,d as W}from"./chunk-vt64gs69.js";function M(B){switch(B.type){case"number":return"1";case"boolean":return"true";default:return"sample"}}function X(B){let z=[],K=B.effectiveFlags;for(let[j,G]of Object.entries(K)){if(G.required!==!0||G.default!==void 0)continue;if(z.push(`--${j}`),G.type!=="boolean")z.push(M(G))}let J=B.args;if(J)for(let j of J){if(j.required!==!0||j.default!==void 0)continue;z.push(M(j))}return z}function _(B){let z=[{command:B,path:[B.meta.name]}],K=new Set;while(z.length>0){let J=z.pop();if(!J)break;let{command:j,path:G}=J;if(K.has(j))continue;K.add(j);try{let H=S(j,X(j));W(j,H)}catch(H){let L=H instanceof Error?H.message:"Unknown validation error";throw new Q("DEFINITION",`Command "${G.join(" ")}" failed runtime validation: ${L}`).withCause(H)}for(let[H,L]of Object.entries(j.subCommands))z.push({command:L,path:[...G,H]})}}export{_ as validateCommandTree};
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
var A=import.meta.require;class W extends Error{code;details;cause;constructor(B,J,...G){super(J);this.name="CrustError",this.code=B,this.details=G[0]}is(B){return this.code===B}withCause(B){return this.cause=B,this}}import{parseArgs as U}from"util";function q(B){let J={},G={};if(!B)return{options:J,aliasToName:G};let z=new Map;for(let j of Object.keys(B))z.set(j,j);for(let[j,H]of Object.entries(B)){if(j.startsWith("no-")){let L=j.slice(3);throw new W("DEFINITION",`Flag "--${j}" must not use "no-" prefix; define "${L}" and negate with "--no-${L}"`)}let K=H.type==="boolean"?"boolean":"string",Q={type:K};if(H.multiple)Q.multiple=!0;if(H.short){if(H.short.startsWith("no-"))throw new W("DEFINITION",`Short alias "-${H.short}" on "--${j}" must not use "no-" prefix (reserved for negation)`);let L=z.get(H.short);if(L)throw new W("DEFINITION",`Alias collision: "-${H.short}" is used by both "--${L}" and "--${j}"`);z.set(H.short,j),G[H.short]=j,Q.short=H.short}if(H.aliases)for(let L of H.aliases){if(L.startsWith("no-"))throw new W("DEFINITION",`Alias "--${L}" on "--${j}" must not use "no-" prefix (reserved for negation)`);let X=z.get(L);if(X)throw new W("DEFINITION",`Alias collision: "${L.length===1?"-":"--"}${L}" is used by both "--${X}" and "--${j}"`);z.set(L,j),G[L]=j;let $={type:K};if(H.multiple)$.multiple=!0;J[L]=$}J[j]=Q}return{options:J,aliasToName:G}}function _(B,J,G){if(J==="number"){let z=Number(B);if(Number.isNaN(z))throw new W("PARSE",`Expected number for ${G}, got "${B}"`);return z}if(J==="boolean")return B==="true"||B==="1";return B}function I(B,J,G){let z=`--${B}`;if(J.multiple&&Array.isArray(G))return J.type==="boolean"?G.filter((j)=>typeof j==="boolean"):G.map((j)=>_(j,J.type,z));if(J.type==="boolean"){if(typeof G==="boolean")return G;throw new W("PARSE",`Expected boolean value for flag "${z}", got ${typeof G}`)}if(typeof G==="string")return _(G,J.type,z);if(G===!0)return J.default??void 0;return G}function M(B,J,G){let z={};for(let j in B){let H=J[j]??j;if(!(H in G))continue;let K=B[j],Q=z[H];if(Q!==void 0&&Array.isArray(Q)&&Array.isArray(K))Q.push(...K);else z[H]=K}return z}function h(B,J,G){if(!B)return{};let z=M(J,G,B),j={};for(let[H,K]of Object.entries(B)){let Q=z[H];if(Q!==void 0){j[H]=I(H,K,Q);continue}j[H]=K.default??void 0}return j}function O(B,J){if(!B)return;for(let[G,z]of Object.entries(B))if(z.required===!0&&z.default===void 0){if(J[G]===void 0)throw new W("VALIDATION",`Missing required flag "--${G}"`)}}function P(B,J){if(!B)return{};let G={},z=0;for(let j of B){let{name:H}=j;if(j.variadic){let K=J.slice(z);G[H]=j.type==="string"?K:K.map((Q)=>_(Q,j.type,`<${H}>`)),z=J.length}else if(z<J.length)G[H]=_(J[z],j.type,`<${H}>`),z++;else G[H]=j.default??void 0}return G}function S(B,J,G){if(!J)return;for(let z of B){if(z==="--")return;if(!z.startsWith("--no-"))continue;let j=z.indexOf("="),H=j===-1?z.slice(5):z.slice(5,j);if(!H)continue;let K=G[H];if(!K)continue;if(K===H)continue;if(J[K]?.type!=="boolean")continue;throw new W("PARSE",`Cannot negate alias "--no-${H}"; use "--no-${K}" instead`)}}function b(B,J){let{args:G,effectiveFlags:z}=B,{options:j,aliasToName:H}=q(z);S(J,z,H);let K;try{K=U({args:J,options:j,strict:!0,allowPositionals:!0,allowNegative:!0,tokens:!0})}catch(Y){if(Y instanceof Error){let Z=Y.message.match(/Unknown option '(.+?)'/);if(Z)throw new W("PARSE",`Unknown flag "${Z[1]}"`).withCause(Y)}throw new W("PARSE","Failed to parse command arguments").withCause(Y)}let Q=[],L=[];if(K.tokens){let Y=!1;for(let Z of K.tokens){if(Z.kind==="option-terminator"){Y=!0;continue}if(Z.kind==="positional")(Y?Q:L).push(Z.value??"")}}else L.push(...K.positionals);let X=h(z,K.values,H);return{args:P(G,L),flags:X,rawArgs:Q}}function E(B,J){let{args:G,effectiveFlags:z}=B,j=J.args,H=J.flags;if(G)for(let K of G){let{name:Q}=K,L=`argument "<${Q}>"`,X=j[Q];if(K.required===!0&&K.default===void 0){if(K.variadic){if(!Array.isArray(X)||X.length===0)throw new W("VALIDATION",`Missing required ${L}`)}else if(X===void 0)throw new W("VALIDATION",`Missing required ${L}`)}}O(z,H)}
|
|
3
|
-
export{A as a,W as b,b as c,E as d};
|