@crustjs/core 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -536,6 +536,12 @@ interface CrustCommandContext<A extends ArgsDef = ArgsDef, F extends FlagsDef =
|
|
|
536
536
|
ctx: ContextBag<Ctx>;
|
|
537
537
|
/** Raw arguments that appeared after the `--` separator */
|
|
538
538
|
rawArgs: string[];
|
|
539
|
+
/**
|
|
540
|
+
* Aborted when the caller cancels the invocation (`execute({ signal })`,
|
|
541
|
+
* `run(path, input, { signal })`, or the first `SIGINT` under `execute()`);
|
|
542
|
+
* see `ExecuteOptions.signal` for how the abort reason maps to the exit code.
|
|
543
|
+
*/
|
|
544
|
+
signal: AbortSignal;
|
|
539
545
|
/** Readonly, serializable snapshot of the resolved command */
|
|
540
546
|
command: CommandSnapshot;
|
|
541
547
|
/** Readonly snapshot of the application root, including Extension contributions */
|
|
@@ -580,18 +586,18 @@ type CompatibleRunValue<Expected, Actual> = Actual extends Expected ? Actual ext
|
|
|
580
586
|
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;
|
|
581
587
|
type CompatibleRunInput<Shape extends CommandShape, Input> = CompatibleRunValue<RunInput<Shape>, Input>;
|
|
582
588
|
type RunInputArguments<Shape extends CommandShape> = {} extends RunInput<Shape> ? readonly [input?: RunInput<Shape>] : readonly [input: RunInput<Shape>];
|
|
583
|
-
type RunArguments<Shape extends CommandShape> = readonly [...RunInputArguments<Shape>,
|
|
589
|
+
type RunArguments<Shape extends CommandShape> = readonly [...RunInputArguments<Shape>, options?: InvocationOptions];
|
|
584
590
|
/**
|
|
585
591
|
* Typed invoker bound to one command in an app, returned by {@link Crust.at}.
|
|
586
592
|
*
|
|
587
|
-
* `run` accepts the same structured input and
|
|
588
|
-
* already applied, so a handle can be re-exported as a plain typed function.
|
|
593
|
+
* `run` accepts the same structured input and options as `Crust.run` with the
|
|
594
|
+
* path already applied, so a handle can be re-exported as a plain typed function.
|
|
589
595
|
*/
|
|
590
596
|
interface CommandHandle<Shape extends CommandShape> {
|
|
591
597
|
/** The typed path this handle was created with (`[]` selects the root). */
|
|
592
598
|
readonly path: readonly string[];
|
|
593
599
|
run(...args: RunArguments<Shape>): Promise<RunOutcome<Shape["result"]>>;
|
|
594
|
-
run<const Input>(input: Input, ...validation: [Input] extends [CompatibleRunInput<Shape, Input>] ? readonly [
|
|
600
|
+
run<const Input>(input: Input, ...validation: [Input] extends [CompatibleRunInput<Shape, Input>] ? readonly [options?: InvocationOptions] : readonly [invalidInput: never]): Promise<RunOutcome<Shape["result"]>>;
|
|
595
601
|
}
|
|
596
602
|
/** Static configuration for a reusable command definition. */
|
|
597
603
|
interface CommandConfig extends Omit<CommandMeta, "name" | "sections" | "version"> {
|
|
@@ -924,8 +930,9 @@ declare class Crust<Flags extends FlagsDef = {}, A extends ArgsDef = [], Ctx ext
|
|
|
924
930
|
*
|
|
925
931
|
* @param path - Typed path to the command to invoke (`[]` selects the root)
|
|
926
932
|
* @param input - Structured argument, flag, and raw values
|
|
927
|
-
* @param
|
|
928
|
-
*
|
|
933
|
+
* @param options - Optional `stdout(text)` / `stderr(text)` callbacks (also
|
|
934
|
+
* exposed to Command Actions and Extensions) and an
|
|
935
|
+
* `AbortSignal` that cancels the invocation
|
|
929
936
|
*/
|
|
930
937
|
run<const Path extends CommandPath<Tree>>(this: {
|
|
931
938
|
readonly _types: {
|
|
@@ -936,7 +943,7 @@ declare class Crust<Flags extends FlagsDef = {}, A extends ArgsDef = [], Ctx ext
|
|
|
936
943
|
readonly _types: {
|
|
937
944
|
readonly caps: "app";
|
|
938
945
|
};
|
|
939
|
-
}, path: Path & KnownCommandPath<Path, Tree>, input: Input, ...validation: [Input] extends [CompatibleRunInput<CommandShapeAt<CommandShape<A, Flags, Tree, Result>, NoInfer<Path>>, Input>] ? readonly [
|
|
946
|
+
}, path: Path & KnownCommandPath<Path, Tree>, input: Input, ...validation: [Input] extends [CompatibleRunInput<CommandShapeAt<CommandShape<A, Flags, Tree, Result>, NoInfer<Path>>, Input>] ? readonly [options?: InvocationOptions] : readonly [invalidInput: never]): Promise<RunOutcome<CommandShapeAt<CommandShape<A, Flags, Tree, Result>, Path>["result"]>>;
|
|
940
947
|
/**
|
|
941
948
|
* Bind a typed command path into a reusable {@link CommandHandle}.
|
|
942
949
|
*
|
|
@@ -962,19 +969,16 @@ declare class Crust<Flags extends FlagsDef = {}, A extends ArgsDef = [], Ctx ext
|
|
|
962
969
|
* Core's default renderer), sets `process.exitCode` (`1`, or
|
|
963
970
|
* `130` for an `AbortError` cancellation), and resolves to the exit code.
|
|
964
971
|
*
|
|
965
|
-
* @param options - Optional overrides
|
|
966
|
-
*
|
|
967
|
-
*
|
|
972
|
+
* @param options - Optional overrides: custom `argv` and captured `io` for
|
|
973
|
+
* in-process testing of exit codes and rendered failures,
|
|
974
|
+
* and a caller `signal` that cancels the invocation
|
|
968
975
|
* @returns The terminal exit code (`0`, `1`, or `130` for cancellation)
|
|
969
976
|
*/
|
|
970
977
|
execute(this: {
|
|
971
978
|
readonly _types: {
|
|
972
979
|
readonly caps: "app";
|
|
973
980
|
};
|
|
974
|
-
}, options?:
|
|
975
|
-
argv?: string[];
|
|
976
|
-
io?: Partial<InvocationIO>;
|
|
977
|
-
}): Promise<number>;
|
|
981
|
+
}, options?: ExecuteOptions): Promise<number>;
|
|
978
982
|
}
|
|
979
983
|
//#endregion
|
|
980
984
|
//#region src/errors.d.ts
|
|
@@ -1195,6 +1199,12 @@ interface ExtensionContext<Defs extends readonly NamedExtensionFlagDef[] = [], D
|
|
|
1195
1199
|
* @example `["--dry-run"]`
|
|
1196
1200
|
*/
|
|
1197
1201
|
readonly rawArgs: readonly string[];
|
|
1202
|
+
/**
|
|
1203
|
+
* Aborted when the caller cancels the invocation (`execute({ signal })`,
|
|
1204
|
+
* `run(path, input, { signal })`, or the first `SIGINT` under `execute()`);
|
|
1205
|
+
* see `ExecuteOptions.signal` for how the abort reason maps to the exit code.
|
|
1206
|
+
*/
|
|
1207
|
+
readonly signal: AbortSignal;
|
|
1198
1208
|
/** Declared Contexts, constructed lazily on first property access. */
|
|
1199
1209
|
readonly ctx: ContextBag<Deps>;
|
|
1200
1210
|
/**
|
|
@@ -1333,11 +1343,17 @@ declare function defineExtension<Args extends readonly unknown[], const Defs ext
|
|
|
1333
1343
|
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>>;
|
|
1334
1344
|
//#endregion
|
|
1335
1345
|
//#region src/parsing/spellings.d.ts
|
|
1346
|
+
/**
|
|
1347
|
+
* One spelling of an effective flag. Entries are immutable and `def` is the
|
|
1348
|
+
* same object as `node.effectiveFlags[canonicalName]`, so node clones may
|
|
1349
|
+
* share entries and only copy the Map container. Writers (`registerFlag`)
|
|
1350
|
+
* always create fresh entries; never mutate one in place.
|
|
1351
|
+
*/
|
|
1336
1352
|
interface FlagSpelling {
|
|
1337
|
-
canonicalName: string;
|
|
1338
|
-
def: FlagDef;
|
|
1339
|
-
kind: "canonical" | "short" | "alias";
|
|
1340
|
-
negatable: boolean;
|
|
1353
|
+
readonly canonicalName: string;
|
|
1354
|
+
readonly def: FlagDef;
|
|
1355
|
+
readonly kind: "canonical" | "short" | "alias";
|
|
1356
|
+
readonly negatable: boolean;
|
|
1341
1357
|
}
|
|
1342
1358
|
//#endregion
|
|
1343
1359
|
//#region src/command/node.d.ts
|
|
@@ -1396,6 +1412,29 @@ interface InvocationIO {
|
|
|
1396
1412
|
/** Write a line of diagnostic output (injectable text callback) */
|
|
1397
1413
|
stderr: (text: string) => void;
|
|
1398
1414
|
}
|
|
1415
|
+
/** Caller options for one programmatic `run()` invocation: IO callbacks plus cancellation. */
|
|
1416
|
+
interface InvocationOptions extends Partial<InvocationIO> {
|
|
1417
|
+
/** Aborting it aborts the invocation's `ctx.signal` with the same reason. */
|
|
1418
|
+
signal?: AbortSignal;
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Caller options for the terminal `execute()` boundary. While an invocation
|
|
1422
|
+
* runs, the first `SIGINT` (Ctrl-C) aborts its `ctx.signal` with an `AbortError`
|
|
1423
|
+
* and a second `SIGINT` terminates unless another listener owns the signal.
|
|
1424
|
+
*/
|
|
1425
|
+
interface ExecuteOptions {
|
|
1426
|
+
/** Arguments to parse; defaults to `process.argv.slice(2)`. */
|
|
1427
|
+
argv?: string[];
|
|
1428
|
+
/** Captured `stdout(text)` / `stderr(text)` callbacks (e.g. in-process tests). */
|
|
1429
|
+
io?: Partial<InvocationIO>;
|
|
1430
|
+
/**
|
|
1431
|
+
* Caller-owned cancellation; aborting it aborts the invocation's `ctx.signal`
|
|
1432
|
+
* with the same reason. If work throws that reason, an `AbortError` exits `130`
|
|
1433
|
+
* and other errors exit `1`. Effect signal interruption instead becomes an
|
|
1434
|
+
* `AbortError`, regardless of the caller's reason.
|
|
1435
|
+
*/
|
|
1436
|
+
signal?: AbortSignal;
|
|
1437
|
+
}
|
|
1399
1438
|
/**
|
|
1400
1439
|
* Supported type literals for args and flags.
|
|
1401
1440
|
*
|
|
@@ -1992,6 +2031,7 @@ type ValidateContextConfig<R extends ContextConfig> = {
|
|
|
1992
2031
|
readonly uses?: R["uses"] extends readonly AnyContextFactory[] ? R["uses"] : "uses" extends keyof R ? {} : never;
|
|
1993
2032
|
};
|
|
1994
2033
|
interface ContextSetupInput<OF extends FlagsDef = FlagsDef> extends InvocationIO {
|
|
2034
|
+
readonly signal: AbortSignal;
|
|
1995
2035
|
readonly flags: InferFlags<OF>;
|
|
1996
2036
|
readonly ctx: ContextBag<ContextMap>;
|
|
1997
2037
|
readonly defer: (cleanup: () => void | PromiseLike<void>) => void;
|
|
@@ -2015,6 +2055,8 @@ type AnyContextInstance = ContextInstance<string, unknown, any, any>;
|
|
|
2015
2055
|
type ContextValue = Awaited<ReturnType<AnyContextInstance["setup"]>>;
|
|
2016
2056
|
interface ContextSetup<Options, OF extends FlagsDef = {}, Deps extends ContextMap = {}> extends InvocationIO {
|
|
2017
2057
|
readonly options: Options;
|
|
2058
|
+
/** The invocation's cancellation signal; pass it to cancellable setup work. */
|
|
2059
|
+
readonly signal: AbortSignal;
|
|
2018
2060
|
readonly flags: InferFlags<OF>;
|
|
2019
2061
|
readonly ctx: ContextBag<Deps>;
|
|
2020
2062
|
/**
|
|
@@ -2056,4 +2098,4 @@ declare function defineContext<Name extends string, Value, Options = void>(name:
|
|
|
2056
2098
|
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>>>;
|
|
2057
2099
|
type FactoryValueOf<F extends AnyContextFactory> = F extends ContextFactory<any, any, infer Value, any, any> ? Awaited<Value> : never;
|
|
2058
2100
|
//#endregion
|
|
2059
|
-
export {
|
|
2101
|
+
export { CrustErrorCode as $, SectionConsumer as A, MergeContext as At, ExtensionContext as B, InvocationOptions as C, ArgSnapshot as Ct, ParsedArgValue as D, LocalFlagNameBrand as Dt, ParseResult as E, LocalFlagBrand as Et, BuildReport as F, Finished as G, ExtensionFlagDef as H, DefineExtensionWith as I, NamedExtensionFlagDef as J, InferExtensionFlags as K, Extension as L, ValueType as M, defineExtensionId as Mt, BuildArtifacts as N, ParsedFlagValue as O, EmptyArgNameBrand as Ot, BuildFile as P, CrustError as Q, ExtensionBuildContext as R, InvocationIO as S, defineCommand as St, NamedFlagDef as T, FlagSnapshot as Tt, ExtensionHooks as U, ExtensionFactory as V, ExtensionSectionContribution as W, defineExtension as X, RootMetaKey as Y, CommandNotFoundErrorDetails as Z, ExecuteOptions as _, RootCommandMeta as _t, ContextInstance as a, ValidationErrorDetails as at, InputArgs as b, RunInputArguments as bt, FactoryValueOf as c, CommandDefinition as ct, ArgDef as d, CommandPath as dt, CrustErrorDetails as et, ArgsDef as f, CommandShape as ft, DeclaredDefault as g, CrustCommandContext as gt, CommandSectionInput as h, Crust as ht, ContextFactory as i, ParseErrorDetails as it, ValidatedInput as j, ExtensionId as jt, SectionAudience as k, LocalValueBrand as kt, contextSources as l, CommandDefinitionBuilder as lt, CommandSection as m, CommandTree as mt, ContextBag as n, CrustErrorJson as nt, ContextMap as o, AnyCrust as ot, CommandMeta as p, CommandShapeAt as pt, InvocationOutcome as q, ContextConfig as r, DefinitionErrorDetails as rt, ContextSetup as s, CommandConfig as st, AnyContextFactory as t, CrustErrorDetailsMap as tt, defineContext as u, CommandHandle as ut, FlagDef as v, RunArguments as vt, MergeFlags as w, CommandSnapshot as wt, InputFlags as x, RunOutcome as xt, FlagsDef as y, RunInput as yt, ExtensionConfig as z };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as CrustErrorCode, A as SectionConsumer, At as MergeContext, B as ExtensionContext, C as InvocationOptions, Ct as ArgSnapshot, D as ParsedArgValue, Dt as LocalFlagNameBrand, E as ParseResult, Et as LocalFlagBrand, F as BuildReport, G as Finished, H as ExtensionFlagDef, I as DefineExtensionWith, J as NamedExtensionFlagDef, K as InferExtensionFlags, L as Extension, M as ValueType, Mt as defineExtensionId, N as BuildArtifacts, O as ParsedFlagValue, Ot as EmptyArgNameBrand, P as BuildFile, Q as CrustError, R as ExtensionBuildContext, S as InvocationIO, St as defineCommand, T as NamedFlagDef, Tt as FlagSnapshot, U as ExtensionHooks, V as ExtensionFactory, W as ExtensionSectionContribution, X as defineExtension, Y as RootMetaKey, Z as CommandNotFoundErrorDetails, _ as ExecuteOptions, _t as RootCommandMeta, a as ContextInstance, at as ValidationErrorDetails, b as InputArgs, bt as RunInputArguments, c as FactoryValueOf, ct as CommandDefinition, d as ArgDef, dt as CommandPath, et as CrustErrorDetails, f as ArgsDef, ft as CommandShape, gt as CrustCommandContext, h as CommandSectionInput, ht as Crust, i as ContextFactory, it as ParseErrorDetails, j as ValidatedInput, jt as ExtensionId, k as SectionAudience, kt as LocalValueBrand, l as contextSources, lt as CommandDefinitionBuilder, m as CommandSection, mt as CommandTree, n as ContextBag, nt as CrustErrorJson, o as ContextMap, ot as AnyCrust, p as CommandMeta, pt as CommandShapeAt, q as InvocationOutcome, r as ContextConfig, rt as DefinitionErrorDetails, s as ContextSetup, st as CommandConfig, t as AnyContextFactory, tt as CrustErrorDetailsMap, u as defineContext, ut as CommandHandle, v as FlagDef, vt as RunArguments, w as MergeFlags, wt as CommandSnapshot, x as InputFlags, xt as RunOutcome, y as FlagsDef, yt as RunInput, z as ExtensionConfig } from "./context-BPR700gY.js";
|
|
2
2
|
import { resolveArtifactDir } from "@crustjs/utils/artifacts";
|
|
3
3
|
//#region src/api/flags.d.ts
|
|
4
4
|
/** Distribute `Omit<_, "name">` over the {@link ArgDef} union. */
|
|
@@ -20,4 +20,4 @@ export declare function defineFlag<const N extends string, const D extends FlagD
|
|
|
20
20
|
/** Define and own one positional argument; layout belongs to its consuming command. */
|
|
21
21
|
export declare function defineArg<const N extends string, const D extends UnnamedArgDef>(name: N & EmptyArgNameBrand<N>, def: D & LocalValueBrand<D>): Named<N, D>;
|
|
22
22
|
//#endregion
|
|
23
|
-
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, Crust, type CrustCommandContext, CrustError, 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, contextSources, defineCommand, defineContext, defineExtension, defineExtensionId, resolveArtifactDir };
|
|
23
|
+
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, Crust, type CrustCommandContext, CrustError, type CrustErrorCode, type CrustErrorDetails, type CrustErrorDetailsMap, type CrustErrorJson, type DefineExtensionWith, type DefinitionErrorDetails, type ExecuteOptions, 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 InvocationOptions, 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, contextSources, defineCommand, defineContext, defineExtension, defineExtensionId, resolveArtifactDir };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as CrustError, S as normalizeFlag, _ as contextSources, a as runInvocation, b as validateContextAvailability, d as cloneFlagRegistry, f as installExtensionContexts, g as defineExtension, h as registerFlag, i as resolveTypedPath, m as createCommandNode, n as executeInvocation, o as snapshotCommand, p as validateCommandSections, r as prepareInvocation, u as cloneCommandNode, v as defineContext, x as normalizeArg, y as definingOf } from "./invocation-BaWf5POY.js";
|
|
2
2
|
import { resolveArtifactDir } from "@crustjs/utils/artifacts";
|
|
3
3
|
//#region src/identity.ts
|
|
4
4
|
/** Mint an Extension identity from any non-blank, trimmed string. */
|
|
@@ -121,18 +121,14 @@ var Crust = class {
|
|
|
121
121
|
_clone(nodeOverrides) {
|
|
122
122
|
const cloned = Object.create(Object.getPrototypeOf(this));
|
|
123
123
|
cloned._node = {
|
|
124
|
-
...
|
|
125
|
-
...this._node,
|
|
126
|
-
subCommands: {}
|
|
127
|
-
}),
|
|
128
|
-
subCommands: { ...this._node.subCommands },
|
|
124
|
+
...this._node,
|
|
129
125
|
...nodeOverrides
|
|
130
126
|
};
|
|
131
127
|
cloned._ancestorOwnedFlags = this._ancestorOwnedFlags;
|
|
132
128
|
return cloned;
|
|
133
129
|
}
|
|
134
130
|
flags(...defs) {
|
|
135
|
-
const cloned = this._clone(
|
|
131
|
+
const cloned = this._clone(cloneFlagRegistry(this._node));
|
|
136
132
|
for (const def of defs) {
|
|
137
133
|
const { name, ...rest } = def;
|
|
138
134
|
registerFlag(cloned._node, name, rest, "local");
|
|
@@ -163,7 +159,10 @@ var Crust = class {
|
|
|
163
159
|
provide(...inputs) {
|
|
164
160
|
const instances = inputs.map(definingOf);
|
|
165
161
|
validateContextAvailability([...this._node.contexts.map(({ instance }) => instance), ...instances], instances);
|
|
166
|
-
const cloned = this._clone({
|
|
162
|
+
const cloned = this._clone({
|
|
163
|
+
...cloneFlagRegistry(this._node),
|
|
164
|
+
contexts: [...this._node.contexts, ...instances.map((instance) => ({ instance }))]
|
|
165
|
+
});
|
|
167
166
|
for (const instance of instances) for (const [name, definition] of Object.entries(instance.ownedFlags)) registerFlag(cloned._node, name, definition, "owned");
|
|
168
167
|
return cloned;
|
|
169
168
|
}
|
|
@@ -220,11 +219,11 @@ var Crust = class {
|
|
|
220
219
|
}
|
|
221
220
|
async run(path, ...args) {
|
|
222
221
|
const structuredInput = args[0] ?? {};
|
|
223
|
-
const
|
|
222
|
+
const options = args[1];
|
|
224
223
|
return await runInvocation(this._node, {
|
|
225
224
|
path,
|
|
226
225
|
input: structuredInput
|
|
227
|
-
},
|
|
226
|
+
}, options, materializeCommandDefinition);
|
|
228
227
|
}
|
|
229
228
|
at(path) {
|
|
230
229
|
resolveTypedPath(prepareInvocation(this._node, materializeCommandDefinition).rootNode, path);
|
|
@@ -234,11 +233,11 @@ var Crust = class {
|
|
|
234
233
|
path: boundPath,
|
|
235
234
|
async run(...args) {
|
|
236
235
|
const structuredInput = args[0] ?? {};
|
|
237
|
-
const
|
|
236
|
+
const options = args[1];
|
|
238
237
|
return await runInvocation(node, {
|
|
239
238
|
path: boundPath,
|
|
240
239
|
input: structuredInput
|
|
241
|
-
},
|
|
240
|
+
}, options, materializeCommandDefinition);
|
|
242
241
|
}
|
|
243
242
|
};
|
|
244
243
|
}
|
|
@@ -234,6 +234,7 @@ var FallbackAsyncDisposableStack = class {
|
|
|
234
234
|
this.#entries.push(onDisposeAsync);
|
|
235
235
|
}
|
|
236
236
|
async [Symbol.asyncDispose]() {
|
|
237
|
+
if (this.#disposed) return;
|
|
237
238
|
this.#disposed = true;
|
|
238
239
|
const errors = [];
|
|
239
240
|
for (let index = this.#entries.length - 1; index >= 0; index--) try {
|
|
@@ -260,11 +261,12 @@ function registerDisposable(value, disposal, registered) {
|
|
|
260
261
|
disposal.use(value);
|
|
261
262
|
}
|
|
262
263
|
/** Internal lazy invocation container. Public consumers receive scoped Context bags. */
|
|
263
|
-
function createContextResolver(contexts, io, disposal) {
|
|
264
|
+
function createContextResolver(contexts, io, disposal, signal) {
|
|
264
265
|
const byName = new Map(contexts.map((context) => [context.name, context]));
|
|
265
266
|
const entries = /* @__PURE__ */ new Map();
|
|
266
267
|
const registered = /* @__PURE__ */ new WeakSet();
|
|
267
268
|
let validatedFlags;
|
|
269
|
+
let constructing = true;
|
|
268
270
|
let disposed = false;
|
|
269
271
|
disposal.defer(() => {
|
|
270
272
|
disposed = true;
|
|
@@ -310,6 +312,11 @@ function createContextResolver(contexts, io, disposal) {
|
|
|
310
312
|
name,
|
|
311
313
|
reason: "missing-context"
|
|
312
314
|
}));
|
|
315
|
+
if (!constructing && !entries.has(name)) return handledRejection(new CrustError("DEFINITION", `Context "${name}" cannot be constructed during invocation cleanup. Read it while setting up the Context whose cleanup needs it.`, {
|
|
316
|
+
subject: "context",
|
|
317
|
+
name,
|
|
318
|
+
reason: "context-during-disposal"
|
|
319
|
+
}));
|
|
313
320
|
if (validatedFlags === void 0 && Object.keys(context.ownedFlags).length > 0) return handledRejection(new CrustError("DEFINITION", `Context "${name}" owns flags and cannot be pulled before flag validation${originSuffix}. Pull it from an action or a postRun hook after a validated invocation.`, {
|
|
314
321
|
subject: "context",
|
|
315
322
|
name,
|
|
@@ -334,6 +341,7 @@ function createContextResolver(contexts, io, disposal) {
|
|
|
334
341
|
const ownedFlags = Object.fromEntries(Object.keys(context.ownedFlags).map((flag) => [flag, validatedFlags?.[flag]]));
|
|
335
342
|
const value = await context.setup({
|
|
336
343
|
...io,
|
|
344
|
+
signal,
|
|
337
345
|
flags: ownedFlags,
|
|
338
346
|
ctx: makeBag(context.uses, current),
|
|
339
347
|
defer(cleanup) {
|
|
@@ -390,7 +398,10 @@ function createContextResolver(contexts, io, disposal) {
|
|
|
390
398
|
async settle() {
|
|
391
399
|
for (;;) {
|
|
392
400
|
const pending = [...entries.values()].filter((entry) => !entry.settled);
|
|
393
|
-
if (pending.length === 0)
|
|
401
|
+
if (pending.length === 0) {
|
|
402
|
+
constructing = false;
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
394
405
|
await Promise.allSettled(pending.map((entry) => entry.promise));
|
|
395
406
|
}
|
|
396
407
|
}
|
|
@@ -496,6 +507,15 @@ function applyExtensionFlags(root, extension) {
|
|
|
496
507
|
injectExtensionFlag(root, name, def, recursive);
|
|
497
508
|
}
|
|
498
509
|
}
|
|
510
|
+
/** Copy the flag registry so `registerFlag` on the copy leaves `node` untouched. */
|
|
511
|
+
function cloneFlagRegistry(node) {
|
|
512
|
+
return {
|
|
513
|
+
localFlags: { ...node.localFlags },
|
|
514
|
+
ownedFlags: { ...node.ownedFlags },
|
|
515
|
+
effectiveFlags: { ...node.effectiveFlags },
|
|
516
|
+
flagSpellings: new Map(node.flagSpellings)
|
|
517
|
+
};
|
|
518
|
+
}
|
|
499
519
|
/** Deep-clone a command subtree without mutating the builder graph. */
|
|
500
520
|
function cloneCommandNode(node) {
|
|
501
521
|
const subCommands = {};
|
|
@@ -503,13 +523,7 @@ function cloneCommandNode(node) {
|
|
|
503
523
|
return {
|
|
504
524
|
...node,
|
|
505
525
|
meta: { ...node.meta },
|
|
506
|
-
|
|
507
|
-
ownedFlags: { ...node.ownedFlags },
|
|
508
|
-
effectiveFlags: { ...node.effectiveFlags },
|
|
509
|
-
flagSpellings: new Map([...node.flagSpellings].map(([spelling, entry]) => [spelling, {
|
|
510
|
-
...entry,
|
|
511
|
-
def: node.effectiveFlags[entry.canonicalName]
|
|
512
|
-
}])),
|
|
526
|
+
...cloneFlagRegistry(node),
|
|
513
527
|
args: [...node.args],
|
|
514
528
|
subCommands,
|
|
515
529
|
contexts: node.contexts.map((context) => ({ ...context })),
|
|
@@ -674,14 +688,11 @@ function coerceJson(raw) {
|
|
|
674
688
|
function buildParseArgsOptionDescriptor(spellings) {
|
|
675
689
|
const options = {};
|
|
676
690
|
for (const [spelling, entry] of spellings) {
|
|
691
|
+
if (entry.kind === "short") continue;
|
|
677
692
|
const descriptor = { type: entry.def.type === "boolean" ? "boolean" : "string" };
|
|
678
693
|
if (entry.def.multiple) descriptor.multiple = true;
|
|
679
|
-
if (entry.kind === "canonical")
|
|
680
|
-
|
|
681
|
-
options[spelling] = descriptor;
|
|
682
|
-
continue;
|
|
683
|
-
}
|
|
684
|
-
if (entry.kind === "alias") options[spelling] = descriptor;
|
|
694
|
+
if (entry.kind === "canonical" && entry.def.short) descriptor.short = entry.def.short;
|
|
695
|
+
options[spelling] = descriptor;
|
|
685
696
|
}
|
|
686
697
|
return options;
|
|
687
698
|
}
|
|
@@ -1405,10 +1416,35 @@ function _usingCtx() {
|
|
|
1405
1416
|
}
|
|
1406
1417
|
//#endregion
|
|
1407
1418
|
//#region src/command/invocation.ts
|
|
1408
|
-
|
|
1419
|
+
const ignoreStreamError = () => {};
|
|
1420
|
+
/**
|
|
1421
|
+
* Line-oriented write to a process stream, with `console.log`'s error tolerance: a
|
|
1422
|
+
* downstream that closed early (`cli | head`) surfaces as EPIPE, synchronously or as a
|
|
1423
|
+
* later `error` event, and must not crash the CLI. Mirrors Node's Console
|
|
1424
|
+
* (`ignoreErrors: true`): a noop listener absorbs the event when nobody else listens.
|
|
1425
|
+
*/
|
|
1426
|
+
function writeLine(stream, text) {
|
|
1427
|
+
const absorb = () => {
|
|
1428
|
+
if (stream.listenerCount("error") === 0) stream.once("error", ignoreStreamError);
|
|
1429
|
+
};
|
|
1430
|
+
try {
|
|
1431
|
+
absorb();
|
|
1432
|
+
stream.write(`${text}\n`, (error) => {
|
|
1433
|
+
if (error) absorb();
|
|
1434
|
+
});
|
|
1435
|
+
} catch {} finally {
|
|
1436
|
+
stream.removeListener("error", ignoreStreamError);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
/**
|
|
1440
|
+
* Terminal defaults: stream writes rather than `console.log`. Once anything materializes
|
|
1441
|
+
* `process.stdout` (a platform layer, a color library probing `isTTY`), Bun's native
|
|
1442
|
+
* console writer silently drops output past the 64 KiB pipe buffer at exit
|
|
1443
|
+
* (oven-sh/bun#36419); the stream writer flushes it.
|
|
1444
|
+
*/
|
|
1409
1445
|
const DEFAULT_IO = {
|
|
1410
|
-
stdout: (text) =>
|
|
1411
|
-
stderr: (text) =>
|
|
1446
|
+
stdout: (text) => writeLine(process.stdout, text),
|
|
1447
|
+
stderr: (text) => writeLine(process.stderr, text)
|
|
1412
1448
|
};
|
|
1413
1449
|
/**
|
|
1414
1450
|
* Snapshot subprocess protocol used by first-party build tooling.
|
|
@@ -1417,6 +1453,9 @@ const DEFAULT_IO = {
|
|
|
1417
1453
|
* validates its documentation sections, optionally runs Extension build hooks when the
|
|
1418
1454
|
* build output directory is set, writes its final JSON snapshot and Build Report, and exits
|
|
1419
1455
|
* without dispatching a Command Action. In-process callers use `Crust.snapshot()`.
|
|
1456
|
+
*
|
|
1457
|
+
* Only source entries run by `crust build` honor it: finished Bun/Node bundles carry
|
|
1458
|
+
* `process.env.CRUST_INTERNAL_BUILD === "1"` as a literal and compile the protocol out.
|
|
1420
1459
|
*/
|
|
1421
1460
|
const SNAPSHOT_PATH_ENV = "CRUST_INTERNAL_SNAPSHOT_PATH";
|
|
1422
1461
|
const EXIT_CODE_CANCELLED = 130;
|
|
@@ -1424,6 +1463,72 @@ function isAbortError(error) {
|
|
|
1424
1463
|
if (!(error instanceof Error)) return false;
|
|
1425
1464
|
return error.name === "AbortError";
|
|
1426
1465
|
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Structural: Node 22 bundles down-level `await using` to a plain `Error`
|
|
1468
|
+
* carrying `error`/`suppressed`, so `instanceof SuppressedError` cannot be used.
|
|
1469
|
+
*/
|
|
1470
|
+
function isSuppressedError(error) {
|
|
1471
|
+
return isObjectLike(error) && "error" in error && "suppressed" in error && error.name === "SuppressedError";
|
|
1472
|
+
}
|
|
1473
|
+
function isObjectLike(value) {
|
|
1474
|
+
return typeof value === "object" && value !== null;
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* One `label: message` line per underlying failure. `SuppressedError` chains and
|
|
1478
|
+
* `AggregateError`s contribute their members, not their own boilerplate message,
|
|
1479
|
+
* so a disposal failure never renders as a bare `Error: `.
|
|
1480
|
+
*/
|
|
1481
|
+
function describeFailure(error, label = "Error") {
|
|
1482
|
+
const messages = [];
|
|
1483
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1484
|
+
let remaining = 256;
|
|
1485
|
+
let truncated = false;
|
|
1486
|
+
const visitMember = (read) => {
|
|
1487
|
+
if (truncated) return;
|
|
1488
|
+
if (remaining === 0) {
|
|
1489
|
+
truncated = true;
|
|
1490
|
+
messages.push("[additional failures omitted]");
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
remaining--;
|
|
1494
|
+
let member;
|
|
1495
|
+
try {
|
|
1496
|
+
member = read();
|
|
1497
|
+
} catch {
|
|
1498
|
+
messages.push("[unreadable failure]");
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
visit(member);
|
|
1502
|
+
};
|
|
1503
|
+
const visit = (value) => {
|
|
1504
|
+
try {
|
|
1505
|
+
if (isObjectLike(value)) {
|
|
1506
|
+
if (seen.has(value)) return;
|
|
1507
|
+
seen.add(value);
|
|
1508
|
+
}
|
|
1509
|
+
const before = messages.length;
|
|
1510
|
+
if (isSuppressedError(value)) {
|
|
1511
|
+
visitMember(() => value.error);
|
|
1512
|
+
visitMember(() => value.suppressed);
|
|
1513
|
+
if (messages.length > before) return;
|
|
1514
|
+
} else if (value instanceof AggregateError) {
|
|
1515
|
+
const members = value.errors;
|
|
1516
|
+
if (Array.isArray(members)) {
|
|
1517
|
+
for (let index = 0; index < members.length; index++) {
|
|
1518
|
+
visitMember(() => members[index]);
|
|
1519
|
+
if (truncated) break;
|
|
1520
|
+
}
|
|
1521
|
+
if (messages.length > before) return;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
messages.push(String(value instanceof Error && value.message || value));
|
|
1525
|
+
} catch {
|
|
1526
|
+
messages.push("[unreadable failure]");
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
visitMember(() => error);
|
|
1530
|
+
return messages.map((message) => `${label}: ${message}`).join("\n");
|
|
1531
|
+
}
|
|
1427
1532
|
function freezeTree(node) {
|
|
1428
1533
|
Object.freeze(node);
|
|
1429
1534
|
Object.freeze(node.localFlags);
|
|
@@ -1462,8 +1567,10 @@ function buildExtensionTree(node, materializeCommandDefinition) {
|
|
|
1462
1567
|
}
|
|
1463
1568
|
/** Evaluate Extension section callbacks against current state and freeze the tree. */
|
|
1464
1569
|
function applySectionsAndFreeze(rootNode, extensions) {
|
|
1465
|
-
|
|
1466
|
-
|
|
1570
|
+
if (extensions.some((extension) => extension.sections !== void 0)) {
|
|
1571
|
+
const authoredSnapshot = snapshotCommand(rootNode);
|
|
1572
|
+
for (const extension of extensions) applyExtensionSections(rootNode, extension, authoredSnapshot);
|
|
1573
|
+
}
|
|
1467
1574
|
freezeTree(rootNode);
|
|
1468
1575
|
return rootNode;
|
|
1469
1576
|
}
|
|
@@ -1508,7 +1615,7 @@ function resolveStructuredInput(root, path, input) {
|
|
|
1508
1615
|
};
|
|
1509
1616
|
}
|
|
1510
1617
|
/** Resolve, parse, and run one invocation without rendering failures. */
|
|
1511
|
-
async function dispatch(input, prepared, io, onExtensionContext, onFailure) {
|
|
1618
|
+
async function dispatch(input, prepared, io, signal, onExtensionContext, onFailure) {
|
|
1512
1619
|
try {
|
|
1513
1620
|
var _usingCtx$1 = _usingCtx();
|
|
1514
1621
|
const { rootNode, extensions } = prepared;
|
|
@@ -1516,16 +1623,19 @@ async function dispatch(input, prepared, io, onExtensionContext, onFailure) {
|
|
|
1516
1623
|
const resolvedNode = route.command;
|
|
1517
1624
|
const disposal = _usingCtx$1.a(new DisposalStack());
|
|
1518
1625
|
const contexts = resolvedNode.contexts.map(({ instance }) => instance);
|
|
1519
|
-
const resolver = createContextResolver(contexts, io, disposal);
|
|
1626
|
+
const resolver = createContextResolver(contexts, io, disposal, signal);
|
|
1520
1627
|
const rootSnapshot = snapshotCommand(rootNode);
|
|
1628
|
+
let commandSnapshot = rootSnapshot;
|
|
1629
|
+
for (const name of route.commandPath.slice(1)) commandSnapshot = commandSnapshot.subCommands[name];
|
|
1521
1630
|
const extensionContext = Object.freeze({
|
|
1522
1631
|
argv: [...argv],
|
|
1523
1632
|
rootCommand: rootSnapshot,
|
|
1524
|
-
command:
|
|
1633
|
+
command: commandSnapshot,
|
|
1525
1634
|
commandPath: Object.freeze([...route.commandPath]),
|
|
1526
1635
|
args: parsed.args,
|
|
1527
1636
|
flags: parsed.flags,
|
|
1528
1637
|
rawArgs: parsed.rawArgs,
|
|
1638
|
+
signal,
|
|
1529
1639
|
ctx: resolver.bag(extensions.flatMap((extension) => extension.uses ?? [])),
|
|
1530
1640
|
finish: finishInvocation,
|
|
1531
1641
|
stdout: io.stdout,
|
|
@@ -1542,6 +1652,7 @@ async function dispatch(input, prepared, io, onExtensionContext, onFailure) {
|
|
|
1542
1652
|
flags: validated.flags,
|
|
1543
1653
|
ctx: resolver.bag(contexts),
|
|
1544
1654
|
rawArgs: parsed.rawArgs,
|
|
1655
|
+
signal,
|
|
1545
1656
|
command: extensionContext.command,
|
|
1546
1657
|
rootCommand: rootSnapshot,
|
|
1547
1658
|
stdout: io.stdout,
|
|
@@ -1599,11 +1710,10 @@ async function dispatch(input, prepared, io, onExtensionContext, onFailure) {
|
|
|
1599
1710
|
}
|
|
1600
1711
|
}
|
|
1601
1712
|
/** Render one failure through Extension onError hooks, ending in Core's default renderer. */
|
|
1602
|
-
async function renderFailure(error, argv, prepared, io, extensionContext, silentDefault = false) {
|
|
1713
|
+
async function renderFailure(error, argv, prepared, io, signal, extensionContext, silentDefault = false) {
|
|
1603
1714
|
const renderDefault = () => {
|
|
1604
1715
|
if (silentDefault) return;
|
|
1605
|
-
|
|
1606
|
-
io.stderr(`Error: ${message}`);
|
|
1716
|
+
io.stderr(describeFailure(error));
|
|
1607
1717
|
};
|
|
1608
1718
|
function unavailable(property) {
|
|
1609
1719
|
return Promise.reject(new CrustError("DEFINITION", `Context "${String(property)}" cannot be pulled from onError because invocation Contexts have already been disposed.`, {
|
|
@@ -1613,19 +1723,24 @@ async function renderFailure(error, argv, prepared, io, extensionContext, silent
|
|
|
1613
1723
|
}));
|
|
1614
1724
|
}
|
|
1615
1725
|
const unavailableContext = new Proxy({}, { get: (_, property) => property === "then" || isSymbol(property) ? void 0 : unavailable(property) });
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1726
|
+
let context = extensionContext;
|
|
1727
|
+
if (!context) {
|
|
1728
|
+
const rootSnapshot = snapshotCommand(prepared.rootNode);
|
|
1729
|
+
context = Object.freeze({
|
|
1730
|
+
argv: [...argv],
|
|
1731
|
+
rootCommand: rootSnapshot,
|
|
1732
|
+
command: rootSnapshot,
|
|
1733
|
+
commandPath: Object.freeze([prepared.rootNode.meta.name]),
|
|
1734
|
+
args: Object.freeze({}),
|
|
1735
|
+
flags: Object.freeze({}),
|
|
1736
|
+
rawArgs: [],
|
|
1737
|
+
signal,
|
|
1738
|
+
finish: finishInvocation,
|
|
1739
|
+
stdout: io.stdout,
|
|
1740
|
+
stderr: io.stderr,
|
|
1741
|
+
ctx: unavailableContext
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1629
1744
|
try {
|
|
1630
1745
|
for (const extension of prepared.extensions) if (await extension.hooks?.onError?.(error, context)) return extension.id;
|
|
1631
1746
|
} catch {}
|
|
@@ -1636,11 +1751,11 @@ function hasInjectedIO(io) {
|
|
|
1636
1751
|
return io !== void 0 && Object.keys(io).length > 0;
|
|
1637
1752
|
}
|
|
1638
1753
|
/** Quiet programmatic boundary: capture output and the failure escaping the complete lifecycle. */
|
|
1639
|
-
async function runInvocation(node, input,
|
|
1754
|
+
async function runInvocation(node, input, options, materializeCommandDefinition) {
|
|
1640
1755
|
const stdout = [];
|
|
1641
1756
|
const stderr = [];
|
|
1642
1757
|
try {
|
|
1643
|
-
const
|
|
1758
|
+
const { signal = new AbortController().signal, ...sinks } = options ?? {};
|
|
1644
1759
|
const resolvedIO = {
|
|
1645
1760
|
stdout(text) {
|
|
1646
1761
|
stdout.push(text);
|
|
@@ -1653,7 +1768,7 @@ async function runInvocation(node, input, io, materializeCommandDefinition) {
|
|
|
1653
1768
|
};
|
|
1654
1769
|
return {
|
|
1655
1770
|
...await withAmbientTerminalIO(resolvedIO, async () => {
|
|
1656
|
-
return await dispatch(input, prepareInvocation(node, materializeCommandDefinition), resolvedIO);
|
|
1771
|
+
return await dispatch(input, prepareInvocation(node, materializeCommandDefinition), resolvedIO, signal);
|
|
1657
1772
|
}),
|
|
1658
1773
|
stdout: stdout.join("\n"),
|
|
1659
1774
|
stderr: stderr.join("\n")
|
|
@@ -1674,7 +1789,9 @@ async function executeInvocation(node, options, materializeCommandDefinition) {
|
|
|
1674
1789
|
...DEFAULT_IO,
|
|
1675
1790
|
...options?.io
|
|
1676
1791
|
};
|
|
1677
|
-
const
|
|
1792
|
+
const controller = new AbortController();
|
|
1793
|
+
const signal = options?.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
|
|
1794
|
+
const snapshotPath = process.env.CRUST_INTERNAL_BUILD === "1" ? void 0 : process.env[SNAPSHOT_PATH_ENV];
|
|
1678
1795
|
if (snapshotPath) {
|
|
1679
1796
|
try {
|
|
1680
1797
|
const base = buildExtensionTree(node, materializeCommandDefinition);
|
|
@@ -1734,35 +1851,52 @@ async function executeInvocation(node, options, materializeCommandDefinition) {
|
|
|
1734
1851
|
process.exitCode = EXIT_CODE_CANCELLED;
|
|
1735
1852
|
return EXIT_CODE_CANCELLED;
|
|
1736
1853
|
}
|
|
1737
|
-
|
|
1738
|
-
io.stderr(`Error: ${message}`);
|
|
1854
|
+
io.stderr(describeFailure(error));
|
|
1739
1855
|
process.exitCode = 1;
|
|
1740
1856
|
return 1;
|
|
1741
1857
|
}
|
|
1858
|
+
const onSigint = () => {
|
|
1859
|
+
if (!controller.signal.aborted) {
|
|
1860
|
+
controller.abort(new DOMException("Interrupted by SIGINT.", "AbortError"));
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
process.removeListener("SIGINT", onSigint);
|
|
1864
|
+
if (process.listenerCount("SIGINT") === 0) process.kill(process.pid, "SIGINT");
|
|
1865
|
+
};
|
|
1866
|
+
process.on("SIGINT", onSigint);
|
|
1742
1867
|
let extensionContext;
|
|
1743
1868
|
let renderedInDispatch = false;
|
|
1869
|
+
let renderedError;
|
|
1744
1870
|
try {
|
|
1745
|
-
await dispatch({ argv }, prepared, io, (context) => {
|
|
1871
|
+
await dispatch({ argv }, prepared, io, signal, (context) => {
|
|
1746
1872
|
extensionContext = context;
|
|
1747
1873
|
}, async (error, context) => {
|
|
1748
1874
|
renderedInDispatch = true;
|
|
1875
|
+
renderedError = error;
|
|
1749
1876
|
const cancelled = isAbortError(error);
|
|
1750
1877
|
process.exitCode = cancelled ? EXIT_CODE_CANCELLED : 1;
|
|
1751
|
-
return renderFailure(error, argv, prepared, io, context, cancelled);
|
|
1878
|
+
return renderFailure(error, argv, prepared, io, signal, context, cancelled);
|
|
1752
1879
|
});
|
|
1753
1880
|
} catch (error) {
|
|
1881
|
+
let cleanupFailure;
|
|
1882
|
+
try {
|
|
1883
|
+
if (renderedInDispatch && !Object.is(error, renderedError) && isSuppressedError(error) && Object.is(error.suppressed, renderedError)) cleanupFailure = { error: error.error };
|
|
1884
|
+
} catch {}
|
|
1885
|
+
if (cleanupFailure) io.stderr(describeFailure(cleanupFailure.error, "Cleanup failed"));
|
|
1754
1886
|
if (isAbortError(error)) {
|
|
1755
|
-
if (!renderedInDispatch) await renderFailure(error, argv, prepared, io, extensionContext, true);
|
|
1887
|
+
if (!renderedInDispatch) await renderFailure(error, argv, prepared, io, signal, extensionContext, true);
|
|
1756
1888
|
process.exitCode = EXIT_CODE_CANCELLED;
|
|
1757
1889
|
return EXIT_CODE_CANCELLED;
|
|
1758
1890
|
}
|
|
1759
1891
|
process.exitCode = 1;
|
|
1760
|
-
if (!renderedInDispatch) await renderFailure(error, argv, prepared, io, extensionContext);
|
|
1892
|
+
if (!renderedInDispatch) await renderFailure(error, argv, prepared, io, signal, extensionContext);
|
|
1761
1893
|
return 1;
|
|
1894
|
+
} finally {
|
|
1895
|
+
process.removeListener("SIGINT", onSigint);
|
|
1762
1896
|
}
|
|
1763
1897
|
return 0;
|
|
1764
1898
|
};
|
|
1765
1899
|
return await (hasInjectedIO(options?.io) ? withAmbientTerminalIO(io, invoke) : invoke());
|
|
1766
1900
|
}
|
|
1767
1901
|
//#endregion
|
|
1768
|
-
export { CrustError as S,
|
|
1902
|
+
export { CrustError as C, normalizeFlag as S, contextSources as _, runInvocation as a, validateContextAvailability as b, sectionsFor as c, cloneFlagRegistry as d, installExtensionContexts as f, defineExtension as g, registerFlag as h, resolveTypedPath as i, visibleSectionsFor as l, createCommandNode as m, executeInvocation as n, snapshotCommand as o, validateCommandSections as p, prepareInvocation as r, isListed as s, SNAPSHOT_PATH_ENV as t, cloneCommandNode as u, defineContext as v, normalizeArg as x, definingOf as y };
|
package/dist/tooling.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Tt as FlagSnapshot, g as DeclaredDefault, jt as ExtensionId, m as CommandSection, wt as CommandSnapshot } from "./context-BPR700gY.js";
|
|
2
2
|
import { BUILD_OUT_DIR_ENV } from "@crustjs/utils/artifacts";
|
|
3
3
|
//#region src/command/invocation.d.ts
|
|
4
4
|
/**
|
|
@@ -8,6 +8,9 @@ import { BUILD_OUT_DIR_ENV } from "@crustjs/utils/artifacts";
|
|
|
8
8
|
* validates its documentation sections, optionally runs Extension build hooks when the
|
|
9
9
|
* build output directory is set, writes its final JSON snapshot and Build Report, and exits
|
|
10
10
|
* without dispatching a Command Action. In-process callers use `Crust.snapshot()`.
|
|
11
|
+
*
|
|
12
|
+
* Only source entries run by `crust build` honor it: finished Bun/Node bundles carry
|
|
13
|
+
* `process.env.CRUST_INTERNAL_BUILD === "1"` as a literal and compile the protocol out.
|
|
11
14
|
*/
|
|
12
15
|
export declare const SNAPSHOT_PATH_ENV = "CRUST_INTERNAL_SNAPSHOT_PATH";
|
|
13
16
|
//#endregion
|
package/dist/tooling.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as sectionsFor, l as visibleSectionsFor, s as isListed, t as SNAPSHOT_PATH_ENV } from "./invocation-
|
|
1
|
+
import { c as sectionsFor, l as visibleSectionsFor, s as isListed, t as SNAPSHOT_PATH_ENV } from "./invocation-BaWf5POY.js";
|
|
2
2
|
import { BUILD_OUT_DIR_ENV } from "@crustjs/utils/artifacts";
|
|
3
3
|
//#region src/command/documentation.ts
|
|
4
4
|
function isNonFiniteNumber(value) {
|