@loomcli/core 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application.d.ts +73 -28
- package/dist/application.js +334 -99
- package/dist/chain.d.ts +68 -0
- package/dist/chain.js +372 -0
- package/dist/command.d.ts +201 -46
- package/dist/command.js +713 -57
- package/dist/environment.d.ts +22 -0
- package/dist/environment.js +1 -0
- package/dist/errors.d.ts +31 -51
- package/dist/errors.js +58 -90
- package/dist/extension.d.ts +99 -0
- package/dist/extension.js +330 -0
- package/dist/facts.d.ts +39 -0
- package/dist/facts.js +95 -0
- package/dist/globals.d.ts +49 -28
- package/dist/globals.js +104 -58
- package/dist/glyphs.generated.d.ts +464 -0
- package/dist/glyphs.generated.js +491 -0
- package/dist/host.js +2 -1
- package/dist/index.d.ts +21 -6
- package/dist/index.js +7 -2
- package/dist/inspect.d.ts +69 -12
- package/dist/inspect.js +83 -26
- package/dist/lanes.d.ts +26 -0
- package/dist/lanes.js +45 -0
- package/dist/options.d.ts +7 -0
- package/dist/options.js +9 -0
- package/dist/output.d.ts +93 -15
- package/dist/output.js +307 -34
- package/dist/plugin.d.ts +132 -0
- package/dist/plugin.js +278 -0
- package/dist/rendering.d.ts +21 -0
- package/dist/rendering.js +72 -0
- package/dist/sequence.d.ts +41 -0
- package/dist/sequence.js +225 -0
- package/dist/signals.d.ts +52 -0
- package/dist/signals.js +85 -0
- package/dist/style-ansi.d.ts +13 -0
- package/dist/style-ansi.js +306 -0
- package/dist/style-layout.d.ts +29 -0
- package/dist/style-layout.js +228 -0
- package/dist/style-resolve.d.ts +6 -0
- package/dist/style-resolve.js +26 -0
- package/dist/style-state.d.ts +14 -0
- package/dist/style-state.js +179 -0
- package/dist/style-wire.d.ts +31 -0
- package/dist/style-wire.js +201 -0
- package/dist/style.d.ts +86 -0
- package/dist/style.js +201 -0
- package/dist/theme.d.ts +3 -0
- package/dist/theme.js +22 -0
- package/dist/types.d.ts +222 -26
- package/dist/validation.d.ts +12 -3
- package/dist/validation.js +34 -17
- package/dist/view.d.ts +180 -0
- package/dist/view.js +307 -0
- package/package.json +2 -1
package/dist/application.d.ts
CHANGED
|
@@ -1,47 +1,90 @@
|
|
|
1
|
-
import type { AfterAction, AfterArgument, AfterCommand, Command, CommandMethod, CommandState } from './command.js';
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
1
|
+
import type { AfterAction, AttachmentConstraint, AfterArgument, AfterCommand, AfterResult, Command, CommandMethod, CommandState, ResultMethod } from './command.js';
|
|
2
|
+
import type { ApplicationEnvironment, applicationEnvironment } from './environment.js';
|
|
3
|
+
import type { ExtensionValue } from './extension.js';
|
|
4
|
+
import type { GlobalsState } from './globals.js';
|
|
4
5
|
import type { CommandGraph } from './inspect.js';
|
|
5
|
-
import type {
|
|
6
|
+
import type { Plugin } from './plugin.js';
|
|
7
|
+
import type { RenderingPolicy } from './rendering.js';
|
|
8
|
+
import type { Action, ArgumentConfig, ArgumentValue, declaredTypes, DeclaredTypes, DefaultConstraint, GlobalNameConstraint, MultipleConstraint, NameConstraint, ExitCode, OptionConfig, OptionValue, ResultViews, ResultViewsOf, RowViews, RunOptions, ValidateOmittedConstraint } from './types.js';
|
|
9
|
+
import type { ViewOverride } from './view.js';
|
|
6
10
|
/**
|
|
7
11
|
* Every authoring call an Application can publish, beside `run()` and `name`, which always remain.
|
|
8
12
|
* An Application's type state is a subset of these, and each call removes the names it invalidates.
|
|
9
13
|
* The unnamed root declares what a named Command declares, except for `alias()`: the root answers
|
|
10
14
|
* to no bare token, so it has no name to alias.
|
|
11
15
|
*/
|
|
12
|
-
export type ApplicationMethod = Exclude<CommandMethod, 'alias'
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
export type ApplicationMethod = Exclude<CommandMethod, 'alias'> | 'globalOption';
|
|
17
|
+
export interface ApplicationOptions<Plugins extends readonly Plugin[] = readonly Plugin[]> {
|
|
18
|
+
rendering?: RenderingPolicy;
|
|
19
|
+
views?: readonly ViewOverride[];
|
|
20
|
+
plugins?: Plugins;
|
|
21
|
+
extensions?: readonly ExtensionValue<'command'>[];
|
|
22
|
+
description?: string;
|
|
23
|
+
version?: string;
|
|
20
24
|
}
|
|
21
25
|
/**
|
|
22
26
|
* The Application holds the unnamed root's declaration state and applies the same transitions a
|
|
23
27
|
* Command does, so each declaration call has one typed implementation and no builder to recover.
|
|
24
28
|
*/
|
|
25
|
-
declare class ApplicationBuilder<Args, Options, Globals, State extends ApplicationMethod = ApplicationMethod> {
|
|
29
|
+
declare class ApplicationBuilder<Args, Options, Globals, State extends ApplicationMethod = ApplicationMethod, Plugins extends readonly Plugin[] = readonly [], Result = unknown> {
|
|
26
30
|
#private;
|
|
27
|
-
readonly [
|
|
31
|
+
readonly [applicationEnvironment]: ApplicationEnvironment<Globals, Plugins>;
|
|
32
|
+
readonly [declaredTypes]: DeclaredTypes<Args, Options, Globals, Result>;
|
|
28
33
|
constructor(name: string, root: CommandState<Args, Options, Globals>, config: {
|
|
29
|
-
|
|
34
|
+
declared: DeclaredFacts;
|
|
35
|
+
views: unknown;
|
|
30
36
|
options?: unknown;
|
|
37
|
+
plugins: Plugins | undefined;
|
|
38
|
+
globals: GlobalsState<Globals>;
|
|
31
39
|
});
|
|
32
40
|
get name(): string;
|
|
33
|
-
argument<const Name extends string, const Config extends ArgumentConfig>(name: Name, config: Config & NameConstraint<Name> & NoInfer<DefaultConstraint<Config>> & NoInfer<ValidateOmittedConstraint<Config>>): Application<Args & Record<Name, ArgumentValue<Config>>, Options, Globals, AfterArgument<State
|
|
34
|
-
option<const Name extends string, const Config extends OptionConfig>(name: Name, config: Config & NameConstraint<Name> & GlobalNameConstraint<Name, Globals> & NoInfer<DefaultConstraint<Config>> & NoInfer<MultipleConstraint<Config>> & NoInfer<ValidateOmittedConstraint<Config>>): Application<Args, Options & Record<Name, OptionValue<Config>>, Globals, State>;
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
argument<const Name extends string, const Config extends ArgumentConfig>(name: Name, config: Config & NameConstraint<Name> & NoInfer<DefaultConstraint<Config>> & NoInfer<ValidateOmittedConstraint<Config>>): Application<Args & Record<Name, ArgumentValue<Config>>, Options, Globals, AfterArgument<State>, Plugins, Result>;
|
|
42
|
+
option<const Name extends string, const Config extends OptionConfig>(name: Name, config: Config & NameConstraint<Name> & GlobalNameConstraint<Name, Globals> & NoInfer<DefaultConstraint<Config>> & NoInfer<MultipleConstraint<Config>> & NoInfer<ValidateOmittedConstraint<Config>>): Application<Args, Options & Record<Name, OptionValue<Config>>, Globals, State, Plugins, Result>;
|
|
43
|
+
globalOption<const Name extends string, const Config extends OptionConfig>(name: Name, config: Config & NameConstraint<Name> & (Name extends keyof Options ? {
|
|
44
|
+
'This option name is already declared as a local option': Name;
|
|
45
|
+
} : unknown) & NoInfer<DefaultConstraint<Config>> & NoInfer<MultipleConstraint<Config>> & NoInfer<ValidateOmittedConstraint<Config>>): Application<Args, Options, Globals & Record<Name, OptionValue<Config>>, State, Plugins, Result>;
|
|
46
|
+
/** Registering the action closes input authoring; extension configuration remains available. */
|
|
47
|
+
action(handler: Action<Args, Globals & Options, Result>): Application<Args, Options, Globals, AfterAction, Plugins, Result>;
|
|
37
48
|
/** A child arrives in any type state, because its own action is the call that finished it. */
|
|
38
|
-
command
|
|
49
|
+
command<const Child extends Command<unknown, unknown, Globals>>(child: Child & NoInfer<AttachmentConstraint<Globals, Child>>): Application<Args, Options, Globals, AfterCommand<Exclude<State, 'globalOption'>>, Plugins, Result>;
|
|
39
50
|
/**
|
|
40
|
-
*
|
|
51
|
+
* The value the root action produces for its consumer. The type argument is stated by the
|
|
52
|
+
* author, as it is on a Command.
|
|
53
|
+
*/
|
|
54
|
+
result<Value>(declaration: {
|
|
55
|
+
views: ResultViews<NoInfer<Value>>;
|
|
56
|
+
}): Application<Args, Options, Globals, AfterResult<State>, Plugins, {
|
|
57
|
+
kind: 'value';
|
|
58
|
+
value: Value;
|
|
59
|
+
}>;
|
|
60
|
+
/** The same declaration over a sequence, whose type argument is one row. */
|
|
61
|
+
rows<Row>(declaration: {
|
|
62
|
+
views: RowViews<NoInfer<Row>>;
|
|
63
|
+
}): Application<Args, Options, Globals, AfterResult<State>, Plugins, {
|
|
64
|
+
kind: 'rows';
|
|
65
|
+
row: Row;
|
|
66
|
+
}>;
|
|
67
|
+
/** Views after the fact, merged by key, as it is on a Command. */
|
|
68
|
+
views(replacements: ResultViewsOf<Result>, options?: {
|
|
69
|
+
default?: string;
|
|
70
|
+
}): Application<Args, Options, Globals, State, Plugins, Result>;
|
|
71
|
+
extend(...values: readonly ExtensionValue<'command'>[]): Application<Args, Options, Globals, State, Plugins, Result>;
|
|
72
|
+
/**
|
|
73
|
+
* Root declaration calls preserve the Application configuration. The next state
|
|
41
74
|
* travels through this call: each method names its transition in its return type, and the
|
|
42
75
|
* wrapper publishes the same runtime value in exactly that state.
|
|
43
76
|
*/
|
|
44
77
|
private derive;
|
|
78
|
+
/**
|
|
79
|
+
* Every rule that reads the declarations alone, in the order `run()` reads them: the
|
|
80
|
+
* application's own view overrides, the options slot, the installed list, each plugin's
|
|
81
|
+
* declarations, then the whole Command graph. The application's overrides are read and published
|
|
82
|
+
* first, because they need core's identities and nothing else, so every later declaration error
|
|
83
|
+
* reaches them, while a fault in that list itself reports through core's own text. The merged
|
|
84
|
+
* registry is published once the whole build has succeeded, so a build-time fault never resolves
|
|
85
|
+
* through a plugin's overrides, which build has not yet validated.
|
|
86
|
+
*/
|
|
87
|
+
private prepare;
|
|
45
88
|
/**
|
|
46
89
|
* The built graph as plain, frozen data. It applies every rule `run()` applies without a schema,
|
|
47
90
|
* in the order `run()` applies them, and throws `DeclarationError` when one fails. Validating a
|
|
@@ -59,14 +102,16 @@ declare class ApplicationBuilder<Args, Options, Globals, State extends Applicati
|
|
|
59
102
|
* It defaults to the state after `action()`, which publishes the fewest calls, so
|
|
60
103
|
* `Application<A, O, G>` accepts an application in any state, a finished one included.
|
|
61
104
|
*/
|
|
62
|
-
export type Application<Args = {}, Options = {}, Globals = {}, State extends ApplicationMethod = AfterAction> = Pick<ApplicationBuilder<Args, Options, Globals, State>, typeof declaredTypes | 'inspect' | 'name' | 'run' | State
|
|
105
|
+
export type Application<Args = {}, Options = {}, Globals = {}, State extends ApplicationMethod = AfterAction, Plugins extends readonly Plugin[] = readonly Plugin[], Result = unknown> = Pick<ApplicationBuilder<Args, Options, Globals, State, Plugins, Result>, typeof applicationEnvironment | typeof declaredTypes | 'extend' | 'inspect' | 'name' | 'run' | State | ResultMethod<Result>>;
|
|
63
106
|
interface ApplicationConstructor {
|
|
64
|
-
new (name: string): Application<{}, {}, {}, ApplicationMethod>;
|
|
65
|
-
new <
|
|
107
|
+
new (name: string): Application<{}, {}, {}, ApplicationMethod, readonly []>;
|
|
108
|
+
new <const Plugins extends readonly Plugin[] = readonly []>(name: string, options: ApplicationOptions<Plugins>): Application<{}, {}, {}, ApplicationMethod, Plugins>;
|
|
109
|
+
}
|
|
110
|
+
/** The same facts as the constructor captured them, before any rule has read them. */
|
|
111
|
+
interface DeclaredFacts {
|
|
112
|
+
rendering: unknown;
|
|
113
|
+
description: unknown;
|
|
114
|
+
version: unknown;
|
|
66
115
|
}
|
|
67
|
-
/**
|
|
68
|
-
* The public constructor takes a name and one options object. The globals type narrows to the
|
|
69
|
-
* supplied value, and the failure renderers configure the application the way its commands do.
|
|
70
|
-
*/
|
|
71
116
|
export declare const Application: ApplicationConstructor;
|
|
72
117
|
export {};
|
package/dist/application.js
CHANGED
|
@@ -1,12 +1,62 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { runInvocation } from './chain.js';
|
|
2
|
+
import { attachChild, buildGraph, collectInputs, declareAction, declareExtensions, declareArgument, declareOption, declareResult, declareResultViews, freshState, recordGlobalOption, } from './command.js';
|
|
3
|
+
import { DeclarationError, InternalError, reasonOf, toFailure } from './errors.js';
|
|
4
|
+
import { checkDescription, checkNoListingFacts, checkVersion, isPlainObject } from './facts.js';
|
|
5
|
+
import { declareGlobalOption, emptyGlobals } from './globals.js';
|
|
4
6
|
import { captureHost } from './host.js';
|
|
5
7
|
import { inspectGraph } from './inspect.js';
|
|
8
|
+
import { coreViews } from './lanes.js';
|
|
6
9
|
import { Output, reportPlainly } from './output.js';
|
|
10
|
+
import { buildPlugins, installPlugins, ownedSignals, pluginSentence } from './plugin.js';
|
|
11
|
+
import { renderingPolicy } from './rendering.js';
|
|
12
|
+
import { bracketRun, cancellationCode, isCancellationEcho } from './signals.js';
|
|
7
13
|
import { captureConfig, checkDeclarations, prepareInputs } from './validation.js';
|
|
14
|
+
import { buildViews, describeFailure, viewIdentities } from './view.js';
|
|
8
15
|
/** The registry a failure is reported through when the application's own could not be built. */
|
|
9
|
-
const
|
|
16
|
+
const noViews = [];
|
|
17
|
+
/**
|
|
18
|
+
* Whether one failure is the cancellation the run already reports, which core does not report a
|
|
19
|
+
* second time. Any other failure after cancellation is rendered as usual.
|
|
20
|
+
*/
|
|
21
|
+
function silenced(thrown, signal, cancelled) {
|
|
22
|
+
return cancelled !== undefined && isCancellationEcho(thrown, signal.reason);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The stand-in for "this run has no primary failure", which is a value no thrown value can be.
|
|
26
|
+
* `undefined` is itself throwable, so the absence is spelled here rather than borrowed from it.
|
|
27
|
+
*/
|
|
28
|
+
const noPrimary = Symbol('no primary');
|
|
29
|
+
/**
|
|
30
|
+
* Whether the primary outcome carries one recorded cause already: the value itself, or a failure
|
|
31
|
+
* that wraps it at any depth, which an action that caught a source failure and rethrew its own
|
|
32
|
+
* produces. Such a cause is reported once, through the primary outcome that carries it.
|
|
33
|
+
*/
|
|
34
|
+
function carried(primary, cause) {
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
let value = primary;
|
|
37
|
+
while (value !== noPrimary && !seen.has(value)) {
|
|
38
|
+
if (value === cause) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
seen.add(value);
|
|
42
|
+
// A failure wraps its own cause under `cause`, and one that declares none ends the walk.
|
|
43
|
+
value = value instanceof Error && 'cause' in value ? value.cause : noPrimary;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The caller's own signal, read where it enters. A JavaScript caller reaches the slot with any
|
|
49
|
+
* value, and a value that is not an `AbortSignal` would otherwise escape as a raw TypeError.
|
|
50
|
+
*/
|
|
51
|
+
function checkSignal(signal) {
|
|
52
|
+
if (signal === undefined) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
if (!(signal instanceof AbortSignal)) {
|
|
56
|
+
throw new InternalError('run() received a signal that is not an AbortSignal. Supply the signal of an AbortController.', undefined);
|
|
57
|
+
}
|
|
58
|
+
return signal;
|
|
59
|
+
}
|
|
10
60
|
/**
|
|
11
61
|
* The Application holds the unnamed root's declaration state and applies the same transitions a
|
|
12
62
|
* Command does, so each declaration call has one typed implementation and no builder to recover.
|
|
@@ -14,15 +64,23 @@ const noRegistrations = new Map();
|
|
|
14
64
|
class ApplicationBuilder {
|
|
15
65
|
#name;
|
|
16
66
|
#root;
|
|
17
|
-
|
|
18
|
-
|
|
67
|
+
// The override list the constructor read out of the options slot, unexamined until build.
|
|
68
|
+
#views;
|
|
69
|
+
#plugins;
|
|
70
|
+
#globals;
|
|
71
|
+
// The constructor's raw options argument, kept for the slot's own shape rules.
|
|
19
72
|
// The options-slot rules answer at the same point every other authoring fault does:
|
|
20
73
|
// `inspect()` and `run()`.
|
|
21
74
|
#options;
|
|
75
|
+
// The facts the constructor read out of that slot, unexamined until build.
|
|
76
|
+
#declared;
|
|
22
77
|
constructor(name, root, config) {
|
|
23
|
-
this.#
|
|
78
|
+
this.#declared = config.declared;
|
|
79
|
+
this.#views = config.views;
|
|
24
80
|
this.#name = name;
|
|
25
81
|
this.#options = config.options;
|
|
82
|
+
this.#plugins = config.plugins;
|
|
83
|
+
this.#globals = config.globals;
|
|
26
84
|
this.#root = root;
|
|
27
85
|
}
|
|
28
86
|
get name() {
|
|
@@ -44,7 +102,21 @@ class ApplicationBuilder {
|
|
|
44
102
|
};
|
|
45
103
|
return this.derive(declareOption(this.#root, input));
|
|
46
104
|
}
|
|
47
|
-
|
|
105
|
+
globalOption(name, config) {
|
|
106
|
+
const input = {
|
|
107
|
+
config: captureConfig(config),
|
|
108
|
+
kind: 'option',
|
|
109
|
+
name,
|
|
110
|
+
};
|
|
111
|
+
return new ApplicationBuilder(this.#name, recordGlobalOption(this.#root, name), {
|
|
112
|
+
declared: this.#declared,
|
|
113
|
+
globals: declareGlobalOption(this.#globals, input),
|
|
114
|
+
options: this.#options,
|
|
115
|
+
plugins: this.#plugins,
|
|
116
|
+
views: this.#views,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/** Registering the action closes input authoring; extension configuration remains available. */
|
|
48
120
|
action(handler) {
|
|
49
121
|
return this.derive(declareAction(this.#root, handler));
|
|
50
122
|
}
|
|
@@ -53,16 +125,62 @@ class ApplicationBuilder {
|
|
|
53
125
|
return this.derive(attachChild(this.#root, child));
|
|
54
126
|
}
|
|
55
127
|
/**
|
|
56
|
-
*
|
|
128
|
+
* The value the root action produces for its consumer. The type argument is stated by the
|
|
129
|
+
* author, as it is on a Command.
|
|
130
|
+
*/
|
|
131
|
+
result(declaration) {
|
|
132
|
+
return this.derive(declareResult(this.#root, 'value', declaration));
|
|
133
|
+
}
|
|
134
|
+
/** The same declaration over a sequence, whose type argument is one row. */
|
|
135
|
+
rows(declaration) {
|
|
136
|
+
return this.derive(declareResult(this.#root, 'rows', declaration));
|
|
137
|
+
}
|
|
138
|
+
/** Views after the fact, merged by key, as it is on a Command. */
|
|
139
|
+
views(replacements, options) {
|
|
140
|
+
return this.derive(declareResultViews(this.#root, replacements, options));
|
|
141
|
+
}
|
|
142
|
+
extend(...values) {
|
|
143
|
+
return this.derive(declareExtensions(this.#root, values));
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Root declaration calls preserve the Application configuration. The next state
|
|
57
147
|
* travels through this call: each method names its transition in its return type, and the
|
|
58
148
|
* wrapper publishes the same runtime value in exactly that state.
|
|
59
149
|
*/
|
|
60
150
|
derive(root) {
|
|
61
151
|
return new ApplicationBuilder(this.#name, root, {
|
|
62
|
-
|
|
152
|
+
declared: this.#declared,
|
|
153
|
+
globals: this.#globals,
|
|
63
154
|
options: this.#options,
|
|
155
|
+
plugins: this.#plugins,
|
|
156
|
+
views: this.#views,
|
|
64
157
|
});
|
|
65
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Every rule that reads the declarations alone, in the order `run()` reads them: the
|
|
161
|
+
* application's own view overrides, the options slot, the installed list, each plugin's
|
|
162
|
+
* declarations, then the whole Command graph. The application's overrides are read and published
|
|
163
|
+
* first, because they need core's identities and nothing else, so every later declaration error
|
|
164
|
+
* reaches them, while a fault in that list itself reports through core's own text. The merged
|
|
165
|
+
* registry is published once the whole build has succeeded, so a build-time fault never resolves
|
|
166
|
+
* through a plugin's overrides, which build has not yet validated.
|
|
167
|
+
*/
|
|
168
|
+
prepare(stage) {
|
|
169
|
+
const identities = viewIdentities(coreViews);
|
|
170
|
+
const application = buildViews({ declares: false, sentence: 'The Application' }, this.#views, identities);
|
|
171
|
+
stage.views([application]);
|
|
172
|
+
stage.rendering(renderingPolicy(this.#declared.rendering));
|
|
173
|
+
const facts = checkOptions(this.#options, this.#declared);
|
|
174
|
+
const installed = installPlugins(this.#plugins ?? []);
|
|
175
|
+
const install = { descriptors: new Map(), extensions: new Map() };
|
|
176
|
+
const plugins = buildPlugins(installed, install);
|
|
177
|
+
stage.plugins(plugins);
|
|
178
|
+
const contributors = plugins.map((entry) => buildViews({ declares: true, sentence: pluginSentence(entry.identity) }, entry.views, identities));
|
|
179
|
+
const graph = buildGraph(this.#root, this.#globals, { ...install, plugins });
|
|
180
|
+
checkDeclarations([...graph.globals.inputs, ...collectInputs(graph.root)]);
|
|
181
|
+
stage.views([application, ...contributors]);
|
|
182
|
+
return { facts, graph, plugins };
|
|
183
|
+
}
|
|
66
184
|
/**
|
|
67
185
|
* The built graph as plain, frozen data. It applies every rule `run()` applies without a schema,
|
|
68
186
|
* in the order `run()` applies them, and throws `DeclarationError` when one fails. Validating a
|
|
@@ -70,11 +188,12 @@ class ApplicationBuilder {
|
|
|
70
188
|
* Nothing is cached: each call builds the graph anew.
|
|
71
189
|
*/
|
|
72
190
|
inspect() {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
191
|
+
const built = this.prepare({
|
|
192
|
+
plugins: () => undefined,
|
|
193
|
+
rendering: () => undefined,
|
|
194
|
+
views: () => undefined,
|
|
195
|
+
});
|
|
196
|
+
return inspectGraph(this.#name, built.graph, built.facts);
|
|
78
197
|
}
|
|
79
198
|
async run(options) {
|
|
80
199
|
let stderr = process.stderr;
|
|
@@ -83,106 +202,222 @@ class ApplicationBuilder {
|
|
|
83
202
|
let reportingFailed = false;
|
|
84
203
|
// A registry that could not be built reports through core's defaults, not through itself.
|
|
85
204
|
let registry = undefined;
|
|
205
|
+
// Faults a plugin raised beside the primary outcome, reported after it and never before it.
|
|
206
|
+
const faults = [];
|
|
207
|
+
// The failure this run reports as its primary outcome, so nothing reports it a second time.
|
|
208
|
+
let primary = noPrimary;
|
|
209
|
+
// One private controller per run, subscribed to the caller's signal at run entry.
|
|
210
|
+
const controller = new AbortController();
|
|
211
|
+
/**
|
|
212
|
+
* The bracket this run holds. It exists once the caller's own signal has been read, so a
|
|
213
|
+
* signal that is not an `AbortSignal` is reported through the failure path like any other.
|
|
214
|
+
*/
|
|
215
|
+
let signals = undefined;
|
|
216
|
+
// A cancelled run resolves its cancellation code whenever it ends after graph build.
|
|
217
|
+
// A declaration or internal failure raised before that ends the run with its own code instead.
|
|
218
|
+
let graphBuilt = false;
|
|
219
|
+
const cancellation = () => {
|
|
220
|
+
const reason = graphBuilt ? signals?.reason() : undefined;
|
|
221
|
+
return reason ? cancellationCode(reason) : undefined;
|
|
222
|
+
};
|
|
223
|
+
/**
|
|
224
|
+
* Every exit path of the run leaves through the removal below, the one place it is written,
|
|
225
|
+
* so no listener this run installed outlives it however the run ends.
|
|
226
|
+
*/
|
|
86
227
|
try {
|
|
87
|
-
const overrides = options?.host;
|
|
88
|
-
stderr = overrides?.stderr ?? stderr;
|
|
89
|
-
const host = captureHost(overrides, stderr);
|
|
90
|
-
output = new Output(host);
|
|
91
|
-
checkOptions(this.#options);
|
|
92
|
-
registry = buildFailures(this.#failures);
|
|
93
|
-
const graph = buildGraph(this.#root);
|
|
94
|
-
const inputs = { globals: graph.globals.inputs, locals: collectInputs(graph.root) };
|
|
95
|
-
const defaults = await prepareInputs(inputs, host);
|
|
96
|
-
const selected = await selectCommand(graph, { defaults, host });
|
|
97
|
-
await selected.dispatch({
|
|
98
|
-
host,
|
|
99
|
-
out: output.out,
|
|
100
|
-
passthrough: selected.passthrough,
|
|
101
|
-
values: selected.values,
|
|
102
|
-
});
|
|
103
|
-
// The fault check covers the same window the write accounting covers.
|
|
104
|
-
// A render failure an unawaited helper raised is still this invocation's failure.
|
|
105
|
-
await output.settle();
|
|
106
|
-
const fault = output.fault;
|
|
107
|
-
if (fault) {
|
|
108
|
-
// The action returned, so the renderer failure is this invocation's own failure.
|
|
109
|
-
throw new InternalError(`Rendering output failed: ${reasonOf(fault.cause)}`, fault.cause);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
catch (error) {
|
|
113
228
|
try {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
229
|
+
const overrides = options?.host;
|
|
230
|
+
stderr = overrides?.stderr ?? stderr;
|
|
231
|
+
const host = captureHost(overrides, stderr);
|
|
232
|
+
const invocationOutput = new Output(host, controller.signal);
|
|
233
|
+
output = invocationOutput;
|
|
234
|
+
// The policy is read inside the build, after the application's own overrides are published.
|
|
235
|
+
// A faulty rendering declaration then reports through the view the application listed.
|
|
236
|
+
let policy = {};
|
|
237
|
+
signals = bracketRun(controller, checkSignal(options?.signal));
|
|
238
|
+
const built = this.prepare({
|
|
239
|
+
plugins: (plugins) => {
|
|
240
|
+
invocationOutput.configure(policy, plugins.find((entry) => entry.theme !== undefined)?.theme ?? new Map());
|
|
241
|
+
},
|
|
242
|
+
rendering: (declared) => {
|
|
243
|
+
policy = { ...declared, ...renderingPolicy(options?.rendering) };
|
|
244
|
+
invocationOutput.configure(policy, new Map());
|
|
245
|
+
},
|
|
246
|
+
views: (value) => {
|
|
247
|
+
registry = value;
|
|
248
|
+
invocationOutput.useViews(value);
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
const { graph } = built;
|
|
252
|
+
const inputs = { globals: graph.globals.inputs, locals: collectInputs(graph.root) };
|
|
253
|
+
const defaults = await prepareInputs(inputs, host);
|
|
254
|
+
graphBuilt = true;
|
|
255
|
+
if (!controller.signal.aborted) {
|
|
256
|
+
/**
|
|
257
|
+
* The listeners the validated signals owner claimed. A build failure installs none, and
|
|
258
|
+
* neither does a run the caller had already cancelled: it touches the process not at all.
|
|
259
|
+
*/
|
|
260
|
+
signals.install(ownedSignals(built.plugins));
|
|
261
|
+
await runInvocation({
|
|
262
|
+
channel: (binding) => invocationOutput.channel(binding),
|
|
263
|
+
defaults,
|
|
264
|
+
facts: built.facts,
|
|
265
|
+
graph,
|
|
266
|
+
host,
|
|
267
|
+
name: this.#name,
|
|
268
|
+
out: output.out,
|
|
269
|
+
plugins: built.plugins,
|
|
270
|
+
report: (fault) => faults.push(fault),
|
|
271
|
+
route: (path) => {
|
|
272
|
+
invocationOutput.useRoute(path);
|
|
273
|
+
},
|
|
274
|
+
signal: controller.signal,
|
|
275
|
+
style: output.style,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
// The fault check covers the same window the write accounting covers.
|
|
279
|
+
// A render failure an unawaited helper raised is still this invocation's failure.
|
|
280
|
+
await output.settle();
|
|
281
|
+
const fault = output.fault;
|
|
282
|
+
if (fault) {
|
|
283
|
+
// The action returned, so the view failure is this invocation's own failure.
|
|
284
|
+
throw new InternalError(`Rendering output failed: ${reasonOf(fault.cause)}`, fault.cause);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
primary = error;
|
|
289
|
+
try {
|
|
290
|
+
const failure = toFailure(error);
|
|
291
|
+
code = failure.exitCode;
|
|
292
|
+
output ??= new Output(captureHost(undefined, stderr), controller.signal);
|
|
293
|
+
const writes = await output.settle();
|
|
294
|
+
if (writes.kind === 'ok' && !silenced(error, controller.signal, cancellation())) {
|
|
295
|
+
const report = describeFailure(registry ?? noViews, failure, output.context('stderr'));
|
|
296
|
+
if (report.kind === 'rendered') {
|
|
297
|
+
// The view owns the trailing newline; output resolves its marked text.
|
|
298
|
+
await output.report(report.text);
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
code = 1;
|
|
302
|
+
// `report.text` is core's default text, which already ends in `\n`.
|
|
303
|
+
await reportPlainly(stderr, `${report.text}Internal error: Rendering the failure failed: ${report.reason}\n`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
code = 1;
|
|
309
|
+
reportingFailed = true;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* A sequence that stopped on its own source reports the same way: the call the action never
|
|
314
|
+
* awaited observed nothing, and a failure the action let propagate is the primary outcome
|
|
315
|
+
* already, so the one it raised is not reported twice.
|
|
316
|
+
*/
|
|
317
|
+
for (const cause of output?.stopped ?? []) {
|
|
318
|
+
if (!carried(primary, cause)) {
|
|
319
|
+
faults.push(toFailure(cause));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// A plugin's own fault is reported after the primary outcome and turns a would-be 0 into 1.
|
|
323
|
+
// The primary outcome keeps its code, the way a view failure leaves it alone.
|
|
324
|
+
// It is reported the way the primary failure is, so an override answers its class.
|
|
325
|
+
for (const fault of faults) {
|
|
326
|
+
if (!silenced(fault, controller.signal, cancellation())) {
|
|
327
|
+
code = code === 0 ? 1 : code;
|
|
328
|
+
try {
|
|
329
|
+
const report = describeFailure(registry ?? noViews, fault, output?.context('stderr'));
|
|
330
|
+
if (report.kind === 'rendered') {
|
|
331
|
+
await output?.report(report.text);
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
code = 1;
|
|
335
|
+
await reportPlainly(stderr, `${report.text}Internal error: Rendering the failure failed: ${report.reason}\n`);
|
|
336
|
+
}
|
|
123
337
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// `report.text` is core's default text, which already ends in `\n`.
|
|
127
|
-
await reportPlainly(stderr, `${report.text}Internal error: Rendering the failure failed: ${report.reason}\n`);
|
|
338
|
+
catch {
|
|
339
|
+
reportingFailed = true;
|
|
128
340
|
}
|
|
129
341
|
}
|
|
130
342
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
343
|
+
if (output) {
|
|
344
|
+
const writes = await output.settle();
|
|
345
|
+
if (writes.kind === 'failed') {
|
|
346
|
+
code = 1;
|
|
347
|
+
reportingFailed = true;
|
|
348
|
+
}
|
|
349
|
+
output.dispose();
|
|
134
350
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const writes = await output.settle();
|
|
138
|
-
if (writes.kind === 'failed') {
|
|
139
|
-
code = 1;
|
|
140
|
-
reportingFailed = true;
|
|
351
|
+
if (reportingFailed) {
|
|
352
|
+
await reportPlainly(stderr, 'Internal error: Could not write invocation output.\n');
|
|
141
353
|
}
|
|
142
|
-
|
|
354
|
+
/**
|
|
355
|
+
* One rule orders every code: a cancelled run resolves its signal's code, and a broken
|
|
356
|
+
* failure view or destination in that run is reported as text without changing it. The
|
|
357
|
+
* signal decides the code whatever the action did afterward, so this reading comes last.
|
|
358
|
+
*/
|
|
359
|
+
code = cancellation() ?? code;
|
|
360
|
+
process.exitCode = code;
|
|
361
|
+
return code;
|
|
143
362
|
}
|
|
144
|
-
|
|
145
|
-
|
|
363
|
+
finally {
|
|
364
|
+
signals?.finish();
|
|
146
365
|
}
|
|
147
|
-
process.exitCode = code;
|
|
148
|
-
return code;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
/** The options slot holds one object literal, so a declaration that carries state is not one. */
|
|
152
|
-
function isPlainObject(value) {
|
|
153
|
-
if (value === null || typeof value !== 'object') {
|
|
154
|
-
return false;
|
|
155
366
|
}
|
|
156
|
-
const prototype = Object.getPrototypeOf(value);
|
|
157
|
-
return prototype === Object.prototype || prototype === null;
|
|
158
367
|
}
|
|
159
|
-
/**
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
368
|
+
/** Reject obsolete wiring before silently losing options that invocations depend on. */
|
|
369
|
+
function checkOptions(options, declared) {
|
|
370
|
+
if (options !== undefined) {
|
|
371
|
+
if (!isPlainObject(options)) {
|
|
372
|
+
throw new DeclarationError('The Application options must be an object. Supply an Application options object.');
|
|
373
|
+
}
|
|
374
|
+
if ('globals' in options) {
|
|
375
|
+
throw new DeclarationError('The Application options contain globals. Declare them with globalOption(name, config).');
|
|
376
|
+
}
|
|
377
|
+
if ('failures' in options) {
|
|
378
|
+
throw new DeclarationError('The Application options contain failures. Declare view overrides under views with override(key, view).');
|
|
379
|
+
}
|
|
380
|
+
// The root is every page's entry point, so it carries neither listing fact.
|
|
381
|
+
// A key that may not be there is a fault of the slot, so it answers with the slot's shape.
|
|
382
|
+
checkNoListingFacts('The Application', options);
|
|
170
383
|
}
|
|
384
|
+
return {
|
|
385
|
+
description: checkDescription('The Application', declared.description),
|
|
386
|
+
version: checkVersion(declared.version),
|
|
387
|
+
};
|
|
171
388
|
}
|
|
172
|
-
/**
|
|
173
|
-
* The runtime class behind the public constructor. It is generic so that an instance's `Globals`
|
|
174
|
-
* is the type of the value it holds, with `{}` standing in when there is none, which is what each
|
|
175
|
-
* signature of the constructor interface publishes.
|
|
176
|
-
*/
|
|
389
|
+
/** Constructor inference preserves the installed plugin tuple; globals start empty. */
|
|
177
390
|
class ApplicationDeclaration extends ApplicationBuilder {
|
|
178
391
|
constructor(name, options) {
|
|
179
|
-
// The options slot is read defensively, never inspected
|
|
180
|
-
//
|
|
181
|
-
|
|
392
|
+
// The options slot is read defensively, never inspected.
|
|
393
|
+
// An invalid value still yields `views` and the facts of some kind.
|
|
394
|
+
// `checkOptions` reports such a value at build.
|
|
395
|
+
// The root's own slot and core facts stay empty, because the Application checks its own slot.
|
|
396
|
+
// Its diagnostics name the Application rather than the root Command.
|
|
397
|
+
super(name, freshState({
|
|
398
|
+
deprecated: undefined,
|
|
399
|
+
description: undefined,
|
|
400
|
+
extensions: options?.extensions,
|
|
401
|
+
hidden: undefined,
|
|
402
|
+
name: null,
|
|
403
|
+
options: undefined,
|
|
404
|
+
}), {
|
|
405
|
+
declared: {
|
|
406
|
+
description: options?.description,
|
|
407
|
+
rendering: isPlainObject(options?.rendering)
|
|
408
|
+
? { ...options.rendering }
|
|
409
|
+
: options?.rendering,
|
|
410
|
+
version: options?.version,
|
|
411
|
+
},
|
|
412
|
+
globals: emptyGlobals(),
|
|
413
|
+
options,
|
|
414
|
+
plugins: options?.plugins,
|
|
415
|
+
// The read is loose because the slot is reachable from JavaScript with any value at all.
|
|
416
|
+
// An Application value passed here answers `views` with its own authoring method.
|
|
417
|
+
// The public `ApplicationOptions.views` stays exactly `readonly ViewOverride[]`.
|
|
418
|
+
// An options slot that is no plain object carries no override list, and its own rule reports it.
|
|
419
|
+
views: isPlainObject(options) ? options.views : undefined,
|
|
420
|
+
});
|
|
182
421
|
}
|
|
183
422
|
}
|
|
184
|
-
/**
|
|
185
|
-
* The public constructor takes a name and one options object. The globals type narrows to the
|
|
186
|
-
* supplied value, and the failure renderers configure the application the way its commands do.
|
|
187
|
-
*/
|
|
188
423
|
export const Application = ApplicationDeclaration;
|