@crustjs/core 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{types-DjMHz7M6.d.ts → artifacts-D8Jlug-a.d.ts} +46 -4
- package/dist/index.d.ts +113 -50
- package/dist/index.js +21 -4
- package/dist/{invocation-DcA5FqF7.js → invocation-CIvHShiW.js} +129 -25
- package/dist/tooling.d.ts +2 -3
- package/dist/tooling.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -92,6 +92,14 @@ type MergeProviders<A, B> = keyof A extends never ? B : keyof B extends never ?
|
|
|
92
92
|
type DefName<T> = T extends {
|
|
93
93
|
name: infer N extends string;
|
|
94
94
|
} ? IsClosedName<N> extends true ? N : never : never;
|
|
95
|
+
/**
|
|
96
|
+
* Statically known `name` members of a definition (or definition union), for spelling grammar.
|
|
97
|
+
* Filtered per variant: combining names first would let an open variant's `string` absorb
|
|
98
|
+
* a literal sibling before {@link ClosedMembers} can see it.
|
|
99
|
+
*/
|
|
100
|
+
type DefNameMembers<T> = T extends {
|
|
101
|
+
name: infer N extends string;
|
|
102
|
+
} ? ClosedMembers<N> : never;
|
|
95
103
|
type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never;
|
|
96
104
|
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;
|
|
97
105
|
/**
|
|
@@ -108,8 +116,14 @@ type HasClosedNames<Ds extends readonly unknown[]> = number extends Ds["length"]
|
|
|
108
116
|
} ? IsUnion<N> extends true ? false : IsClosedName<N> : false; }[number] ? false : true;
|
|
109
117
|
/** Brand statically known spelling collisions while allowing open names. */
|
|
110
118
|
type CollisionBrand<S extends string, Existing extends string, Key extends string, Before extends string, After extends string> = string extends S | Existing ? {} : [S & Existing] extends [never] ? {} : { readonly [K in Key]: `${Before}"${S & Existing}"${After}`; };
|
|
111
|
-
/**
|
|
112
|
-
|
|
119
|
+
/**
|
|
120
|
+
* The statically known members of a name union; open members stay runtime-owned.
|
|
121
|
+
* Only for local spelling grammar: a literal beside an open template is still
|
|
122
|
+
* independently invalid, but must never become key or collision evidence.
|
|
123
|
+
*/
|
|
124
|
+
type ClosedMembers<N extends string> = N extends unknown ? IsClosedName<N> extends true ? N : never : never;
|
|
125
|
+
/** Brand a statically known empty literal member while allowing widened and generic names. */
|
|
126
|
+
type EmptyLiteralNameBrand<Name extends string, Err> = "" extends ClosedMembers<Name> ? Err : {};
|
|
113
127
|
/**
|
|
114
128
|
* Brand a definition whose custom parser can return a Promise — parse results
|
|
115
129
|
* are consumed synchronously during argv parsing. `Extract` keeps the check
|
|
@@ -600,7 +614,7 @@ type InferArgValue<A extends ArgDef> = A extends {
|
|
|
600
614
|
} ? ResolveBaseType<A> : A extends {
|
|
601
615
|
default: infer Default;
|
|
602
616
|
} ? undefined extends Default ? ResolveBaseType<A> | undefined : ResolveBaseType<A> : ResolveBaseType<A> | undefined);
|
|
603
|
-
type DuplicateArgNames<A extends readonly ArgDef[], Seen extends string = never> = A extends readonly [infer Head extends ArgDef, ...infer Tail extends readonly ArgDef[]] ?
|
|
617
|
+
type DuplicateArgNames<A extends readonly ArgDef[], Seen extends string = never, Duplicates extends string = never> = A extends readonly [infer Head extends ArgDef, ...infer Tail extends readonly ArgDef[]] ? DuplicateArgNames<Tail, Seen | Head["name"], Duplicates | (Head["name"] & Seen)> : Duplicates;
|
|
604
618
|
type InferDuplicateArgs<A extends readonly ArgDef[]> = A extends readonly [infer Head extends ArgDef, ...infer Tail extends readonly ArgDef[]] ? { [K in Head["name"]]: InferArgValue<Head>; } & InferDuplicateArgs<Tail> : {};
|
|
605
619
|
/**
|
|
606
620
|
* Convert a literal ArgsDef tuple into resolved values keyed by argument name.
|
|
@@ -825,4 +839,32 @@ interface ValidatedInput<A extends ArgsDef = ArgsDef, F extends FlagsDef = Flags
|
|
|
825
839
|
flags: InferFlags<F>;
|
|
826
840
|
}
|
|
827
841
|
//#endregion
|
|
828
|
-
|
|
842
|
+
//#region ../utils/src/artifacts.d.ts
|
|
843
|
+
/**
|
|
844
|
+
* Set by `crust build` while it prepares the Command Snapshot: the absolute
|
|
845
|
+
* entry-isolated directory Extension build hooks write into. Core reads it
|
|
846
|
+
* to run the hooks; `resolveArtifactDir` reads it so sections evaluated during
|
|
847
|
+
* that run see the artifacts being built instead of the wiped `.crust/root`.
|
|
848
|
+
*/
|
|
849
|
+
declare const BUILD_OUT_DIR_ENV = "CRUST_INTERNAL_BUILD_OUT_DIR";
|
|
850
|
+
/**
|
|
851
|
+
* Absolute path of a build artifact or `crust.include` directory shipped with
|
|
852
|
+
* this CLI. `name` is a top-level directory name such as `"skills"`.
|
|
853
|
+
*
|
|
854
|
+
* The artifact path is computed from how the CLI is running — never probed:
|
|
855
|
+
* - Compiled executable (Bun or Deno): `<dir of the executable>/<name>`, which
|
|
856
|
+
* is a platform package's `bin/` or wherever the binary was placed.
|
|
857
|
+
* - Crust-built Node bundle: `<name>` next to the bundle's `bin/` directory,
|
|
858
|
+
* i.e. `.crust/root/<name>` in place and `<installed root>/<name>` after install.
|
|
859
|
+
* - Snapshot preparation inside `crust build`: `<build output dir>/<name>`, the
|
|
860
|
+
* artifacts earlier Extension build hooks wrote in this same build.
|
|
861
|
+
* - Source (`bun run`, `node`, `deno run`): `.crust/root/<name>` under the
|
|
862
|
+
* nearest package root of the real `process.argv[1]` entrypoint (following source
|
|
863
|
+
* links) — the output of the last `crust build`.
|
|
864
|
+
*
|
|
865
|
+
* @throws {Error} when `name` is not a single path segment, or in source mode
|
|
866
|
+
* when the entrypoint cannot be resolved or has no enclosing `package.json`.
|
|
867
|
+
*/
|
|
868
|
+
declare function resolveArtifactDir(name: string): string;
|
|
869
|
+
//#endregion
|
|
870
|
+
export { FlagSnapshot as A, IsUnion as B, SectionAudience as C, RunInputPayload as D, ValueType as E, DefNameMembers as F, ExtensionId as G, MergeContext as H, EmptyLiteralNameBrand as I, JsonValue as J, defineExtensionId as K, HasClosedNames as L, ClosedMembers as M, CollisionBrand as N, ArgSnapshot as O, DefName as P, IsClosedName as R, RuntimeCommandSectionInput as S, ValidatedInput as T, MergeProviders as U, LocalValueBrand as V, UnionToIntersection as W, NamedFlagDef as _, CommandMeta as a, ParsedArgValue as b, DeclaredDefault as c, InferArgs as d, InferFlags as f, MergeFlags as g, InvocationIO as h, ArgsDef as i, Awaitable as j, CommandSnapshot as k, FlagDef as l, InputFlags as m, resolveArtifactDir as n, CommandSection as o, InputArgs as p, JsonCompatible as q, ArgDef as r, CommandSectionInput as s, BUILD_OUT_DIR_ENV as t, FlagsDef as u, NamedFlagsRecord as v, SectionConsumer as w, ParsedFlagValue as x, ParseResult as y, IsStaticTuple as z };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as FlagSnapshot, B as IsUnion, C as SectionAudience, D as RunInputPayload, E as ValueType, F as DefNameMembers, G as ExtensionId, H as MergeContext, I as EmptyLiteralNameBrand, J as JsonValue, K as defineExtensionId, L as HasClosedNames, M as ClosedMembers, N as CollisionBrand, O as ArgSnapshot, P as DefName, R as IsClosedName, S as RuntimeCommandSectionInput, T as ValidatedInput, U as MergeProviders, V as LocalValueBrand, W as UnionToIntersection, _ as NamedFlagDef, a as CommandMeta, b as ParsedArgValue, d as InferArgs, f as InferFlags, g as MergeFlags, h as InvocationIO, i as ArgsDef, j as Awaitable, k as CommandSnapshot, l as FlagDef, m as InputFlags, n as resolveArtifactDir, o as CommandSection, p as InputArgs, q as JsonCompatible, r as ArgDef, s as CommandSectionInput, u as FlagsDef, v as NamedFlagsRecord, w as SectionConsumer, x as ParsedFlagValue, y as ParseResult, z as IsStaticTuple } from "./artifacts-D8Jlug-a.js";
|
|
2
2
|
//#region src/validation/args.brands.d.ts
|
|
3
3
|
type ArgNames<A extends readonly object[]> = DefName<A[number]>;
|
|
4
4
|
type DuplicateArgBrand<A, Existing extends string> = CollisionBrand<DefName<A>, Existing, "FIX_DUPLICATE_ARG", "Argument name ", " is already defined">;
|
|
@@ -7,7 +7,7 @@ type EmptyArgNameError = {
|
|
|
7
7
|
};
|
|
8
8
|
/** Reject empty argument names, including empty members of a name union. */
|
|
9
9
|
type EmptyArgNameBrand<Name extends string> = EmptyLiteralNameBrand<Name, EmptyArgNameError>;
|
|
10
|
-
type EmptyArgDefinitionNameBrand<A> =
|
|
10
|
+
type EmptyArgDefinitionNameBrand<A> = EmptyArgNameBrand<DefNameMembers<A>>;
|
|
11
11
|
type ArgChecks<A, Existing extends string> = A & DuplicateArgBrand<A, Existing> & LocalValueBrand<A> & EmptyArgDefinitionNameBrand<A>;
|
|
12
12
|
/**
|
|
13
13
|
* Per-arg validation tuple type. Resolves to `A` when the constraints are
|
|
@@ -44,8 +44,9 @@ type AliasesOf<C> = C extends {
|
|
|
44
44
|
readonly aliases: infer A extends readonly string[];
|
|
45
45
|
} ? A : "aliases" extends keyof C ? readonly string[] : readonly [];
|
|
46
46
|
type NarrowAliases<A extends readonly string[]> = IsClosedName<A[number]> extends true ? A[number] : never;
|
|
47
|
+
type AliasMembers<A extends readonly string[]> = ClosedMembers<A[number]>;
|
|
47
48
|
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> =
|
|
49
|
+
type AliasShapeErrors<Name extends string, C> = AliasMembers<AliasesOf<C>> extends (infer Alias) ? Alias extends string ? AliasShapeError<Name, Alias> : never : never;
|
|
49
50
|
type AliasShapeBrand<Name extends string, C> = [AliasShapeErrors<Name, C>] extends [never] ? {} : {
|
|
50
51
|
readonly FIX_ALIAS_SHAPE: AliasShapeErrors<Name, C>;
|
|
51
52
|
};
|
|
@@ -59,8 +60,8 @@ type EmptyNameError = {
|
|
|
59
60
|
};
|
|
60
61
|
type TrimWhitespace = " " | "\t" | "\n" | "\r" | "\v" | "\f" | " " | " " | " " | " " | " " | " " | " " | " " | " " | " " | " " | " " | " " | "
" | "
" | " " | " " | " " | "";
|
|
61
62
|
type BlankName<Name extends string> = Name extends `${TrimWhitespace}${infer Tail}` ? BlankName<Tail> : Name extends "" ? true : false;
|
|
62
|
-
/** Runtime checks own open
|
|
63
|
-
type CommandNameBrand<Name extends string> =
|
|
63
|
+
/** Runtime checks own open members; every statically known member is validated, even beside an open one. */
|
|
64
|
+
type CommandNameBrand<Name extends string> = true extends BlankName<ClosedMembers<Name>> ? EmptyNameError : "__proto__" extends ClosedMembers<Name> ? {
|
|
64
65
|
readonly FIX_RESERVED_NAME: "Command name \"__proto__\" is reserved";
|
|
65
66
|
} : {};
|
|
66
67
|
type DefinitionAliases<D> = CommandDefinitionData<D> extends {
|
|
@@ -70,9 +71,9 @@ type DefinitionAliases<D> = CommandDefinitionData<D> extends {
|
|
|
70
71
|
type CommandDefinitionSpellings<D> = D extends unknown ? D extends {
|
|
71
72
|
name: infer N extends string;
|
|
72
73
|
} ? 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> =
|
|
74
|
+
type SelfAliasBrand<D> = UnionToIntersection<D extends unknown ? DefNameMembers<D> & AliasMembers<DefinitionAliases<D>> extends (infer Dup extends string) ? [Dup] extends [never] ? {} : {
|
|
74
75
|
readonly FIX_ALIAS_SHAPE: `Command "${Dup}" must not list its own canonical name as an alias`;
|
|
75
|
-
} : never
|
|
76
|
+
} : never : never>;
|
|
76
77
|
type CommandCollisionBrand<Spellings extends string, Existing extends string> = CollisionBrand<Spellings, Existing, "FIX_COMMAND_COLLISION", "Command name or alias ", " collides with a sibling command">;
|
|
77
78
|
/**
|
|
78
79
|
* Validate definitions against existing siblings and definitions earlier in
|
|
@@ -80,7 +81,7 @@ type CommandCollisionBrand<Spellings extends string, Existing extends string> =
|
|
|
80
81
|
* not statically knowable; their literal aliases opt out with them
|
|
81
82
|
* (see {@link CommandDefinitionSpellings}).
|
|
82
83
|
*/
|
|
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<
|
|
84
|
+
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<DefNameMembers<Head>> & SelfAliasBrand<Head>, ...ValidateCommandDefinitions<Tail, Existing | Spellings>] : never : Ds;
|
|
84
85
|
type ExtensionCommandDefs<E> = [E] extends [never] ? readonly [] : DefiningOf<E> extends {
|
|
85
86
|
readonly commands?: infer Cs extends readonly unknown[];
|
|
86
87
|
} ? Cs : readonly [];
|
|
@@ -180,17 +181,29 @@ type KnownContextInstances<Cs extends readonly AnyContextInstance[]> = { [I in k
|
|
|
180
181
|
//#region src/validation/flags.brands.d.ts
|
|
181
182
|
/** Brand an incoming definition when one of its spellings is already claimed. */
|
|
182
183
|
type ExistingFlagCollisionBrand<F, Existing extends string> = CollisionBrand<DefName<F> | ExtractAllAliases<F>, Existing, "FIX_ALIAS_COLLISION", "Flag spelling ", " collides with an existing flag">;
|
|
184
|
+
/**
|
|
185
|
+
* Every statically known canonical, short, and long-alias member, including literals
|
|
186
|
+
* beside an open member. Spelling grammar only: collision evidence stays with
|
|
187
|
+
* {@link ExtractAllAliases}, which must not leak literals out of an open domain.
|
|
188
|
+
* Each field is filtered on its own so a `string`-typed field cannot absorb a
|
|
189
|
+
* sibling field's invalid literal before the filter runs.
|
|
190
|
+
*/
|
|
191
|
+
type SpellingMembers<F> = DefNameMembers<F> | ClosedMembers<F extends {
|
|
192
|
+
short: infer S extends string;
|
|
193
|
+
} ? S : never> | ClosedMembers<F extends {
|
|
194
|
+
aliases: infer A extends readonly string[];
|
|
195
|
+
} ? A[number] : never>;
|
|
183
196
|
/** Reject `__proto__`, which mutates the prototype of plain-object flag registries. */
|
|
184
|
-
type ReservedSpellingBrand<F> = "__proto__" extends
|
|
197
|
+
type ReservedSpellingBrand<F> = "__proto__" extends SpellingMembers<F> ? {
|
|
185
198
|
readonly FIX_RESERVED_SPELLING: "Flag spelling \"__proto__\" is reserved";
|
|
186
199
|
} : {};
|
|
187
200
|
type EmptySpellingError = {
|
|
188
201
|
readonly FIX_EMPTY_SPELLING: "Flag names and aliases must be non-empty strings";
|
|
189
202
|
};
|
|
190
|
-
/** Reject empty flag names, including empty
|
|
203
|
+
/** Reject empty flag names, including an empty member of a name union beside an open member. */
|
|
191
204
|
type EmptyFlagSpellingBrand<Name extends string> = EmptyLiteralNameBrand<Name, EmptySpellingError>;
|
|
192
205
|
/** Reject empty spellings: their CLI tokens (`--`, `-`) are unparseable, so the flag can never be supplied. */
|
|
193
|
-
type EmptySpellingBrand<F> = "" extends
|
|
206
|
+
type EmptySpellingBrand<F> = "" extends SpellingMembers<F> ? EmptySpellingError : {};
|
|
194
207
|
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
208
|
type OwnAliasesBrand<F> = F extends {
|
|
196
209
|
aliases: infer Aliases extends readonly string[];
|
|
@@ -298,7 +311,7 @@ type HasClosedFlagSpellings<F> = F extends {
|
|
|
298
311
|
aliases: infer A extends readonly string[];
|
|
299
312
|
} ? false extends IsStaticTuple<A> | IsClosedName<A[number]> ? false : true : false : true) ? false : true : false;
|
|
300
313
|
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<
|
|
314
|
+
type LocalFlagBranchBrand<F> = LocalValueBrand<F> & OwnAliasesBrand<F> & ShortLengthBrand<F> & ReservedSpellingBrand<F> & EmptySpellingBrand<F> & NoPrefixBrand<SpellingMembers<F>> & ([DefName<F> & ExtractAllAliases<F>] extends [never] ? {} : {
|
|
302
315
|
readonly FIX_ALIAS_COLLISION: "Flag repeats one of its own spellings";
|
|
303
316
|
});
|
|
304
317
|
/** Validate provable local fields and destination relations without inventing names for open inputs. */
|
|
@@ -379,6 +392,18 @@ type CompatibleRunBranch<Expected, Actual> = Actual extends Expected ? Actual :
|
|
|
379
392
|
type CompatibleRunInput<Shape extends CommandShape, Input> = CompatibleRunValue<RunInput<Shape>, Input>;
|
|
380
393
|
type RunInputArguments<Shape extends CommandShape> = {} extends RunInput<Shape> ? readonly [input?: RunInput<Shape>] : readonly [input: RunInput<Shape>];
|
|
381
394
|
type RunArguments<Shape extends CommandShape> = readonly [...RunInputArguments<Shape>, io?: Partial<InvocationIO>];
|
|
395
|
+
/**
|
|
396
|
+
* Typed invoker bound to one command in an app, returned by {@link Crust.at}.
|
|
397
|
+
*
|
|
398
|
+
* `run` accepts the same structured input and IO as `Crust.run` with the path
|
|
399
|
+
* already applied, so a handle can be re-exported as a plain typed function.
|
|
400
|
+
*/
|
|
401
|
+
interface CommandHandle<Shape extends CommandShape> {
|
|
402
|
+
/** The typed path this handle was created with (`[]` selects the root). */
|
|
403
|
+
readonly path: readonly string[];
|
|
404
|
+
run(...args: RunArguments<Shape>): Promise<RunOutcome<Shape["result"]>>;
|
|
405
|
+
run<const Input>(input: Input, ...validation: [Input] extends [CompatibleRunInput<Shape, Input>] ? readonly [io?: Partial<InvocationIO>] : readonly [invalidInput: never]): Promise<RunOutcome<Shape["result"]>>;
|
|
406
|
+
}
|
|
382
407
|
/** Static configuration for a reusable command definition. */
|
|
383
408
|
interface CommandConfig extends Omit<CommandMeta, "name" | "sections" | "version"> {
|
|
384
409
|
/** Plain-text sections rendered after built-in command documentation. */
|
|
@@ -435,9 +460,9 @@ type DepsOfBuilder<B> = UnionToIntersection<B extends {
|
|
|
435
460
|
deps: infer Deps extends ContextMap;
|
|
436
461
|
};
|
|
437
462
|
} ? Deps : {}> extends (infer Merged extends ContextMap) ? Merged : {};
|
|
438
|
-
type DefinitionShapeForSpelling<D, Spelling extends string> = CommandDefinitionData<D> extends {
|
|
463
|
+
type DefinitionShapeForSpelling<D, Spelling extends string> = D extends unknown ? CommandDefinitionData<D> extends {
|
|
439
464
|
readonly _shape?: infer Shape extends CommandShape;
|
|
440
|
-
} ? Spelling extends CommandDefinitionSpellings<D> ? Shape : never : never;
|
|
465
|
+
} ? Spelling extends CommandDefinitionSpellings<D> ? Shape : never : never : never;
|
|
441
466
|
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
467
|
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
468
|
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> : {};
|
|
@@ -502,7 +527,8 @@ export declare function defineCommand<const Name extends string, const C extends
|
|
|
502
527
|
* });
|
|
503
528
|
* ```
|
|
504
529
|
*/
|
|
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> = {}> = {
|
|
530
|
+
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> = {}, ActionDeps extends ContextMap = {}> = {
|
|
531
|
+
readonly actionDeps: ActionDeps;
|
|
506
532
|
readonly pending: Pending;
|
|
507
533
|
readonly demands: Demands;
|
|
508
534
|
readonly extension: Extensions;
|
|
@@ -511,6 +537,7 @@ type CollisionSpellings<Extensions extends string = never, Tree extends string =
|
|
|
511
537
|
readonly providers: Providers;
|
|
512
538
|
};
|
|
513
539
|
type AnyCollisionSpellings = {
|
|
540
|
+
readonly actionDeps?: ContextMap;
|
|
514
541
|
readonly pending: string;
|
|
515
542
|
readonly demands: ContextMap;
|
|
516
543
|
readonly extension: string;
|
|
@@ -524,13 +551,19 @@ type RecipeDepsOf<S extends AnyCollisionSpellings> = S extends {
|
|
|
524
551
|
type ProvidersOf<S extends AnyCollisionSpellings> = S extends {
|
|
525
552
|
readonly providers: infer Providers extends Record<string, ContextValue>;
|
|
526
553
|
} ? Providers : {};
|
|
554
|
+
/** Obligations of this node's stored action, never inherited by descendants. */
|
|
555
|
+
type ActionDepsOf<S extends AnyCollisionSpellings> = S extends {
|
|
556
|
+
readonly actionDeps: infer Deps extends ContextMap;
|
|
557
|
+
} ? Deps : {};
|
|
527
558
|
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
559
|
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
|
|
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,
|
|
532
|
-
|
|
533
|
-
|
|
560
|
+
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>, ActionDepsOf<CollisionSp>>, Result, Meta, string, "recipe">;
|
|
561
|
+
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>>, ActionDepsOf<CollisionSp>>, Result, Meta, string, Caps>;
|
|
562
|
+
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, Omit<CollisionSp, "actionDeps"> & {
|
|
563
|
+
readonly actionDeps: Ctx;
|
|
564
|
+
}, Awaited<R>, Meta, string, Caps>;
|
|
565
|
+
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>>, ActionDepsOf<CollisionSp>>, Result, Meta, string, Caps>;
|
|
566
|
+
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>, ActionDepsOf<CollisionSp>>, Result, Meta, string, Caps>;
|
|
534
567
|
type ExtensionDemandValues<Es extends readonly AnyExtension[]> = UnionToIntersection<DefiningOf<Es[number]> extends {
|
|
535
568
|
readonly _hookDeps?: infer H extends ContextMap;
|
|
536
569
|
} ? H : Record<string, ContextValue>> extends (infer D extends ContextMap) ? D : {};
|
|
@@ -588,7 +621,7 @@ export declare class Crust<Flags extends FlagsDef = {}, A extends ArgsDef = [],
|
|
|
588
621
|
* @param defs - Named flag definitions
|
|
589
622
|
* @returns A new `Crust` instance with the given flags
|
|
590
623
|
*/
|
|
591
|
-
flags<const Defs extends readonly NamedFlagDef[]>(...defs: ValidateLocalFlagDefs<Defs, Sp>): AfterFlags<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, Defs, Meta, Caps>;
|
|
624
|
+
flags<const Defs extends readonly NamedFlagDef[], _Union extends this = never>(...defs: ValidateLocalFlagDefs<Defs, Sp>): AfterFlags<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, Defs, Meta, Caps>;
|
|
592
625
|
/**
|
|
593
626
|
* Define positional arguments for this command; argument order is the
|
|
594
627
|
* order they are passed (created with `defineArg(name, def)` or written
|
|
@@ -600,12 +633,12 @@ export declare class Crust<Flags extends FlagsDef = {}, A extends ArgsDef = [],
|
|
|
600
633
|
* @param defs - Positional argument definitions, in positional order
|
|
601
634
|
* @returns A new `Crust` instance with the combined args
|
|
602
635
|
*/
|
|
603
|
-
args<const NewA extends ArgsDef>(...defs: NewA & AppendArgsChecks<A, NewA>): AfterArgs<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, NewA, Meta, Caps>;
|
|
636
|
+
args<const NewA extends ArgsDef, _Union extends this = never>(...defs: NewA & AppendArgsChecks<A, NewA>): AfterArgs<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, NewA, Meta, Caps>;
|
|
604
637
|
/**
|
|
605
638
|
* Declare Contexts this command consumes without supplying their values.
|
|
606
639
|
* Factory references are retained while setup stays lazy.
|
|
607
640
|
*/
|
|
608
|
-
use<const Fs extends readonly [AnyContextFactory, ...AnyContextFactory[]]>(this: {
|
|
641
|
+
use<const Fs extends readonly [AnyContextFactory, ...AnyContextFactory[]] = never>(this: {
|
|
609
642
|
readonly _types: {
|
|
610
643
|
readonly caps: "recipe";
|
|
611
644
|
};
|
|
@@ -620,7 +653,7 @@ export declare class Crust<Flags extends FlagsDef = {}, A extends ArgsDef = [],
|
|
|
620
653
|
* Extension commands. Consuming operations throw `DEFINITION` for actual collisions.
|
|
621
654
|
*
|
|
622
655
|
*/
|
|
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>;
|
|
656
|
+
provide<const Cs extends readonly AnyContextInstance[] = never>(...instances: KnownContextInstances<Cs> & ProvideChecks<Sp | CollisionSp["pending"], Cs> & ValidateContextNames<Caps extends "recipe" ? ProvidersOf<CollisionSp> : Ctx, Cs> & ValidateContextDeps<Ctx, Cs> & DeclaredDependencyValuesBrand<CollisionSp["demands"] & RecipeDepsOf<CollisionSp> & ActionDepsOf<CollisionSp>, ContextsOutput<NoInfer<Cs>>>): AfterProvide<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, Cs, Meta, Caps>;
|
|
624
657
|
/**
|
|
625
658
|
* Define the Command Action — the function that implements this
|
|
626
659
|
* command's behavior after its inputs are ready.
|
|
@@ -628,8 +661,9 @@ export declare class Crust<Flags extends FlagsDef = {}, A extends ArgsDef = [],
|
|
|
628
661
|
* The action receives a {@link CrustCommandContext} with `args` typed from
|
|
629
662
|
* `.args()` and `flags` typed from the accumulated `Flags`.
|
|
630
663
|
*
|
|
631
|
-
* Calling `.action()` again replaces the command behavior
|
|
632
|
-
*
|
|
664
|
+
* Calling `.action()` again replaces the command behavior and its Context obligations
|
|
665
|
+
* on the new builder. Later local providers must satisfy the bound Context value types;
|
|
666
|
+
* descendants may shadow independently. The original builder is not mutated.
|
|
633
667
|
*
|
|
634
668
|
* @param action - The Command Action function
|
|
635
669
|
* @returns A new `Crust` instance with the action registered
|
|
@@ -650,7 +684,7 @@ export declare class Crust<Flags extends FlagsDef = {}, A extends ArgsDef = [],
|
|
|
650
684
|
* metadata by TypeScript, not at runtime.
|
|
651
685
|
* Recipe builders cannot call this root-only method.
|
|
652
686
|
*/
|
|
653
|
-
extend<const Es extends readonly Extension<any, any, any, any, DefinedRootMetaKeys<Meta>>[]>(this: {
|
|
687
|
+
extend<const Es extends readonly Extension<any, any, any, any, DefinedRootMetaKeys<Meta>>[], _Union extends this = never>(this: {
|
|
654
688
|
readonly _types: {
|
|
655
689
|
readonly caps: "app";
|
|
656
690
|
};
|
|
@@ -662,7 +696,7 @@ export declare class Crust<Flags extends FlagsDef = {}, A extends ArgsDef = [],
|
|
|
662
696
|
* under its own carried name (use `.as(name)` to rename).
|
|
663
697
|
*
|
|
664
698
|
*/
|
|
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>;
|
|
699
|
+
add<const Ds extends readonly CommandDefinition<any, any, any, any>[] = never>(...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
700
|
/**
|
|
667
701
|
* Define an app-local leaf subcommand inline (root-only sugar for
|
|
668
702
|
* `.add(defineCommand(name, recipe))`).
|
|
@@ -714,6 +748,22 @@ export declare class Crust<Flags extends FlagsDef = {}, A extends ArgsDef = [],
|
|
|
714
748
|
readonly caps: "app";
|
|
715
749
|
};
|
|
716
750
|
}, 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"]>>;
|
|
751
|
+
/**
|
|
752
|
+
* Bind a typed command path into a reusable {@link CommandHandle}.
|
|
753
|
+
*
|
|
754
|
+
* Validates the path eagerly against the prepared command tree, so an
|
|
755
|
+
* unknown path throws `COMMAND_NOT_FOUND` here rather than on the first
|
|
756
|
+
* `handle.run()`. The handle is bound to this builder's tree; commands added
|
|
757
|
+
* afterwards belong to the new builder returned by `.add()`.
|
|
758
|
+
*
|
|
759
|
+
* @param path - Typed path to the command to bind (`[]` selects the root)
|
|
760
|
+
* @throws {CrustError} COMMAND_NOT_FOUND when the path does not name a command
|
|
761
|
+
*/
|
|
762
|
+
at<const Path extends CommandPath<Tree>>(this: {
|
|
763
|
+
readonly _types: {
|
|
764
|
+
readonly caps: "app";
|
|
765
|
+
};
|
|
766
|
+
}, path: Path & KnownCommandPath<Path, Tree>): CommandHandle<CommandShapeAt<CommandShape<A, Flags, Tree, Result>, Path>>;
|
|
717
767
|
/**
|
|
718
768
|
* Parse `process.argv`, resolve subcommands, run Extension hooks, and
|
|
719
769
|
* execute the matched Command Action.
|
|
@@ -867,24 +917,31 @@ type RootMetaKey = keyof RootCommandMeta;
|
|
|
867
917
|
type RootCommandSnapshot<K extends RootMetaKey> = CommandSnapshot & {
|
|
868
918
|
readonly meta: Readonly<Required<Pick<CommandMeta, K>>>;
|
|
869
919
|
};
|
|
870
|
-
/**
|
|
871
|
-
|
|
872
|
-
|
|
920
|
+
/** One file a build hook produces; `path` is POSIX-relative to the build output directory. */
|
|
921
|
+
interface BuildFile {
|
|
922
|
+
readonly path: string;
|
|
923
|
+
readonly content: string | Uint8Array;
|
|
924
|
+
}
|
|
925
|
+
/** Files returned by a build hook; build tooling writes them into the build output directory. */
|
|
926
|
+
type BuildArtifacts = readonly BuildFile[];
|
|
927
|
+
/** Files written for each Extension build hook, in hook execution order. */
|
|
873
928
|
interface BuildReport {
|
|
874
929
|
readonly extensions: readonly {
|
|
875
930
|
readonly id: ExtensionId;
|
|
876
|
-
readonly files: readonly string[]
|
|
931
|
+
readonly files: readonly string[];
|
|
877
932
|
}[];
|
|
878
933
|
}
|
|
879
|
-
/**
|
|
934
|
+
/**
|
|
935
|
+
* Build-time context passed to an Extension's artifact generator. It deliberately
|
|
936
|
+
* carries no output directory: build tooling owns that tree, so every shipped file
|
|
937
|
+
* is a returned {@link BuildFile} and the {@link BuildReport} is exact.
|
|
938
|
+
*/
|
|
880
939
|
interface ExtensionBuildContext<MetaKeys extends RootMetaKey = never> {
|
|
881
940
|
/**
|
|
882
941
|
* Frozen snapshot prepared before this hook starts. It does not include this hook's own
|
|
883
942
|
* outputs; later-registered hooks receive refreshed snapshots.
|
|
884
943
|
*/
|
|
885
944
|
readonly snapshot: RootCommandSnapshot<MetaKeys>;
|
|
886
|
-
/** Resolved absolute output directory. */
|
|
887
|
-
readonly outDir: string;
|
|
888
945
|
}
|
|
889
946
|
/**
|
|
890
947
|
* Readonly invocation view passed to Extension hooks.
|
|
@@ -998,15 +1055,14 @@ type ExtensionFlagDef = FlagDef & {
|
|
|
998
1055
|
type NamedExtensionFlagDef = NamedFlagDef & {
|
|
999
1056
|
readonly recursive?: boolean;
|
|
1000
1057
|
};
|
|
1058
|
+
type SchemaToken<F> = F extends {
|
|
1059
|
+
type: "boolean";
|
|
1060
|
+
} ? boolean : string;
|
|
1001
1061
|
type InferPreSchemaExtensionFlag<F extends ExtensionFlagDef> = F extends {
|
|
1002
1062
|
schema: unknown;
|
|
1003
|
-
} ? F extends {
|
|
1063
|
+
} ? (F extends {
|
|
1004
1064
|
multiple: true;
|
|
1005
|
-
} ? F extends {
|
|
1006
|
-
type: "boolean";
|
|
1007
|
-
} ? boolean[] | undefined : string[] | undefined : F extends {
|
|
1008
|
-
type: "boolean";
|
|
1009
|
-
} ? boolean | undefined : string | undefined : F extends {
|
|
1065
|
+
} ? never : SchemaToken<F>) | ("multiple" extends keyof F ? true extends F["multiple"] ? SchemaToken<F>[] : never : never) | undefined : F extends {
|
|
1010
1066
|
required: true;
|
|
1011
1067
|
} ? F extends {
|
|
1012
1068
|
default: unknown;
|
|
@@ -1017,11 +1073,9 @@ type InferPreSchemaExtensionFlag<F extends ExtensionFlagDef> = F extends {
|
|
|
1017
1073
|
}>["value"] | undefined : InferFlags<{
|
|
1018
1074
|
value: F;
|
|
1019
1075
|
}>["value"];
|
|
1020
|
-
type InferExtensionFlag<F> = F extends ExtensionFlagDef ? F extends
|
|
1021
|
-
|
|
1022
|
-
|
|
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]>; };
|
|
1076
|
+
type InferExtensionFlag<F> = F extends ExtensionFlagDef ? InferPreSchemaExtensionFlag<F> | ("recursive" extends keyof F ? (false extends F["recursive"] ? undefined : never) : never) : never;
|
|
1077
|
+
/** Infer pre-validation hook values; uncertain collections claim no typed ownership. */
|
|
1078
|
+
type InferExtensionFlags<Defs extends readonly NamedExtensionFlagDef[]> = IsStaticTuple<Defs> extends true ? HasClosedNames<Defs> extends true ? { [K in keyof NamedFlagsRecord<Defs>]: InferExtensionFlag<NamedFlagsRecord<Defs>[K]>; } : Record<string, ParsedFlagValue> : Record<string, ParsedFlagValue>;
|
|
1025
1079
|
/** A documentation section an Extension contributes to one command path. */
|
|
1026
1080
|
type ExtensionSectionContribution = RuntimeCommandSectionInput & {
|
|
1027
1081
|
readonly command: readonly string[];
|
|
@@ -1033,7 +1087,7 @@ interface ExtensionConfig<Defs extends readonly NamedExtensionFlagDef[] = readon
|
|
|
1033
1087
|
readonly uses?: Uses;
|
|
1034
1088
|
readonly provides?: Provides;
|
|
1035
1089
|
readonly sections?: (snapshot: RootCommandSnapshot<MetaKeys>) => readonly ExtensionSectionContribution[];
|
|
1036
|
-
readonly build?: (ctx: ExtensionBuildContext<MetaKeys>) =>
|
|
1090
|
+
readonly build?: (ctx: ExtensionBuildContext<MetaKeys>) => Awaitable<BuildArtifacts>;
|
|
1037
1091
|
readonly hooks?: ExtensionHooks<Defs, ContextDependencies<Uses>, MetaKeys>;
|
|
1038
1092
|
}
|
|
1039
1093
|
type ValidateExtensionConfig<Defs extends readonly NamedExtensionFlagDef[], Provides extends readonly AnyContextInstance[], Commands extends readonly CommandDefinition<any, any, any, any>[], Uses extends readonly AnyContextFactory[]> = {
|
|
@@ -1055,7 +1109,7 @@ interface Extension<Deps extends ContextMap = ContextMap, Provides extends reado
|
|
|
1055
1109
|
readonly uses: readonly AnyContextFactory[];
|
|
1056
1110
|
readonly provides?: Provides;
|
|
1057
1111
|
readonly sections?: (snapshot: RootCommandSnapshot<MetaKeys>) => readonly ExtensionSectionContribution[];
|
|
1058
|
-
readonly build?: (ctx: ExtensionBuildContext<MetaKeys>) =>
|
|
1112
|
+
readonly build?: (ctx: ExtensionBuildContext<MetaKeys>) => Awaitable<BuildArtifacts>;
|
|
1059
1113
|
readonly hooks?: ExtensionHooks<any, Deps, MetaKeys>;
|
|
1060
1114
|
readonly _deps?: Deps;
|
|
1061
1115
|
}
|
|
@@ -1148,8 +1202,15 @@ interface Defining<T> {
|
|
|
1148
1202
|
}
|
|
1149
1203
|
/** @internal */
|
|
1150
1204
|
type DefiningOf<T> = T extends Defining<unknown> ? T[typeof defining] : T;
|
|
1205
|
+
/**
|
|
1206
|
+
* Adapter hook: the Context sources a bag was built from, exposed as a
|
|
1207
|
+
* non-enumerable symbol property. Reading it never starts construction.
|
|
1208
|
+
*/
|
|
1209
|
+
export declare const contextSources: unique symbol;
|
|
1151
1210
|
/** Lazy, invocation-scoped Context values. Reading a property starts construction. */
|
|
1152
|
-
type ContextBag<Deps extends ContextMap = {}> = { readonly [K in keyof Deps]: Promise<Deps[K]>; }
|
|
1211
|
+
type ContextBag<Deps extends ContextMap = {}> = { readonly [K in keyof Deps]: Promise<Deps[K]>; } & {
|
|
1212
|
+
readonly [contextSources]?: readonly (AnyContextFactory | AnyContextInstance)[];
|
|
1213
|
+
};
|
|
1153
1214
|
interface ContextConfig {
|
|
1154
1215
|
readonly flags?: readonly NamedFlagDef[];
|
|
1155
1216
|
readonly uses?: readonly AnyContextFactory[];
|
|
@@ -1169,6 +1230,8 @@ interface ContextInstance<Name extends string = string, Value = unknown, OF exte
|
|
|
1169
1230
|
readonly ownedFlags: FlagsDef;
|
|
1170
1231
|
/** @internal — declared direct dependency factories */
|
|
1171
1232
|
readonly uses: readonly AnyContextFactory[];
|
|
1233
|
+
/** @internal defining factory, for adapters that identify Contexts by factory */
|
|
1234
|
+
readonly factory: AnyContextFactory;
|
|
1172
1235
|
setup(input: ContextSetupInput<OF>): Awaitable<Value>;
|
|
1173
1236
|
readonly _ownedFlags?: OF;
|
|
1174
1237
|
/** @internal — phantom carrying the transitive dependency closure */
|
|
@@ -1241,4 +1304,4 @@ export declare function defineFlag<const N extends string, const D extends FlagD
|
|
|
1241
1304
|
/** Define and own one positional argument; layout belongs to its consuming command. */
|
|
1242
1305
|
export declare function defineArg<const N extends string, const D extends UnnamedArgDef>(name: N & EmptyArgNameBrand<N>, def: D & LocalValueBrand<D>): Named<N, D>;
|
|
1243
1306
|
//#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 };
|
|
1307
|
+
export { type AnyContextFactory, type AnyCrust, type ArgDef, type ArgSnapshot, type ArgsDef, type BuildArtifacts, type BuildFile, type BuildReport, type CommandConfig, type CommandDefinition, type CommandDefinitionBuilder, type CommandHandle, 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, resolveArtifactDir };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as
|
|
1
|
+
import { C as normalizeFlag, S as normalizeArg, _ as defineExtension, a as runInvocation, b as definingOf, d as resolveArtifactDir, f as cloneCommandNode, g as registerFlag, h as createCommandNode, i as resolveTypedPath, m as validateCommandSections, n as executeInvocation, o as snapshotCommand, p as installExtensionContexts, r as prepareInvocation, v as contextSources, w as CrustError, x as validateContextAvailability, y as defineContext } from "./invocation-CIvHShiW.js";
|
|
2
2
|
//#region src/identity.ts
|
|
3
3
|
/** Mint an Extension identity from any non-blank, trimmed string. */
|
|
4
4
|
function defineExtensionId(id) {
|
|
@@ -173,8 +173,9 @@ var Crust = class {
|
|
|
173
173
|
* The action receives a {@link CrustCommandContext} with `args` typed from
|
|
174
174
|
* `.args()` and `flags` typed from the accumulated `Flags`.
|
|
175
175
|
*
|
|
176
|
-
* Calling `.action()` again replaces the command behavior
|
|
177
|
-
*
|
|
176
|
+
* Calling `.action()` again replaces the command behavior and its Context obligations
|
|
177
|
+
* on the new builder. Later local providers must satisfy the bound Context value types;
|
|
178
|
+
* descendants may shadow independently. The original builder is not mutated.
|
|
178
179
|
*
|
|
179
180
|
* @param action - The Command Action function
|
|
180
181
|
* @returns A new `Crust` instance with the action registered
|
|
@@ -224,9 +225,25 @@ var Crust = class {
|
|
|
224
225
|
input: structuredInput
|
|
225
226
|
}, io, materializeCommandDefinition);
|
|
226
227
|
}
|
|
228
|
+
at(path) {
|
|
229
|
+
resolveTypedPath(prepareInvocation(this._node, materializeCommandDefinition).rootNode, path);
|
|
230
|
+
const node = this._node;
|
|
231
|
+
const boundPath = Object.freeze([...path]);
|
|
232
|
+
return {
|
|
233
|
+
path: boundPath,
|
|
234
|
+
async run(...args) {
|
|
235
|
+
const structuredInput = args[0] ?? {};
|
|
236
|
+
const io = args[1];
|
|
237
|
+
return await runInvocation(node, {
|
|
238
|
+
path: boundPath,
|
|
239
|
+
input: structuredInput
|
|
240
|
+
}, io, materializeCommandDefinition);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
227
244
|
async execute(options) {
|
|
228
245
|
return await executeInvocation(this._node, options, materializeCommandDefinition);
|
|
229
246
|
}
|
|
230
247
|
};
|
|
231
248
|
//#endregion
|
|
232
|
-
export { Crust, CrustError, defineArg, defineCommand, defineContext, defineExtension, defineExtensionId, defineFlag };
|
|
249
|
+
export { Crust, CrustError, contextSources, defineArg, defineCommand, defineContext, defineExtension, defineExtensionId, defineFlag, resolveArtifactDir };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { writeFile } from "node:fs/promises";
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, posix, resolve, win32 } from "node:path";
|
|
3
|
+
import { existsSync, realpathSync, statSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
3
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
6
|
import { Writable } from "node:stream";
|
|
5
7
|
import { parseArgs } from "node:util";
|
|
@@ -148,6 +150,11 @@ function definingOf(value) {
|
|
|
148
150
|
function seal(value) {
|
|
149
151
|
return Object.freeze(Object.assign(value, { [defining]: value }));
|
|
150
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Adapter hook: the Context sources a bag was built from, exposed as a
|
|
155
|
+
* non-enumerable symbol property. Reading it never starts construction.
|
|
156
|
+
*/
|
|
157
|
+
const contextSources = Symbol.for("crust.contextSources");
|
|
151
158
|
/** Check only declared availability; setup, callback values, and cycles belong to invocation. */
|
|
152
159
|
function validateContextAvailability(contexts, sources) {
|
|
153
160
|
const names = new Set(contexts.map((instance) => instance.name));
|
|
@@ -179,6 +186,7 @@ function defineContext(name, configOrSetup, maybeSetup) {
|
|
|
179
186
|
name,
|
|
180
187
|
ownedFlags,
|
|
181
188
|
uses: instanceUses,
|
|
189
|
+
factory: sealed,
|
|
182
190
|
setup: run
|
|
183
191
|
});
|
|
184
192
|
};
|
|
@@ -191,7 +199,8 @@ function defineContext(name, configOrSetup, maybeSetup) {
|
|
|
191
199
|
factory.contextName = name;
|
|
192
200
|
factory.uses = uses;
|
|
193
201
|
factory.of = (value) => instance(Object.freeze([]), () => value);
|
|
194
|
-
|
|
202
|
+
const sealed = seal(factory);
|
|
203
|
+
return sealed;
|
|
195
204
|
}
|
|
196
205
|
function isDisposeCallback(value) {
|
|
197
206
|
return typeof value === "function";
|
|
@@ -370,6 +379,7 @@ function createContextResolver(contexts, io, disposal) {
|
|
|
370
379
|
for (const dependency of source.uses ?? []) add(dependency);
|
|
371
380
|
};
|
|
372
381
|
for (const source of sources) add(source);
|
|
382
|
+
Object.defineProperty(bag, contextSources, { value: Object.freeze([...sources]) });
|
|
373
383
|
return Object.freeze(bag);
|
|
374
384
|
};
|
|
375
385
|
return {
|
|
@@ -604,6 +614,83 @@ function installExtensionContexts(node, extensions, reRegisteredIds) {
|
|
|
604
614
|
return cloned;
|
|
605
615
|
}
|
|
606
616
|
//#endregion
|
|
617
|
+
//#region ../utils/src/source.ts
|
|
618
|
+
/**
|
|
619
|
+
* Find the nearest directory containing `package.json` by walking up the
|
|
620
|
+
* filesystem from `startPath`.
|
|
621
|
+
*
|
|
622
|
+
* If `startPath` points at an existing file, its parent directory is used as
|
|
623
|
+
* the starting point. The directory walk uses `path.resolve()` (lexical), not
|
|
624
|
+
* `fs.realpath()`, so a symlink's parent chain is walked rather than the
|
|
625
|
+
* symlink target's parent chain.
|
|
626
|
+
*
|
|
627
|
+
* @param startPath - Directory or file path to start walking from.
|
|
628
|
+
* @returns The absolute path of the nearest enclosing directory containing
|
|
629
|
+
* `package.json`, or `null` if the filesystem root is reached first.
|
|
630
|
+
*
|
|
631
|
+
* @internal Shared by {@link resolveSourceDir} and `resolveArtifactDir`.
|
|
632
|
+
*/
|
|
633
|
+
function findNearestPackageRoot(startPath) {
|
|
634
|
+
let current = resolve(startPath);
|
|
635
|
+
if (existsSync(current) && !statSync(current).isDirectory()) current = dirname(current);
|
|
636
|
+
while (true) {
|
|
637
|
+
if (existsSync(join(current, "package.json"))) return current;
|
|
638
|
+
const parent = dirname(current);
|
|
639
|
+
if (parent === current) return null;
|
|
640
|
+
current = parent;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
//#endregion
|
|
644
|
+
//#region ../utils/src/artifacts.ts
|
|
645
|
+
/**
|
|
646
|
+
* Set by `crust build` while it prepares the Command Snapshot: the absolute
|
|
647
|
+
* entry-isolated directory Extension build hooks write into. Core reads it
|
|
648
|
+
* to run the hooks; `resolveArtifactDir` reads it so sections evaluated during
|
|
649
|
+
* that run see the artifacts being built instead of the wiped `.crust/root`.
|
|
650
|
+
*/
|
|
651
|
+
const BUILD_OUT_DIR_ENV = "CRUST_INTERNAL_BUILD_OUT_DIR";
|
|
652
|
+
/** True inside a `bun build --compile` or `deno compile` executable. */
|
|
653
|
+
function isCompiledExecutable() {
|
|
654
|
+
const { Bun, Deno } = globalThis;
|
|
655
|
+
const bunMain = Bun?.main ?? "";
|
|
656
|
+
return bunMain.startsWith("/$bunfs/") || /^[A-Za-z]:[\\/]~BUN[\\/]/.test(bunMain) || Deno?.build?.standalone === true;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Absolute path of a build artifact or `crust.include` directory shipped with
|
|
660
|
+
* this CLI. `name` is a top-level directory name such as `"skills"`.
|
|
661
|
+
*
|
|
662
|
+
* The artifact path is computed from how the CLI is running — never probed:
|
|
663
|
+
* - Compiled executable (Bun or Deno): `<dir of the executable>/<name>`, which
|
|
664
|
+
* is a platform package's `bin/` or wherever the binary was placed.
|
|
665
|
+
* - Crust-built Node bundle: `<name>` next to the bundle's `bin/` directory,
|
|
666
|
+
* i.e. `.crust/root/<name>` in place and `<installed root>/<name>` after install.
|
|
667
|
+
* - Snapshot preparation inside `crust build`: `<build output dir>/<name>`, the
|
|
668
|
+
* artifacts earlier Extension build hooks wrote in this same build.
|
|
669
|
+
* - Source (`bun run`, `node`, `deno run`): `.crust/root/<name>` under the
|
|
670
|
+
* nearest package root of the real `process.argv[1]` entrypoint (following source
|
|
671
|
+
* links) — the output of the last `crust build`.
|
|
672
|
+
*
|
|
673
|
+
* @throws {Error} when `name` is not a single path segment, or in source mode
|
|
674
|
+
* when the entrypoint cannot be resolved or has no enclosing `package.json`.
|
|
675
|
+
*/
|
|
676
|
+
function resolveArtifactDir(name) {
|
|
677
|
+
if (name === "" || name === "." || name === ".." || /[\\/]/.test(name)) throw new Error(`Artifact name must be a single directory name, got ${JSON.stringify(name)}.`);
|
|
678
|
+
if (isCompiledExecutable()) return join(dirname(process.execPath), name);
|
|
679
|
+
if (process.env.CRUST_INTERNAL_BUILD === "1") return resolve(fileURLToPath(import.meta.url), "..", "..", name);
|
|
680
|
+
const buildOutDir = process.env[BUILD_OUT_DIR_ENV];
|
|
681
|
+
if (buildOutDir) return join(buildOutDir, name);
|
|
682
|
+
const entrypoint = process.argv[1];
|
|
683
|
+
let sourceEntrypoint = entrypoint;
|
|
684
|
+
if (entrypoint) try {
|
|
685
|
+
sourceEntrypoint = realpathSync(entrypoint);
|
|
686
|
+
} catch (cause) {
|
|
687
|
+
throw new Error(`Could not resolve artifact "${name}": could not resolve source entrypoint "${entrypoint}".`, { cause });
|
|
688
|
+
}
|
|
689
|
+
const packageRoot = sourceEntrypoint ? findNearestPackageRoot(sourceEntrypoint) : null;
|
|
690
|
+
if (!packageRoot) throw new Error(`Could not resolve artifact "${name}": no package.json was found above ${entrypoint ? `entrypoint "${entrypoint}"` : "process.argv[1] (unset)"}.`);
|
|
691
|
+
return join(packageRoot, ".crust", "root", name);
|
|
692
|
+
}
|
|
693
|
+
//#endregion
|
|
607
694
|
//#region ../utils/src/terminal.ts
|
|
608
695
|
const STORAGE_KEY = Symbol.for("crustjs.terminal.io");
|
|
609
696
|
const AMBIENT_CALLBACKS_KEY = Symbol.for("crustjs.terminal.ambient-callbacks");
|
|
@@ -733,11 +820,9 @@ function coerceJson(raw) {
|
|
|
733
820
|
//#region src/parsing/parser.ts
|
|
734
821
|
/**
|
|
735
822
|
* Build the options config for `util.parseArgs` from the shared spelling table.
|
|
736
|
-
* Also returns a reverse alias→name mapping for resolving parsed results.
|
|
737
823
|
*/
|
|
738
824
|
function buildParseArgsOptionDescriptor(spellings) {
|
|
739
825
|
const options = {};
|
|
740
|
-
const aliasToName = {};
|
|
741
826
|
for (const [spelling, entry] of spellings) {
|
|
742
827
|
const descriptor = { type: entry.def.type === "boolean" ? "boolean" : "string" };
|
|
743
828
|
if (entry.def.multiple) descriptor.multiple = true;
|
|
@@ -746,13 +831,9 @@ function buildParseArgsOptionDescriptor(spellings) {
|
|
|
746
831
|
options[spelling] = descriptor;
|
|
747
832
|
continue;
|
|
748
833
|
}
|
|
749
|
-
aliasToName[spelling] = entry.canonicalName;
|
|
750
834
|
if (entry.kind === "alias") options[spelling] = descriptor;
|
|
751
835
|
}
|
|
752
|
-
return
|
|
753
|
-
options,
|
|
754
|
-
aliasToName
|
|
755
|
-
};
|
|
836
|
+
return options;
|
|
756
837
|
}
|
|
757
838
|
/**
|
|
758
839
|
* Coerce a string value to the expected type based on the type literal.
|
|
@@ -848,12 +929,11 @@ function coerceFlagValue(name, def, parsed) {
|
|
|
848
929
|
* must be `true`), and `multiple` flags spread across aliases would lose
|
|
849
930
|
* their interleaved argv order.
|
|
850
931
|
*/
|
|
851
|
-
function resolveAliases(tokens,
|
|
932
|
+
function resolveAliases(tokens, spellings) {
|
|
852
933
|
const canonical = {};
|
|
853
934
|
for (const token of tokens) {
|
|
854
935
|
if (token.kind !== "option") continue;
|
|
855
|
-
const canonicalName =
|
|
856
|
-
const def = flagsDef[canonicalName];
|
|
936
|
+
const { canonicalName, def } = spellings.get(token.name);
|
|
857
937
|
const existing = canonical[canonicalName];
|
|
858
938
|
if (def.type === "boolean") {
|
|
859
939
|
const value = !token.rawName.startsWith("--no-");
|
|
@@ -913,6 +993,12 @@ function resolveArgs(argsDef, positionals, coerce) {
|
|
|
913
993
|
for (const def of argsDef) {
|
|
914
994
|
const { name } = def;
|
|
915
995
|
const label = `<${name}>`;
|
|
996
|
+
Object.defineProperty(resolved, name, {
|
|
997
|
+
value: void 0,
|
|
998
|
+
writable: true,
|
|
999
|
+
enumerable: true,
|
|
1000
|
+
configurable: true
|
|
1001
|
+
});
|
|
916
1002
|
if (def.variadic) {
|
|
917
1003
|
resolved[name] = positionals.slice(index).map((v, i) => coerce(def, v, label, i));
|
|
918
1004
|
index = positionals.length;
|
|
@@ -947,7 +1033,7 @@ function validateNoNegateUsage(argv, spellings) {
|
|
|
947
1033
|
}
|
|
948
1034
|
function tokenizeArgv(command, argv) {
|
|
949
1035
|
const spellings = command.flagSpellings;
|
|
950
|
-
const
|
|
1036
|
+
const parseOptions = buildParseArgsOptionDescriptor(spellings);
|
|
951
1037
|
validateNoNegateUsage(argv, spellings);
|
|
952
1038
|
let parsed;
|
|
953
1039
|
try {
|
|
@@ -979,7 +1065,7 @@ function tokenizeArgv(command, argv) {
|
|
|
979
1065
|
}
|
|
980
1066
|
return {
|
|
981
1067
|
positionals: preSeparatorPositionals,
|
|
982
|
-
flagValues: resolveAliases(parsed.tokens,
|
|
1068
|
+
flagValues: resolveAliases(parsed.tokens, spellings),
|
|
983
1069
|
rawArgs
|
|
984
1070
|
};
|
|
985
1071
|
}
|
|
@@ -1180,8 +1266,7 @@ function normalizeStandardIssues(issues, prefix = []) {
|
|
|
1180
1266
|
//#endregion
|
|
1181
1267
|
//#region src/parsing/schema.ts
|
|
1182
1268
|
async function runSchema(schema, raw, path, issues) {
|
|
1183
|
-
|
|
1184
|
-
if (result instanceof Promise) result = await result;
|
|
1269
|
+
const result = await schema["~standard"].validate(raw);
|
|
1185
1270
|
if (result.issues) {
|
|
1186
1271
|
issues.push(...normalizeStandardIssues(result.issues, path));
|
|
1187
1272
|
return { ok: false };
|
|
@@ -1374,8 +1459,8 @@ function matchKnownFlagToken(spellings, token) {
|
|
|
1374
1459
|
* Resolve a command from an argv array by walking the subcommand tree.
|
|
1375
1460
|
*
|
|
1376
1461
|
* Subcommand matching happens BEFORE flag parsing, so:
|
|
1377
|
-
* `crust build --
|
|
1378
|
-
* then passes `["--
|
|
1462
|
+
* `crust build --target host` first resolves "build" as a subcommand,
|
|
1463
|
+
* then passes `["--target", "host"]` to the build command's parser.
|
|
1379
1464
|
*
|
|
1380
1465
|
* Resolution rules:
|
|
1381
1466
|
* 1. If `argv[0]` matches a subcommand key, recurse into that subcommand
|
|
@@ -1545,7 +1630,6 @@ const DEFAULT_IO = {
|
|
|
1545
1630
|
* without dispatching a Command Action. In-process callers use `Crust.snapshot()`.
|
|
1546
1631
|
*/
|
|
1547
1632
|
const SNAPSHOT_PATH_ENV = "CRUST_INTERNAL_SNAPSHOT_PATH";
|
|
1548
|
-
const BUILD_OUT_DIR_ENV = "CRUST_INTERNAL_BUILD_OUT_DIR";
|
|
1549
1633
|
const EXIT_CODE_CANCELLED = 130;
|
|
1550
1634
|
function isAbortError(error) {
|
|
1551
1635
|
if (!(error instanceof Error)) return false;
|
|
@@ -1611,7 +1695,8 @@ function resolveArgvInput(root, argv) {
|
|
|
1611
1695
|
parsed: parseArgs$1(route.command, route.argv)
|
|
1612
1696
|
};
|
|
1613
1697
|
}
|
|
1614
|
-
|
|
1698
|
+
/** Resolve a typed path, rejecting any element the router cannot consume as a command. */
|
|
1699
|
+
function resolveTypedPath(root, path) {
|
|
1615
1700
|
const route = resolveCommand(root, [...path]);
|
|
1616
1701
|
if (route.argv.length > 0) {
|
|
1617
1702
|
const candidate = route.argv[0];
|
|
@@ -1623,6 +1708,10 @@ function resolveStructuredInput(root, path, input) {
|
|
|
1623
1708
|
parentCommand
|
|
1624
1709
|
});
|
|
1625
1710
|
}
|
|
1711
|
+
return route;
|
|
1712
|
+
}
|
|
1713
|
+
function resolveStructuredInput(root, path, input) {
|
|
1714
|
+
const route = resolveTypedPath(root, path);
|
|
1626
1715
|
return {
|
|
1627
1716
|
argv: path,
|
|
1628
1717
|
route,
|
|
@@ -1805,16 +1894,31 @@ async function executeInvocation(node, options, materializeCommandDefinition) {
|
|
|
1805
1894
|
const buildOutDir = process.env[BUILD_OUT_DIR_ENV];
|
|
1806
1895
|
if (buildOutDir) {
|
|
1807
1896
|
const extensions = [];
|
|
1897
|
+
const owners = /* @__PURE__ */ new Map();
|
|
1808
1898
|
for (const extension of base.extensions) {
|
|
1809
1899
|
if (!extension.build) continue;
|
|
1810
1900
|
try {
|
|
1811
|
-
const
|
|
1812
|
-
|
|
1813
|
-
|
|
1901
|
+
const files = (await extension.build({ snapshot })).map((file) => {
|
|
1902
|
+
const path = normalizeArtifactPath(file.path);
|
|
1903
|
+
const key = path.toLowerCase();
|
|
1904
|
+
for (const [ownedKey, owner] of owners) if (ownedKey === key || ownedKey.startsWith(`${key}/`) || key.startsWith(`${ownedKey}/`)) throw new Error(`Artifact path "${path}" collides with "${owner.path}" produced by Extension "${owner.id}".`);
|
|
1905
|
+
owners.set(key, {
|
|
1906
|
+
id: extension.id,
|
|
1907
|
+
path
|
|
1908
|
+
});
|
|
1909
|
+
return {
|
|
1910
|
+
path,
|
|
1911
|
+
content: file.content
|
|
1912
|
+
};
|
|
1814
1913
|
});
|
|
1914
|
+
for (const file of files) {
|
|
1915
|
+
const target = join(buildOutDir, file.path);
|
|
1916
|
+
await mkdir(dirname(target), { recursive: true });
|
|
1917
|
+
await writeFile(target, file.content);
|
|
1918
|
+
}
|
|
1815
1919
|
extensions.push({
|
|
1816
1920
|
id: extension.id,
|
|
1817
|
-
files:
|
|
1921
|
+
files: files.map((file) => file.path)
|
|
1818
1922
|
});
|
|
1819
1923
|
} catch (error) {
|
|
1820
1924
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1872,4 +1976,4 @@ async function executeInvocation(node, options, materializeCommandDefinition) {
|
|
|
1872
1976
|
return await (hasInjectedIO(options?.io) ? withAmbientTerminalIO(io, invoke) : invoke());
|
|
1873
1977
|
}
|
|
1874
1978
|
//#endregion
|
|
1875
|
-
export {
|
|
1979
|
+
export { normalizeFlag as C, normalizeArg as S, defineExtension as _, runInvocation as a, definingOf as b, sectionsFor as c, resolveArtifactDir as d, cloneCommandNode as f, registerFlag as g, createCommandNode as h, resolveTypedPath as i, visibleSectionsFor as l, validateCommandSections as m, executeInvocation as n, snapshotCommand as o, installExtensionContexts as p, prepareInvocation as r, isListed as s, SNAPSHOT_PATH_ENV as t, BUILD_OUT_DIR_ENV as u, contextSources as v, CrustError as w, validateContextAvailability as x, defineContext as y };
|
package/dist/tooling.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as FlagSnapshot, G as ExtensionId, c as DeclaredDefault, k as CommandSnapshot, o as CommandSection, t as BUILD_OUT_DIR_ENV } from "./artifacts-D8Jlug-a.js";
|
|
2
2
|
//#region src/command/invocation.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Snapshot subprocess protocol used by first-party build tooling.
|
|
@@ -9,7 +9,6 @@ import { D as CommandSnapshot, O as FlagSnapshot, V as ExtensionId, i as Command
|
|
|
9
9
|
* without dispatching a Command Action. In-process callers use `Crust.snapshot()`.
|
|
10
10
|
*/
|
|
11
11
|
export declare const SNAPSHOT_PATH_ENV = "CRUST_INTERNAL_SNAPSHOT_PATH";
|
|
12
|
-
export declare const BUILD_OUT_DIR_ENV = "CRUST_INTERNAL_BUILD_OUT_DIR";
|
|
13
12
|
//#endregion
|
|
14
13
|
//#region src/command/documentation.d.ts
|
|
15
14
|
/** Format a declared default value for command documentation. */
|
|
@@ -157,4 +156,4 @@ export declare function visibleSectionsFor(snapshot: CommandSnapshot, consumer:
|
|
|
157
156
|
readonly sections: readonly CommandSection[];
|
|
158
157
|
}[];
|
|
159
158
|
//#endregion
|
|
160
|
-
export type
|
|
159
|
+
export { BUILD_OUT_DIR_ENV, type CommandDocumentation, type CommandSnapshot, type DocumentationArg, type DocumentationFlag, type UsageSegment };
|
package/dist/tooling.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as sectionsFor, l as visibleSectionsFor,
|
|
1
|
+
import { c as sectionsFor, l as visibleSectionsFor, s as isListed, t as SNAPSHOT_PATH_ENV, u as BUILD_OUT_DIR_ENV } from "./invocation-CIvHShiW.js";
|
|
2
2
|
//#region src/command/documentation.ts
|
|
3
3
|
function isNonFiniteNumber(value) {
|
|
4
4
|
return typeof value === "number" && !Number.isFinite(value);
|