@crustjs/core 0.0.19 → 0.2.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/LICENSE +21 -0
- package/README.md +3 -48
- package/dist/index.d.ts +1203 -1209
- package/dist/index.js +232 -2
- package/dist/invocation-DcA5FqF7.js +1875 -0
- package/dist/tooling.d.ts +160 -0
- package/dist/tooling.js +96 -0
- package/dist/types-DjMHz7M6.d.ts +828 -0
- package/package.json +27 -11
- package/dist/shared/chunk-1670njz2.js +0 -2
- package/dist/shared/chunk-q8y07jw2.js +0 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,1250 +1,1244 @@
|
|
|
1
|
+
import { A as CollisionBrand, B as UnionToIntersection, C as ValidatedInput, D as CommandSnapshot, E as ArgSnapshot, F as IsStaticTuple, H as defineExtensionId, I as IsUnion, L as LocalValueBrand, M as EmptyLiteralNameBrand, N as HasClosedNames, O as FlagSnapshot, P as IsClosedName, R as MergeContext, S as SectionConsumer, T as RunInputPayload, U as JsonCompatible, V as ExtensionId, W as JsonValue, _ as ParseResult, a as CommandSectionInput, b as RuntimeCommandSectionInput, c as FlagsDef, d as InputArgs, f as InputFlags, g as NamedFlagsRecord, h as NamedFlagDef, i as CommandSection, j as DefName, k as Awaitable, l as InferArgs, m as MergeFlags, n as ArgsDef, p as InvocationIO, r as CommandMeta, s as FlagDef, t as ArgDef, u as InferFlags, v as ParsedArgValue, w as ValueType, x as SectionAudience, y as ParsedFlagValue, z as MergeProviders } from "./types-DjMHz7M6.js";
|
|
2
|
+
//#region src/validation/args.brands.d.ts
|
|
3
|
+
type ArgNames<A extends readonly object[]> = DefName<A[number]>;
|
|
4
|
+
type DuplicateArgBrand<A, Existing extends string> = CollisionBrand<DefName<A>, Existing, "FIX_DUPLICATE_ARG", "Argument name ", " is already defined">;
|
|
5
|
+
type EmptyArgNameError = {
|
|
6
|
+
readonly FIX_EMPTY_NAME: "Argument names must be non-empty";
|
|
7
|
+
};
|
|
8
|
+
/** Reject empty argument names, including empty members of a name union. */
|
|
9
|
+
type EmptyArgNameBrand<Name extends string> = EmptyLiteralNameBrand<Name, EmptyArgNameError>;
|
|
10
|
+
type EmptyArgDefinitionNameBrand<A> = "" extends DefName<A> ? EmptyArgNameError : {};
|
|
11
|
+
type ArgChecks<A, Existing extends string> = A & DuplicateArgBrand<A, Existing> & LocalValueBrand<A> & EmptyArgDefinitionNameBrand<A>;
|
|
1
12
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
* Per-arg validation tuple type. Resolves to `A` when the constraints are
|
|
14
|
+
* satisfied: only the last arg is variadic, names are unique, and custom
|
|
15
|
+
* parsers are synchronous. Invalid definitions receive a branded property.
|
|
16
|
+
*
|
|
17
|
+
* Generalized to work with any ordered tuple of object-typed definitions.
|
|
18
|
+
* Uses `readonly object[]` to avoid TypeScript's weak type detection
|
|
19
|
+
* (all-optional constraint rejection).
|
|
20
|
+
*
|
|
21
|
+
* ```
|
|
22
|
+
* Property 'FIX_VARIADIC_POSITION' is missing in type '{ name: "files"; ... variadic: true }'
|
|
23
|
+
* but required in type
|
|
24
|
+
* '{ readonly FIX_VARIADIC_POSITION: "Only the last positional argument can be variadic" }'.
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
type ValidateVariadicArgs<A extends readonly object[], Existing extends string = never> = A extends readonly [infer Head, ...infer Tail extends readonly object[]] ? Tail extends readonly [unknown, ...unknown[]] ? Head extends {
|
|
28
|
+
variadic: true;
|
|
29
|
+
} ? readonly [ArgChecks<Head, Existing> & {
|
|
30
|
+
readonly FIX_VARIADIC_POSITION: "Only the last positional argument can be variadic";
|
|
31
|
+
}, ...ValidateVariadicArgs<Tail, Existing | DefName<Head>>] : readonly [ArgChecks<Head, Existing>, ...ValidateVariadicArgs<Tail, Existing | DefName<Head>>] : readonly [ArgChecks<Head, Existing>] : { [I in keyof A]: ArgChecks<A[I], Existing>; };
|
|
32
|
+
type BrandVariadicPosition<A extends readonly object[]> = { [I in keyof A]: A[I] & {
|
|
33
|
+
readonly FIX_VARIADIC_POSITION: "Only the last positional argument can be variadic";
|
|
34
|
+
}; };
|
|
35
|
+
type AppendArgsChecks<A extends ArgsDef, NewA extends ArgsDef> = A extends readonly [...unknown[], infer Last] ? Last extends {
|
|
36
|
+
variadic: true;
|
|
37
|
+
} ? BrandVariadicPosition<ValidateVariadicArgs<NewA, ArgNames<A>>> : ValidateVariadicArgs<NewA, ArgNames<A>> : ValidateVariadicArgs<NewA>;
|
|
38
|
+
/** Conditional collections and uncertain canonical identities cannot promise every alternative output key. */
|
|
39
|
+
type AttachedArgs<A extends ArgsDef> = HasClosedNames<A> extends true ? A : ArgsDef;
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/validation/commands.brands.d.ts
|
|
42
|
+
/** Preserve configured aliases; only a genuinely absent field proves an empty set. */
|
|
43
|
+
type AliasesOf<C> = C extends {
|
|
44
|
+
readonly aliases: infer A extends readonly string[];
|
|
45
|
+
} ? A : "aliases" extends keyof C ? readonly string[] : readonly [];
|
|
46
|
+
type NarrowAliases<A extends readonly string[]> = IsClosedName<A[number]> extends true ? A[number] : never;
|
|
47
|
+
type AliasShapeError<Name extends string, Alias extends string> = Alias extends "" ? `Subcommand "${Name}" has an invalid alias: must be a non-empty string` : Alias extends `${string} ${string}` | `${string}\t${string}` | `${string}\n${string}` | `${string}\r${string}` | `${string}\v${string}` | `${string}\f${string}` ? `Subcommand "${Name}" alias "${Alias}" must not contain whitespace` : Alias extends `-${string}` ? `Subcommand "${Name}" alias "${Alias}" must not start with "-" (reserved for flags)` : string extends Name ? never : Alias extends Name ? `Subcommand "${Name}" alias "${Alias}" must not equal its own canonical name` : never;
|
|
48
|
+
type AliasShapeErrors<Name extends string, C> = NarrowAliases<AliasesOf<C>> extends (infer Alias) ? Alias extends string ? AliasShapeError<Name, Alias> : never : never;
|
|
49
|
+
type AliasShapeBrand<Name extends string, C> = [AliasShapeErrors<Name, C>] extends [never] ? {} : {
|
|
50
|
+
readonly FIX_ALIAS_SHAPE: AliasShapeErrors<Name, C>;
|
|
51
|
+
};
|
|
52
|
+
type RootVersionBrand<C> = "version" extends keyof C ? {
|
|
53
|
+
readonly FIX_ROOT_VERSION: "Command config \"version\" belongs on the root Crust constructor";
|
|
54
|
+
} : {};
|
|
55
|
+
/** Brand command config containing statically known invalid metadata. */
|
|
56
|
+
type ValidateCommandConfig<Name extends string, C> = AliasShapeBrand<Name, C> & RootVersionBrand<C>;
|
|
57
|
+
type EmptyNameError = {
|
|
58
|
+
readonly FIX_EMPTY_NAME: "Command name must be a non-empty string";
|
|
59
|
+
};
|
|
60
|
+
type TrimWhitespace = " " | "\t" | "\n" | "\r" | "\v" | "\f" | " " | " " | " " | " " | " " | " " | " " | " " | " " | " " | " " | " " | " " | "
" | "
" | " " | " " | " " | "";
|
|
61
|
+
type BlankName<Name extends string> = Name extends `${TrimWhitespace}${infer Tail}` ? BlankName<Tail> : Name extends "" ? true : false;
|
|
62
|
+
/** Runtime checks own open names; provably invalid literal members remain errors. */
|
|
63
|
+
type CommandNameBrand<Name extends string> = IsClosedName<Name> extends false ? {} : true extends BlankName<Name> ? EmptyNameError : "__proto__" extends Name ? {
|
|
64
|
+
readonly FIX_RESERVED_NAME: "Command name \"__proto__\" is reserved";
|
|
65
|
+
} : {};
|
|
66
|
+
type DefinitionAliases<D> = CommandDefinitionData<D> extends {
|
|
67
|
+
readonly _aliases?: infer A extends readonly string[];
|
|
68
|
+
} ? A : readonly string[];
|
|
69
|
+
/** All statically known canonical and alias spellings carried by a command definition. */
|
|
70
|
+
type CommandDefinitionSpellings<D> = D extends unknown ? D extends {
|
|
71
|
+
name: infer N extends string;
|
|
72
|
+
} ? IsUnion<N> extends true ? never : DefName<D> extends (infer Name extends string) ? [Name] extends [never] ? never : Name | NarrowAliases<DefinitionAliases<D>> : never : never : never;
|
|
73
|
+
type SelfAliasBrand<D> = DefName<D> & NarrowAliases<DefinitionAliases<D>> extends (infer Dup extends string) ? [Dup] extends [never] ? {} : {
|
|
74
|
+
readonly FIX_ALIAS_SHAPE: `Command "${Dup}" must not list its own canonical name as an alias`;
|
|
75
|
+
} : never;
|
|
76
|
+
type CommandCollisionBrand<Spellings extends string, Existing extends string> = CollisionBrand<Spellings, Existing, "FIX_COMMAND_COLLISION", "Command name or alias ", " collides with a sibling command">;
|
|
15
77
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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`.
|
|
37
|
-
*
|
|
38
|
-
* @param command - The root command to resolve from
|
|
39
|
-
* @param argv - The argv array to resolve against
|
|
40
|
-
* @returns The resolved command, argv, and the command path
|
|
41
|
-
* @throws {CrustError} COMMAND_NOT_FOUND when an unknown subcommand is given and the parent has no run()
|
|
42
|
-
*/
|
|
43
|
-
declare function resolveCommand(command: CommandNode, argv: string[]): CommandRoute;
|
|
44
|
-
import { BaseValueType, ResolvePrimitive } from "@crustjs/utils";
|
|
78
|
+
* Validate definitions against existing siblings and definitions earlier in
|
|
79
|
+
* the same `.add()` call. Widened names opt out because their spellings are
|
|
80
|
+
* not statically knowable; their literal aliases opt out with them
|
|
81
|
+
* (see {@link CommandDefinitionSpellings}).
|
|
82
|
+
*/
|
|
83
|
+
type ValidateCommandDefinitions<Ds extends readonly unknown[], Existing extends string = never> = Ds extends readonly [infer Head, ...infer Tail] ? CommandDefinitionSpellings<Head> extends (infer Spellings extends string) ? readonly [Head & CommandCollisionBrand<Spellings, Existing> & CommandNameBrand<DefName<Head>> & SelfAliasBrand<Head>, ...ValidateCommandDefinitions<Tail, Existing | Spellings>] : never : Ds;
|
|
84
|
+
type ExtensionCommandDefs<E> = [E] extends [never] ? readonly [] : DefiningOf<E> extends {
|
|
85
|
+
readonly commands?: infer Cs extends readonly unknown[];
|
|
86
|
+
} ? Cs : readonly [];
|
|
87
|
+
/** An uncertain command collection opens only the child namespace. */
|
|
88
|
+
type ExtensionCommandSpellings<E> = AttachedCommandSpellings<ExtensionCommandDefs<E>>;
|
|
89
|
+
type ExtensionsCommandSpellings<Es extends readonly unknown[]> = IsStaticTuple<Es> extends true ? { [I in keyof Es]: ExtensionCommandSpellings<Es[I]>; }[number] : ExtensionCommandDefs<Es[number]>[number] extends never ? never : string;
|
|
90
|
+
type ExtensionCommandCollisionBrand<E, Existing extends string> = CollisionBrand<ExtensionCommandSpellings<E>, Existing, "FIX_COMMAND_COLLISION", "Extension command ", " collides with an existing command">;
|
|
45
91
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
92
|
+
* Validate each Extension's contributed command spellings against existing
|
|
93
|
+
* root commands and against Extensions earlier in the same `.extend()` call.
|
|
94
|
+
* Runtime preparation resolves collisions last-write-wins, so a statically
|
|
95
|
+
* known collision would silently retype `run()` against a command that
|
|
96
|
+
* dispatch replaces.
|
|
97
|
+
*/
|
|
98
|
+
type ValidateExtensionCommands<Es extends readonly unknown[], Existing extends string> = Es extends readonly [infer H, ...infer T extends readonly unknown[]] ? readonly [H & ExtensionCommandCollisionBrand<H, Existing>, ...ValidateExtensionCommands<T, Existing | ExtensionCommandSpellings<H>>] : Es;
|
|
99
|
+
/** Local metadata checks; unrelated description/version/usage text has no grammar. */
|
|
100
|
+
type SectionTextBrand<S> = S extends {
|
|
101
|
+
title: infer T extends string;
|
|
102
|
+
body: infer B extends string;
|
|
103
|
+
} ? true extends BlankName<T> | BlankName<B> ? {
|
|
104
|
+
readonly FIX_SECTION_TEXT: "Section title/body must be nonblank";
|
|
105
|
+
} : Extract<T, `${string}\r${string}` | `${string}\n${string}`> extends never ? {} : {
|
|
106
|
+
readonly FIX_SECTION_TEXT: "Section title must be a single line";
|
|
107
|
+
} : {};
|
|
108
|
+
type SectionAudienceBrand<S> = S extends {
|
|
109
|
+
only: readonly [];
|
|
110
|
+
} | {
|
|
111
|
+
except: readonly [];
|
|
112
|
+
} ? {
|
|
113
|
+
readonly FIX_SECTION_AUDIENCE: "Section audience must be nonempty";
|
|
114
|
+
} : {};
|
|
115
|
+
type LocalSectionsBrand<C> = "sections" extends keyof C ? C extends {
|
|
116
|
+
sections: infer S extends readonly unknown[];
|
|
117
|
+
} ? {
|
|
118
|
+
readonly sections: { [I in keyof S]: S[I] & UnionToIntersection<SectionTextBrand<S[I]>> & UnionToIntersection<SectionAudienceBrand<S[I]>>; };
|
|
119
|
+
} : {} : {};
|
|
120
|
+
type LocalCommandConfigBrand<N extends string, C> = ValidateCommandConfig<N, C> & LocalSectionsBrand<C>;
|
|
121
|
+
type AttachedCommandSpellings<Ds extends readonly unknown[]> = [Ds] extends [readonly []] ? never : IsStaticTuple<Ds> extends true ? HasClosedNames<Ds> extends true ? false extends { [I in keyof Ds]: IsClosedName<DefinitionAliases<Ds[I]>[number]>; }[number] ? string : CommandDefinitionSpellings<Ds[number]> : string : string;
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/validation/contexts.brands.d.ts
|
|
124
|
+
/** Canonical names claimed by more than one instance in the same `.provide()` call. */
|
|
125
|
+
type DuplicateContextNames<Cs extends readonly AnyContextInstance[], Seen extends string = never> = Cs extends readonly [infer Head, ...infer Tail extends readonly AnyContextInstance[]] ? (DefName<Head> & Seen) | DuplicateContextNames<Tail, Seen | DefName<Head>> : never;
|
|
126
|
+
type DuplicateContextBrand<C, Existing extends string> = CollisionBrand<DefName<C>, Existing, "FIX_DUPLICATE_CONTEXT", "Context ", " is already provided on this command path">;
|
|
57
127
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
|
|
64
|
-
|
|
128
|
+
* Brand instances whose name is already provided on this builder chain or
|
|
129
|
+
* repeated within the same `.provide()` call. The accumulated Context value
|
|
130
|
+
* map doubles as the name registry (its keys are the provided names), so no
|
|
131
|
+
* separate accumulator is needed. Wrappers generic over the builder type
|
|
132
|
+
* use `any`, which opts out via the
|
|
133
|
+
* `string extends keyof Ctx` guard instead of deferring. Widened names opt
|
|
134
|
+
* out via `DefName`; a parent-provided Context is not in the definition's
|
|
135
|
+
* `Ctx` and therefore cannot be checked at this call site.
|
|
136
|
+
*/
|
|
137
|
+
type ValidateContextNames<Ctx extends ContextMap, Cs extends readonly AnyContextInstance[], Existing extends string = string extends keyof Ctx ? never : keyof Ctx & string, Dups extends string = DuplicateContextNames<Cs>> = { [I in keyof Cs]: Cs[I] & DuplicateContextBrand<Cs[I], Existing | Dups>; };
|
|
138
|
+
type InstanceNames<P extends readonly unknown[]> = P extends readonly [infer H, ...infer T extends readonly unknown[]] ? DefName<H> | InstanceNames<T> : never;
|
|
139
|
+
/** Statically known names of an Extension's provided Contexts; widened Extensions opt out. */
|
|
140
|
+
type ExtensionProvidedNames<E> = DefiningOf<E> extends {
|
|
141
|
+
readonly provides?: infer P extends readonly unknown[];
|
|
142
|
+
} ? InstanceNames<P> : never;
|
|
143
|
+
type ExtensionContextBrand<E, Existing extends string> = CollisionBrand<ExtensionProvidedNames<E>, Existing, "FIX_DUPLICATE_CONTEXT", "Extension-provided Context ", " is already provided on this command path">;
|
|
144
|
+
type ValidateExtensionProvidesWorker<Es extends readonly unknown[], Existing extends string> = Es extends readonly [infer H, ...infer T extends readonly unknown[]] ? readonly [H & ExtensionContextBrand<H, Existing>, ...ValidateExtensionProvidesWorker<T, Existing | ExtensionProvidedNames<H>>] : Es;
|
|
65
145
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
type
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
146
|
+
* Brand Extensions whose provided Context names replace one already on the
|
|
147
|
+
* command path (or one provided by an earlier Extension in the same call).
|
|
148
|
+
* The resolver is last-write-wins, so a silent replacement would hand actions
|
|
149
|
+
* bound before `.extend()` a value of a different static type.
|
|
150
|
+
*/
|
|
151
|
+
type ValidateExtensionProvides<Es extends readonly unknown[], Ctx extends ContextMap> = ValidateExtensionProvidesWorker<Es, string extends keyof Ctx ? never : keyof Ctx & string>;
|
|
152
|
+
type ProvidedDepsOf<C> = IsAny<C> extends true ? {} : IsAny<ContextDepsOf<C>> extends true ? {} : string extends keyof ContextDepsOf<C> ? {} : ContextDepsOf<C>;
|
|
153
|
+
type MissingDependencyBrand<C, Known extends string> = Exclude<keyof ProvidedDepsOf<C> & string, Known> extends (infer Missing extends string) ? [Missing] extends [never] ? {} : {
|
|
154
|
+
readonly FIX_MISSING_DEPENDENCY: `Context "${DefName<C>}" uses Context "${Missing}" which is not provided on this command path`;
|
|
155
|
+
} : never;
|
|
156
|
+
type MismatchedDependencyNames<Deps, KnownValues> = keyof Deps extends never ? never : KnownValues extends unknown ? { [K in keyof Deps & keyof KnownValues & string]: IsAny<KnownValues[K]> extends true ? never : IsAny<Deps[K]> extends true ? never : [unknown, string] extends [KnownValues[K], keyof KnownValues] ? never : KnownValues[K] extends Deps[K] ? never : K; }[keyof Deps & keyof KnownValues & string] : never;
|
|
157
|
+
type MismatchedDependencyBrand<C, KnownValues> = MismatchedDependencyNames<ProvidedDepsOf<C>, KnownValues> extends (infer Mismatched extends string) ? [Mismatched] extends [never] ? {} : {
|
|
158
|
+
readonly FIX_DEPENDENCY_TYPE: `Context "${DefName<C>}" uses Context "${Mismatched}" whose provided value does not satisfy the declared dependency type`;
|
|
159
|
+
} : never;
|
|
160
|
+
/** Brand provided instances whose transitive dependency closure is unsatisfied. */
|
|
161
|
+
type ValidateContextDeps<Ctx extends ContextMap, Cs extends readonly AnyContextInstance[], Known extends string = (keyof Ctx & string) | DefName<Cs[number]>, KnownValues extends ContextMap = Ctx & ContextsOutput<Cs>> = { [I in keyof Cs]: Cs[I] & MissingDependencyBrand<Cs[I], Known> & MismatchedDependencyBrand<Cs[I], KnownValues>; };
|
|
162
|
+
/** Dependency closure carried by command definitions and Extensions. */
|
|
163
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
164
|
+
type DeclaredDepsOf<T> = IsAny<T> extends true ? Record<string, ContextValue> : CommandDefinitionData<DefiningOf<T>> extends {
|
|
165
|
+
readonly _deps?: infer D extends ContextMap;
|
|
166
|
+
} ? IsAny<D> extends true ? Record<string, ContextValue> : D : {};
|
|
167
|
+
/** Missing-dependency brand shared by `ValidateDeclaredDeps` and inline `.command()`. */
|
|
168
|
+
type MissingDeclaredDependencyBrand<T, Known extends string> = string extends keyof DeclaredDepsOf<T> ? {} : Exclude<keyof DeclaredDepsOf<T> & string, Known> extends (infer Missing extends string) ? [Missing] extends [never] ? {} : {
|
|
169
|
+
readonly FIX_MISSING_DEPENDENCY: `Uses Context "${Missing}" which is not provided`;
|
|
170
|
+
} : never;
|
|
171
|
+
/** Brand sealed units whose declared dependencies are absent at a composition site. */
|
|
172
|
+
type ValidateDeclaredDeps<Ctx extends ContextMap, Items extends readonly unknown[]> = { [I in keyof Items]: Items[I] & MissingDeclaredDependencyBrand<Items[I], keyof Ctx & string> & DeclaredDependencyValuesBrand<DeclaredDepsOf<Items[I]>, Ctx>; };
|
|
173
|
+
/** Callback values stay TypeScript-owned, including at dynamic composition. */
|
|
174
|
+
type DeclaredDependencyValuesBrand<Deps, Values> = MismatchedDependencyNames<Deps, Values> extends (infer Names extends string) ? [Names] extends [never] ? {} : {
|
|
175
|
+
readonly FIX_DEPENDENCY_TYPE: `Provided Context "${Names}" does not satisfy its declared value type`;
|
|
176
|
+
} : never;
|
|
177
|
+
/** Structural provider copies retain their defining name. */
|
|
178
|
+
type KnownContextInstances<Cs extends readonly AnyContextInstance[]> = { [I in keyof Cs]: Cs[I] & Pick<DefiningOf<Cs[I]>, "name">; };
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region src/validation/flags.brands.d.ts
|
|
181
|
+
/** Brand an incoming definition when one of its spellings is already claimed. */
|
|
182
|
+
type ExistingFlagCollisionBrand<F, Existing extends string> = CollisionBrand<DefName<F> | ExtractAllAliases<F>, Existing, "FIX_ALIAS_COLLISION", "Flag spelling ", " collides with an existing flag">;
|
|
183
|
+
/** Reject `__proto__`, which mutates the prototype of plain-object flag registries. */
|
|
184
|
+
type ReservedSpellingBrand<F> = "__proto__" extends DefName<F> | ExtractAllAliases<F> ? {
|
|
185
|
+
readonly FIX_RESERVED_SPELLING: "Flag spelling \"__proto__\" is reserved";
|
|
186
|
+
} : {};
|
|
187
|
+
type EmptySpellingError = {
|
|
188
|
+
readonly FIX_EMPTY_SPELLING: "Flag names and aliases must be non-empty strings";
|
|
189
|
+
};
|
|
190
|
+
/** Reject empty flag names, including empty members of a name union. */
|
|
191
|
+
type EmptyFlagSpellingBrand<Name extends string> = EmptyLiteralNameBrand<Name, EmptySpellingError>;
|
|
192
|
+
/** Reject empty spellings: their CLI tokens (`--`, `-`) are unparseable, so the flag can never be supplied. */
|
|
193
|
+
type EmptySpellingBrand<F> = "" extends DefName<F> | ExtractAllAliases<F> ? EmptySpellingError : {};
|
|
194
|
+
type RepeatedAliases<Aliases extends readonly string[], Seen extends string> = Aliases extends readonly [infer Head extends string, ...infer Tail extends readonly string[]] ? (Head & Seen) | RepeatedAliases<Tail, Seen | Head> : never;
|
|
195
|
+
type OwnAliasesBrand<F> = F extends {
|
|
196
|
+
aliases: infer Aliases extends readonly string[];
|
|
197
|
+
} ? RepeatedAliases<Aliases, ExtractShort<F>> extends (infer Duplicate extends string) ? [Duplicate] extends [never] ? {} : {
|
|
198
|
+
readonly FIX_ALIAS_COLLISION: "Flag repeats one of its own spellings";
|
|
199
|
+
} : never : {};
|
|
200
|
+
type InvalidShort<S extends string> = S extends `${infer _First}${infer Rest}` ? Rest extends "" ? never : S : S;
|
|
201
|
+
type ShortLengthBrand<F> = F extends {
|
|
202
|
+
short: infer Short extends string;
|
|
203
|
+
} ? string extends Short ? {} : [InvalidShort<Short>] extends [never] ? {} : {
|
|
204
|
+
readonly FIX_SHORT_LENGTH: "Short flags must be one character";
|
|
205
|
+
} : {};
|
|
206
|
+
/**
|
|
207
|
+
* Extract the `short` alias literal from a flag definition.
|
|
208
|
+
* Resolves to `never` when the field is absent or its spelling domain is open.
|
|
209
|
+
*/
|
|
210
|
+
type ExtractShort<F> = F extends {
|
|
211
|
+
short: infer S;
|
|
212
|
+
} ? S extends string ? IsClosedName<S> extends true ? S : never : never : never;
|
|
213
|
+
/**
|
|
214
|
+
* Extract alias string literals from the `aliases` array of a flag definition.
|
|
215
|
+
* Resolves to `never` when the field is absent or its element domain is open.
|
|
216
|
+
*/
|
|
217
|
+
type ExtractLongAliases<F> = F extends {
|
|
218
|
+
aliases: infer A;
|
|
219
|
+
} ? A extends readonly string[] ? IsClosedName<A[number]> extends true ? A[number] : never : never : never;
|
|
220
|
+
/**
|
|
221
|
+
* Extract all alias identifiers (short + long) from a flag definition.
|
|
222
|
+
*
|
|
223
|
+
* Generalized to work with any shape; values without `short`/`aliases`
|
|
224
|
+
* fields resolve to `never`.
|
|
225
|
+
*
|
|
226
|
+
* Closed-name proof excludes open domains from literal collision evidence.
|
|
227
|
+
* Attachment separately keeps their spelling namespace open.
|
|
228
|
+
*/
|
|
229
|
+
type ExtractAllAliases<F> = ExtractShort<F> | ExtractLongAliases<F>;
|
|
230
|
+
/** All narrowed canonical, short, and long-alias spellings in a flags record. */
|
|
231
|
+
type SpellingsOf<F extends FlagsDef> = string extends keyof F ? never : (keyof F & string) | { [K in keyof F & string]: ExtractAllAliases<F[K]>; }[keyof F & string];
|
|
232
|
+
type NoPrefixBrand<S extends string> = [Extract<S, `no-${string}`>] extends [never] ? {} : {
|
|
233
|
+
readonly FIX_NO_PREFIX: "Names must not start with no-";
|
|
234
|
+
};
|
|
235
|
+
type ContextOwnedFlags<C> = C extends unknown ? DefiningOf<C> extends {
|
|
236
|
+
readonly _ownedFlags?: infer OF extends FlagsDef;
|
|
237
|
+
} ? OF : {} : never;
|
|
238
|
+
type ContextFlagCollisionBrand<C, Existing extends string> = CollisionBrand<LocalSpellingsOf<ContextOwnedFlags<C>>, Existing, "FIX_ALIAS_COLLISION", "Flag spelling ", " collides with an existing flag">;
|
|
239
|
+
/** Declared flag literals carried by an Extension's `_flagDefs` phantom; widened Extensions opt out. */
|
|
240
|
+
type ExtensionFlagDefsOf<E> = [E] extends [never] ? readonly [] : DefiningOf<E> extends {
|
|
241
|
+
readonly _flagDefs?: infer D extends readonly NamedFlagDef[];
|
|
242
|
+
} ? D : readonly NamedFlagDef[];
|
|
243
|
+
/** All statically known owned-flag spellings across a tuple of Context instances. */
|
|
244
|
+
type ProvidedContextSpellings<P extends readonly unknown[]> = P extends readonly [infer H, ...infer T extends readonly unknown[]] ? LocalSpellingsOf<ContextOwnedFlags<H>> | ProvidedContextSpellings<T> : [P[number]] extends [never] ? never : LocalSpellingsOf<ContextOwnedFlags<P[number]>>;
|
|
245
|
+
/** All statically known flag spellings an Extension contributes: declared flags plus provided Context-owned flags. */
|
|
246
|
+
type ExtensionSpellings<E> = AttachedSpellings<ExtensionFlagDefsOf<E>> | ([E] extends [never] ? never : DefiningOf<E> extends {
|
|
247
|
+
readonly provides?: infer P extends readonly unknown[];
|
|
248
|
+
} ? ProvidedContextSpellings<P> : never);
|
|
249
|
+
type ExtensionFlagCollisionBrand<E, Existing extends string> = CollisionBrand<ExtensionSpellings<E>, Existing, "FIX_ALIAS_COLLISION", "Extension flag spelling ", " collides with an existing flag">;
|
|
250
|
+
/**
|
|
251
|
+
* Validate each Extension's contributed flag spellings against accumulated
|
|
252
|
+
* existing spellings and against Extensions earlier in the same `.extend()`
|
|
253
|
+
* call. Extensions must not override application flags: a silent overwrite
|
|
254
|
+
* would retype an already-bound action's flag at parse time.
|
|
255
|
+
*/
|
|
256
|
+
type ValidateExtensionFlags<Es extends readonly unknown[], Existing extends string> = Es extends readonly [infer H, ...infer T extends readonly unknown[]] ? readonly [H & ExtensionFlagCollisionBrand<H, Existing>, ...ValidateExtensionFlags<T, Existing | ExtensionSpellings<H>>] : Es;
|
|
257
|
+
type ShapeSpellings<S> = 0 extends 1 & S ? string : S extends {
|
|
258
|
+
readonly flags: infer F extends FlagsDef;
|
|
259
|
+
readonly children: infer C;
|
|
260
|
+
} ? LocalSpellingsOf<F> | TreeSpellings<C> : never;
|
|
261
|
+
/** Every flag spelling reachable in a compile-time command tree (`Record<spelling, CommandShape>`), recursively. */
|
|
262
|
+
type TreeSpellings<Tree> = 0 extends 1 & Tree ? string : string extends keyof Tree ? string : Tree extends object ? { [K in keyof Tree]: ShapeSpellings<Tree[K]>; }[keyof Tree] : never;
|
|
263
|
+
type DefinitionSpellings<D> = CommandDefinitionData<D> extends {
|
|
264
|
+
readonly _shape?: infer S;
|
|
265
|
+
} ? ShapeSpellings<S> : never;
|
|
266
|
+
/** Flag spellings contributed by a tuple of command definitions. */
|
|
267
|
+
type DefinitionTreeSpellings<Ds extends readonly unknown[]> = DefinitionSpellings<Ds[number]>;
|
|
268
|
+
/**
|
|
269
|
+
* Extension-collision brand over a built command shape. Shared by `.add()`
|
|
270
|
+
* (via {@link ValidateDefinitionFlags}) and inline `.command()`, whose recipe
|
|
271
|
+
* builder exposes a shape instead of a definition tuple.
|
|
272
|
+
*/
|
|
273
|
+
type ShapeFlagCollisionBrand<S, Ext extends string> = CollisionBrand<ShapeSpellings<S>, Ext, "FIX_ALIAS_COLLISION", "Flag spelling ", " collides with a registered Extension flag">;
|
|
274
|
+
type DefinitionFlagCollisionBrand<D, Ext extends string> = CommandDefinitionData<D> extends {
|
|
275
|
+
readonly _shape?: infer S;
|
|
276
|
+
} ? ShapeFlagCollisionBrand<S, Ext> : {};
|
|
277
|
+
/**
|
|
278
|
+
* Validate an added definition tree's flag spellings against already-registered
|
|
279
|
+
* Extension flags. Recursive Extension flags inject into every node at prepare
|
|
280
|
+
* time, so a colliding local flag would be silently retyped for its action.
|
|
281
|
+
*/
|
|
282
|
+
type ValidateDefinitionFlags<Ds extends readonly unknown[], Ext extends string> = { [I in keyof Ds]: Ds[I] & DefinitionFlagCollisionBrand<Ds[I], Ext>; };
|
|
283
|
+
/** Union of every statically known flag spelling contributed by a tuple of Extensions. */
|
|
284
|
+
type ExtensionsSpellings<Es extends readonly unknown[]> = Es extends readonly [infer H, ...infer T extends readonly unknown[]] ? ExtensionSpellings<H> | ExtensionsSpellings<T> : ExtensionSpellings<Es[number]> extends never ? never : string;
|
|
285
|
+
/**
|
|
286
|
+
* Validate Context-owned flags against accumulated existing spellings and
|
|
287
|
+
* against instances earlier in the same `.provide(a(), b())` call. Without
|
|
288
|
+
* the batch check a same-call collision silently resolves last-write-wins,
|
|
289
|
+
* and a required flag shadowed by a peer's alias becomes impossible to supply.
|
|
290
|
+
*/
|
|
291
|
+
type ProvideChecks<Sp extends string, Cs extends readonly unknown[]> = Cs extends readonly [infer H, ...infer T extends readonly unknown[]] ? readonly [H & ContextFlagCollisionBrand<H, Sp>, ...ProvideChecks<Sp | LocalSpellingsOf<ContextOwnedFlags<H>>, T>] : Cs;
|
|
292
|
+
/** Whether canonical and alias spellings form a closed, fixed local namespace. */
|
|
293
|
+
type HasClosedFlagSpellings<F> = F extends {
|
|
294
|
+
name: infer N extends string;
|
|
295
|
+
} ? false extends IsClosedName<N> | ("short" extends keyof F ? F extends {
|
|
296
|
+
short: infer S extends string;
|
|
297
|
+
} ? IsClosedName<S> : false : true) | ("aliases" extends keyof F ? F extends {
|
|
298
|
+
aliases: infer A extends readonly string[];
|
|
299
|
+
} ? false extends IsStaticTuple<A> | IsClosedName<A[number]> ? false : true : false : true) ? false : true : false;
|
|
300
|
+
type LocalFlagBrand<F> = UnionToIntersection<F extends unknown ? LocalFlagBranchBrand<F> : never>;
|
|
301
|
+
type LocalFlagBranchBrand<F> = LocalValueBrand<F> & OwnAliasesBrand<F> & ShortLengthBrand<F> & ReservedSpellingBrand<F> & EmptySpellingBrand<F> & NoPrefixBrand<DefName<F> | ExtractAllAliases<F>> & ([DefName<F> & ExtractAllAliases<F>] extends [never] ? {} : {
|
|
302
|
+
readonly FIX_ALIAS_COLLISION: "Flag repeats one of its own spellings";
|
|
303
|
+
});
|
|
304
|
+
/** Validate provable local fields and destination relations without inventing names for open inputs. */
|
|
305
|
+
type ValidateLocalFlagDefs<Defs extends readonly NamedFlagDef[], Existing extends string> = Defs & UnionToIntersection<LocalFlagTupleChecks<Defs, Existing>>;
|
|
306
|
+
type LocalFlagTupleChecks<Defs extends readonly NamedFlagDef[], Existing extends string, Errors = {}> = Defs extends readonly [infer Head extends NamedFlagDef, ...infer Tail extends readonly NamedFlagDef[]] ? LocalFlagTupleChecks<Tail, Existing | DefName<Head> | ExtractAllAliases<Head>, Errors & LocalFlagBrand<Head> & ExistingFlagCollisionBrand<Head, Existing>> : Defs extends readonly [] ? Errors : Errors & LocalFlagBrand<Defs[number]>;
|
|
307
|
+
/** An open collection is not an empty or guaranteed-present flag record. */
|
|
308
|
+
type KnownNamedFlag<D> = D extends NamedFlagDef ? HasClosedNames<readonly [D]> extends true ? D : never : never;
|
|
309
|
+
type AttachedFlags<Defs extends readonly NamedFlagDef[]> = HasClosedNames<Defs> extends true ? NamedFlagsRecord<Defs> : IsStaticTuple<Defs> extends true ? FlagsDef & NamedFlagsRecord<readonly KnownNamedFlag<Defs[number]>[]> : FlagsDef;
|
|
310
|
+
type AttachedSpellings<Defs extends readonly NamedFlagDef[]> = IsStaticTuple<Defs> extends true ? false extends { [I in keyof Defs]: HasClosedFlagSpellings<Defs[I]>; }[number] ? string : SpellingsOf<NamedFlagsRecord<Defs>> : string;
|
|
311
|
+
type LocalFlagNameBrand<N extends string> = EmptyFlagSpellingBrand<N> & ReservedSpellingBrand<{
|
|
312
|
+
name: N;
|
|
313
|
+
}> & NoPrefixBrand<N>;
|
|
314
|
+
/** Broad structural builder holders must not default their spelling state to empty. */
|
|
315
|
+
type LocalSpellingsOf<F extends FlagsDef> = string extends keyof F ? string : true extends { [K in keyof F]: HasClosedFlagSpellings<F[K] & {
|
|
316
|
+
name: K;
|
|
317
|
+
}> extends false ? true : false; }[keyof F] ? string : SpellingsOf<F>;
|
|
318
|
+
//#endregion
|
|
319
|
+
//#region src/command/crust.d.ts
|
|
320
|
+
/**
|
|
321
|
+
* The runtime context object passed to the Command Action defined with
|
|
322
|
+
* `.action()`.
|
|
323
|
+
*
|
|
324
|
+
* Generic parameters:
|
|
325
|
+
* - `A` — positional argument definitions tuple
|
|
326
|
+
* - `F` — the effective (Context-owned + local merged) flag definitions
|
|
327
|
+
*/
|
|
328
|
+
interface CrustCommandContext<A extends ArgsDef = ArgsDef, F extends FlagsDef = FlagsDef, Ctx extends ContextMap = {}> extends InvocationIO {
|
|
329
|
+
/** Resolved positional arguments, keyed by arg name */
|
|
330
|
+
args: InferArgs<A>;
|
|
331
|
+
/** Resolved flags, keyed by flag name */
|
|
332
|
+
flags: InferFlags<F>;
|
|
333
|
+
/** Lazy Context values available on this command path. */
|
|
334
|
+
ctx: ContextBag<Ctx>;
|
|
335
|
+
/** Raw arguments that appeared after the `--` separator */
|
|
336
|
+
rawArgs: string[];
|
|
337
|
+
/** Readonly, serializable snapshot of the resolved command */
|
|
338
|
+
command: CommandSnapshot;
|
|
339
|
+
/** Readonly snapshot of the application root, including Extension contributions */
|
|
340
|
+
rootCommand: CommandSnapshot;
|
|
135
341
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
342
|
+
declare const commandProviders: unique symbol;
|
|
343
|
+
/** Compile-time description of one command's programmatic input and action result. */
|
|
344
|
+
interface CommandShape<A extends ArgsDef = ArgsDef, F extends FlagsDef = FlagsDef, Children extends object = {}, Result = unknown, Providers extends Record<string, ContextValue> = Record<string, ContextValue>> {
|
|
345
|
+
readonly [commandProviders]?: Providers;
|
|
346
|
+
readonly args: A;
|
|
347
|
+
readonly flags: F;
|
|
348
|
+
readonly children: Children;
|
|
349
|
+
readonly result: Result;
|
|
143
350
|
}
|
|
144
|
-
/**
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
351
|
+
/** Captured invocation after lifecycle cleanup. */
|
|
352
|
+
type RunOutcome<Result> = {
|
|
353
|
+
readonly stdout: string;
|
|
354
|
+
readonly stderr: string;
|
|
355
|
+
} & ({
|
|
356
|
+
readonly status: "completed";
|
|
357
|
+
readonly result: Result;
|
|
358
|
+
} | {
|
|
359
|
+
readonly status: "finished";
|
|
360
|
+
readonly by: ExtensionId;
|
|
361
|
+
} | {
|
|
362
|
+
readonly status: "failed";
|
|
363
|
+
readonly error: unknown;
|
|
364
|
+
});
|
|
365
|
+
/** Compile-time command tree accumulated by `.add()`. */
|
|
366
|
+
type CommandTree = Record<string, CommandShape>;
|
|
367
|
+
/** Every valid path through a command tree, including the root path (`[]`). */
|
|
368
|
+
type CommandPath<Tree extends object, Depth extends readonly unknown[] = readonly []> = Depth["length"] extends 15 ? readonly string[] : string extends keyof Tree ? readonly string[] : readonly [] | { [K in keyof Tree & string]: Tree[K] extends CommandShape ? readonly [K, ...CommandPath<Tree[K]["children"], readonly [...Depth, unknown]>] : never; }[keyof Tree & string];
|
|
369
|
+
type KnownCommandPath<Path extends readonly string[], Tree> = string extends keyof Tree ? Path : IsStaticTuple<Path> extends true ? string extends Path[number] ? never : Path : never;
|
|
370
|
+
/** Resolve the command shape at a typed path. */
|
|
371
|
+
type CommandShapeAt<Shape extends CommandShape, Path extends readonly string[]> = Path extends readonly [infer Head, ...infer Tail extends readonly string[]] ? Head extends keyof Shape["children"] ? Shape["children"][Head] extends (infer Child extends CommandShape) ? CommandShapeAt<Child, Tail> : never : string extends Head ? CommandShape : never : Path extends readonly [] ? Shape : CommandShape;
|
|
372
|
+
type RunSection<Name extends string, Values> = keyof Values extends never ? { [K in Name]?: never; } : {} extends Values ? { [K in Name]?: Values; } : { [K in Name]: Values; };
|
|
373
|
+
/** Structured values bound directly against the selected command's definitions; no argv is produced. */
|
|
374
|
+
type RunInput<Shape extends CommandShape> = RunSection<"args", ArgsDef extends Shape["args"] ? NonNullable<RunInputPayload["args"]> : InputArgs<Shape["args"]>> & RunSection<"flags", FlagsDef extends Shape["flags"] ? NonNullable<RunInputPayload["flags"]> : InputFlags<Shape["flags"]>> & {
|
|
375
|
+
readonly raw?: readonly string[];
|
|
376
|
+
};
|
|
377
|
+
type CompatibleRunValue<Expected, Actual> = Actual extends Expected ? Actual extends object ? Expected extends unknown ? Actual extends Expected ? Actual & { [K in Exclude<keyof Actual, keyof Expected>]: never; } & { [K in keyof Actual & keyof Expected]: CompatibleRunValue<Expected[K], Actual[K]>; } : never : never : Actual : JsonValue extends Expected ? Actual extends JsonCompatible<Actual> ? Actual : never : Expected extends unknown ? CompatibleRunBranch<Expected, Actual> : never;
|
|
378
|
+
type CompatibleRunBranch<Expected, Actual> = Actual extends Expected ? Actual : Expected extends readonly (infer Item)[] ? JsonValue extends Item ? Actual extends (Expected extends readonly [unknown, ...unknown[]] ? readonly [unknown, ...unknown[]] : readonly unknown[]) ? Actual extends JsonCompatible<Actual> ? Actual : never : never : never : Actual extends object ? string extends keyof Expected ? { [K in keyof Actual]: CompatibleRunValue<Exclude<Expected[K & keyof Expected], undefined>, Actual[K]>; } : { [K in keyof Expected]: K extends keyof Actual ? CompatibleRunValue<Exclude<Expected[K], undefined>, Actual[K]> : Expected[K]; } & { [K in Exclude<keyof Actual, keyof Expected>]: never; } : never;
|
|
379
|
+
type CompatibleRunInput<Shape extends CommandShape, Input> = CompatibleRunValue<RunInput<Shape>, Input>;
|
|
380
|
+
type RunInputArguments<Shape extends CommandShape> = {} extends RunInput<Shape> ? readonly [input?: RunInput<Shape>] : readonly [input: RunInput<Shape>];
|
|
381
|
+
type RunArguments<Shape extends CommandShape> = readonly [...RunInputArguments<Shape>, io?: Partial<InvocationIO>];
|
|
382
|
+
/** Static configuration for a reusable command definition. */
|
|
383
|
+
interface CommandConfig extends Omit<CommandMeta, "name" | "sections" | "version"> {
|
|
384
|
+
/** Plain-text sections rendered after built-in command documentation. */
|
|
385
|
+
readonly sections?: readonly RuntimeCommandSectionInput[];
|
|
151
386
|
}
|
|
152
|
-
/**
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
387
|
+
/** Static metadata accepted by the root command constructor. */
|
|
388
|
+
type RootCommandMeta = Pick<CommandMeta, "description" | "version" | "usage"> & {
|
|
389
|
+
/** Plain-text sections rendered after built-in command documentation. */
|
|
390
|
+
readonly sections?: readonly RuntimeCommandSectionInput[];
|
|
391
|
+
};
|
|
392
|
+
type AnyCommandDefinitionBuilder = Crust<any, any, any, any, any, any, any, any, any, any, any, "recipe">;
|
|
393
|
+
type CommandRecipe<Builder extends AnyCommandDefinitionBuilder = AnyCommandDefinitionBuilder> = (command: CommandDefinitionBuilder<{}, [], {}, never, never>) => Builder;
|
|
394
|
+
declare const commandDefinitionInternal: unique symbol;
|
|
395
|
+
interface CommandDefinitionInternal {
|
|
396
|
+
readonly name: string;
|
|
397
|
+
readonly recipe: (command: AnyCommandDefinitionBuilder) => AnyCommandDefinitionBuilder;
|
|
398
|
+
readonly meta: Omit<CommandMeta, "name">;
|
|
159
399
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
400
|
+
type CommandInputShape<S extends CommandShape> = {
|
|
401
|
+
readonly args: S["args"];
|
|
402
|
+
readonly flags: S["flags"];
|
|
403
|
+
readonly providers: S[typeof commandProviders];
|
|
404
|
+
readonly children: { [K in keyof S["children"]]: S["children"][K] extends CommandShape ? CommandInputShape<S["children"][K]> : never; };
|
|
405
|
+
};
|
|
406
|
+
interface CommandDefinition<Name extends string = string, Aliases extends readonly string[] = readonly string[], Shape extends CommandShape = CommandShape, Deps extends ContextMap = {}> {
|
|
407
|
+
/** The subcommand name this definition is added under */
|
|
408
|
+
readonly name: Name;
|
|
409
|
+
/** The same definition under a different name; configured aliases travel with it. */
|
|
410
|
+
as<const N extends string>(name: N & CommandNameBrand<N> & ValidateCommandConfig<N, {
|
|
411
|
+
aliases: Aliases;
|
|
412
|
+
}>): CommandDefinition<N, Aliases, Shape, Deps>;
|
|
413
|
+
/** @internal */
|
|
414
|
+
readonly [commandDefinitionInternal]: CommandDefinitionInternal & {
|
|
415
|
+
readonly _aliases?: Aliases;
|
|
416
|
+
readonly _shape?: Shape;
|
|
417
|
+
readonly _deps?: Deps;
|
|
418
|
+
readonly proof?: [Shape] extends [never] ? unknown : string extends keyof Shape["flags"] | keyof Deps ? unknown : (state: [CommandInputShape<Shape>, Deps]) => void;
|
|
419
|
+
};
|
|
167
420
|
}
|
|
421
|
+
/** @internal */
|
|
422
|
+
type CommandDefinitionData<D> = D extends {
|
|
423
|
+
readonly [commandDefinitionInternal]: infer Data;
|
|
424
|
+
} ? Data : D;
|
|
425
|
+
type AppendedArgs<A extends ArgsDef, NewA extends ArgsDef> = readonly [...A, ...AttachedArgs<NewA>];
|
|
426
|
+
/** Configure-only capability of {@link Crust} passed to command recipes. */
|
|
427
|
+
type CommandDefinitionBuilder<Flags extends FlagsDef = {}, A extends ArgsDef = ArgsDef, Ctx extends ContextMap = {}, Sibs extends string = never, Sp extends string = LocalSpellingsOf<Flags>, Tree extends object = {}, CtxFlags extends FlagsDef = {}, Result = void, Deps extends ContextMap = {}, Providers extends Record<string, ContextValue> = {}> = Crust<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSpellings<never, never, {}, never, Deps, Providers>, Result, {}, string, "recipe">;
|
|
428
|
+
type ShapeOfBuilder<B> = [B] extends [never] ? CommandShape<[], {}, {}, never, {}> : B extends {
|
|
429
|
+
readonly _recipeTypes: {
|
|
430
|
+
shape: infer S extends CommandShape;
|
|
431
|
+
};
|
|
432
|
+
} ? S : never;
|
|
433
|
+
type DepsOfBuilder<B> = UnionToIntersection<B extends {
|
|
434
|
+
readonly _recipeTypes: {
|
|
435
|
+
deps: infer Deps extends ContextMap;
|
|
436
|
+
};
|
|
437
|
+
} ? Deps : {}> extends (infer Merged extends ContextMap) ? Merged : {};
|
|
438
|
+
type DefinitionShapeForSpelling<D, Spelling extends string> = CommandDefinitionData<D> extends {
|
|
439
|
+
readonly _shape?: infer Shape extends CommandShape;
|
|
440
|
+
} ? Spelling extends CommandDefinitionSpellings<D> ? Shape : never : never;
|
|
441
|
+
type ShapeWithInheritedFlags<S, CF extends FlagsDef> = keyof CF extends never ? S : S extends CommandShape<infer SA, infer SF, infer SC, infer SR, infer P> ? CommandShape<SA, MergeFlags<CF, SF>, { [K in keyof SC]: ShapeWithInheritedFlags<SC[K], CF>; }, SR, P> : never;
|
|
442
|
+
type DefinitionsTree<Ds extends readonly CommandDefinition<any, any, any, any>[], CtxFlags extends FlagsDef = {}> = string extends AttachedCommandSpellings<Ds> ? Record<string, CommandShape> & KnownDefinitionTree<Ds, CtxFlags> : { [K in CommandDefinitionSpellings<Ds[number]>]: ShapeWithInheritedFlags<DefinitionShapeForSpelling<Ds[number], K>, CtxFlags>; };
|
|
443
|
+
type KnownDefinitionTree<Ds extends readonly unknown[], CF extends FlagsDef> = IsUnion<Ds> extends true ? {} : Ds extends readonly [infer H, ...infer T] ? (IsUnion<H> extends true ? {} : { [K in CommandDefinitionSpellings<H>]: ShapeWithInheritedFlags<DefinitionShapeForSpelling<H, K>, CF>; }) & KnownDefinitionTree<T, CF> : {};
|
|
444
|
+
type ExtensionCheckAt<Checks, I> = I extends keyof Checks ? Checks[I] : never;
|
|
445
|
+
type ExtensionCommands<Es extends readonly AnyExtension[]> = Es extends readonly [infer H, ...infer T extends readonly AnyExtension[]] ? readonly [...ExtensionCommandDefs<H>, ...ExtensionCommands<T>] : ExtensionCommandDefs<Es[number]>[number] extends never ? readonly [] : readonly CommandDefinition<any, any, any, any>[];
|
|
446
|
+
type ExtensionProviders<E> = [E] extends [never] ? [] : DefiningOf<E> extends {
|
|
447
|
+
provides?: infer P extends readonly AnyContextInstance[];
|
|
448
|
+
} ? P : [];
|
|
449
|
+
type ExtensionOwnDefs<E> = [E] extends [never] ? [] : DefiningOf<E> extends {
|
|
450
|
+
_flagDefs?: infer F extends readonly NamedFlagDef[];
|
|
451
|
+
} ? F : [];
|
|
452
|
+
type ExtensionFlags<Es extends readonly AnyExtension[]> = Es extends readonly [infer H, ...infer T extends readonly AnyExtension[]] ? AttachedFlags<ExtensionOwnDefs<H>> & ContextsOwnedFlags<ExtensionProviders<H>> & ExtensionFlags<T> : ExtensionOwnDefs<Es[number]>[number] extends never ? keyof ContextsOwnedFlags<ExtensionProviders<Es[number]>> extends never ? {} : FlagsDef : FlagsDef;
|
|
453
|
+
type RecursiveFlagsOf<E> = ExtensionOwnDefs<E>[number] extends never ? {} : true extends { [K in keyof ExtensionOwnDefs<E>]: ExtensionOwnDefs<E>[K] extends (infer D extends NamedFlagDef) ? D extends {
|
|
454
|
+
readonly recursive: false;
|
|
455
|
+
} ? false : IsClosedName<D["name"]> extends false ? true : IsUnion<D["name"]> extends true ? true : D extends {
|
|
456
|
+
recursive: infer R;
|
|
457
|
+
} ? boolean extends R ? true : false : false : false; }[number] ? FlagsDef : IsStaticTuple<ExtensionOwnDefs<E>> extends true ? { [D in ExtensionOwnDefs<E>[number] as D extends {
|
|
458
|
+
readonly recursive: infer R;
|
|
459
|
+
} ? [R] extends [false] ? never : D["name"] : D["name"]]: Omit<D, "name">; } : FlagsDef;
|
|
460
|
+
type RecursiveExtensionFlags<Es extends readonly AnyExtension[]> = Es extends readonly [infer H, ...infer T extends readonly AnyExtension[]] ? RecursiveFlagsOf<H> & ContextsOwnedFlags<ExtensionProviders<H>> & RecursiveExtensionFlags<T> : ExtensionFlags<Es>;
|
|
461
|
+
type TreeWithInheritedFlags<Tree, F extends FlagsDef> = { [K in keyof Tree]: ShapeWithInheritedFlags<Tree[K], F>; };
|
|
462
|
+
type ReplacementTree<Tree, D extends CommandDefinition<any, any, any, any>, CF extends FlagsDef> = CommandDefinitionData<D> extends {
|
|
463
|
+
readonly _aliases?: infer Aliases extends readonly string[];
|
|
464
|
+
} ? IsClosedName<Aliases[number]> extends false ? Record<string, CommandShape> & { [K in D["name"]]: ShapeWithInheritedFlags<DefinitionShapeForSpelling<D, K>, CF>; } : Omit<Tree, CommandDefinitionSpellings<D>> & { [K in CommandDefinitionSpellings<D>]: K extends D["name"] ? ShapeWithInheritedFlags<DefinitionShapeForSpelling<D, K>, CF> : K extends keyof Tree ? CommandShape : ShapeWithInheritedFlags<DefinitionShapeForSpelling<D, K>, CF>; } : Record<string, CommandShape>;
|
|
465
|
+
type ReplacedTree<Tree, Commands extends readonly unknown[], CF extends FlagsDef> = Commands extends readonly [infer H extends CommandDefinition<any, any, any, any>, ...infer T extends readonly unknown[]] ? ReplacedTree<IsClosedName<H["name"]> extends false ? Record<string, CommandShape> : true extends IsUnion<H> | IsUnion<H["name"]> ? Record<string, CommandShape> : ReplacementTree<Tree, H, CF>, T, CF> : number extends Commands["length"] ? Record<string, CommandShape> : Tree;
|
|
466
|
+
type ReplacedExtensionTree<Tree, Es extends readonly AnyExtension[], CF extends FlagsDef> = Es extends readonly [infer H extends AnyExtension, ...infer T extends readonly AnyExtension[]] ? ReplacedExtensionTree<ReplacedTree<Tree, ExtensionCommandDefs<H>, CF>, T, CF> : ExtensionCommandDefs<Es[number]>[number] extends never ? Tree : Record<string, CommandShape>;
|
|
467
|
+
type ExtendedTree<Tree, Es extends readonly AnyExtension[], RecursiveFlags extends FlagsDef, InheritedFlags extends FlagsDef> = keyof RecursiveFlags extends never ? ReplacedExtensionTree<Tree, Es, InheritedFlags> : TreeWithInheritedFlags<ReplacedExtensionTree<Tree, Es, InheritedFlags>, RecursiveFlags>;
|
|
168
468
|
/**
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
469
|
+
* Define a reusable, inert command under a required name.
|
|
470
|
+
*
|
|
471
|
+
* The recipe runs once per `.add()`, receiving a fresh builder.
|
|
472
|
+
*
|
|
473
|
+
* Static metadata belongs in `config`. Use `.as(name)`
|
|
474
|
+
* to add one definition under a different name; configured aliases travel with it.
|
|
475
|
+
*/
|
|
476
|
+
export declare function defineCommand<const Name extends string, Builder extends AnyCommandDefinitionBuilder>(name: Name & CommandNameBrand<Name>, recipe: CommandRecipe<Builder>): CommandDefinition<Name, readonly [], ShapeOfBuilder<Builder>, DepsOfBuilder<Builder>>;
|
|
477
|
+
export declare function defineCommand<const Name extends string, const C extends CommandConfig, Builder extends AnyCommandDefinitionBuilder>(name: Name & CommandNameBrand<Name>, config: C & LocalCommandConfigBrand<Name, C>, recipe: CommandRecipe<Builder>): CommandDefinition<Name, AliasesOf<C>, ShapeOfBuilder<Builder>, DepsOfBuilder<Builder>>;
|
|
478
|
+
/**
|
|
479
|
+
* Chainable builder for defining CLI commands with full type inference.
|
|
480
|
+
*
|
|
481
|
+
* Generic parameters:
|
|
482
|
+
* - `Flags` — flags defined locally or installed by provided Contexts
|
|
483
|
+
* - `A` — positional argument definitions
|
|
484
|
+
* - `Ctx` — provided Context values
|
|
485
|
+
* - `Sibs` — sibling command names and aliases already registered
|
|
486
|
+
* - `Sp` — accumulated flag spellings used for collision checks
|
|
487
|
+
* - `Tree` — command shapes accumulated by `.add()` for typed `run()`
|
|
488
|
+
* - `CtxFlags` — Context-owned flags accumulated by `.provide()` and recursive
|
|
489
|
+
* Extension flags accumulated by `.extend()`, inherited by the shapes of
|
|
490
|
+
* definitions added afterwards
|
|
491
|
+
* - `Result` — awaited return type of this command's action
|
|
492
|
+
* - `Meta` — authored root metadata available to Extension requirements
|
|
493
|
+
* - `Caps` — root application or configure-only recipe capabilities
|
|
494
|
+
*
|
|
495
|
+
* @example
|
|
496
|
+
* ```ts
|
|
497
|
+
* const app = new Crust("my-cli")
|
|
498
|
+
* .flags({ name: "verbose", type: "boolean", short: "v" })
|
|
499
|
+
* .args({ name: "file", type: "string", required: true })
|
|
500
|
+
* .action(({ args, flags }) => {
|
|
501
|
+
* console.log(args.file, flags.verbose);
|
|
502
|
+
* });
|
|
503
|
+
* ```
|
|
504
|
+
*/
|
|
505
|
+
type CollisionSpellings<Extensions extends string = never, Tree extends string = never, Demands extends ContextMap = {}, Pending extends string = never, RecipeDeps extends ContextMap = {}, Providers extends Record<string, ContextValue> = {}> = {
|
|
506
|
+
readonly pending: Pending;
|
|
507
|
+
readonly demands: Demands;
|
|
508
|
+
readonly extension: Extensions;
|
|
509
|
+
readonly tree: Tree;
|
|
510
|
+
readonly recipeDeps: RecipeDeps;
|
|
511
|
+
readonly providers: Providers;
|
|
512
|
+
};
|
|
513
|
+
type AnyCollisionSpellings = {
|
|
514
|
+
readonly pending: string;
|
|
515
|
+
readonly demands: ContextMap;
|
|
516
|
+
readonly extension: string;
|
|
517
|
+
readonly tree: string;
|
|
518
|
+
readonly recipeDeps?: ContextMap;
|
|
519
|
+
readonly providers?: Record<string, ContextValue>;
|
|
520
|
+
};
|
|
521
|
+
type RecipeDepsOf<S extends AnyCollisionSpellings> = S extends {
|
|
522
|
+
readonly recipeDeps: infer Deps extends ContextMap;
|
|
523
|
+
} ? Deps : {};
|
|
524
|
+
type ProvidersOf<S extends AnyCollisionSpellings> = S extends {
|
|
525
|
+
readonly providers: infer Providers extends Record<string, ContextValue>;
|
|
526
|
+
} ? Providers : {};
|
|
527
|
+
type AfterFlags<Flags extends FlagsDef, A extends ArgsDef, Ctx extends ContextMap, Sibs extends string, Sp extends string, Tree extends object, CtxFlags extends FlagsDef, CollisionSp extends AnyCollisionSpellings, Result, Defs extends readonly NamedFlagDef[], Meta extends RootCommandMeta | undefined, Caps extends "app" | "recipe"> = Crust<MergeFlags<Flags, AttachedFlags<Defs>>, A, Ctx, Sibs, Sp | AttachedSpellings<Defs>, Tree, CtxFlags, CollisionSp, Result, Meta, string, Caps>;
|
|
528
|
+
type AfterArgs<Flags extends FlagsDef, A extends ArgsDef, Ctx extends ContextMap, Sibs extends string, Sp extends string, Tree extends object, CtxFlags extends FlagsDef, CollisionSp extends AnyCollisionSpellings, Result, NewA extends ArgsDef, Meta extends RootCommandMeta | undefined, Caps extends "app" | "recipe"> = Crust<Flags, AppendedArgs<A, NewA>, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, Meta, string, Caps>;
|
|
529
|
+
type AfterUse<Flags extends FlagsDef, A extends ArgsDef, Ctx extends ContextMap, Sibs extends string, Sp extends string, Tree extends object, CtxFlags extends FlagsDef, CollisionSp extends AnyCollisionSpellings, Result, Fs extends readonly AnyContextFactory[], Meta extends RootCommandMeta | undefined> = Crust<Flags, A, MergeContext<Ctx, ContextDependencies<Fs>>, Sibs, Sp, Tree, CtxFlags, CollisionSpellings<CollisionSp["extension"], CollisionSp["tree"], CollisionSp["demands"], CollisionSp["pending"], MergeContext<RecipeDepsOf<CollisionSp>, ContextDependencies<Fs>>, ProvidersOf<CollisionSp>>, Result, Meta, string, "recipe">;
|
|
530
|
+
type AfterProvide<Flags extends FlagsDef, A extends ArgsDef, Ctx extends ContextMap, Sibs extends string, Sp extends string, Tree extends object, CtxFlags extends FlagsDef, CollisionSp extends AnyCollisionSpellings, Result, Cs extends readonly AnyContextInstance[], Meta extends RootCommandMeta | undefined, Caps extends "app" | "recipe"> = Crust<MergeFlags<Flags, ContextsOwnedFlags<Cs>>, A, MergeProviders<Ctx, ContextsOutput<Cs>>, Sibs, Sp | LocalSpellingsOf<ContextsOwnedFlags<Cs>>, Tree, MergeFlags<CtxFlags, ContextsOwnedFlags<Cs>>, CollisionSpellings<CollisionSp["extension"], CollisionSp["tree"], CollisionSp["demands"], CollisionSp["pending"], RecipeDepsOf<CollisionSp>, MergeProviders<ProvidersOf<CollisionSp>, ContextsOutput<Cs>>>, Result, Meta, string, Caps>;
|
|
531
|
+
type AfterAction<Flags extends FlagsDef, A extends ArgsDef, Ctx extends ContextMap, Sibs extends string, Sp extends string, Tree extends object, CtxFlags extends FlagsDef, CollisionSp extends AnyCollisionSpellings, R, Meta extends RootCommandMeta | undefined, Caps extends "app" | "recipe"> = Crust<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Awaited<R>, Meta, string, Caps>;
|
|
532
|
+
type AfterExtend<Flags extends FlagsDef, A extends ArgsDef, Ctx extends ContextMap, Sibs extends string, Sp extends string, Tree extends object, CtxFlags extends FlagsDef, CollisionSp extends AnyCollisionSpellings, Result, Es extends readonly AnyExtension[], Meta extends RootCommandMeta | undefined, Caps extends "app" | "recipe"> = Crust<MergeFlags<Flags, ExtensionFlags<Es>>, A, MergeProviders<Ctx, ExtensionsProvidesOutput<Es>>, Sibs | ExtensionsCommandSpellings<Es>, Sp | ExtensionsSpellings<Es>, ExtendedTree<Tree, Es, RecursiveExtensionFlags<Es>, CtxFlags>, MergeFlags<CtxFlags, RecursiveExtensionFlags<Es>>, CollisionSpellings<CollisionSp["extension"] | ExtensionsSpellings<Es>, CollisionSp["tree"] | DefinitionTreeSpellings<ExtensionCommands<Es>>, CollisionSp["demands"] & ExtensionDemandValues<Es>, CollisionSp["pending"] | DefinitionTreeSpellings<ExtensionCommands<Es>>, RecipeDepsOf<CollisionSp>, MergeProviders<ProvidersOf<CollisionSp>, ExtensionsProvidesOutput<Es>>>, Result, Meta, string, Caps>;
|
|
533
|
+
type AfterAdd<Flags extends FlagsDef, A extends ArgsDef, Ctx extends ContextMap, Sibs extends string, Sp extends string, Tree extends object, CtxFlags extends FlagsDef, CollisionSp extends AnyCollisionSpellings, Result, Ds extends readonly CommandDefinition<any, any, any, any>[], Meta extends RootCommandMeta | undefined, Caps extends "app" | "recipe"> = Crust<Flags, A, Ctx, Sibs | AttachedCommandSpellings<Ds>, Sp, Tree & DefinitionsTree<Ds, CtxFlags>, CtxFlags, CollisionSpellings<CollisionSp["extension"], CollisionSp["tree"] | DefinitionTreeSpellings<Ds>, CollisionSp["demands"], CollisionSp["pending"], RecipeDepsOf<CollisionSp>, ProvidersOf<CollisionSp>>, Result, Meta, string, Caps>;
|
|
534
|
+
type ExtensionDemandValues<Es extends readonly AnyExtension[]> = UnionToIntersection<DefiningOf<Es[number]> extends {
|
|
535
|
+
readonly _hookDeps?: infer H extends ContextMap;
|
|
536
|
+
} ? H : Record<string, ContextValue>> extends (infer D extends ContextMap) ? D : {};
|
|
537
|
+
type DescendantShapeValuesBrand<S, Deps> = S extends CommandShape ? DeclaredDependencyValuesBrand<Deps, NonNullable<S[typeof commandProviders]>> & DescendantValuesBrand<S["children"], Deps> : {};
|
|
538
|
+
type DescendantValuesBrand<Tree, Deps> = keyof Deps extends never ? {} : UnionToIntersection<Tree extends unknown ? keyof Tree extends never ? {} : { [K in keyof Tree]: DescendantShapeValuesBrand<Tree[K], Deps>; }[keyof Tree] : never>;
|
|
539
|
+
type DefinitionDescendantValuesBrand<Ds extends readonly CommandDefinition<any, any, any, any>[], Deps> = DescendantValuesBrand<DefinitionsTree<Ds>, Deps>;
|
|
540
|
+
type ValidateInlineCommandDeps<Ctx extends ContextMap, B> = MissingDeclaredDependencyBrand<{
|
|
541
|
+
readonly _deps?: DepsOfBuilder<B>;
|
|
542
|
+
}, keyof Ctx & string> & DeclaredDependencyValuesBrand<DepsOfBuilder<B>, Ctx>;
|
|
543
|
+
/** Completed applications expose inspection and invocation, not authoring after erasure. */
|
|
544
|
+
type AnyCrust = Pick<Crust<FlagsDef, ArgsDef, Record<string, ContextValue>, string, string, CommandTree, FlagsDef, AnyCollisionSpellings, unknown, RootCommandMeta>, "_types" | "run" | "execute" | "snapshot">;
|
|
545
|
+
type DefinedRootMetaKeys<Meta extends RootCommandMeta | undefined> = { [K in RootMetaKey]: [Meta] extends [Required<Pick<RootCommandMeta, K>>] ? K : never; }[RootMetaKey];
|
|
546
|
+
export declare class Crust<Flags extends FlagsDef = {}, A extends ArgsDef = [], Ctx extends ContextMap = {}, Sibs extends string = never, Sp extends string = LocalSpellingsOf<Flags>, Tree extends object = {}, CtxFlags extends FlagsDef = {}, CollisionSp extends AnyCollisionSpellings = CollisionSpellings, Result = void, const out Meta extends RootCommandMeta | undefined = {}, const Name extends string = string, Caps extends "app" | "recipe" = "app"> {
|
|
547
|
+
private readonly _contextProof;
|
|
548
|
+
/** @internal — recipe state, without structural inference through builder methods. */
|
|
549
|
+
readonly _recipeTypes: {
|
|
550
|
+
readonly shape: CommandShape<A, Flags, Tree, Result, ProvidersOf<CollisionSp>>;
|
|
551
|
+
readonly deps: RecipeDepsOf<CollisionSp>;
|
|
552
|
+
readonly proof?: (state: [CommandInputShape<CommandShape<A, Flags, Tree, Result, ProvidersOf<CollisionSp>>>, RecipeDepsOf<CollisionSp>]) => void;
|
|
553
|
+
};
|
|
554
|
+
/** Supported type-level seam exposing the application's inferred command types. */
|
|
555
|
+
readonly _types: {
|
|
556
|
+
flags: Flags;
|
|
557
|
+
args: A;
|
|
558
|
+
ctx: Ctx;
|
|
559
|
+
tree: Tree;
|
|
560
|
+
shape: CommandShape<A, Flags, Tree, Result>;
|
|
561
|
+
readonly rootMeta: Meta;
|
|
562
|
+
readonly caps: Caps;
|
|
563
|
+
};
|
|
564
|
+
/** @internal */
|
|
565
|
+
_node: CommandNode;
|
|
566
|
+
/** @internal — Recipe-builder lineage anchor, unique per materialization and preserved by clones */
|
|
567
|
+
_ancestorOwnedFlags: FlagsDef;
|
|
568
|
+
/**
|
|
569
|
+
* Create a new root command builder.
|
|
570
|
+
*
|
|
571
|
+
* @param name - The command name.
|
|
572
|
+
* @param metadata - Optional root description, version, usage, and documentation sections.
|
|
573
|
+
*/
|
|
574
|
+
constructor(nameInput: (Name & CommandNameBrand<Name>) & ({} extends Meta ? {} : {
|
|
575
|
+
readonly FIX_ROOT_META: "This root requires metadata";
|
|
576
|
+
}));
|
|
577
|
+
constructor(nameInput: Name & CommandNameBrand<Name>, meta: Meta & (undefined | (RootCommandMeta & LocalSectionsBrand<NoInfer<NonNullable<Meta>>> & { [K in Exclude<keyof Meta, RootMetaKey>]: never; })));
|
|
578
|
+
/** @internal — Clone this builder with a new node, preserving generics. */
|
|
579
|
+
_clone<Out = this>(nodeOverrides: Partial<CommandNode>): Out;
|
|
580
|
+
/**
|
|
581
|
+
* Define local flags for this command from named flag definitions
|
|
582
|
+
* (created with `defineFlag(name, def)` or written inline as
|
|
583
|
+
* `{ name: "dry-run", type: "boolean" }`).
|
|
584
|
+
*
|
|
585
|
+
* Repeated `.flags()` calls accumulate local flags. Returns a new builder
|
|
586
|
+
* with the combined local flag types. The original builder is not mutated.
|
|
587
|
+
*
|
|
588
|
+
* @param defs - Named flag definitions
|
|
589
|
+
* @returns A new `Crust` instance with the given flags
|
|
590
|
+
*/
|
|
591
|
+
flags<const Defs extends readonly NamedFlagDef[]>(...defs: ValidateLocalFlagDefs<Defs, Sp>): AfterFlags<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, Defs, Meta, Caps>;
|
|
592
|
+
/**
|
|
593
|
+
* Define positional arguments for this command; argument order is the
|
|
594
|
+
* order they are passed (created with `defineArg(name, def)` or written
|
|
595
|
+
* inline).
|
|
596
|
+
*
|
|
597
|
+
* Repeated `.args()` calls append in call order. Returns a new builder with
|
|
598
|
+
* the combined args types. The original builder is not mutated.
|
|
599
|
+
*
|
|
600
|
+
* @param defs - Positional argument definitions, in positional order
|
|
601
|
+
* @returns A new `Crust` instance with the combined args
|
|
602
|
+
*/
|
|
603
|
+
args<const NewA extends ArgsDef>(...defs: NewA & AppendArgsChecks<A, NewA>): AfterArgs<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, NewA, Meta, Caps>;
|
|
604
|
+
/**
|
|
605
|
+
* Declare Contexts this command consumes without supplying their values.
|
|
606
|
+
* Factory references are retained while setup stays lazy.
|
|
607
|
+
*/
|
|
608
|
+
use<const Fs extends readonly [AnyContextFactory, ...AnyContextFactory[]]>(this: {
|
|
609
|
+
readonly _types: {
|
|
610
|
+
readonly caps: "recipe";
|
|
611
|
+
};
|
|
612
|
+
}, ...factories: Fs & DeclaredDependencyValuesBrand<ContextDependencies<Fs>, ProvidersOf<CollisionSp>>): AfterUse<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, Fs, Meta>;
|
|
613
|
+
/**
|
|
614
|
+
* Attach Contexts — named command dependencies — to this command.
|
|
615
|
+
*
|
|
616
|
+
* Contexts are inherited by descendant commands and constructed lazily when
|
|
617
|
+
* their `ctx` property is accessed. Dependency order within one call does not
|
|
618
|
+
* affect construction. Disposable values are released in
|
|
619
|
+
* reverse construction order after post-run hooks. TypeScript rejects known Context-owned flag collisions, including pending
|
|
620
|
+
* Extension commands. Consuming operations throw `DEFINITION` for actual collisions.
|
|
621
|
+
*
|
|
622
|
+
*/
|
|
623
|
+
provide<const Cs extends readonly AnyContextInstance[]>(...instances: KnownContextInstances<Cs> & ProvideChecks<Sp | CollisionSp["pending"], Cs> & ValidateContextNames<Caps extends "recipe" ? ProvidersOf<CollisionSp> : Ctx, Cs> & ValidateContextDeps<Ctx, Cs> & DeclaredDependencyValuesBrand<CollisionSp["demands"] & RecipeDepsOf<CollisionSp>, ContextsOutput<NoInfer<Cs>>>): AfterProvide<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, Cs, Meta, Caps>;
|
|
624
|
+
/**
|
|
625
|
+
* Define the Command Action — the function that implements this
|
|
626
|
+
* command's behavior after its inputs are ready.
|
|
627
|
+
*
|
|
628
|
+
* The action receives a {@link CrustCommandContext} with `args` typed from
|
|
629
|
+
* `.args()` and `flags` typed from the accumulated `Flags`.
|
|
630
|
+
*
|
|
631
|
+
* Calling `.action()` again replaces the command behavior on the new builder.
|
|
632
|
+
* The original builder is not mutated.
|
|
633
|
+
*
|
|
634
|
+
* @param action - The Command Action function
|
|
635
|
+
* @returns A new `Crust` instance with the action registered
|
|
636
|
+
*/
|
|
637
|
+
action<R>(action: (ctx: NoInfer<CrustCommandContext<A, Flags, Ctx>>) => R): AfterAction<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, R, Meta, Caps>;
|
|
638
|
+
/**
|
|
639
|
+
* Register one or more CLI Extensions on the application root.
|
|
640
|
+
*
|
|
641
|
+
* Extensions are application-wide: they own the flags and commands they
|
|
642
|
+
* contribute. Repeated calls accumulate Extensions in registration order,
|
|
643
|
+
* except that registering an `ExtensionId` again keeps only the last
|
|
644
|
+
* registration — its contributions and providers replace the earlier ones
|
|
645
|
+
* and its hooks run once, at the later position. ID deduplication is
|
|
646
|
+
* runtime-only, so removed paths can remain statically visible and reject
|
|
647
|
+
* with `COMMAND_NOT_FOUND`. Canonical replacements use the new
|
|
648
|
+
* shape; open replacements and ambiguous aliases have unknown results.
|
|
649
|
+
* Required root metadata keys are checked against the constructor's inferred
|
|
650
|
+
* metadata by TypeScript, not at runtime.
|
|
651
|
+
* Recipe builders cannot call this root-only method.
|
|
652
|
+
*/
|
|
653
|
+
extend<const Es extends readonly Extension<any, any, any, any, DefinedRootMetaKeys<Meta>>[]>(this: {
|
|
654
|
+
readonly _types: {
|
|
655
|
+
readonly caps: "app";
|
|
656
|
+
};
|
|
657
|
+
}, ...extensions: Es & { [I in keyof Es]: (ExtensionCommandDefs<Es[I]> extends ValidateDefinitionFlags<ExtensionCommandDefs<Es[I]>, LocalSpellingsOf<CtxFlags>> ? {} : {
|
|
658
|
+
readonly FIX_ALIAS_COLLISION: "Extension command flags collide with inherited Context flags";
|
|
659
|
+
}) & ValidateDeclaredDeps<MergeProviders<Ctx, ExtensionsProvidesOutput<Es>>, Es>[I] & DeclaredDependencyValuesBrand<CollisionSp["demands"], MergeProviders<Ctx, ExtensionsProvidesOutput<Es>>> & DescendantValuesBrand<Tree, ExtensionDemandValues<Es>> & DefinitionDescendantValuesBrand<ExtensionCommands<Es>, CollisionSp["demands"] & ExtensionDemandValues<Es>> & ExtensionCheckAt<ValidateExtensionFlags<Es, Sp | CollisionSp["tree"] | DefinitionTreeSpellings<ExtensionCommands<Es>>>, I> & ExtensionCheckAt<ValidateExtensionCommands<Es, Sibs>, I> & ExtensionCheckAt<ValidateExtensionProvides<Es, Ctx>, I>; }): AfterExtend<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, Es, Meta, Caps>;
|
|
660
|
+
/**
|
|
661
|
+
* Materialize and register inert reusable command definitions, each
|
|
662
|
+
* under its own carried name (use `.as(name)` to rename).
|
|
663
|
+
*
|
|
664
|
+
*/
|
|
665
|
+
add<const Ds extends readonly CommandDefinition<any, any, any, any>[]>(...definitions: Ds & ValidateCommandDefinitions<Ds, Sibs> & ValidateDeclaredDeps<Ctx, Ds> & DefinitionDescendantValuesBrand<Ds, CollisionSp["demands"]> & ValidateDefinitionFlags<Ds, CollisionSp["extension"] | LocalSpellingsOf<CtxFlags>>): AfterAdd<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, Ds, Meta, Caps>;
|
|
666
|
+
/**
|
|
667
|
+
* Define an app-local leaf subcommand inline (root-only sugar for
|
|
668
|
+
* `.add(defineCommand(name, recipe))`).
|
|
669
|
+
*
|
|
670
|
+
* The recipe builder is seeded with the Contexts and Context-owned flags
|
|
671
|
+
* accumulated on this builder so far — the call site. Contexts provided
|
|
672
|
+
* after `.command()` are not visible to it, matching the positional runtime
|
|
673
|
+
* semantics of `.provide()`. Extract to `defineCommand` when a command needs
|
|
674
|
+
* its own file, reuse, or a package. Recipe builders cannot call this
|
|
675
|
+
* root-only method.
|
|
676
|
+
*/
|
|
677
|
+
command<const N extends string, B extends AnyCommandDefinitionBuilder>(this: {
|
|
678
|
+
readonly _types: {
|
|
679
|
+
readonly caps: "app";
|
|
680
|
+
};
|
|
681
|
+
}, name: N & CommandNameBrand<N> & CommandCollisionBrand<N, Sibs>, recipe: ((command: CommandDefinitionBuilder<{}, [], Ctx, never, LocalSpellingsOf<CtxFlags>, {}, CtxFlags>) => B & ShapeFlagCollisionBrand<ShapeOfBuilder<B>, CollisionSp["extension"]> & ValidateInlineCommandDeps<Ctx, NoInfer<B>>) & NoInfer<DescendantValuesBrand<{
|
|
682
|
+
child: ShapeOfBuilder<B>;
|
|
683
|
+
}, CollisionSp["demands"]>>): AfterAdd<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, readonly [CommandDefinition<N, readonly [], ShapeOfBuilder<B>, DepsOfBuilder<B>>], Meta, Caps>;
|
|
684
|
+
private _addDefinitions;
|
|
685
|
+
/**
|
|
686
|
+
* Prepare a frozen Command Snapshot for tooling such as man-page, skill,
|
|
687
|
+
* and build generators.
|
|
688
|
+
*
|
|
689
|
+
* Materializes Extension contributions and command definitions without
|
|
690
|
+
* calling Command Actions.
|
|
691
|
+
*/
|
|
692
|
+
snapshot(this: {
|
|
693
|
+
readonly _types: {
|
|
694
|
+
readonly caps: "app";
|
|
695
|
+
};
|
|
696
|
+
}): Promise<CommandSnapshot>;
|
|
697
|
+
/**
|
|
698
|
+
* Programmatically invoke a typed command, quietly capturing its output.
|
|
699
|
+
* Returns completed, finished, or failed after cleanup without presenting errors.
|
|
700
|
+
* Use {@link execute} as the streaming terminal adapter.
|
|
701
|
+
*
|
|
702
|
+
* @param path - Typed path to the command to invoke (`[]` selects the root)
|
|
703
|
+
* @param input - Structured argument, flag, and raw values
|
|
704
|
+
* @param io - Optional `stdout(text)` / `stderr(text)` callbacks, also
|
|
705
|
+
* exposed to Command Actions and Extensions
|
|
706
|
+
*/
|
|
707
|
+
run<const Path extends CommandPath<Tree>>(this: {
|
|
708
|
+
readonly _types: {
|
|
709
|
+
readonly caps: "app";
|
|
710
|
+
};
|
|
711
|
+
}, path: Path & KnownCommandPath<Path, Tree>, ...args: RunArguments<CommandShapeAt<CommandShape<A, Flags, Tree, Result>, Path>>): Promise<RunOutcome<CommandShapeAt<CommandShape<A, Flags, Tree, Result>, Path>["result"]>>;
|
|
712
|
+
run<const Path extends CommandPath<Tree>, const Input>(this: {
|
|
713
|
+
readonly _types: {
|
|
714
|
+
readonly caps: "app";
|
|
715
|
+
};
|
|
716
|
+
}, path: Path & KnownCommandPath<Path, Tree>, input: Input, ...validation: [Input] extends [CompatibleRunInput<CommandShapeAt<CommandShape<A, Flags, Tree, Result>, NoInfer<Path>>, Input>] ? readonly [io?: Partial<InvocationIO>] : readonly [invalidInput: never]): Promise<RunOutcome<CommandShapeAt<CommandShape<A, Flags, Tree, Result>, Path>["result"]>>;
|
|
717
|
+
/**
|
|
718
|
+
* Parse `process.argv`, resolve subcommands, run Extension hooks, and
|
|
719
|
+
* execute the matched Command Action.
|
|
720
|
+
*
|
|
721
|
+
* This is the terminal CLI boundary — call it on the root builder. It
|
|
722
|
+
* renders a failure once (through Extension `onError` hooks, ending in
|
|
723
|
+
* Core's default renderer), sets `process.exitCode` (`1`, or
|
|
724
|
+
* `130` for an `AbortError` cancellation), and resolves to the exit code.
|
|
725
|
+
*
|
|
726
|
+
* @param options - Optional overrides (e.g. custom `argv` and captured
|
|
727
|
+
* `io` for in-process testing of exit codes and
|
|
728
|
+
* rendered failures)
|
|
729
|
+
* @returns The terminal exit code (`0`, `1`, or `130` for cancellation)
|
|
730
|
+
*/
|
|
731
|
+
execute(this: {
|
|
732
|
+
readonly _types: {
|
|
733
|
+
readonly caps: "app";
|
|
734
|
+
};
|
|
735
|
+
}, options?: {
|
|
736
|
+
argv?: string[];
|
|
737
|
+
io?: Partial<InvocationIO>;
|
|
738
|
+
}): Promise<number>;
|
|
253
739
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
740
|
+
//#endregion
|
|
741
|
+
//#region src/errors.d.ts
|
|
742
|
+
/** A value thrown by user code crossing a Core error boundary. */
|
|
743
|
+
type CaughtError = unknown;
|
|
744
|
+
/** Details for a subcommand token that could not be resolved. */
|
|
745
|
+
interface CommandNotFoundErrorDetails {
|
|
746
|
+
/** Unrecognized subcommand token. */
|
|
747
|
+
input: string;
|
|
748
|
+
/** Canonical names of visible available child commands. */
|
|
749
|
+
available: string[];
|
|
750
|
+
/** Canonical path to the command whose child could not be resolved. */
|
|
751
|
+
commandPath: string[];
|
|
752
|
+
/** Readonly, serializable snapshot of the parent command. */
|
|
753
|
+
parentCommand: CommandSnapshot;
|
|
261
754
|
}
|
|
262
|
-
/**
|
|
263
|
-
interface
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
/** Not supported on boolean flags — use `type: "string"` with `parse`. */
|
|
270
|
-
parse?: never;
|
|
755
|
+
/** Aggregated Standard Schema and required-value validation failures. */
|
|
756
|
+
interface ValidationErrorDetails {
|
|
757
|
+
/** Issues normalized under `args.<name>` or `flags.<name>`. */
|
|
758
|
+
issues: readonly {
|
|
759
|
+
readonly message: string;
|
|
760
|
+
readonly path: string;
|
|
761
|
+
}[];
|
|
271
762
|
}
|
|
272
|
-
/**
|
|
273
|
-
interface
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
763
|
+
/** Details for argv syntax or built-in value parsing failures. */
|
|
764
|
+
interface ParseErrorDetails {
|
|
765
|
+
readonly flag?: string;
|
|
766
|
+
readonly argument?: string;
|
|
767
|
+
/** Retained for compatibility; Core no longer populates this field. */
|
|
768
|
+
readonly value?: string;
|
|
769
|
+
readonly reason?: string;
|
|
279
770
|
}
|
|
280
|
-
/**
|
|
281
|
-
interface
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
/** Not supported on path flags — use `type: "string"` with `parse`. */
|
|
286
|
-
parse?: never;
|
|
771
|
+
/** Details for runtime recipe, Extension, Context, and documentation failures. */
|
|
772
|
+
interface DefinitionErrorDetails {
|
|
773
|
+
readonly subject?: "command" | "context" | "extension" | "flag" | "argument";
|
|
774
|
+
readonly name?: string;
|
|
775
|
+
readonly reason?: string;
|
|
287
776
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
/** Not supported on json flags — use `type: "string"` with `parse`. */
|
|
294
|
-
parse?: never;
|
|
777
|
+
interface CrustErrorDetailsMap {
|
|
778
|
+
DEFINITION: DefinitionErrorDetails | undefined;
|
|
779
|
+
VALIDATION: ValidationErrorDetails | undefined;
|
|
780
|
+
PARSE: ParseErrorDetails | undefined;
|
|
781
|
+
COMMAND_NOT_FOUND: CommandNotFoundErrorDetails;
|
|
295
782
|
}
|
|
296
|
-
/**
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
783
|
+
/**
|
|
784
|
+
* All possible error codes emitted by Crust.
|
|
785
|
+
*
|
|
786
|
+
* - `DEFINITION` — Runtime recipe, Extension, Context, or documentation definition failure
|
|
787
|
+
* - `VALIDATION` — Missing required arguments or flags
|
|
788
|
+
* - `PARSE` — Argv parsing failures (unknown flags, type coercion)
|
|
789
|
+
* - `COMMAND_NOT_FOUND` — Unrecognised subcommand at the current level
|
|
790
|
+
*
|
|
791
|
+
* @example
|
|
792
|
+
* ```ts
|
|
793
|
+
* const outcome = await app.run(path, input);
|
|
794
|
+
* if (outcome.status === "failed") {
|
|
795
|
+
* const err = outcome.error;
|
|
796
|
+
* if (err instanceof CrustError) {
|
|
797
|
+
* switch (err.code) {
|
|
798
|
+
* case "VALIDATION":
|
|
799
|
+
* console.error(err.message);
|
|
800
|
+
* showHelp(cmd);
|
|
801
|
+
* break;
|
|
802
|
+
* case "PARSE":
|
|
803
|
+
* console.error(err.message);
|
|
804
|
+
* break;
|
|
805
|
+
* }
|
|
806
|
+
* }
|
|
807
|
+
* }
|
|
808
|
+
* ```
|
|
809
|
+
*/
|
|
810
|
+
type CrustErrorCode = keyof CrustErrorDetailsMap;
|
|
811
|
+
type CrustErrorDetails<C extends CrustErrorCode> = CrustErrorDetailsMap[C];
|
|
812
|
+
interface CrustErrorJson<C extends CrustErrorCode> {
|
|
813
|
+
code: C;
|
|
814
|
+
message: string;
|
|
815
|
+
details: CrustErrorDetails<C>;
|
|
300
816
|
}
|
|
301
|
-
/**
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
817
|
+
/**
|
|
818
|
+
* A typed error for runtime recipe, Extension, Context, documentation, argv, and validation failures.
|
|
819
|
+
*
|
|
820
|
+
* Every `CrustError` carries a {@link CrustErrorCode} that identifies the specific
|
|
821
|
+
* failure, enabling programmatic error handling without fragile message parsing.
|
|
822
|
+
*
|
|
823
|
+
* @example
|
|
824
|
+
* ```ts
|
|
825
|
+
* import { CrustError } from "@crustjs/core";
|
|
826
|
+
*
|
|
827
|
+
* const outcome = await app.run(["deploy"], { args: { target: "prod" } });
|
|
828
|
+
* if (outcome.status === "failed") {
|
|
829
|
+
* const err = outcome.error;
|
|
830
|
+
* if (err instanceof CrustError) {
|
|
831
|
+
* console.error(`[${err.code}] ${err.message}`);
|
|
832
|
+
* }
|
|
833
|
+
* }
|
|
834
|
+
* ```
|
|
835
|
+
*/
|
|
836
|
+
export declare class CrustError<C extends CrustErrorCode = CrustErrorCode> extends Error {
|
|
837
|
+
/** Machine-readable error code for programmatic handling */
|
|
838
|
+
readonly code: C;
|
|
839
|
+
/** Structured payload for programmatic handling */
|
|
840
|
+
readonly details: CrustErrorDetails<C>;
|
|
841
|
+
/** Optional wrapped original error/value */
|
|
842
|
+
override cause?: unknown;
|
|
843
|
+
constructor(code: C, message: string, ...details: undefined extends CrustErrorDetails<C> ? [] | [CrustErrorDetails<C>] : [CrustErrorDetails<C>]);
|
|
844
|
+
is<T extends CrustErrorCode>(code: T): this is CrustError<T>;
|
|
845
|
+
withCause(cause: unknown): this;
|
|
846
|
+
toJSON(): CrustErrorJson<C>;
|
|
326
847
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
parse?: never;
|
|
848
|
+
//#endregion
|
|
849
|
+
//#region src/api/extension.d.ts
|
|
850
|
+
declare const finishedBrand: unique symbol;
|
|
851
|
+
/** Opaque token returned by {@link ExtensionContext.finish} to end an invocation successfully. */
|
|
852
|
+
interface Finished {
|
|
853
|
+
readonly [finishedBrand]: true;
|
|
334
854
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
855
|
+
type InvocationOutcome = {
|
|
856
|
+
readonly status: "completed";
|
|
857
|
+
} | {
|
|
858
|
+
readonly status: "finished";
|
|
859
|
+
readonly by: ExtensionId;
|
|
860
|
+
} | {
|
|
861
|
+
readonly status: "failed";
|
|
862
|
+
readonly error: unknown;
|
|
863
|
+
readonly by?: ExtensionId;
|
|
864
|
+
};
|
|
865
|
+
/** Authored root metadata fields an Extension may require. */
|
|
866
|
+
type RootMetaKey = keyof RootCommandMeta;
|
|
867
|
+
type RootCommandSnapshot<K extends RootMetaKey> = CommandSnapshot & {
|
|
868
|
+
readonly meta: Readonly<Required<Pick<CommandMeta, K>>>;
|
|
869
|
+
};
|
|
870
|
+
/** Artifact paths relative to `outDir`. */
|
|
871
|
+
type BuildArtifacts = readonly string[];
|
|
872
|
+
/** Artifacts reported by Extension build hooks, in hook execution order. */
|
|
873
|
+
interface BuildReport {
|
|
874
|
+
readonly extensions: readonly {
|
|
875
|
+
readonly id: ExtensionId;
|
|
876
|
+
readonly files: readonly string[] | "unknown";
|
|
877
|
+
}[];
|
|
344
878
|
}
|
|
345
|
-
/**
|
|
346
|
-
interface
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
879
|
+
/** Build-time context passed to an Extension's artifact generator. */
|
|
880
|
+
interface ExtensionBuildContext<MetaKeys extends RootMetaKey = never> {
|
|
881
|
+
/**
|
|
882
|
+
* Frozen snapshot prepared before this hook starts. It does not include this hook's own
|
|
883
|
+
* outputs; later-registered hooks receive refreshed snapshots.
|
|
884
|
+
*/
|
|
885
|
+
readonly snapshot: RootCommandSnapshot<MetaKeys>;
|
|
886
|
+
/** Resolved absolute output directory. */
|
|
887
|
+
readonly outDir: string;
|
|
352
888
|
}
|
|
353
|
-
/**
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
889
|
+
/**
|
|
890
|
+
* Readonly invocation view passed to Extension hooks.
|
|
891
|
+
*
|
|
892
|
+
* Commands cross this boundary as readonly, serializable
|
|
893
|
+
* {@link CommandSnapshot}s — never as internal command nodes.
|
|
894
|
+
*
|
|
895
|
+
* Examples below assume the `tool deploy api --trace -- --dry-run` invocation.
|
|
896
|
+
*/
|
|
897
|
+
interface ExtensionContext<Defs extends readonly NamedExtensionFlagDef[] = [], Deps extends ContextMap = {}, MetaKeys extends RootMetaKey = never> extends Readonly<InvocationIO> {
|
|
898
|
+
/**
|
|
899
|
+
* Complete argv passed to the application, including routed command names.
|
|
900
|
+
* For typed `run()` this is the command path only; structured values are never rendered as argv.
|
|
901
|
+
*
|
|
902
|
+
* @example `["deploy", "api", "--trace", "--", "--dry-run"]`
|
|
903
|
+
*/
|
|
904
|
+
readonly argv: readonly string[];
|
|
905
|
+
/**
|
|
906
|
+
* Snapshot of the application root, including Extension-contributed flags/commands.
|
|
907
|
+
*
|
|
908
|
+
* @example
|
|
909
|
+
* ```ts
|
|
910
|
+
* ctx.rootCommand.meta.name; // "tool"
|
|
911
|
+
* Object.keys(ctx.rootCommand.subCommands); // ["deploy"]
|
|
912
|
+
* ```
|
|
913
|
+
*/
|
|
914
|
+
readonly rootCommand: RootCommandSnapshot<MetaKeys>;
|
|
915
|
+
/**
|
|
916
|
+
* Snapshot of the resolved command (the root when routing failed).
|
|
917
|
+
*
|
|
918
|
+
* @example
|
|
919
|
+
* ```ts
|
|
920
|
+
* ctx.command.meta.name; // "deploy"
|
|
921
|
+
* ctx.command.args; // [{ name: "target", type: "string", required: true }]
|
|
922
|
+
* ```
|
|
923
|
+
*/
|
|
924
|
+
readonly command: CommandSnapshot;
|
|
925
|
+
/**
|
|
926
|
+
* Canonical names from the application root through the resolved command.
|
|
927
|
+
*
|
|
928
|
+
* @example `["tool", "deploy"]`
|
|
929
|
+
*/
|
|
930
|
+
readonly commandPath: readonly string[];
|
|
931
|
+
/**
|
|
932
|
+
* Bound positional values for the resolved command, before validation: parsed from
|
|
933
|
+
* argv tokens for `execute()`, taken as-is from the structured input for typed `run()`
|
|
934
|
+
* (URL/JSON values keep their identity).
|
|
935
|
+
*
|
|
936
|
+
* @example `{ target: "api" }`
|
|
937
|
+
*/
|
|
938
|
+
readonly args: Readonly<Record<string, ParsedArgValue>>;
|
|
939
|
+
/**
|
|
940
|
+
* Bound own flags plus unknown flags from the resolved command, before validation;
|
|
941
|
+
* parsed from argv tokens for `execute()`, taken as-is from the structured input for typed `run()`.
|
|
942
|
+
*
|
|
943
|
+
* @example `{ trace: true }`
|
|
944
|
+
*/
|
|
945
|
+
readonly flags: Readonly<InferExtensionFlags<Defs> & Record<string, ParsedFlagValue>>;
|
|
946
|
+
/**
|
|
947
|
+
* Positional values that appeared after the `--` separator, or the `raw` array passed to typed `run()`.
|
|
948
|
+
*
|
|
949
|
+
* @example `["--dry-run"]`
|
|
950
|
+
*/
|
|
951
|
+
readonly rawArgs: readonly string[];
|
|
952
|
+
/** Declared Contexts, constructed lazily on first property access. */
|
|
953
|
+
readonly ctx: ContextBag<Deps>;
|
|
954
|
+
/**
|
|
955
|
+
* End the invocation successfully before validation, Context construction, and the action.
|
|
956
|
+
*
|
|
957
|
+
* @example
|
|
958
|
+
* ```ts
|
|
959
|
+
* preRun(ctx) {
|
|
960
|
+
* if (ctx.flags.help === true) return ctx.finish();
|
|
961
|
+
* }
|
|
962
|
+
* ```
|
|
963
|
+
*/
|
|
964
|
+
readonly finish: () => Finished;
|
|
360
965
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
966
|
+
interface ExtensionHooks<Defs extends readonly NamedExtensionFlagDef[] = [], Deps extends ContextMap = {}, MetaKeys extends RootMetaKey = never> {
|
|
967
|
+
/**
|
|
968
|
+
* Runs after routing and input binding (argv parsing for `execute()`, structured
|
|
969
|
+
* binding for typed `run()`), before validation, in `.extend()` order.
|
|
970
|
+
* Return `ctx.finish()` to end the invocation successfully; later pre-run hooks,
|
|
971
|
+
* validation, schemas, Contexts, and the Command Action do not run.
|
|
972
|
+
*/
|
|
973
|
+
readonly preRun?: (ctx: ExtensionContext<Defs, Deps, MetaKeys>) => Awaitable<void | Finished>;
|
|
974
|
+
/**
|
|
975
|
+
* Runs after the invocation settles, in reverse `.extend()` order. This is the
|
|
976
|
+
* `finally` slot for cleanup and post-run side effects.
|
|
977
|
+
*/
|
|
978
|
+
readonly postRun?: (ctx: ExtensionContext<Defs, Deps, MetaKeys>, outcome: InvocationOutcome) => Awaitable<void>;
|
|
979
|
+
/**
|
|
980
|
+
* Renders a failure in `execute()` only. Return true when rendered to stop the
|
|
981
|
+
* chain; falsy values delegate to the next Extension and then Core's renderer.
|
|
982
|
+
* A hook that throws ends the chain: remaining hooks are skipped and Core's
|
|
983
|
+
* default renderer reports the original failure.
|
|
984
|
+
*
|
|
985
|
+
* Receives the base context: routing or syntax-parse failures render with a
|
|
986
|
+
* fallback context whose `flags` are empty, so owned-flag inference would lie here.
|
|
987
|
+
*/
|
|
988
|
+
readonly onError?: (error: CaughtError, ctx: ExtensionContext<[], Deps, MetaKeys>) => Awaitable<boolean | void>;
|
|
368
989
|
}
|
|
369
990
|
/**
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
* ```
|
|
383
|
-
*/
|
|
384
|
-
type FlagDef = StringFlagDef | NumberFlagDef | BooleanFlagDef | UrlFlagDef | PathFlagDef | JsonFlagDef | StringMultiFlagDef | NumberMultiFlagDef | BooleanMultiFlagDef | UrlMultiFlagDef | PathMultiFlagDef | JsonMultiFlagDef;
|
|
385
|
-
/** Record mapping flag names to their definitions */
|
|
386
|
-
type FlagsDef = Record<string, FlagDef>;
|
|
387
|
-
/**
|
|
388
|
-
* Extract the `short` alias literal from a flag definition.
|
|
389
|
-
* Resolves to `never` when no `short` field exists or when the type
|
|
390
|
-
* is the broad `string` (not a narrowed literal).
|
|
391
|
-
*/
|
|
392
|
-
type ExtractShort<F> = F extends {
|
|
393
|
-
short: infer S;
|
|
394
|
-
} ? S extends string ? string extends S ? never : S : never : never;
|
|
395
|
-
/**
|
|
396
|
-
* Extract alias string literals from the `aliases` array of a flag definition.
|
|
397
|
-
* Resolves to `never` when no `aliases` field exists or when the element type
|
|
398
|
-
* is the broad `string` (not narrowed literals).
|
|
399
|
-
*/
|
|
400
|
-
type ExtractLongAliases<F> = F extends {
|
|
401
|
-
aliases: infer A;
|
|
402
|
-
} ? A extends readonly string[] ? string extends A[number] ? never : A[number] : never : never;
|
|
403
|
-
/**
|
|
404
|
-
* Extract all alias identifiers (short + long) from a flag definition.
|
|
405
|
-
*
|
|
406
|
-
* Generalized to work with any shape (`FlagDef`, `FlagSpec`, etc.) —
|
|
407
|
-
* values without `short`/`aliases` fields resolve to `never`.
|
|
408
|
-
*
|
|
409
|
-
* Includes `string extends ...` guards so non-narrowed types (e.g. the
|
|
410
|
-
* broad `string` type from a default generic) resolve to `never` instead
|
|
411
|
-
* of causing false-positive collisions.
|
|
412
|
-
*/
|
|
413
|
-
type ExtractAllAliases<F> = ExtractShort<F> | ExtractLongAliases<F>;
|
|
414
|
-
/**
|
|
415
|
-
* Collects aliases from every flag *except* flag K.
|
|
416
|
-
* Used to detect alias→alias duplicates across different flags.
|
|
417
|
-
*/
|
|
418
|
-
type AliasesExcluding<
|
|
419
|
-
F extends Record<string, unknown>,
|
|
420
|
-
K extends keyof F & string
|
|
421
|
-
> = { [J in Exclude<keyof F & string, K>] : ExtractAllAliases<F[J]> }[Exclude<keyof F & string, K>];
|
|
422
|
-
/**
|
|
423
|
-
* Per-flag collision detection: resolves to the alias literal(s) of flag K
|
|
424
|
-
* that collide with another flag's name or another flag's alias,
|
|
425
|
-
* or `never` when K's aliases are all unique.
|
|
426
|
-
*/
|
|
427
|
-
type CollidingAliases<
|
|
428
|
-
F extends Record<string, unknown>,
|
|
429
|
-
K extends keyof F & string
|
|
430
|
-
> = (ExtractAllAliases<F[K]> & Exclude<keyof F & string, K>) | (ExtractAllAliases<F[K]> & AliasesExcluding<F, K>);
|
|
431
|
-
/**
|
|
432
|
-
* Per-flag validation mapped type. Resolves to `F` when no collisions exist.
|
|
433
|
-
* For flags with colliding aliases, adds a branded error property to the
|
|
434
|
-
* specific flag definition, causing a type error on that flag's value.
|
|
435
|
-
*
|
|
436
|
-
* Generalized to work with any `Record<string, unknown>` shape — core uses
|
|
437
|
-
* it with `FlagsDef`, the validate package uses it with `FlagShape`, etc.
|
|
438
|
-
*
|
|
439
|
-
* ```
|
|
440
|
-
* Property 'FIX_ALIAS_COLLISION' is missing in type '{ type: "string"; short: "m" }'
|
|
441
|
-
* but required in type
|
|
442
|
-
* '{ readonly FIX_ALIAS_COLLISION: "Alias \"m\" collides with another flag name or alias" }'.
|
|
443
|
-
* ```
|
|
444
|
-
*/
|
|
445
|
-
type ValidateFlagAliases<F extends Record<string, unknown>> = { [K in keyof F & string] : CollidingAliases<F, K> extends never ? F[K] : F[K] & {
|
|
446
|
-
readonly FIX_ALIAS_COLLISION: `Alias "${CollidingAliases<F, K>}" collides with another flag name or alias`;
|
|
447
|
-
} };
|
|
448
|
-
/**
|
|
449
|
-
* Collects aliases from inherited flags, excluding those whose keys the
|
|
450
|
-
* child overrides (intentional override — child redefines a flag by name).
|
|
451
|
-
*/
|
|
452
|
-
type InheritedAliasesExcluding<
|
|
453
|
-
I extends Record<string, unknown>,
|
|
454
|
-
OverrideKeys extends string
|
|
455
|
-
> = { [K in Exclude<keyof I & string, OverrideKeys>] : ExtractAllAliases<I[K]> }[Exclude<keyof I & string, OverrideKeys>];
|
|
456
|
-
/**
|
|
457
|
-
* Per-flag cross-collision detection between a child flag K (from local
|
|
458
|
-
* flags F) and the inherited flag set I. Resolves to the colliding
|
|
459
|
-
* identifier, or `never` when no collision exists.
|
|
460
|
-
*
|
|
461
|
-
* Detects three collision classes:
|
|
462
|
-
* 1. Child alias → inherited flag name
|
|
463
|
-
* 2. Child alias → inherited flag alias
|
|
464
|
-
* 3. Child flag name → inherited flag alias
|
|
465
|
-
*
|
|
466
|
-
* Intentional name overrides (child defines a flag with the same key as
|
|
467
|
-
* an inherited flag) are excluded — those are handled by `MergeFlags`.
|
|
468
|
-
*/
|
|
469
|
-
type CrossCollision<
|
|
470
|
-
I extends Record<string, unknown>,
|
|
471
|
-
F extends Record<string, unknown>,
|
|
472
|
-
K extends keyof F & string
|
|
473
|
-
> = (ExtractAllAliases<F[K]> & Exclude<keyof I & string, keyof F & string>) | (ExtractAllAliases<F[K]> & InheritedAliasesExcluding<I, keyof F & string>) | (K & InheritedAliasesExcluding<I, keyof F & string>);
|
|
474
|
-
/**
|
|
475
|
-
* Per-flag validation mapped type for cross-collisions between inherited
|
|
476
|
-
* and local flags. Resolves to `F` when no collisions exist.
|
|
477
|
-
*
|
|
478
|
-
* When `Inherited` is the wide `FlagsDef` type (root commands with no
|
|
479
|
-
* parent), the validation is skipped to avoid false positives since
|
|
480
|
-
* `keyof FlagsDef` is `string`.
|
|
481
|
-
*
|
|
482
|
-
* ```
|
|
483
|
-
* Property 'FIX_INHERITED_COLLISION' is missing in type '{ type: "string"; aliases: ["verbose"] }'
|
|
484
|
-
* but required in type
|
|
485
|
-
* '{ readonly FIX_INHERITED_COLLISION: "\"verbose\" collides with inherited flag" }'.
|
|
486
|
-
* ```
|
|
487
|
-
*/
|
|
488
|
-
type ValidateCrossCollisions<
|
|
489
|
-
I extends Record<string, unknown>,
|
|
490
|
-
F extends Record<string, unknown>
|
|
491
|
-
> = string extends keyof I ? F : { [K in keyof F & string] : CrossCollision<I, F, K> extends never ? F[K] : F[K] & {
|
|
492
|
-
readonly FIX_INHERITED_COLLISION: `"${CrossCollision<I, F, K> & string}" collides with inherited flag`;
|
|
493
|
-
} };
|
|
494
|
-
/**
|
|
495
|
-
* Detects whether a single alias literal starts with `"no-"`.
|
|
496
|
-
* Resolves to the offending alias, or `never` when it is clean.
|
|
497
|
-
*/
|
|
498
|
-
type NoPrefixedAlias<A> = A extends `no-${string}` ? A : never;
|
|
499
|
-
/**
|
|
500
|
-
* Collects all `"no-"`-prefixed alias literals from a flag definition.
|
|
501
|
-
* Checks both `short` and `aliases` fields.
|
|
502
|
-
* Non-narrowed `string` types resolve to `never` to avoid false positives.
|
|
503
|
-
*/
|
|
504
|
-
type NoPrefixedAliases<F> = NoPrefixedAlias<ExtractShort<F>> | NoPrefixedAlias<ExtractLongAliases<F>>;
|
|
505
|
-
/**
|
|
506
|
-
* Per-flag validation mapped type. Resolves to `F` when no `"no-"` prefixes
|
|
507
|
-
* exist on flag names, short aliases, or long aliases. For flags with offending values,
|
|
508
|
-
* adds a branded error property causing a compile-time type error.
|
|
509
|
-
*
|
|
510
|
-
* The `"no-"` prefix is reserved for boolean flag negation (`--no-flag`).
|
|
511
|
-
* Define only the positive form (e.g. `cache`) and use `--no-cache` at runtime.
|
|
512
|
-
*
|
|
513
|
-
* ```
|
|
514
|
-
* Property 'FIX_NO_PREFIX' is missing in type '{ type: "boolean" }'
|
|
515
|
-
* but required in type
|
|
516
|
-
* '{ readonly FIX_NO_PREFIX: "Flag name \"no-cache\" must not start with \"no-\"; define \"cache\" instead and use \"--no-cache\" at runtime" }'.
|
|
517
|
-
* ```
|
|
518
|
-
*/
|
|
519
|
-
type ValidateNoPrefixedFlags<F extends Record<string, unknown>> = { [K in keyof F & string] : K extends `no-${infer Base}` ? F[K] & {
|
|
520
|
-
readonly FIX_NO_PREFIX: `Flag name "${K}" must not start with "no-"; define "${Base}" instead and use "--no-${Base}" at runtime`;
|
|
521
|
-
} : NoPrefixedAliases<F[K]> extends never ? F[K] : F[K] & {
|
|
522
|
-
readonly FIX_NO_PREFIX: `Alias "${NoPrefixedAliases<F[K]>}" must not start with "no-"; the "no-" prefix is reserved for boolean negation`;
|
|
523
|
-
} };
|
|
524
|
-
/**
|
|
525
|
-
* Per-arg validation tuple type. Resolves to `A` when the constraint is
|
|
526
|
-
* satisfied (only the last arg is variadic). For non-last args that have
|
|
527
|
-
* `variadic: true`, adds a branded error property to the specific arg.
|
|
528
|
-
*
|
|
529
|
-
* Generalized to work with any ordered tuple of object-typed definitions —
|
|
530
|
-
* core uses it with `ArgsDef`, the validate package uses it with
|
|
531
|
-
* `ArgSpec[]`, etc. Uses `readonly object[]` to avoid TypeScript's weak
|
|
532
|
-
* type detection (all-optional constraint rejection).
|
|
533
|
-
*
|
|
534
|
-
* ```
|
|
535
|
-
* Property 'FIX_VARIADIC_POSITION' is missing in type '{ name: "files"; ... variadic: true }'
|
|
536
|
-
* but required in type
|
|
537
|
-
* '{ readonly FIX_VARIADIC_POSITION: "Only the last positional argument can be variadic" }'.
|
|
538
|
-
* ```
|
|
539
|
-
*/
|
|
540
|
-
type ValidateVariadicArgs<A extends readonly object[]> = A extends readonly [infer Head, ...infer Tail extends readonly object[]] ? Tail extends readonly [unknown, ...unknown[]] ? Head extends {
|
|
541
|
-
variadic: true;
|
|
542
|
-
} ? readonly [Head & {
|
|
543
|
-
readonly FIX_VARIADIC_POSITION: "Only the last positional argument can be variadic";
|
|
544
|
-
}, ...ValidateVariadicArgs<Tail>] : readonly [Head, ...ValidateVariadicArgs<Tail>] : readonly [Head] : A;
|
|
545
|
-
/**
|
|
546
|
-
* Picks only the flags from `F` that have `inherit: true`.
|
|
547
|
-
*
|
|
548
|
-
* Flags without `inherit` (or with `inherit` omitted) are excluded.
|
|
549
|
-
*
|
|
550
|
-
* @example
|
|
551
|
-
* ```ts
|
|
552
|
-
* type Flags = {
|
|
553
|
-
* verbose: { type: "boolean"; inherit: true };
|
|
554
|
-
* port: { type: "number" };
|
|
555
|
-
* };
|
|
556
|
-
* type Result = InheritableFlags<Flags>;
|
|
557
|
-
* // Result = { verbose: { type: "boolean"; inherit: true } }
|
|
558
|
-
* ```
|
|
559
|
-
*/
|
|
560
|
-
type InheritableFlags<F extends FlagsDef> = { [K in keyof F as F[K] extends {
|
|
561
|
-
inherit: true;
|
|
562
|
-
} ? K : never] : F[K] };
|
|
563
|
-
/**
|
|
564
|
-
* Merges parent flags with local flags, where local keys override parent keys.
|
|
565
|
-
*
|
|
566
|
-
* @example
|
|
567
|
-
* ```ts
|
|
568
|
-
* type Parent = { verbose: { type: "boolean" }; port: { type: "number" } };
|
|
569
|
-
* type Local = { port: { type: "string" } };
|
|
570
|
-
* type Result = MergeFlags<Parent, Local>;
|
|
571
|
-
* // Result = { verbose: { type: "boolean" }; port: { type: "string" } }
|
|
572
|
-
* ```
|
|
573
|
-
*/
|
|
574
|
-
type MergeFlags<
|
|
575
|
-
Parent extends FlagsDef,
|
|
576
|
-
Local extends FlagsDef
|
|
577
|
-
> = Simplify<Omit<Parent, keyof Local> & Local>;
|
|
578
|
-
/**
|
|
579
|
-
* Computes the effective flags for a command by filtering the inherited flags
|
|
580
|
-
* (only those with `inherit: true`) and merging them with local flags.
|
|
581
|
-
*
|
|
582
|
-
* Local flags override inherited flags with the same key.
|
|
583
|
-
*
|
|
584
|
-
* @example
|
|
585
|
-
* ```ts
|
|
586
|
-
* type Inherited = {
|
|
587
|
-
* verbose: { type: "boolean"; inherit: true };
|
|
588
|
-
* port: { type: "number" };
|
|
589
|
-
* };
|
|
590
|
-
* type Local = { output: { type: "string" } };
|
|
591
|
-
* type Result = EffectiveFlags<Inherited, Local>;
|
|
592
|
-
* // Result = { verbose: { type: "boolean"; inherit: true }; output: { type: "string" } }
|
|
593
|
-
* ```
|
|
594
|
-
*/
|
|
595
|
-
type EffectiveFlags<
|
|
596
|
-
Inherited extends FlagsDef,
|
|
597
|
-
Local extends FlagsDef
|
|
598
|
-
> = string extends keyof Inherited ? Local : MergeFlags<InheritableFlags<Inherited>, Local>;
|
|
599
|
-
/**
|
|
600
|
-
* Infer the resolved type for a single ArgDef:
|
|
601
|
-
*
|
|
602
|
-
* - **variadic** → `primitive[]` (always an array, never `undefined`,
|
|
603
|
-
* regardless of `required` or `default`)
|
|
604
|
-
* - **required** or **has default** → `primitive` (non-optional)
|
|
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.
|
|
610
|
-
*/
|
|
611
|
-
type InferArgValue<A extends ArgDef> = A extends {
|
|
612
|
-
type: infer _T extends ValueType;
|
|
613
|
-
} ? A extends {
|
|
614
|
-
variadic: true;
|
|
615
|
-
} ? ResolveBaseType<A>[] : A extends {
|
|
616
|
-
required: true;
|
|
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;
|
|
626
|
-
/**
|
|
627
|
-
* Recursively converts an ArgsDef tuple into a named object type.
|
|
628
|
-
*
|
|
629
|
-
* Each element's `name` literal becomes a key, and its value is resolved
|
|
630
|
-
* via {@link InferArgValue}. Uses intersection + `Simplify` to flatten.
|
|
631
|
-
*/
|
|
632
|
-
type InferArgsTuple<A extends readonly ArgDef[]> = A extends readonly [infer Head extends ArgDef, ...infer Tail extends readonly ArgDef[]] ? { [K in Head["name"]] : InferArgValue<Head> } & InferArgsTuple<Tail> : {};
|
|
633
|
-
/** Flattens an intersection of objects into a single object type for readability */
|
|
634
|
-
type Simplify<T> = { [K in keyof T] : T[K] };
|
|
635
|
-
/**
|
|
636
|
-
* Maps an ArgsDef tuple to resolved arg types keyed by each arg's `name`.
|
|
637
|
-
*
|
|
638
|
-
* @example
|
|
639
|
-
* ```ts
|
|
640
|
-
* type Result = InferArgs<readonly [
|
|
641
|
-
* { name: "port"; type: "number"; default: 3000 },
|
|
642
|
-
* { name: "name"; type: "string"; required: true },
|
|
643
|
-
* { name: "files"; type: "string"; variadic: true },
|
|
644
|
-
* ]>;
|
|
645
|
-
* // Result = { port: number; name: string; files: string[] }
|
|
646
|
-
* ```
|
|
647
|
-
*/
|
|
648
|
-
type InferArgs<A> = A extends ArgsDef ? Simplify<InferArgsTuple<A>> : Record<string, never>;
|
|
649
|
-
/**
|
|
650
|
-
* Infer the resolved type for a single FlagDef:
|
|
651
|
-
*
|
|
652
|
-
* - **multiple** → wraps the resolved type in an array
|
|
653
|
-
* - **required** or **has default** → `primitive` (non-optional)
|
|
654
|
-
* - otherwise → `primitive | undefined`
|
|
655
|
-
*/
|
|
656
|
-
type InferFlagValue<F extends FlagDef> = F extends {
|
|
657
|
-
type: infer _T extends ValueType;
|
|
991
|
+
* A flag owned by an Extension. `recursive` (default `true`) contributes the
|
|
992
|
+
* flag to every command in the application; set `false` for a root-only flag.
|
|
993
|
+
*/
|
|
994
|
+
type ExtensionFlagDef = FlagDef & {
|
|
995
|
+
readonly recursive?: boolean;
|
|
996
|
+
};
|
|
997
|
+
/** A named flag definition accepted by {@link defineExtension}. */
|
|
998
|
+
type NamedExtensionFlagDef = NamedFlagDef & {
|
|
999
|
+
readonly recursive?: boolean;
|
|
1000
|
+
};
|
|
1001
|
+
type InferPreSchemaExtensionFlag<F extends ExtensionFlagDef> = F extends {
|
|
1002
|
+
schema: unknown;
|
|
658
1003
|
} ? F extends {
|
|
659
|
-
|
|
1004
|
+
multiple: true;
|
|
660
1005
|
} ? F extends {
|
|
661
|
-
|
|
662
|
-
} ?
|
|
663
|
-
|
|
664
|
-
} ?
|
|
665
|
-
|
|
666
|
-
} ?
|
|
667
|
-
|
|
668
|
-
} ?
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
*/
|
|
681
|
-
type
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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;
|
|
749
|
-
}
|
|
750
|
-
/**
|
|
751
|
-
* The result of parsing argv against a command's arg/flag definitions.
|
|
752
|
-
*
|
|
753
|
-
* Generic parameters flow from the command definition to provide
|
|
754
|
-
* strongly-typed `args` and `flags` objects.
|
|
755
|
-
*/
|
|
756
|
-
interface ParseResult<
|
|
757
|
-
A extends ArgsDef = ArgsDef,
|
|
758
|
-
F extends FlagsDef = FlagsDef
|
|
759
|
-
> {
|
|
760
|
-
/** Resolved positional arguments, keyed by arg name */
|
|
761
|
-
args: InferArgs<A>;
|
|
762
|
-
/** Resolved flags, keyed by flag name */
|
|
763
|
-
flags: InferFlags<F>;
|
|
764
|
-
/** Raw arguments that appeared after the `--` separator */
|
|
765
|
-
rawArgs: string[];
|
|
766
|
-
}
|
|
767
|
-
interface PluginState {
|
|
768
|
-
get<T = unknown>(key: string): T | undefined;
|
|
769
|
-
has(key: string): boolean;
|
|
770
|
-
set(key: string, value: unknown): void;
|
|
771
|
-
delete(key: string): boolean;
|
|
1006
|
+
type: "boolean";
|
|
1007
|
+
} ? boolean[] | undefined : string[] | undefined : F extends {
|
|
1008
|
+
type: "boolean";
|
|
1009
|
+
} ? boolean | undefined : string | undefined : F extends {
|
|
1010
|
+
required: true;
|
|
1011
|
+
} ? F extends {
|
|
1012
|
+
default: unknown;
|
|
1013
|
+
} ? InferFlags<{
|
|
1014
|
+
value: F;
|
|
1015
|
+
}>["value"] : InferFlags<{
|
|
1016
|
+
value: F;
|
|
1017
|
+
}>["value"] | undefined : InferFlags<{
|
|
1018
|
+
value: F;
|
|
1019
|
+
}>["value"];
|
|
1020
|
+
type InferExtensionFlag<F> = F extends ExtensionFlagDef ? F extends {
|
|
1021
|
+
recursive: false;
|
|
1022
|
+
} ? InferPreSchemaExtensionFlag<F> | undefined : InferPreSchemaExtensionFlag<F> : never;
|
|
1023
|
+
/** Infer the syntax-parsed values visible to an Extension's hooks. */
|
|
1024
|
+
type InferExtensionFlags<Defs extends readonly NamedExtensionFlagDef[]> = { [K in keyof NamedFlagsRecord<Defs>]: InferExtensionFlag<NamedFlagsRecord<Defs>[K]>; };
|
|
1025
|
+
/** A documentation section an Extension contributes to one command path. */
|
|
1026
|
+
type ExtensionSectionContribution = RuntimeCommandSectionInput & {
|
|
1027
|
+
readonly command: readonly string[];
|
|
1028
|
+
};
|
|
1029
|
+
type CommandDefinitionsDependencies<Commands extends readonly CommandDefinition<any, any, any, any>[]> = Commands extends readonly [infer H extends CommandDefinition<any, any, any, any>, ...infer T extends readonly CommandDefinition<any, any, any, any>[]] ? ([H] extends [never] ? {} : DeclaredDepsOf<H>) & CommandDefinitionsDependencies<T> : Commands extends readonly [] ? {} : Record<string, ContextValue>;
|
|
1030
|
+
interface ExtensionConfig<Defs extends readonly NamedExtensionFlagDef[] = readonly NamedExtensionFlagDef[], Uses extends readonly AnyContextFactory[] = readonly AnyContextFactory[], Provides extends readonly AnyContextInstance[] = readonly AnyContextInstance[], Commands extends readonly CommandDefinition<any, any, any, any>[] = readonly CommandDefinition<any, any, any, any>[], MetaKeys extends RootMetaKey = never> {
|
|
1031
|
+
readonly flags?: Defs;
|
|
1032
|
+
readonly commands?: Commands;
|
|
1033
|
+
readonly uses?: Uses;
|
|
1034
|
+
readonly provides?: Provides;
|
|
1035
|
+
readonly sections?: (snapshot: RootCommandSnapshot<MetaKeys>) => readonly ExtensionSectionContribution[];
|
|
1036
|
+
readonly build?: (ctx: ExtensionBuildContext<MetaKeys>) => BuildArtifacts | void | Promise<BuildArtifacts | void>;
|
|
1037
|
+
readonly hooks?: ExtensionHooks<Defs, ContextDependencies<Uses>, MetaKeys>;
|
|
772
1038
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
* (e.g. a "skill" management command). If the parent already has a
|
|
796
|
-
* subcommand with the same name (user-defined), the call is silently
|
|
797
|
-
* skipped — user definitions always take priority over plugin injections.
|
|
798
|
-
*
|
|
799
|
-
* @param parent - The parent command node to add the subcommand to
|
|
800
|
-
* @param name - The subcommand name (used for routing)
|
|
801
|
-
* @param command - The subcommand node to register
|
|
802
|
-
*/
|
|
803
|
-
addSubCommand(parent: CommandTarget, name: string, command: CommandTarget): void;
|
|
1039
|
+
type ValidateExtensionConfig<Defs extends readonly NamedExtensionFlagDef[], Provides extends readonly AnyContextInstance[], Commands extends readonly CommandDefinition<any, any, any, any>[], Uses extends readonly AnyContextFactory[]> = {
|
|
1040
|
+
readonly uses?: Uses;
|
|
1041
|
+
readonly commands?: ValidateCommandDefinitions<Commands>;
|
|
1042
|
+
readonly flags?: ValidateLocalFlagDefs<Defs, ProvidedContextSpellings<Provides>>;
|
|
1043
|
+
readonly provides?: KnownContextInstances<Provides> & ProvideChecks<never, Provides>;
|
|
1044
|
+
};
|
|
1045
|
+
declare const extensionHookProof: unique symbol;
|
|
1046
|
+
interface Extension<Deps extends ContextMap = ContextMap, Provides extends readonly AnyContextInstance[] = readonly AnyContextInstance[], FlagDefs extends readonly NamedExtensionFlagDef[] = readonly NamedExtensionFlagDef[], Commands extends readonly CommandDefinition<any, any, any, any>[] = readonly CommandDefinition<any, any, any, any>[], out MetaKeys extends RootMetaKey = never, HookDeps extends ContextMap = Deps> extends Defining<Extension<Deps, Provides, FlagDefs, Commands, MetaKeys, HookDeps>> {
|
|
1047
|
+
/** @internal Hook demands are distinct from command/provider attachment dependencies. */
|
|
1048
|
+
readonly _hookDeps?: HookDeps;
|
|
1049
|
+
readonly [extensionHookProof]?: (deps: HookDeps) => void;
|
|
1050
|
+
readonly id: ExtensionId;
|
|
1051
|
+
readonly flags?: Readonly<Record<string, ExtensionFlagDef>>;
|
|
1052
|
+
/** @internal — phantom carrying declared flag literals for extend-time collision checks */
|
|
1053
|
+
readonly _flagDefs?: FlagDefs;
|
|
1054
|
+
readonly commands?: Commands;
|
|
1055
|
+
readonly uses: readonly AnyContextFactory[];
|
|
1056
|
+
readonly provides?: Provides;
|
|
1057
|
+
readonly sections?: (snapshot: RootCommandSnapshot<MetaKeys>) => readonly ExtensionSectionContribution[];
|
|
1058
|
+
readonly build?: (ctx: ExtensionBuildContext<MetaKeys>) => BuildArtifacts | void | Promise<BuildArtifacts | void>;
|
|
1059
|
+
readonly hooks?: ExtensionHooks<any, Deps, MetaKeys>;
|
|
1060
|
+
readonly _deps?: Deps;
|
|
804
1061
|
}
|
|
805
|
-
/**
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
1062
|
+
/** @internal Broad Extension constraint; contravariance requires the full metadata key set. */
|
|
1063
|
+
type AnyExtension = Extension<any, any, any, any, RootMetaKey>;
|
|
1064
|
+
type ExtensionProvidesOutput<E> = DefiningOf<E> extends Extension<any, infer Provides, any, any, RootMetaKey> ? ContextsOutput<Provides> : {};
|
|
1065
|
+
type ExtensionsProvidesOutput<Es extends readonly AnyExtension[]> = Es extends readonly [infer H, ...infer T extends readonly AnyExtension[]] ? MergeProviders<ExtensionProvidesOutput<H>, ExtensionsProvidesOutput<T>> : Es extends readonly [] ? {} : Es[number] extends Extension<any, infer P, any, any, RootMetaKey> ? P[number] extends never ? {} : Record<string, ContextValue> : {};
|
|
1066
|
+
/**
|
|
1067
|
+
* A callable Extension constructor whose identity is also a section consumer.
|
|
1068
|
+
* Contribution parameters default to closed sets. Use `ContextMap`,
|
|
1069
|
+
* `readonly AnyContextInstance[]`, `readonly NamedExtensionFlagDef[]`, or
|
|
1070
|
+
* `readonly CommandDefinition<any, any, any, any>[]` to keep a namespace open.
|
|
1071
|
+
*/
|
|
1072
|
+
type ExtensionFactory<Args extends readonly unknown[] = [], Deps extends ContextMap = {}, Provides extends readonly AnyContextInstance[] = [], Defs extends readonly NamedExtensionFlagDef[] = [], Commands extends readonly CommandDefinition<any, any, any, any>[] = [], MetaKeys extends RootMetaKey = never, HookDeps extends ContextMap = Deps> = ((...args: Args) => Extension<Deps, Provides, Defs, Commands, MetaKeys, HookDeps>) & {
|
|
1073
|
+
readonly id: ExtensionId;
|
|
1074
|
+
};
|
|
1075
|
+
/** Curried Extension definer: explicit metadata keys leave all other types inferred. */
|
|
1076
|
+
interface DefineExtensionWith<MetaKeys extends RootMetaKey> {
|
|
1077
|
+
<Args extends readonly unknown[], const Defs extends readonly NamedExtensionFlagDef[] = [], const Uses extends readonly AnyContextFactory[] = [], const Provides extends readonly AnyContextInstance[] = [], const Commands extends readonly CommandDefinition<any, any, any, any>[] = []>(id: ExtensionId, factory: (...args: Args) => ExtensionConfig<Defs, Uses, Provides, Commands, MetaKeys> & ValidateExtensionConfig<Defs, Provides, Commands, Uses>): ExtensionFactory<Args, ContextDependencies<Uses> & ContextsDependencies<Provides> & CommandDefinitionsDependencies<Commands>, Provides, Defs, Commands, MetaKeys, ContextDependencies<Uses>>;
|
|
1078
|
+
<const Defs extends readonly NamedExtensionFlagDef[] = [], const Uses extends readonly AnyContextFactory[] = [], const Provides extends readonly AnyContextInstance[] = [], const Commands extends readonly CommandDefinition<any, any, any, any>[] = []>(id: ExtensionId, config?: ExtensionConfig<Defs, Uses, Provides, Commands, MetaKeys> & ValidateExtensionConfig<Defs, Provides, Commands, Uses>): Extension<ContextDependencies<Uses> & ContextsDependencies<Provides> & CommandDefinitionsDependencies<Commands>, Provides, Defs, Commands, MetaKeys, ContextDependencies<Uses>>;
|
|
810
1079
|
}
|
|
811
|
-
/**
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
1080
|
+
/**
|
|
1081
|
+
* Define an Extension, or a factory that builds one from config on each call.
|
|
1082
|
+
*
|
|
1083
|
+
* Extensions apply to the whole application and own the flags and commands
|
|
1084
|
+
* they contribute. Factories expose the same identity for section audiences.
|
|
1085
|
+
* `defineExtension<"version">()(id, configOrFactory)` declares required root
|
|
1086
|
+
* metadata keys without preventing inference of flags, Contexts, or commands.
|
|
1087
|
+
*/
|
|
1088
|
+
export declare function defineExtension<MetaKeys extends RootMetaKey = never>(): DefineExtensionWith<MetaKeys>;
|
|
1089
|
+
export declare function defineExtension<Args extends readonly unknown[], const Defs extends readonly NamedExtensionFlagDef[] = [], const Uses extends readonly AnyContextFactory[] = [], const Provides extends readonly AnyContextInstance[] = [], const Commands extends readonly CommandDefinition<any, any, any, any>[] = []>(id: ExtensionId, factory: (...args: Args) => ExtensionConfig<Defs, Uses, Provides, Commands> & ValidateExtensionConfig<Defs, Provides, Commands, Uses>): ExtensionFactory<Args, ContextDependencies<Uses> & ContextsDependencies<Provides> & CommandDefinitionsDependencies<Commands>, Provides, Defs, Commands, never, ContextDependencies<Uses>>;
|
|
1090
|
+
export declare function defineExtension<const Defs extends readonly NamedExtensionFlagDef[] = [], const Uses extends readonly AnyContextFactory[] = [], const Provides extends readonly AnyContextInstance[] = [], const Commands extends readonly CommandDefinition<any, any, any, any>[] = []>(id: ExtensionId, config?: ExtensionConfig<Defs, Uses, Provides, Commands> & ValidateExtensionConfig<Defs, Provides, Commands, Uses>): Extension<ContextDependencies<Uses> & ContextsDependencies<Provides> & CommandDefinitionsDependencies<Commands>, Provides, Defs, Commands, never, ContextDependencies<Uses>>;
|
|
1091
|
+
//#endregion
|
|
1092
|
+
//#region src/parsing/spellings.d.ts
|
|
1093
|
+
interface FlagSpelling {
|
|
1094
|
+
canonicalName: string;
|
|
1095
|
+
def: FlagDef;
|
|
1096
|
+
kind: "canonical" | "short" | "alias";
|
|
1097
|
+
negatable: boolean;
|
|
817
1098
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1099
|
+
//#endregion
|
|
1100
|
+
//#region src/command/node.d.ts
|
|
1101
|
+
/** Runtime-erased Command Action; typed builders and run() own the specific result contract. */
|
|
1102
|
+
type CommandAction = (ctx: CrustCommandContext) => unknown;
|
|
1103
|
+
interface CommandContext {
|
|
1104
|
+
instance: AnyContextInstance;
|
|
1105
|
+
extensionId?: ExtensionId;
|
|
824
1106
|
}
|
|
825
1107
|
/**
|
|
826
|
-
* Internal representation of a single node in the command tree.
|
|
827
|
-
*
|
|
828
|
-
* Built by the `Crust` builder class; not part of the public API.
|
|
829
|
-
* Each node carries its own local flags, the pre-computed effective
|
|
830
|
-
* (
|
|
831
|
-
*
|
|
832
|
-
*/
|
|
1108
|
+
* Internal representation of a single node in the command tree.
|
|
1109
|
+
*
|
|
1110
|
+
* Built by the `Crust` builder class; not part of the public API.
|
|
1111
|
+
* Each node carries its own local flags, the pre-computed effective
|
|
1112
|
+
* (Context-owned + local merged) flags, positional args, subcommands,
|
|
1113
|
+
* extensions, and the Command Action.
|
|
1114
|
+
*/
|
|
833
1115
|
interface CommandNode {
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1116
|
+
/** Command metadata (name, description, usage) */
|
|
1117
|
+
meta: CommandMeta;
|
|
1118
|
+
/** Flags defined directly on this command via `.flags()` */
|
|
1119
|
+
localFlags: FlagsDef;
|
|
1120
|
+
/** Accumulated flags owned by Contexts provided on this command path */
|
|
1121
|
+
ownedFlags: FlagsDef;
|
|
1122
|
+
/** Context-owned and local flags merged for parsing */
|
|
1123
|
+
effectiveFlags: FlagsDef;
|
|
1124
|
+
/** Cached canonical/short/alias table for the effective flags. */
|
|
1125
|
+
flagSpellings: Map<string, FlagSpelling>;
|
|
1126
|
+
/** Positional argument definitions */
|
|
1127
|
+
args: ArgsDef;
|
|
1128
|
+
/** Named subcommands keyed by name */
|
|
1129
|
+
subCommands: Record<string, CommandNode>;
|
|
1130
|
+
/** Contexts available to this command in provide order (construction order is pull-driven). */
|
|
1131
|
+
contexts: CommandContext[];
|
|
1132
|
+
/** Declared command demands; validated when recipes are materialized. */
|
|
1133
|
+
demands: readonly AnyContextFactory[];
|
|
1134
|
+
/** Extensions registered via `.extend()` (root builder only) */
|
|
1135
|
+
extensions: Extension[];
|
|
1136
|
+
/** The Command Action */
|
|
1137
|
+
run?: CommandAction;
|
|
852
1138
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
A extends ArgsDef = ArgsDef,
|
|
863
|
-
F extends FlagsDef = FlagsDef
|
|
864
|
-
> {
|
|
865
|
-
/** Resolved positional arguments, keyed by arg name */
|
|
866
|
-
args: InferArgs<A>;
|
|
867
|
-
/** Resolved flags, keyed by flag name */
|
|
868
|
-
flags: InferFlags<F>;
|
|
869
|
-
/** Raw arguments that appeared after the `--` separator */
|
|
870
|
-
rawArgs: string[];
|
|
871
|
-
/** The resolved command node that is being executed */
|
|
872
|
-
command: CommandNode;
|
|
1139
|
+
//#endregion
|
|
1140
|
+
//#region src/api/context.d.ts
|
|
1141
|
+
/** Upper bound for phantom name-to-value context maps. */
|
|
1142
|
+
type ContextMap = object;
|
|
1143
|
+
declare const defining: unique symbol;
|
|
1144
|
+
declare const contextProof: unique symbol;
|
|
1145
|
+
/** @internal Immutable defining data retained through public structural copies. */
|
|
1146
|
+
interface Defining<T> {
|
|
1147
|
+
readonly [defining]: T;
|
|
873
1148
|
}
|
|
874
|
-
/**
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
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.
|
|
886
|
-
*/
|
|
887
|
-
declare const VALIDATION_MODE_ENV = "CRUST_INTERNAL_VALIDATE_ONLY";
|
|
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
|
-
/**
|
|
902
|
-
* Chainable builder for defining CLI commands with full type inference.
|
|
903
|
-
*
|
|
904
|
-
* Generic parameters:
|
|
905
|
-
* - `Inherited` — flags inherited from a parent command (populated by `.command()`)
|
|
906
|
-
* - `Local` — flags defined on this command via `.flags()`
|
|
907
|
-
* - `A` — positional argument definitions
|
|
908
|
-
* - `Eff` — effective flags (merged inherited + local flags, computed internally)
|
|
909
|
-
*
|
|
910
|
-
* @example
|
|
911
|
-
* ```ts
|
|
912
|
-
* const app = new Crust("my-cli")
|
|
913
|
-
* .flags({
|
|
914
|
-
* verbose: { type: "boolean", short: "v", inherit: true },
|
|
915
|
-
* })
|
|
916
|
-
* .args([{ name: "file", type: "string", required: true }])
|
|
917
|
-
* .run(({ args, flags }) => {
|
|
918
|
-
* console.log(args.file, flags.verbose);
|
|
919
|
-
* });
|
|
920
|
-
* ```
|
|
921
|
-
*/
|
|
922
|
-
declare class Crust<
|
|
923
|
-
Inherited extends FlagsDef = FlagsDef,
|
|
924
|
-
Local extends FlagsDef = FlagsDef,
|
|
925
|
-
A extends ArgsDef = ArgsDef,
|
|
926
|
-
Eff extends FlagsDef = EffectiveFlags<Inherited, Local>
|
|
927
|
-
> {
|
|
928
|
-
/** @internal — Phantom property exposing generic parameters for type-level testing */
|
|
929
|
-
readonly _types: {
|
|
930
|
-
inherited: Inherited;
|
|
931
|
-
local: Local;
|
|
932
|
-
args: A;
|
|
933
|
-
effective: Eff;
|
|
934
|
-
};
|
|
935
|
-
/** @internal */
|
|
936
|
-
readonly _node: CommandNode;
|
|
937
|
-
/** @internal — The inherited flags record (runtime counterpart of Inherited generic) */
|
|
938
|
-
readonly _inheritedFlags: FlagsDef;
|
|
939
|
-
/**
|
|
940
|
-
* Create a new root or standalone command builder.
|
|
941
|
-
*
|
|
942
|
-
* @param name - The command name.
|
|
943
|
-
* @throws {CrustError} `DEFINITION` if name is empty or whitespace-only
|
|
944
|
-
*/
|
|
945
|
-
constructor(name: string);
|
|
946
|
-
/**
|
|
947
|
-
* @internal — Create a child builder with pre-populated inherited flags.
|
|
948
|
-
* Used by `.command()` to propagate parent flags to the child.
|
|
949
|
-
*/
|
|
950
|
-
static _createChild<I extends FlagsDef>(name: string, inheritedFlags: FlagsDef): Crust<I, {}, [], EffectiveFlags<I, {}>>;
|
|
951
|
-
/**
|
|
952
|
-
* @internal — Clone this builder with a new node, preserving generics.
|
|
953
|
-
*/
|
|
954
|
-
private _clone;
|
|
955
|
-
/**
|
|
956
|
-
* Set metadata (description, usage) for this command.
|
|
957
|
-
*
|
|
958
|
-
* The command name is already set by the builder source (constructor,
|
|
959
|
-
* `.sub()`, or the child builder passed into `.command(name, cb)`).
|
|
960
|
-
* Provide `description`, `usage`, and/or `aliases` here.
|
|
961
|
-
*
|
|
962
|
-
* Returns a new builder with updated metadata. The original builder
|
|
963
|
-
* is not mutated.
|
|
964
|
-
*
|
|
965
|
-
* @param meta - Metadata fields to set (description, usage, aliases)
|
|
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
|
-
* ```
|
|
973
|
-
*/
|
|
974
|
-
meta(meta: Omit<CommandMeta, "name">): Crust<Inherited, Local, A, Eff>;
|
|
975
|
-
/**
|
|
976
|
-
* Define local flags for this command.
|
|
977
|
-
*
|
|
978
|
-
* Returns a new builder with updated local flag types. The original
|
|
979
|
-
* builder is not mutated.
|
|
980
|
-
*
|
|
981
|
-
* NOTE: Compile-time inherited/local cross-collision checks are intentionally
|
|
982
|
-
* omitted here to reduce TypeScript type-check cost in large projects.
|
|
983
|
-
* Runtime collision checks still run during parsing and command-tree validation.
|
|
984
|
-
*
|
|
985
|
-
* @param defs - Flag definitions record
|
|
986
|
-
* @returns A new `Crust` instance with the given flags
|
|
987
|
-
* @throws {CrustError} `DEFINITION` if flag names/aliases violate constraints
|
|
988
|
-
*/
|
|
989
|
-
flags<const F extends FlagsDef>(defs: F & ValidateNoPrefixedFlags<ValidateFlagAliases<F>>): Crust<Inherited, F, A, EffectiveFlags<Inherited, F>>;
|
|
990
|
-
/**
|
|
991
|
-
* Define positional arguments for this command.
|
|
992
|
-
*
|
|
993
|
-
* Returns a new builder with updated args types. The original
|
|
994
|
-
* builder is not mutated.
|
|
995
|
-
*
|
|
996
|
-
* @param defs - Ordered tuple of positional argument definitions
|
|
997
|
-
* @returns A new `Crust` instance with the given args
|
|
998
|
-
*/
|
|
999
|
-
args<const NewA extends ArgsDef>(defs: NewA & ValidateVariadicArgs<NewA>): Crust<Inherited, Local, NewA, Eff>;
|
|
1000
|
-
/**
|
|
1001
|
-
* Set the main command handler.
|
|
1002
|
-
*
|
|
1003
|
-
* The handler receives a {@link CrustCommandContext} with `args` typed from
|
|
1004
|
-
* `.args()` and `flags` typed as `EffectiveFlags<Inherited, Local>` (inherited
|
|
1005
|
-
* flags merged with local flags).
|
|
1006
|
-
*
|
|
1007
|
-
* Returns a new builder with the handler stored. The original builder is
|
|
1008
|
-
* not mutated.
|
|
1009
|
-
*
|
|
1010
|
-
* @param handler - The main command handler function
|
|
1011
|
-
* @returns A new `Crust` instance with the handler registered
|
|
1012
|
-
*/
|
|
1013
|
-
run(handler: (ctx: NoInfer<CrustCommandContext<A, Eff>>) => void | Promise<void>): Crust<Inherited, Local, A, Eff>;
|
|
1014
|
-
/**
|
|
1015
|
-
* Set the pre-run lifecycle hook.
|
|
1016
|
-
*
|
|
1017
|
-
* Called before `run()` — useful for initialization and setup.
|
|
1018
|
-
* Receives the same {@link CrustCommandContext} as `run()`.
|
|
1019
|
-
*
|
|
1020
|
-
* @param handler - The pre-run handler function
|
|
1021
|
-
* @returns A new `Crust` instance with the preRun handler registered
|
|
1022
|
-
*/
|
|
1023
|
-
preRun(handler: (ctx: NoInfer<CrustCommandContext<A, Eff>>) => void | Promise<void>): Crust<Inherited, Local, A, Eff>;
|
|
1024
|
-
/**
|
|
1025
|
-
* Set the post-run lifecycle hook.
|
|
1026
|
-
*
|
|
1027
|
-
* Called after `run()` (even if it throws) — useful for teardown and cleanup.
|
|
1028
|
-
* Receives the same {@link CrustCommandContext} as `run()`.
|
|
1029
|
-
*
|
|
1030
|
-
* @param handler - The post-run handler function
|
|
1031
|
-
* @returns A new `Crust` instance with the postRun handler registered
|
|
1032
|
-
*/
|
|
1033
|
-
postRun(handler: (ctx: NoInfer<CrustCommandContext<A, Eff>>) => void | Promise<void>): Crust<Inherited, Local, A, Eff>;
|
|
1034
|
-
/**
|
|
1035
|
-
* Register a plugin on this command.
|
|
1036
|
-
*
|
|
1037
|
-
* Plugins are collected during `.execute()` and their `setup()` hooks
|
|
1038
|
-
* receive `SetupContext` and `SetupActions`. Middleware hooks run in
|
|
1039
|
-
* registration order.
|
|
1040
|
-
*
|
|
1041
|
-
* Returns a new builder with the plugin appended. The original builder
|
|
1042
|
-
* is not mutated.
|
|
1043
|
-
*
|
|
1044
|
-
* @param plugin - The plugin to register
|
|
1045
|
-
* @returns A new `Crust` instance with the plugin registered
|
|
1046
|
-
*/
|
|
1047
|
-
use(plugin: CrustPlugin): Crust<Inherited, Local, A, Eff>;
|
|
1048
|
-
/**
|
|
1049
|
-
* Create a subcommand builder pre-typed with this command's inheritable flags.
|
|
1050
|
-
*
|
|
1051
|
-
* This is the factory method for the file-splitting pattern. The returned
|
|
1052
|
-
* builder carries this command's effective flags (filtered for `inherit: true`)
|
|
1053
|
-
* as its `Inherited` generic, enabling full type inference in split files
|
|
1054
|
-
* without needing `Crust<any, any, any>`.
|
|
1055
|
-
*
|
|
1056
|
-
* Register the resulting builder with `.command(builder)` on the parent.
|
|
1057
|
-
*
|
|
1058
|
-
* @param name - Subcommand name (must be non-empty)
|
|
1059
|
-
* @returns A new `Crust` builder pre-typed with inherited flags
|
|
1060
|
-
* @throws {CrustError} `DEFINITION` if name is empty or whitespace-only
|
|
1061
|
-
*
|
|
1062
|
-
* @example
|
|
1063
|
-
* ```ts
|
|
1064
|
-
* // shared.ts
|
|
1065
|
-
* const app = new Crust("my-cli")
|
|
1066
|
-
* .flags({ verbose: { type: "boolean", inherit: true } });
|
|
1067
|
-
*
|
|
1068
|
-
* // commands/deploy.ts
|
|
1069
|
-
* const deployCmd = app.sub("deploy")
|
|
1070
|
-
* .flags({ env: { type: "string", required: true } })
|
|
1071
|
-
* .run(({ flags }) => {
|
|
1072
|
-
* flags.verbose; // boolean | undefined — typed!
|
|
1073
|
-
* flags.env; // string — typed!
|
|
1074
|
-
* });
|
|
1075
|
-
*
|
|
1076
|
-
* // cli.ts
|
|
1077
|
-
* app.command(deployCmd).execute();
|
|
1078
|
-
* ```
|
|
1079
|
-
*/
|
|
1080
|
-
sub<N extends string>(name: N): Crust<Eff, {}, [], EffectiveFlags<Eff, {}>>;
|
|
1081
|
-
/**
|
|
1082
|
-
* Register a named subcommand via inline callback.
|
|
1083
|
-
*
|
|
1084
|
-
* The callback receives a fresh `Crust` builder pre-typed with this
|
|
1085
|
-
* command's effective inheritable flags, enabling TypeScript contextual
|
|
1086
|
-
* typing to flow inherited flag types into subcommand definitions.
|
|
1087
|
-
*
|
|
1088
|
-
* @param name - Subcommand name (must be non-empty, unique among siblings)
|
|
1089
|
-
* @param cb - Callback that receives a child builder and returns the configured builder
|
|
1090
|
-
* @returns A new `Crust` instance with the subcommand registered
|
|
1091
|
-
* @throws {CrustError} `DEFINITION` if name is empty or already registered
|
|
1092
|
-
*/
|
|
1093
|
-
command<N extends string>(name: N, cb: (cmd: Crust<Eff, {}, [], EffectiveFlags<Eff, {}>>) => Crust<any, any, any>): Crust<Inherited, Local, A, Eff>;
|
|
1094
|
-
/**
|
|
1095
|
-
* Register a pre-built subcommand builder.
|
|
1096
|
-
*
|
|
1097
|
-
* The builder's name (from its constructor or `.sub()`) is used as the
|
|
1098
|
-
* subcommand name. Builders created with `.sub()` inherit the parent's
|
|
1099
|
-
* `inherit: true` flags; standalone `new Crust(name)` builders remain
|
|
1100
|
-
* isolated. This is the complement to `.sub()` for the file-splitting
|
|
1101
|
-
* pattern.
|
|
1102
|
-
*
|
|
1103
|
-
* @param builder - A pre-configured `Crust` builder instance
|
|
1104
|
-
* @returns A new `Crust` instance with the subcommand registered
|
|
1105
|
-
* @throws {CrustError} `DEFINITION` if builder name is empty or already registered
|
|
1106
|
-
*/
|
|
1107
|
-
command(builder: Crust<any, any, any>): Crust<Inherited, Local, A, Eff>;
|
|
1108
|
-
/**
|
|
1109
|
-
* Build a frozen, validated copy of the command tree after running plugin
|
|
1110
|
-
* `setup()` hooks. Does not mutate this builder or call command handlers.
|
|
1111
|
-
*
|
|
1112
|
-
* Use for documentation generators (e.g. man pages) that need the same
|
|
1113
|
-
* tree shape as runtime, including flags injected by plugins.
|
|
1114
|
-
*
|
|
1115
|
-
* @param options - Optional synthetic `argv` passed to `setup()` (defaults to `[]`)
|
|
1116
|
-
* @returns The cloned root node and any plugin-setup warnings
|
|
1117
|
-
* @throws {CrustError} When the tree fails validation (same as `execute()`)
|
|
1118
|
-
*/
|
|
1119
|
-
prepareCommandTree(options?: {
|
|
1120
|
-
argv?: readonly string[];
|
|
1121
|
-
}): Promise<{
|
|
1122
|
-
root: CommandNode;
|
|
1123
|
-
warnings: readonly string[];
|
|
1124
|
-
}>;
|
|
1125
|
-
/**
|
|
1126
|
-
* Parse `process.argv`, resolve subcommands, run plugins and middleware,
|
|
1127
|
-
* and execute the matched command handler.
|
|
1128
|
-
*
|
|
1129
|
-
* This is the entry point for CLI execution — call it on the root builder.
|
|
1130
|
-
*
|
|
1131
|
-
* @param options - Optional overrides (e.g. custom `argv` for testing)
|
|
1132
|
-
* @returns A promise that resolves when execution completes
|
|
1133
|
-
*/
|
|
1134
|
-
execute(options?: {
|
|
1135
|
-
argv?: string[];
|
|
1136
|
-
}): Promise<void>;
|
|
1149
|
+
/** @internal */
|
|
1150
|
+
type DefiningOf<T> = T extends Defining<unknown> ? T[typeof defining] : T;
|
|
1151
|
+
/** Lazy, invocation-scoped Context values. Reading a property starts construction. */
|
|
1152
|
+
type ContextBag<Deps extends ContextMap = {}> = { readonly [K in keyof Deps]: Promise<Deps[K]>; };
|
|
1153
|
+
interface ContextConfig {
|
|
1154
|
+
readonly flags?: readonly NamedFlagDef[];
|
|
1155
|
+
readonly uses?: readonly AnyContextFactory[];
|
|
1137
1156
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1157
|
+
type ValidateContextConfig<R extends ContextConfig> = {
|
|
1158
|
+
readonly flags?: R["flags"] extends readonly NamedFlagDef[] ? ValidateLocalFlagDefs<R["flags"], never> : "flags" extends keyof R ? {} : never;
|
|
1159
|
+
readonly uses?: R["uses"] extends readonly AnyContextFactory[] ? R["uses"] : "uses" extends keyof R ? {} : never;
|
|
1160
|
+
};
|
|
1161
|
+
interface ContextSetupInput<OF extends FlagsDef = FlagsDef> extends InvocationIO {
|
|
1162
|
+
readonly flags: InferFlags<OF>;
|
|
1163
|
+
readonly ctx: ContextBag<ContextMap>;
|
|
1164
|
+
readonly defer: (cleanup: () => void | PromiseLike<void>) => void;
|
|
1143
1165
|
}
|
|
1144
|
-
interface
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1166
|
+
interface ContextInstance<Name extends string = string, Value = unknown, OF extends FlagsDef = FlagsDef, Deps extends ContextMap = Record<string, ContextValue>> extends Defining<ContextInstance<Name, Value, OF, Deps>> {
|
|
1167
|
+
readonly [contextProof]?: string extends keyof OF | keyof Deps ? unknown : (state: [OF, Deps]) => void;
|
|
1168
|
+
readonly name: Name;
|
|
1169
|
+
readonly ownedFlags: FlagsDef;
|
|
1170
|
+
/** @internal — declared direct dependency factories */
|
|
1171
|
+
readonly uses: readonly AnyContextFactory[];
|
|
1172
|
+
setup(input: ContextSetupInput<OF>): Awaitable<Value>;
|
|
1173
|
+
readonly _ownedFlags?: OF;
|
|
1174
|
+
/** @internal — phantom carrying the transitive dependency closure */
|
|
1175
|
+
readonly _deps?: Deps;
|
|
1149
1176
|
}
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1177
|
+
/** @internal Existential registry constraint; never evidence for trusted attachment. */
|
|
1178
|
+
type AnyContextInstance = ContextInstance<string, unknown, any, any>;
|
|
1179
|
+
/** Whatever a Context setup produces, erased at the runtime registry. */
|
|
1180
|
+
type ContextValue = Awaited<ReturnType<AnyContextInstance["setup"]>>;
|
|
1181
|
+
interface ContextSetup<Options, OF extends FlagsDef = {}, Deps extends ContextMap = {}> extends InvocationIO {
|
|
1182
|
+
readonly options: Options;
|
|
1183
|
+
readonly flags: InferFlags<OF>;
|
|
1184
|
+
readonly ctx: ContextBag<Deps>;
|
|
1185
|
+
/**
|
|
1186
|
+
* Registers cleanup on the invocation's disposal stack: callbacks run after
|
|
1187
|
+
* post-run hooks in reverse registration order. Throws once setup has settled.
|
|
1188
|
+
*/
|
|
1189
|
+
readonly defer: (cleanup: () => void | PromiseLike<void>) => void;
|
|
1157
1190
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
* - `COMMAND_NOT_FOUND` — Unrecognised subcommand at the current level
|
|
1166
|
-
* - `CONFIG` — Unsupported command/flag definition surfaced at setup time (e.g. async `parse`)
|
|
1167
|
-
*
|
|
1168
|
-
* @example
|
|
1169
|
-
* ```ts
|
|
1170
|
-
* try {
|
|
1171
|
-
* parseArgs(cmd, argv);
|
|
1172
|
-
* } catch (err) {
|
|
1173
|
-
* if (err instanceof CrustError) {
|
|
1174
|
-
* switch (err.code) {
|
|
1175
|
-
* case "VALIDATION":
|
|
1176
|
-
* console.error(err.message);
|
|
1177
|
-
* showHelp(cmd);
|
|
1178
|
-
* break;
|
|
1179
|
-
* case "PARSE":
|
|
1180
|
-
* console.error(err.message);
|
|
1181
|
-
* break;
|
|
1182
|
-
* }
|
|
1183
|
-
* }
|
|
1184
|
-
* }
|
|
1185
|
-
* ```
|
|
1186
|
-
*/
|
|
1187
|
-
type CrustErrorCode = keyof CrustErrorDetailsMap;
|
|
1188
|
-
type CrustErrorDetails<C extends CrustErrorCode> = CrustErrorDetailsMap[C];
|
|
1189
|
-
/**
|
|
1190
|
-
* A typed error thrown by Crust when command definition or argument parsing fails.
|
|
1191
|
-
*
|
|
1192
|
-
* Every `CrustError` carries a {@link CrustErrorCode} that identifies the specific
|
|
1193
|
-
* failure, enabling programmatic error handling without fragile message parsing.
|
|
1194
|
-
*
|
|
1195
|
-
* @example
|
|
1196
|
-
* ```ts
|
|
1197
|
-
* import { CrustError, parseArgs } from "@crustjs/core";
|
|
1198
|
-
*
|
|
1199
|
-
* try {
|
|
1200
|
-
* const result = parseArgs(cmd, process.argv.slice(2));
|
|
1201
|
-
* } catch (err) {
|
|
1202
|
-
* if (err instanceof CrustError) {
|
|
1203
|
-
* console.error(`[${err.code}] ${err.message}`);
|
|
1204
|
-
* }
|
|
1205
|
-
* }
|
|
1206
|
-
* ```
|
|
1207
|
-
*/
|
|
1208
|
-
declare class CrustError<C extends CrustErrorCode = CrustErrorCode> extends Error {
|
|
1209
|
-
/** Machine-readable error code for programmatic handling */
|
|
1210
|
-
readonly code: C;
|
|
1211
|
-
/** Structured payload for programmatic handling */
|
|
1212
|
-
readonly details: CrustErrorDetails<C>;
|
|
1213
|
-
/** Optional wrapped original error/value */
|
|
1214
|
-
cause?: unknown;
|
|
1215
|
-
constructor(code: C, message: string, ...details: undefined extends CrustErrorDetails<C> ? [] | [CrustErrorDetails<C>] : [CrustErrorDetails<C>]);
|
|
1216
|
-
is<T extends CrustErrorCode>(code: T): this is CrustError<T>;
|
|
1217
|
-
withCause(cause: unknown): this;
|
|
1191
|
+
interface ContextFactory<Name extends string, Options, Value, OF extends FlagsDef = {}, Deps extends ContextMap = {}> extends Defining<ContextFactory<Name, Options, Value, OF, Deps>> {
|
|
1192
|
+
(options: Options): ContextInstance<Name, Value, OF, Deps>;
|
|
1193
|
+
readonly contextName: Name;
|
|
1194
|
+
/** @internal — declared direct dependency factories */
|
|
1195
|
+
readonly uses: readonly AnyContextFactory[];
|
|
1196
|
+
of(value: Value): ContextInstance<Name, Value, OF, {}>;
|
|
1197
|
+
readonly _deps?: Deps;
|
|
1218
1198
|
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
/**
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1199
|
+
type AnyContextFactory = ContextFactory<string, any, any, any, any>;
|
|
1200
|
+
type NamedOutput<Name extends string, Value> = IsClosedName<Name> extends false ? Record<string, Awaited<Value>> : IsUnion<Name> extends true ? Record<string, Awaited<Value>> : { [K in Name]: Awaited<Value>; };
|
|
1201
|
+
type ContextOutput<C> = C extends AnyContextInstance ? DefiningOf<C> extends ContextInstance<infer Name, infer Value, any, any> ? NamedOutput<Name, Value> : never : never;
|
|
1202
|
+
type ContextsOutput<Cs extends readonly AnyContextInstance[]> = IsStaticTuple<Cs> extends true ? Cs extends readonly [infer H, ...infer T extends readonly AnyContextInstance[]] ? MergeProviders<ContextOutput<H>, ContextsOutput<T>> : {} : Cs[number] extends never ? {} : Record<string, ContextOutput<Cs[number]> extends (infer O) ? O extends unknown ? O[keyof O] : never : never>;
|
|
1203
|
+
type ContextsOwnedFlags<Cs extends readonly AnyContextInstance[]> = IsStaticTuple<Cs> extends true ? Cs extends readonly [infer H, ...infer T extends readonly AnyContextInstance[]] ? MergeFlags<ContextOwnedFlags<H>, ContextsOwnedFlags<T>> : {} : keyof UnionToIntersection<ContextOwnedFlags<Cs[number]>> extends never ? {} : FlagsDef;
|
|
1204
|
+
type FactoryOutput<F> = F extends AnyContextFactory ? DefiningOf<F> extends ContextFactory<infer Name, any, infer Value, any, any> ? NamedOutput<Name, Value> : never : never;
|
|
1205
|
+
type FactoriesOutput<Fs extends readonly AnyContextFactory[]> = Fs extends readonly [infer H, ...infer T extends readonly AnyContextFactory[]] ? FactoryOutput<H> & FactoriesOutput<T> : {};
|
|
1206
|
+
type FactoryDeps<F> = F extends AnyContextFactory ? DefiningOf<F> extends ContextFactory<any, any, any, any, infer Deps> ? Deps : {} : {};
|
|
1207
|
+
type FactoriesDeps<Fs extends readonly AnyContextFactory[]> = Fs extends readonly [infer H, ...infer T extends readonly AnyContextFactory[]] ? FactoryDeps<H> & FactoriesDeps<T> : {};
|
|
1208
|
+
type ContextDependencies<Uses extends readonly AnyContextFactory[]> = IsStaticTuple<Uses> extends true ? FactoriesOutput<Uses> & FactoriesDeps<Uses> : Record<string, ContextValue>;
|
|
1209
|
+
type ContextDepsOf<C> = C extends AnyContextInstance ? DefiningOf<C> extends {
|
|
1210
|
+
readonly _deps?: infer Deps extends ContextMap;
|
|
1211
|
+
} ? Deps : {} : {};
|
|
1212
|
+
type ContextsDependencies<Cs extends readonly AnyContextInstance[]> = Cs extends readonly [infer H, ...infer T extends readonly AnyContextInstance[]] ? ContextDepsOf<H> & ContextsDependencies<T> : {};
|
|
1213
|
+
type OwnedFlagsOf<R extends ContextConfig> = R extends {
|
|
1214
|
+
flags: infer F extends readonly NamedFlagDef[];
|
|
1215
|
+
} ? AttachedFlags<F> : "flags" extends keyof R ? FlagsDef : {};
|
|
1216
|
+
type UsesOf<R extends ContextConfig> = R extends {
|
|
1217
|
+
uses: infer Uses extends readonly AnyContextFactory[];
|
|
1218
|
+
} ? Uses : "uses" extends keyof R ? readonly AnyContextFactory[] : readonly [];
|
|
1219
|
+
/** Define a named, lazy command dependency. Declared `uses` are exposed on `ctx`. */
|
|
1220
|
+
export declare function defineContext<Name extends string, Value, Options = void>(name: Name, setup: (input: ContextSetup<Options>) => Awaitable<Value>): ContextFactory<Name, Options, Value>;
|
|
1221
|
+
export declare function defineContext<Name extends string, const R extends ContextConfig, Value, Options = void>(name: Name, config: R & ValidateContextConfig<R> & ContextConfig, setup: (input: ContextSetup<Options, OwnedFlagsOf<R>, ContextDependencies<UsesOf<R>>>) => Awaitable<Value>): ContextFactory<Name, Options, Value, OwnedFlagsOf<R>, ContextDependencies<UsesOf<R>>>;
|
|
1222
|
+
type FactoryValueOf<F extends AnyContextFactory> = F extends ContextFactory<any, any, infer Value, any, any> ? Awaited<Value> : never;
|
|
1223
|
+
//#endregion
|
|
1224
|
+
//#region src/api/flags.d.ts
|
|
1225
|
+
/** Distribute `Omit<_, "name">` over the {@link ArgDef} union. */
|
|
1226
|
+
type OmitName<T> = T extends {
|
|
1227
|
+
name: string;
|
|
1228
|
+
} ? Omit<T, "name"> : never;
|
|
1229
|
+
/** A positional argument definition without its name — the `defineArg` input shape. */
|
|
1230
|
+
type UnnamedArgDef = OmitName<ArgDef>;
|
|
1231
|
+
type Frozen<T> = { readonly [K in keyof T]: K extends "aliases" | "choices" | (T extends {
|
|
1232
|
+
multiple: true;
|
|
1233
|
+
} ? "default" : never) ? Readonly<T[K]> : T[K]; };
|
|
1234
|
+
type Named<N extends string, D> = D extends unknown ? Frozen<{
|
|
1235
|
+
name: N;
|
|
1236
|
+
} & D> : never;
|
|
1237
|
+
/** Define and own one flag locally; attachment checks destination collisions. */
|
|
1238
|
+
export declare function defineFlag<const N extends string, const D extends FlagDef>(name: N & LocalFlagNameBrand<N>, def: D & LocalFlagBrand<{
|
|
1239
|
+
name: N;
|
|
1240
|
+
} & D>): Named<N, D>;
|
|
1241
|
+
/** Define and own one positional argument; layout belongs to its consuming command. */
|
|
1242
|
+
export declare function defineArg<const N extends string, const D extends UnnamedArgDef>(name: N & EmptyArgNameBrand<N>, def: D & LocalValueBrand<D>): Named<N, D>;
|
|
1243
|
+
//#endregion
|
|
1244
|
+
export { type AnyContextFactory, type AnyCrust, type ArgDef, type ArgSnapshot, type ArgsDef, type BuildArtifacts, type BuildReport, type CommandConfig, type CommandDefinition, type CommandDefinitionBuilder, type CommandMeta, type CommandNotFoundErrorDetails, type CommandPath, type CommandSection, type CommandSectionInput, type CommandShape, type CommandShapeAt, type CommandSnapshot, type CommandTree, type ContextBag, type ContextConfig, type ContextFactory, type ContextInstance, type ContextMap, type ContextSetup, type CrustCommandContext, type CrustErrorCode, type CrustErrorDetails, type CrustErrorDetailsMap, type CrustErrorJson, type DefineExtensionWith, type DefinitionErrorDetails, type Extension, type ExtensionBuildContext, type ExtensionConfig, type ExtensionContext, type ExtensionFactory, type ExtensionFlagDef, type ExtensionHooks, type ExtensionId, type ExtensionSectionContribution, type FactoryValueOf, type Finished, type FlagDef, type FlagSnapshot, type FlagsDef, type InferExtensionFlags, type InputArgs, type InputFlags, type InvocationIO, type InvocationOutcome, type MergeContext, type MergeFlags, type NamedExtensionFlagDef, type NamedFlagDef, type ParseErrorDetails, type ParseResult, type ParsedArgValue, type ParsedFlagValue, type RootCommandMeta, type RootMetaKey, type RunArguments, type RunInput, type RunInputArguments, type RunOutcome, type SectionAudience, type SectionConsumer, type UnnamedArgDef, type ValidatedInput, type ValidationErrorDetails, type ValueType, defineExtensionId };
|