@crustjs/core 0.4.1 → 0.5.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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, posix, resolve, win32 } from "node:path";
|
|
3
|
-
import { BUILD_OUT_DIR_ENV } from "@crustjs/utils/artifacts";
|
|
3
|
+
import { BUILD_OUT_DIR_ENV, isPackagedBuild } from "@crustjs/utils/artifacts";
|
|
4
4
|
import { withAmbientTerminalIO } from "@crustjs/utils/terminal";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
6
6
|
import { isPromise } from "node:util/types";
|
|
@@ -170,6 +170,74 @@ function normalizeArg(def) {
|
|
|
170
170
|
return ownDefinition(def);
|
|
171
171
|
}
|
|
172
172
|
//#endregion
|
|
173
|
+
//#region src/sections.ts
|
|
174
|
+
/** Whether a command belongs in user-facing listings. */
|
|
175
|
+
function isListed(command) {
|
|
176
|
+
return command.meta.hidden !== true;
|
|
177
|
+
}
|
|
178
|
+
/** Select and merge sections visible to the given consumer. */
|
|
179
|
+
function sectionsFor(sections, consumer) {
|
|
180
|
+
const visible = (sections ?? []).filter((section) => {
|
|
181
|
+
if (section.only) return section.only.includes(consumer);
|
|
182
|
+
if (section.except) return !section.except.includes(consumer);
|
|
183
|
+
return true;
|
|
184
|
+
});
|
|
185
|
+
const merged = /* @__PURE__ */ new Map();
|
|
186
|
+
for (const section of visible) {
|
|
187
|
+
const existing = merged.get(section.title);
|
|
188
|
+
merged.set(section.title, existing ? {
|
|
189
|
+
title: section.title,
|
|
190
|
+
body: `${existing.body}\n${section.body}`
|
|
191
|
+
} : section);
|
|
192
|
+
}
|
|
193
|
+
return [...merged.values()];
|
|
194
|
+
}
|
|
195
|
+
/** Collect section-bearing visible commands in canonical path order. The root path is `[]`. */
|
|
196
|
+
function visibleSectionsFor(snapshot, consumer) {
|
|
197
|
+
const groups = [];
|
|
198
|
+
function visit(command, path) {
|
|
199
|
+
const sections = sectionsFor(command.meta.sections, consumer);
|
|
200
|
+
if (sections.length > 0) groups.push({
|
|
201
|
+
path,
|
|
202
|
+
sections
|
|
203
|
+
});
|
|
204
|
+
for (const [name, child] of Object.entries(command.subCommands).sort(([a], [b]) => a.localeCompare(b))) if (isListed(child)) visit(child, [...path, name]);
|
|
205
|
+
}
|
|
206
|
+
visit(snapshot, []);
|
|
207
|
+
return groups;
|
|
208
|
+
}
|
|
209
|
+
const sectionOwnerLabels = {
|
|
210
|
+
command: "Command",
|
|
211
|
+
context: "Context",
|
|
212
|
+
extension: "Extension"
|
|
213
|
+
};
|
|
214
|
+
function invalidSections({ subject, name }) {
|
|
215
|
+
const label = sectionOwnerLabels[subject];
|
|
216
|
+
return new CrustError("DEFINITION", `${label} "${name}" contains invalid documentation sections`, {
|
|
217
|
+
subject,
|
|
218
|
+
name,
|
|
219
|
+
reason: "invalid-sections"
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
function normalizeSection(section, owner) {
|
|
223
|
+
const { title, body, only, except } = section;
|
|
224
|
+
if (!title.trim() || /[\r\n]/.test(title) || !body.trim() || only?.length === 0 || except?.length === 0 || only !== void 0 && except !== void 0) throw invalidSections(owner);
|
|
225
|
+
const audience = (ids) => {
|
|
226
|
+
return Object.freeze(ids.map((consumer) => typeof consumer === "string" ? consumer : consumer.id));
|
|
227
|
+
};
|
|
228
|
+
return Object.freeze({
|
|
229
|
+
title,
|
|
230
|
+
body,
|
|
231
|
+
...only ? { only: audience(only) } : except ? { except: audience(except) } : {}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
function validateCommandSections(name, sections, subject = "command") {
|
|
235
|
+
return sections.map((section) => normalizeSection(section, {
|
|
236
|
+
subject,
|
|
237
|
+
name
|
|
238
|
+
}));
|
|
239
|
+
}
|
|
240
|
+
//#endregion
|
|
173
241
|
//#region src/api/context.ts
|
|
174
242
|
const defining = Symbol("crust.defining");
|
|
175
243
|
/** @internal */
|
|
@@ -178,7 +246,11 @@ function definingOf(value) {
|
|
|
178
246
|
}
|
|
179
247
|
/** @internal */
|
|
180
248
|
function seal(value) {
|
|
181
|
-
return
|
|
249
|
+
return sealHandle(value, value);
|
|
250
|
+
}
|
|
251
|
+
/** @internal Freeze a public handle whose defining data is a separate record. */
|
|
252
|
+
function sealHandle(handle, data) {
|
|
253
|
+
return Object.freeze(Object.assign(handle, { [defining]: data }));
|
|
182
254
|
}
|
|
183
255
|
/**
|
|
184
256
|
* Adapter hook: the Context sources a bag was built from, exposed as a
|
|
@@ -198,87 +270,42 @@ function validateContextAvailability(contexts, sources) {
|
|
|
198
270
|
name,
|
|
199
271
|
reason: "missing-context"
|
|
200
272
|
});
|
|
201
|
-
for (const dependency of source.
|
|
273
|
+
for (const dependency of source.use) visit(dependency);
|
|
202
274
|
};
|
|
203
275
|
for (const source of sources) visit(source);
|
|
204
276
|
}
|
|
205
|
-
function
|
|
206
|
-
return
|
|
277
|
+
function createContextBuilder(name, use, ownedFlags, sections) {
|
|
278
|
+
return Object.freeze({
|
|
279
|
+
use: (...factories) => createContextBuilder(name, Object.freeze([...use, ...factories.map(definingOf)]), ownedFlags, sections),
|
|
280
|
+
flags: (...defs) => createContextBuilder(name, use, Object.freeze(toFlagsRecord(defs, ownedFlags)), sections),
|
|
281
|
+
sections: (...inputs) => createContextBuilder(name, use, ownedFlags, Object.freeze([...sections, ...validateCommandSections(name, inputs, "context")])),
|
|
282
|
+
setup: (setup) => createContextFactory(name, use, ownedFlags, sections, setup)
|
|
283
|
+
});
|
|
207
284
|
}
|
|
208
|
-
function
|
|
209
|
-
const
|
|
210
|
-
const config = hasConfig ? configOrSetup : {};
|
|
211
|
-
const setup = hasConfig ? maybeSetup : configOrSetup;
|
|
212
|
-
const ownedFlags = Object.freeze(toFlagsRecord(config.flags ?? []));
|
|
213
|
-
const uses = Object.freeze((config.uses ?? []).map(definingOf));
|
|
214
|
-
const instance = (instanceUses, run) => {
|
|
285
|
+
function createContextFactory(name, use, ownedFlags, sections, setup) {
|
|
286
|
+
const instance = (instanceUse, run) => {
|
|
215
287
|
return seal({
|
|
216
288
|
name,
|
|
217
289
|
ownedFlags,
|
|
218
|
-
|
|
290
|
+
sections,
|
|
291
|
+
use: instanceUse,
|
|
219
292
|
factory: sealed,
|
|
220
293
|
setup: run
|
|
221
294
|
});
|
|
222
295
|
};
|
|
223
|
-
const factory = (options) => instance(
|
|
224
|
-
return setup({
|
|
225
|
-
options,
|
|
226
|
-
...input
|
|
227
|
-
});
|
|
228
|
-
});
|
|
296
|
+
const factory = (options) => instance(use, (input) => setup(input, options));
|
|
229
297
|
factory.contextName = name;
|
|
230
|
-
factory.
|
|
298
|
+
factory.use = use;
|
|
231
299
|
factory.of = (value) => instance(Object.freeze([]), () => value);
|
|
232
300
|
const sealed = seal(factory);
|
|
233
301
|
return sealed;
|
|
234
302
|
}
|
|
235
|
-
function isDisposeCallback(value) {
|
|
236
|
-
return typeof value === "function";
|
|
237
|
-
}
|
|
238
303
|
/**
|
|
239
|
-
*
|
|
240
|
-
* (
|
|
241
|
-
* preferring `Symbol.asyncDispose` over `Symbol.dispose`.
|
|
242
|
-
*
|
|
243
|
-
* ponytail: multiple disposal errors aggregate as `AggregateError` instead of
|
|
244
|
-
* the native `SuppressedError` chain; delete this class when Node 22 leaves
|
|
245
|
-
* the support floor.
|
|
246
|
-
*
|
|
247
|
-
* @internal Exported for unit testing and invocation wiring.
|
|
304
|
+
* Start an immutable fluent Context definition: a named, lazy command
|
|
305
|
+
* dependency. Chain `.use()`, `.flags()`, and `.sections()`, then `.setup()` for the factory.
|
|
248
306
|
*/
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
#disposed = false;
|
|
252
|
-
#assertPending() {
|
|
253
|
-
if (this.#disposed) throw new ReferenceError("AsyncDisposableStack is already disposed");
|
|
254
|
-
}
|
|
255
|
-
use(value) {
|
|
256
|
-
this.#assertPending();
|
|
257
|
-
const dispose = hasAsyncDispose(value) ? value[Symbol.asyncDispose] : value[Symbol.dispose];
|
|
258
|
-
this.#entries.push(() => dispose.call(value));
|
|
259
|
-
return value;
|
|
260
|
-
}
|
|
261
|
-
defer(onDisposeAsync) {
|
|
262
|
-
this.#assertPending();
|
|
263
|
-
if (!isDisposeCallback(onDisposeAsync)) throw new TypeError("defer callback is not callable");
|
|
264
|
-
this.#entries.push(onDisposeAsync);
|
|
265
|
-
}
|
|
266
|
-
async [Symbol.asyncDispose]() {
|
|
267
|
-
if (this.#disposed) return;
|
|
268
|
-
this.#disposed = true;
|
|
269
|
-
const errors = [];
|
|
270
|
-
for (let index = this.#entries.length - 1; index >= 0; index--) try {
|
|
271
|
-
await this.#entries[index]();
|
|
272
|
-
} catch (error) {
|
|
273
|
-
errors.push(error);
|
|
274
|
-
}
|
|
275
|
-
if (errors.length === 1) throw errors[0];
|
|
276
|
-
if (errors.length > 1) throw new AggregateError(errors, "Disposal failed");
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
const DisposalStack = globalThis.AsyncDisposableStack ?? FallbackAsyncDisposableStack;
|
|
280
|
-
function hasAsyncDispose(value) {
|
|
281
|
-
return typeof value[Symbol.asyncDispose] === "function";
|
|
307
|
+
function defineContext(name) {
|
|
308
|
+
return createContextBuilder(name, Object.freeze([]), Object.freeze({}), Object.freeze([]));
|
|
282
309
|
}
|
|
283
310
|
function isDisposableValue(value) {
|
|
284
311
|
if (value === null || typeof value !== "object" && typeof value !== "function") return false;
|
|
@@ -373,7 +400,7 @@ function createContextResolver(contexts, io, disposal, signal) {
|
|
|
373
400
|
...io,
|
|
374
401
|
signal,
|
|
375
402
|
flags: ownedFlags,
|
|
376
|
-
ctx: makeBag(context.
|
|
403
|
+
ctx: makeBag(context.use, current),
|
|
377
404
|
defer(cleanup) {
|
|
378
405
|
if (current.settled) throw new CrustError("DEFINITION", `Context "${name}" cannot register cleanup after its setup has finished.`, {
|
|
379
406
|
subject: "context",
|
|
@@ -414,7 +441,7 @@ function createContextResolver(contexts, io, disposal, signal) {
|
|
|
414
441
|
enumerable: true,
|
|
415
442
|
get: () => makePull(origin)(name)
|
|
416
443
|
});
|
|
417
|
-
for (const dependency of source.
|
|
444
|
+
for (const dependency of source.use ?? []) add(dependency);
|
|
418
445
|
};
|
|
419
446
|
for (const source of sources) add(source);
|
|
420
447
|
Object.defineProperty(bag, contextSources, { value: Object.freeze([...sources]) });
|
|
@@ -444,24 +471,75 @@ const FINISHED = Object.freeze({ [Symbol("crust.finished")]: true });
|
|
|
444
471
|
function finishInvocation() {
|
|
445
472
|
return FINISHED;
|
|
446
473
|
}
|
|
447
|
-
function
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
function defineExtension(id, config = {}) {
|
|
451
|
-
if (id === void 0) return defineExtension;
|
|
452
|
-
if (isExtensionFactory(config)) return Object.assign((...args) => defineExtension(id, config(...args)), { id });
|
|
453
|
-
const ownedFlags = Object.freeze(toFlagsRecord(config.flags ?? []));
|
|
454
|
-
toFlagsRecord((config.provides ?? []).flatMap((instance) => Object.entries(definingOf(instance).ownedFlags).map(([name, def]) => ({
|
|
474
|
+
function createExtension(state) {
|
|
475
|
+
const flags = Object.freeze(toFlagsRecord(state.flags));
|
|
476
|
+
toFlagsRecord(state.provide.flatMap((instance) => Object.entries(instance.ownedFlags).map(([name, def]) => ({
|
|
455
477
|
...def,
|
|
456
478
|
name
|
|
457
|
-
}))),
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
479
|
+
}))), flags);
|
|
480
|
+
const data = Object.freeze({
|
|
481
|
+
id: state.id,
|
|
482
|
+
flags,
|
|
483
|
+
commands: Object.freeze([...state.commands]),
|
|
484
|
+
use: Object.freeze([...state.use]),
|
|
485
|
+
provide: Object.freeze([...state.provide]),
|
|
486
|
+
hooks: Object.freeze({ ...state.hooks }),
|
|
487
|
+
...state.sections ? { sections: state.sections } : {},
|
|
488
|
+
...state.build ? { build: state.build } : {}
|
|
489
|
+
});
|
|
490
|
+
const next = (change) => createExtension({
|
|
491
|
+
...state,
|
|
492
|
+
...change
|
|
493
|
+
});
|
|
494
|
+
const sealed = sealHandle({
|
|
495
|
+
id: state.id,
|
|
496
|
+
use: (...factories) => next({ use: [...state.use, ...factories.map(definingOf)] }),
|
|
497
|
+
provide: (...instances) => next({ provide: [...state.provide, ...instances.map(definingOf)] }),
|
|
498
|
+
flags: (...defs) => next({ flags: [...state.flags, ...defs.map(ownDefinition)] }),
|
|
499
|
+
add: (...definitions) => next({ commands: [...state.commands, ...definitions] }),
|
|
500
|
+
preRun: (preRun) => next({ hooks: {
|
|
501
|
+
...state.hooks,
|
|
502
|
+
preRun
|
|
503
|
+
} }),
|
|
504
|
+
postRun: (postRun) => next({ hooks: {
|
|
505
|
+
...state.hooks,
|
|
506
|
+
postRun
|
|
507
|
+
} }),
|
|
508
|
+
onError: (onError) => next({ hooks: {
|
|
509
|
+
...state.hooks,
|
|
510
|
+
onError
|
|
511
|
+
} }),
|
|
512
|
+
sections: (sections) => next({ sections }),
|
|
513
|
+
build: (build) => next({ build }),
|
|
514
|
+
factory: (define) => Object.assign((...args) => {
|
|
515
|
+
const extension = define(sealed, ...args);
|
|
516
|
+
const returned = definingOf(extension).id;
|
|
517
|
+
if (returned !== state.id) throw new CrustError("DEFINITION", `Extension factory "${state.id}" returned Extension "${returned}"; return an Extension built from the handle it receives`, {
|
|
518
|
+
subject: "extension",
|
|
519
|
+
name: state.id,
|
|
520
|
+
reason: "factory-id-mismatch"
|
|
521
|
+
});
|
|
522
|
+
return extension;
|
|
523
|
+
}, { id: state.id })
|
|
524
|
+
}, data);
|
|
525
|
+
return sealed;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Start an immutable fluent Extension definition.
|
|
529
|
+
*
|
|
530
|
+
* Extensions apply to the whole application and own the flags and commands
|
|
531
|
+
* they contribute. `defineExtension<"version">(id)` requires root metadata keys
|
|
532
|
+
* without preventing inference of flags, Contexts, or commands; chain
|
|
533
|
+
* `.factory()` for a configurable Extension whose identity is a section consumer.
|
|
534
|
+
*/
|
|
535
|
+
function defineExtension(id) {
|
|
536
|
+
return createExtension({
|
|
463
537
|
id,
|
|
464
|
-
|
|
538
|
+
use: [],
|
|
539
|
+
provide: [],
|
|
540
|
+
flags: [],
|
|
541
|
+
commands: [],
|
|
542
|
+
hooks: {}
|
|
465
543
|
});
|
|
466
544
|
}
|
|
467
545
|
//#endregion
|
|
@@ -483,6 +561,7 @@ function createCommandNode(name) {
|
|
|
483
561
|
args: [],
|
|
484
562
|
subCommands: {},
|
|
485
563
|
contexts: [],
|
|
564
|
+
providedContexts: [],
|
|
486
565
|
demands: [],
|
|
487
566
|
extensions: [],
|
|
488
567
|
run: void 0
|
|
@@ -528,11 +607,11 @@ function injectExtensionFlag(node, name, def, recursive) {
|
|
|
528
607
|
}
|
|
529
608
|
/** Attach one Extension's owned root commands to a cloned tree. */
|
|
530
609
|
function applyExtensionCommands(root, extension, materializeCommandDefinition) {
|
|
531
|
-
for (const definition of extension.commands
|
|
610
|
+
for (const definition of extension.commands) root.subCommands[definition.name] = materializeCommandDefinition(definition, root, extension.id);
|
|
532
611
|
}
|
|
533
612
|
/** Inject one Extension's owned flags across a cloned tree. */
|
|
534
613
|
function applyExtensionFlags(root, extension) {
|
|
535
|
-
for (const [name, defWithScope] of Object.entries(extension.flags
|
|
614
|
+
for (const [name, defWithScope] of Object.entries(extension.flags)) {
|
|
536
615
|
const { recursive = true, ...def } = defWithScope;
|
|
537
616
|
injectExtensionFlag(root, name, def, recursive);
|
|
538
617
|
}
|
|
@@ -557,36 +636,12 @@ function cloneCommandNode(node) {
|
|
|
557
636
|
args: [...node.args],
|
|
558
637
|
subCommands,
|
|
559
638
|
contexts: node.contexts.map((context) => ({ ...context })),
|
|
639
|
+
providedContexts: [...node.providedContexts],
|
|
560
640
|
demands: [...node.demands],
|
|
561
641
|
extensions: [...node.extensions],
|
|
562
642
|
run: node.run
|
|
563
643
|
};
|
|
564
644
|
}
|
|
565
|
-
function invalidSections({ subject, name }) {
|
|
566
|
-
return new CrustError("DEFINITION", `${subject === "command" ? "Command" : "Extension"} "${name}" contains invalid documentation sections`, {
|
|
567
|
-
subject,
|
|
568
|
-
name,
|
|
569
|
-
reason: "invalid-sections"
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
function normalizeSection(section, owner) {
|
|
573
|
-
const { title, body, only, except } = section;
|
|
574
|
-
if (!title.trim() || /[\r\n]/.test(title) || !body.trim() || only?.length === 0 || except?.length === 0 || only !== void 0 && except !== void 0) throw invalidSections(owner);
|
|
575
|
-
const audience = (ids) => {
|
|
576
|
-
return Object.freeze(ids.map((consumer) => typeof consumer === "string" ? consumer : consumer.id));
|
|
577
|
-
};
|
|
578
|
-
return Object.freeze({
|
|
579
|
-
title,
|
|
580
|
-
body,
|
|
581
|
-
...only ? { only: audience(only) } : except ? { except: audience(except) } : {}
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
function validateCommandSections(name, sections) {
|
|
585
|
-
return sections.map((section) => normalizeSection(section, {
|
|
586
|
-
subject: "command",
|
|
587
|
-
name
|
|
588
|
-
}));
|
|
589
|
-
}
|
|
590
645
|
function contributionTarget(root, command, extension) {
|
|
591
646
|
let target = root;
|
|
592
647
|
for (const segment of command) {
|
|
@@ -613,6 +668,30 @@ function applyExtensionSections(root, extension, snapshot) {
|
|
|
613
668
|
target.meta.sections = [...target.meta.sections ?? [], section];
|
|
614
669
|
}
|
|
615
670
|
}
|
|
671
|
+
/**
|
|
672
|
+
* Install Context sections where each Context is provided: `.provide()` on its own
|
|
673
|
+
* command only, Extension providers once on the root. Unlike Extension section
|
|
674
|
+
* contributions, a title already on the command is a `DEFINITION` error.
|
|
675
|
+
*/
|
|
676
|
+
function applyContextSections(root, extensions) {
|
|
677
|
+
const visit = (node, path, instances) => {
|
|
678
|
+
const effective = [...new Map(instances.map((instance) => [instance.name, instance])).values()];
|
|
679
|
+
if (effective.some((instance) => instance.sections.length > 0)) {
|
|
680
|
+
const titles = new Set((node.meta.sections ?? []).map(({ title }) => title));
|
|
681
|
+
for (const instance of effective) {
|
|
682
|
+
for (const { title } of instance.sections) if (titles.has(title)) throw new CrustError("DEFINITION", `Context "${instance.name}" section "${title}" duplicates a section on command "${path}"`, {
|
|
683
|
+
subject: "context",
|
|
684
|
+
name: instance.name,
|
|
685
|
+
reason: "duplicate-section"
|
|
686
|
+
});
|
|
687
|
+
for (const { title } of instance.sections) titles.add(title);
|
|
688
|
+
}
|
|
689
|
+
node.meta.sections = [...node.meta.sections ?? [], ...effective.flatMap((instance) => instance.sections)];
|
|
690
|
+
}
|
|
691
|
+
for (const [name, child] of Object.entries(node.subCommands)) visit(child, `${path} ${name}`, child.providedContexts);
|
|
692
|
+
};
|
|
693
|
+
visit(root, root.meta.name, [...root.providedContexts, ...extensions.flatMap((extension) => extension.provide)]);
|
|
694
|
+
}
|
|
616
695
|
function installExtensionContexts(node, extensions, reRegisteredIds) {
|
|
617
696
|
const cloned = cloneCommandNode(node);
|
|
618
697
|
const kept = new Set(extensions.flatMap((e) => !reRegisteredIds.has(e.id) && node.extensions.includes(e) ? [e.id] : []));
|
|
@@ -636,7 +715,7 @@ function installExtensionContexts(node, extensions, reRegisteredIds) {
|
|
|
636
715
|
prune(cloned);
|
|
637
716
|
for (const extension of extensions) {
|
|
638
717
|
if (kept.has(extension.id)) continue;
|
|
639
|
-
const instances = extension.
|
|
718
|
+
const instances = extension.provide;
|
|
640
719
|
if (instances.length === 0) continue;
|
|
641
720
|
const walk = (target, skip) => {
|
|
642
721
|
const installed = instances.filter((instance) => !skip.has(instance.name));
|
|
@@ -711,6 +790,50 @@ function coerceJson(raw) {
|
|
|
711
790
|
}
|
|
712
791
|
}
|
|
713
792
|
//#endregion
|
|
793
|
+
//#region src/parsing/schema.ts
|
|
794
|
+
async function runSchema(schema, raw, path, issues) {
|
|
795
|
+
const result = await schema["~standard"].validate(raw);
|
|
796
|
+
if (result.issues) {
|
|
797
|
+
issues.push(...normalizeStandardIssues(result.issues, path));
|
|
798
|
+
return { ok: false };
|
|
799
|
+
}
|
|
800
|
+
return {
|
|
801
|
+
ok: true,
|
|
802
|
+
value: result.value
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Apply Standard Schemas declared on arg/flag definitions.
|
|
807
|
+
*
|
|
808
|
+
* Runs after syntax parsing and structural validation: each schema receives
|
|
809
|
+
* the raw parsed value (`string | undefined` for args, the raw token value
|
|
810
|
+
* for flags, arrays for variadic/multiple) and exclusively owns coercion,
|
|
811
|
+
* defaults, requiredness, and validation. Returns transformed copies of
|
|
812
|
+
* `args`/`flags`; definitions without a schema pass through unchanged.
|
|
813
|
+
*
|
|
814
|
+
* @throws {CrustError} `VALIDATION` aggregating every schema issue
|
|
815
|
+
*/
|
|
816
|
+
async function applySchemas(node, parsed) {
|
|
817
|
+
const issues = [];
|
|
818
|
+
const args = new Map(Object.entries(parsed.args));
|
|
819
|
+
const flags = new Map(Object.entries(parsed.flags));
|
|
820
|
+
for (const def of node.args) {
|
|
821
|
+
if (def.schema === void 0) continue;
|
|
822
|
+
const result = await runSchema(def.schema, args.get(def.name), ["args", def.name], issues);
|
|
823
|
+
if (result.ok) args.set(def.name, result.value);
|
|
824
|
+
}
|
|
825
|
+
for (const [name, def] of Object.entries(node.effectiveFlags)) {
|
|
826
|
+
if (def.schema === void 0) continue;
|
|
827
|
+
const result = await runSchema(def.schema, flags.get(name), ["flags", name], issues);
|
|
828
|
+
if (result.ok) flags.set(name, result.value);
|
|
829
|
+
}
|
|
830
|
+
if (issues.length > 0) throw new CrustError("VALIDATION", `Invalid input:\n${issues.map((issue) => ` - ${issue.path}: ${issue.message}`).join("\n")}`, { issues });
|
|
831
|
+
return {
|
|
832
|
+
args: Object.fromEntries(args),
|
|
833
|
+
flags: Object.fromEntries(flags)
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
//#endregion
|
|
714
837
|
//#region src/parsing/parser.ts
|
|
715
838
|
/**
|
|
716
839
|
* Build the options config for `util.parseArgs` from the shared spelling table.
|
|
@@ -945,7 +1068,8 @@ function resolveArgs(argsDef, positionals, coerce) {
|
|
|
945
1068
|
configurable: true
|
|
946
1069
|
});
|
|
947
1070
|
if (def.variadic) {
|
|
948
|
-
|
|
1071
|
+
const remaining = positionals.slice(index);
|
|
1072
|
+
resolved[name] = remaining.length === 0 && def.default !== void 0 ? [resolveDefault(def, label)] : remaining.map((v, i) => coerce(def, v, label, i));
|
|
949
1073
|
index = positionals.length;
|
|
950
1074
|
} else if (index < positionals.length) {
|
|
951
1075
|
resolved[name] = coerce(def, positionals[index], label);
|
|
@@ -1088,6 +1212,31 @@ function parseArgs$1(command, argv, env = process.env) {
|
|
|
1088
1212
|
rawArgs
|
|
1089
1213
|
};
|
|
1090
1214
|
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Resolve flags from their `env` bindings alone, as {@link parseArgs} does for an
|
|
1217
|
+
* empty argv: `env.delimiter` splitting, `choices`, `parse`, built-in coercion,
|
|
1218
|
+
* defaults, requiredness, then Standard Schemas. Flags without `env` resolve
|
|
1219
|
+
* to their defaults.
|
|
1220
|
+
*
|
|
1221
|
+
* @param flags - Flag definitions keyed by result name; each `env.name` selects its variable
|
|
1222
|
+
* @param env - Raw variable text; only own properties are read
|
|
1223
|
+
* @throws {CrustError} `DEFINITION` for a definition `defineFlag` rejects (e.g. a
|
|
1224
|
+
* `__proto__` key or a default outside `choices`); `PARSE` or `VALIDATION` on the first
|
|
1225
|
+
* coercion or requiredness failure, or `VALIDATION` aggregating schema issues. Messages
|
|
1226
|
+
* may quote raw values.
|
|
1227
|
+
*/
|
|
1228
|
+
async function parseFlagValues(flags, env) {
|
|
1229
|
+
for (const [name, def] of Object.entries(flags)) normalizeFlag(name, def);
|
|
1230
|
+
const resolved = resolveFlags(flags, applyEnvAndDelimiter(flags, {}, env), coerceFlagValue);
|
|
1231
|
+
validateRequiredFlags(flags, resolved);
|
|
1232
|
+
return (await applySchemas({
|
|
1233
|
+
args: [],
|
|
1234
|
+
effectiveFlags: flags
|
|
1235
|
+
}, {
|
|
1236
|
+
args: {},
|
|
1237
|
+
flags: resolved
|
|
1238
|
+
})).flags;
|
|
1239
|
+
}
|
|
1091
1240
|
/** Bind typed input without producing argv; the path alone selects the command. */
|
|
1092
1241
|
function parseStructured(command, input) {
|
|
1093
1242
|
const { args: inputArgs, flags: inputFlags, raw } = input;
|
|
@@ -1149,87 +1298,6 @@ function validateParsed(command, parsed) {
|
|
|
1149
1298
|
validateRequiredFlags(flagsDef, flags);
|
|
1150
1299
|
}
|
|
1151
1300
|
//#endregion
|
|
1152
|
-
//#region src/parsing/schema.ts
|
|
1153
|
-
async function runSchema(schema, raw, path, issues) {
|
|
1154
|
-
const result = await schema["~standard"].validate(raw);
|
|
1155
|
-
if (result.issues) {
|
|
1156
|
-
issues.push(...normalizeStandardIssues(result.issues, path));
|
|
1157
|
-
return { ok: false };
|
|
1158
|
-
}
|
|
1159
|
-
return {
|
|
1160
|
-
ok: true,
|
|
1161
|
-
value: result.value
|
|
1162
|
-
};
|
|
1163
|
-
}
|
|
1164
|
-
/**
|
|
1165
|
-
* Apply Standard Schemas declared on arg/flag definitions.
|
|
1166
|
-
*
|
|
1167
|
-
* Runs after syntax parsing and structural validation: each schema receives
|
|
1168
|
-
* the raw parsed value (`string | undefined` for args, the raw token value
|
|
1169
|
-
* for flags, arrays for variadic/multiple) and exclusively owns coercion,
|
|
1170
|
-
* defaults, requiredness, and validation. Returns transformed copies of
|
|
1171
|
-
* `args`/`flags`; definitions without a schema pass through unchanged.
|
|
1172
|
-
*
|
|
1173
|
-
* @throws {CrustError} `VALIDATION` aggregating every schema issue
|
|
1174
|
-
*/
|
|
1175
|
-
async function applySchemas(node, parsed) {
|
|
1176
|
-
const issues = [];
|
|
1177
|
-
const args = new Map(Object.entries(parsed.args));
|
|
1178
|
-
const flags = new Map(Object.entries(parsed.flags));
|
|
1179
|
-
for (const def of node.args) {
|
|
1180
|
-
if (def.schema === void 0) continue;
|
|
1181
|
-
const result = await runSchema(def.schema, args.get(def.name), ["args", def.name], issues);
|
|
1182
|
-
if (result.ok) args.set(def.name, result.value);
|
|
1183
|
-
}
|
|
1184
|
-
for (const [name, def] of Object.entries(node.effectiveFlags)) {
|
|
1185
|
-
if (def.schema === void 0) continue;
|
|
1186
|
-
const result = await runSchema(def.schema, flags.get(name), ["flags", name], issues);
|
|
1187
|
-
if (result.ok) flags.set(name, result.value);
|
|
1188
|
-
}
|
|
1189
|
-
if (issues.length > 0) throw new CrustError("VALIDATION", `Invalid input:\n${issues.map((issue) => ` - ${issue.path}: ${issue.message}`).join("\n")}`, { issues });
|
|
1190
|
-
return {
|
|
1191
|
-
args: Object.fromEntries(args),
|
|
1192
|
-
flags: Object.fromEntries(flags)
|
|
1193
|
-
};
|
|
1194
|
-
}
|
|
1195
|
-
//#endregion
|
|
1196
|
-
//#region src/sections.ts
|
|
1197
|
-
/** Whether a command belongs in user-facing listings. */
|
|
1198
|
-
function isListed(command) {
|
|
1199
|
-
return command.meta.hidden !== true;
|
|
1200
|
-
}
|
|
1201
|
-
/** Select and merge sections visible to the given consumer. */
|
|
1202
|
-
function sectionsFor(sections, consumer) {
|
|
1203
|
-
const visible = (sections ?? []).filter((section) => {
|
|
1204
|
-
if (section.only) return section.only.includes(consumer);
|
|
1205
|
-
if (section.except) return !section.except.includes(consumer);
|
|
1206
|
-
return true;
|
|
1207
|
-
});
|
|
1208
|
-
const merged = /* @__PURE__ */ new Map();
|
|
1209
|
-
for (const section of visible) {
|
|
1210
|
-
const existing = merged.get(section.title);
|
|
1211
|
-
merged.set(section.title, existing ? {
|
|
1212
|
-
title: section.title,
|
|
1213
|
-
body: `${existing.body}\n${section.body}`
|
|
1214
|
-
} : section);
|
|
1215
|
-
}
|
|
1216
|
-
return [...merged.values()];
|
|
1217
|
-
}
|
|
1218
|
-
/** Collect section-bearing visible commands in canonical path order. The root path is `[]`. */
|
|
1219
|
-
function visibleSectionsFor(snapshot, consumer) {
|
|
1220
|
-
const groups = [];
|
|
1221
|
-
function visit(command, path) {
|
|
1222
|
-
const sections = sectionsFor(command.meta.sections, consumer);
|
|
1223
|
-
if (sections.length > 0) groups.push({
|
|
1224
|
-
path,
|
|
1225
|
-
sections
|
|
1226
|
-
});
|
|
1227
|
-
for (const [name, child] of Object.entries(command.subCommands).sort(([a], [b]) => a.localeCompare(b))) if (isListed(child)) visit(child, [...path, name]);
|
|
1228
|
-
}
|
|
1229
|
-
visit(snapshot, []);
|
|
1230
|
-
return groups;
|
|
1231
|
-
}
|
|
1232
|
-
//#endregion
|
|
1233
1301
|
//#region src/command/snapshot.ts
|
|
1234
1302
|
/** Drop keys with `undefined` values so snapshots serialize cleanly, then freeze. */
|
|
1235
1303
|
function freezeCompact(obj) {
|
|
@@ -1449,62 +1517,6 @@ function resolveCommand(command, argv) {
|
|
|
1449
1517
|
};
|
|
1450
1518
|
}
|
|
1451
1519
|
//#endregion
|
|
1452
|
-
//#region \0@oxc-project+runtime@0.148.0/helpers/esm/usingCtx.js
|
|
1453
|
-
function _usingCtx() {
|
|
1454
|
-
var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
|
|
1455
|
-
var n = Error();
|
|
1456
|
-
return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
|
|
1457
|
-
}, e = {}, n = [];
|
|
1458
|
-
function using(r, e) {
|
|
1459
|
-
if (null != e) {
|
|
1460
|
-
if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
|
|
1461
|
-
if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
|
|
1462
|
-
if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
|
|
1463
|
-
if ("function" != typeof o) throw new TypeError("Object is not disposable.");
|
|
1464
|
-
t && (o = function o() {
|
|
1465
|
-
try {
|
|
1466
|
-
t.call(e);
|
|
1467
|
-
} catch (r) {
|
|
1468
|
-
return Promise.reject(r);
|
|
1469
|
-
}
|
|
1470
|
-
}), n.push({
|
|
1471
|
-
v: e,
|
|
1472
|
-
d: o,
|
|
1473
|
-
a: r
|
|
1474
|
-
});
|
|
1475
|
-
} else r && n.push({
|
|
1476
|
-
d: e,
|
|
1477
|
-
a: r
|
|
1478
|
-
});
|
|
1479
|
-
return e;
|
|
1480
|
-
}
|
|
1481
|
-
return {
|
|
1482
|
-
e,
|
|
1483
|
-
u: using.bind(null, !1),
|
|
1484
|
-
a: using.bind(null, !0),
|
|
1485
|
-
d: function d() {
|
|
1486
|
-
var o, t = this.e, s = 0;
|
|
1487
|
-
function next() {
|
|
1488
|
-
for (; o = n.pop();) try {
|
|
1489
|
-
if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
|
|
1490
|
-
if (o.d) {
|
|
1491
|
-
var r = o.d.call(o.v);
|
|
1492
|
-
if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
|
|
1493
|
-
} else s |= 1;
|
|
1494
|
-
} catch (r) {
|
|
1495
|
-
return err(r);
|
|
1496
|
-
}
|
|
1497
|
-
if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
|
|
1498
|
-
if (t !== e) throw t;
|
|
1499
|
-
}
|
|
1500
|
-
function err(n) {
|
|
1501
|
-
return t = t !== e ? new r(n, t) : n, next();
|
|
1502
|
-
}
|
|
1503
|
-
return next();
|
|
1504
|
-
}
|
|
1505
|
-
};
|
|
1506
|
-
}
|
|
1507
|
-
//#endregion
|
|
1508
1520
|
//#region src/command/invocation.ts
|
|
1509
1521
|
const ignoreStreamError = () => {};
|
|
1510
1522
|
/**
|
|
@@ -1545,7 +1557,9 @@ const DEFAULT_IO = {
|
|
|
1545
1557
|
* without dispatching a Command Action. In-process callers use `Crust.snapshot()`.
|
|
1546
1558
|
*
|
|
1547
1559
|
* Only source entries run by `crust build` honor it: finished Bun/Node bundles carry
|
|
1548
|
-
* `process.env.CRUST_INTERNAL_BUILD === "1"` as a literal and compile the protocol out
|
|
1560
|
+
* `process.env.CRUST_INTERNAL_BUILD === "1"` as a literal and compile the protocol out;
|
|
1561
|
+
* packaged Deno bundles set the `isPackagedBuild()` marker and skip it without
|
|
1562
|
+
* reading the environment.
|
|
1549
1563
|
*/
|
|
1550
1564
|
const SNAPSHOT_PATH_ENV = "CRUST_INTERNAL_SNAPSHOT_PATH";
|
|
1551
1565
|
const EXIT_CODE_CANCELLED = 130;
|
|
@@ -1554,7 +1568,7 @@ function isAbortError(error) {
|
|
|
1554
1568
|
return error.name === "AbortError";
|
|
1555
1569
|
}
|
|
1556
1570
|
/**
|
|
1557
|
-
* Structural:
|
|
1571
|
+
* Structural: down-level bundles can implement `await using` with a plain `Error`
|
|
1558
1572
|
* carrying `error`/`suppressed`, so `instanceof SuppressedError` cannot be used.
|
|
1559
1573
|
*/
|
|
1560
1574
|
function isSuppressedError(error) {
|
|
@@ -1655,8 +1669,9 @@ function buildExtensionTree(node, materializeCommandDefinition) {
|
|
|
1655
1669
|
extensions
|
|
1656
1670
|
};
|
|
1657
1671
|
}
|
|
1658
|
-
/**
|
|
1672
|
+
/** Install Context sections, evaluate Extension section callbacks, and freeze the tree. */
|
|
1659
1673
|
function applySectionsAndFreeze(rootNode, extensions) {
|
|
1674
|
+
applyContextSections(rootNode, extensions);
|
|
1660
1675
|
if (extensions.some((extension) => extension.sections !== void 0)) {
|
|
1661
1676
|
const authoredSnapshot = snapshotCommand(rootNode);
|
|
1662
1677
|
for (const extension of extensions) applyExtensionSections(rootNode, extension, authoredSnapshot);
|
|
@@ -1706,98 +1721,91 @@ function resolveStructuredInput(root, path, input) {
|
|
|
1706
1721
|
}
|
|
1707
1722
|
/** Resolve, parse, and run one invocation without rendering failures. */
|
|
1708
1723
|
async function dispatch(input, prepared, io, signal, onExtensionContext, onFailure) {
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1724
|
+
const { rootNode, extensions } = prepared;
|
|
1725
|
+
const { argv, route, parsed } = "argv" in input ? resolveArgvInput(rootNode, input.argv) : resolveStructuredInput(rootNode, input.path, input.input);
|
|
1726
|
+
const resolvedNode = route.command;
|
|
1727
|
+
await using disposal = new AsyncDisposableStack();
|
|
1728
|
+
const contexts = resolvedNode.contexts.map(({ instance }) => instance);
|
|
1729
|
+
const resolver = createContextResolver(contexts, io, disposal, signal);
|
|
1730
|
+
const rootSnapshot = snapshotCommand(rootNode);
|
|
1731
|
+
let commandSnapshot = rootSnapshot;
|
|
1732
|
+
for (const name of route.commandPath.slice(1)) commandSnapshot = commandSnapshot.subCommands[name];
|
|
1733
|
+
const extensionContext = Object.freeze({
|
|
1734
|
+
argv: [...argv],
|
|
1735
|
+
rootCommand: rootSnapshot,
|
|
1736
|
+
command: commandSnapshot,
|
|
1737
|
+
commandPath: Object.freeze([...route.commandPath]),
|
|
1738
|
+
args: parsed.args,
|
|
1739
|
+
flags: parsed.flags,
|
|
1740
|
+
rawArgs: parsed.rawArgs,
|
|
1741
|
+
signal,
|
|
1742
|
+
ctx: resolver.bag(extensions.flatMap((extension) => extension.use)),
|
|
1743
|
+
finish: finishInvocation,
|
|
1744
|
+
stdout: io.stdout,
|
|
1745
|
+
stderr: io.stderr
|
|
1746
|
+
});
|
|
1747
|
+
onExtensionContext?.(extensionContext);
|
|
1748
|
+
const terminal = async () => {
|
|
1749
|
+
validateParsed(resolvedNode, parsed);
|
|
1750
|
+
const validated = await applySchemas(resolvedNode, parsed);
|
|
1751
|
+
resolver.setValidatedFlags(validated.flags);
|
|
1752
|
+
if (!resolvedNode.run) return;
|
|
1753
|
+
const context = {
|
|
1754
|
+
args: validated.args,
|
|
1755
|
+
flags: validated.flags,
|
|
1756
|
+
ctx: resolver.bag(contexts),
|
|
1727
1757
|
rawArgs: parsed.rawArgs,
|
|
1728
1758
|
signal,
|
|
1729
|
-
|
|
1730
|
-
|
|
1759
|
+
command: extensionContext.command,
|
|
1760
|
+
rootCommand: rootSnapshot,
|
|
1731
1761
|
stdout: io.stdout,
|
|
1732
1762
|
stderr: io.stderr
|
|
1733
|
-
});
|
|
1734
|
-
onExtensionContext?.(extensionContext);
|
|
1735
|
-
const terminal = async () => {
|
|
1736
|
-
validateParsed(resolvedNode, parsed);
|
|
1737
|
-
const validated = await applySchemas(resolvedNode, parsed);
|
|
1738
|
-
resolver.setValidatedFlags(validated.flags);
|
|
1739
|
-
if (!resolvedNode.run) return;
|
|
1740
|
-
const context = {
|
|
1741
|
-
args: validated.args,
|
|
1742
|
-
flags: validated.flags,
|
|
1743
|
-
ctx: resolver.bag(contexts),
|
|
1744
|
-
rawArgs: parsed.rawArgs,
|
|
1745
|
-
signal,
|
|
1746
|
-
command: extensionContext.command,
|
|
1747
|
-
rootCommand: rootSnapshot,
|
|
1748
|
-
stdout: io.stdout,
|
|
1749
|
-
stderr: io.stderr
|
|
1750
|
-
};
|
|
1751
|
-
return await resolvedNode.run(context);
|
|
1752
1763
|
};
|
|
1753
|
-
|
|
1754
|
-
|
|
1764
|
+
return await resolvedNode.run(context);
|
|
1765
|
+
};
|
|
1766
|
+
let result;
|
|
1767
|
+
let outcome = { status: "completed" };
|
|
1768
|
+
try {
|
|
1755
1769
|
try {
|
|
1756
|
-
|
|
1757
|
-
for (const extension of extensions) if (await extension.hooks?.preRun?.(extensionContext) === finishInvocation()) {
|
|
1758
|
-
outcome = {
|
|
1759
|
-
status: "finished",
|
|
1760
|
-
by: extension.id
|
|
1761
|
-
};
|
|
1762
|
-
break;
|
|
1763
|
-
}
|
|
1764
|
-
if (outcome.status !== "finished") result = await terminal();
|
|
1765
|
-
} catch (error) {
|
|
1766
|
-
const by = await onFailure?.(error, extensionContext);
|
|
1770
|
+
for (const extension of extensions) if (await extension.hooks?.preRun?.(extensionContext) === finishInvocation()) {
|
|
1767
1771
|
outcome = {
|
|
1768
|
-
status: "
|
|
1769
|
-
|
|
1770
|
-
...by === void 0 ? {} : { by }
|
|
1772
|
+
status: "finished",
|
|
1773
|
+
by: extension.id
|
|
1771
1774
|
};
|
|
1775
|
+
break;
|
|
1772
1776
|
}
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1777
|
+
if (outcome.status !== "finished") result = await terminal();
|
|
1778
|
+
} catch (error) {
|
|
1779
|
+
const by = await onFailure?.(error, extensionContext);
|
|
1780
|
+
outcome = {
|
|
1781
|
+
status: "failed",
|
|
1782
|
+
error,
|
|
1783
|
+
...by === void 0 ? {} : { by }
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
Object.freeze(outcome);
|
|
1787
|
+
let postRunFailed = false;
|
|
1788
|
+
let postRunError;
|
|
1789
|
+
for (const extension of extensions.toReversed()) try {
|
|
1790
|
+
await extension.hooks?.postRun?.(extensionContext, outcome);
|
|
1791
|
+
} catch (error) {
|
|
1792
|
+
if (outcome.status !== "failed" && !postRunFailed) {
|
|
1793
|
+
postRunFailed = true;
|
|
1794
|
+
postRunError = error;
|
|
1783
1795
|
}
|
|
1784
|
-
if (outcome.status === "failed") throw outcome.error;
|
|
1785
|
-
if (postRunFailed) throw postRunError;
|
|
1786
|
-
} finally {
|
|
1787
|
-
await resolver.settle();
|
|
1788
1796
|
}
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
by: outcome.by
|
|
1792
|
-
} : {
|
|
1793
|
-
status: "completed",
|
|
1794
|
-
result
|
|
1795
|
-
};
|
|
1796
|
-
} catch (_) {
|
|
1797
|
-
_usingCtx$1.e = _;
|
|
1797
|
+
if (outcome.status === "failed") throw outcome.error;
|
|
1798
|
+
if (postRunFailed) throw postRunError;
|
|
1798
1799
|
} finally {
|
|
1799
|
-
await
|
|
1800
|
+
await resolver.settle();
|
|
1800
1801
|
}
|
|
1802
|
+
return outcome.status === "finished" ? {
|
|
1803
|
+
status: "finished",
|
|
1804
|
+
by: outcome.by
|
|
1805
|
+
} : {
|
|
1806
|
+
status: "completed",
|
|
1807
|
+
result
|
|
1808
|
+
};
|
|
1801
1809
|
}
|
|
1802
1810
|
/** Render one failure through Extension onError hooks, ending in Core's default renderer. */
|
|
1803
1811
|
async function renderFailure(error, argv, prepared, io, signal, extensionContext, silentDefault = false) {
|
|
@@ -1881,56 +1889,58 @@ async function executeInvocation(node, options, materializeCommandDefinition) {
|
|
|
1881
1889
|
};
|
|
1882
1890
|
const controller = new AbortController();
|
|
1883
1891
|
const signal = options?.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
const
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1892
|
+
if (!isPackagedBuild()) {
|
|
1893
|
+
const snapshotPath = process.env.CRUST_INTERNAL_BUILD === "1" ? void 0 : process.env[SNAPSHOT_PATH_ENV];
|
|
1894
|
+
if (snapshotPath) {
|
|
1895
|
+
try {
|
|
1896
|
+
const base = buildExtensionTree(node, materializeCommandDefinition);
|
|
1897
|
+
const takeSnapshot = () => snapshotCommand(applySectionsAndFreeze(cloneCommandNode(base.rootNode), base.extensions));
|
|
1898
|
+
let snapshot = takeSnapshot();
|
|
1899
|
+
const buildOutDir = process.env[BUILD_OUT_DIR_ENV];
|
|
1900
|
+
if (buildOutDir) {
|
|
1901
|
+
const extensions = [];
|
|
1902
|
+
const owners = /* @__PURE__ */ new Map();
|
|
1903
|
+
for (const extension of base.extensions) {
|
|
1904
|
+
if (!extension.build) continue;
|
|
1905
|
+
try {
|
|
1906
|
+
const files = (await extension.build({ snapshot })).map((file) => {
|
|
1907
|
+
const path = normalizeArtifactPath(file.path);
|
|
1908
|
+
const key = path.toLowerCase();
|
|
1909
|
+
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}".`);
|
|
1910
|
+
owners.set(key, {
|
|
1911
|
+
id: extension.id,
|
|
1912
|
+
path
|
|
1913
|
+
});
|
|
1914
|
+
return {
|
|
1915
|
+
path,
|
|
1916
|
+
content: file.content
|
|
1917
|
+
};
|
|
1918
|
+
});
|
|
1919
|
+
for (const file of files) {
|
|
1920
|
+
const target = join(buildOutDir, file.path);
|
|
1921
|
+
await mkdir(dirname(target), { recursive: true });
|
|
1922
|
+
await writeFile(target, file.content);
|
|
1923
|
+
}
|
|
1924
|
+
extensions.push({
|
|
1902
1925
|
id: extension.id,
|
|
1903
|
-
path
|
|
1926
|
+
files: files.map((file) => file.path)
|
|
1904
1927
|
});
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
};
|
|
1909
|
-
});
|
|
1910
|
-
for (const file of files) {
|
|
1911
|
-
const target = join(buildOutDir, file.path);
|
|
1912
|
-
await mkdir(dirname(target), { recursive: true });
|
|
1913
|
-
await writeFile(target, file.content);
|
|
1928
|
+
} catch (error) {
|
|
1929
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1930
|
+
throw new Error(`Extension "${extension.id}" build failed: ${message}`, { cause: error });
|
|
1914
1931
|
}
|
|
1915
|
-
|
|
1916
|
-
id: extension.id,
|
|
1917
|
-
files: files.map((file) => file.path)
|
|
1918
|
-
});
|
|
1919
|
-
} catch (error) {
|
|
1920
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1921
|
-
throw new Error(`Extension "${extension.id}" build failed: ${message}`, { cause: error });
|
|
1932
|
+
snapshot = takeSnapshot();
|
|
1922
1933
|
}
|
|
1923
|
-
|
|
1934
|
+
await writeFile(join(dirname(snapshotPath), "build-report.json"), JSON.stringify({ extensions }));
|
|
1924
1935
|
}
|
|
1925
|
-
await writeFile(
|
|
1936
|
+
await writeFile(snapshotPath, JSON.stringify(snapshot));
|
|
1937
|
+
} catch (error) {
|
|
1938
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1939
|
+
console.error(message);
|
|
1940
|
+
return process.exit(1);
|
|
1926
1941
|
}
|
|
1927
|
-
|
|
1928
|
-
} catch (error) {
|
|
1929
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1930
|
-
console.error(message);
|
|
1931
|
-
return process.exit(1);
|
|
1942
|
+
return process.exit(0);
|
|
1932
1943
|
}
|
|
1933
|
-
return process.exit(0);
|
|
1934
1944
|
}
|
|
1935
1945
|
const invoke = async () => {
|
|
1936
1946
|
let prepared;
|
|
@@ -1989,4 +1999,4 @@ async function executeInvocation(node, options, materializeCommandDefinition) {
|
|
|
1989
1999
|
return await (hasInjectedIO(options?.io) ? withAmbientTerminalIO(io, invoke) : invoke());
|
|
1990
2000
|
}
|
|
1991
2001
|
//#endregion
|
|
1992
|
-
export {
|
|
2002
|
+
export { normalizeFlag as C, normalizeArg as S, validateContextAvailability as _, runInvocation as a, validateCommandSections as b, cloneCommandNode as c, createCommandNode as d, registerFlag as f, definingOf as g, defineContext as h, resolveTypedPath as i, cloneFlagRegistry as l, contextSources as m, executeInvocation as n, snapshotCommand as o, defineExtension as p, prepareInvocation as r, parseFlagValues as s, SNAPSHOT_PATH_ENV as t, installExtensionContexts as u, isListed as v, CrustError as w, visibleSectionsFor as x, sectionsFor as y };
|