@loomcli/core 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/application.d.ts +44 -31
- package/dist/application.js +122 -110
- package/dist/bindings.d.ts +26 -0
- package/dist/bindings.js +45 -0
- package/dist/chain.d.ts +11 -6
- package/dist/chain.js +28 -81
- package/dist/command.d.ts +161 -84
- package/dist/command.js +692 -369
- package/dist/errors.d.ts +7 -2
- package/dist/errors.js +9 -1
- package/dist/extension.d.ts +81 -23
- package/dist/extension.js +119 -54
- package/dist/facts.d.ts +5 -0
- package/dist/facts.js +10 -3
- package/dist/globals.d.ts +45 -12
- package/dist/globals.js +74 -18
- package/dist/index.d.ts +5 -3
- package/dist/index.js +1 -0
- package/dist/inspect.d.ts +37 -3
- package/dist/inspect.js +93 -6
- package/dist/locate.d.ts +41 -0
- package/dist/locate.js +121 -0
- package/dist/options.d.ts +73 -0
- package/dist/options.js +124 -27
- package/dist/output.d.ts +2 -0
- package/dist/output.js +5 -1
- package/dist/plugin.d.ts +107 -53
- package/dist/plugin.js +204 -66
- package/dist/sources.d.ts +56 -0
- package/dist/sources.js +249 -0
- package/dist/types.d.ts +70 -20
- package/dist/validation.d.ts +22 -8
- package/dist/validation.js +184 -77
- package/dist/view.d.ts +1 -1
- package/dist/view.js +2 -2
- package/package.json +3 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Drew Butler
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/application.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type { AfterAction, AttachmentConstraint, AfterArgument, AfterCommand, AfterResult, Command, CommandMethod, CommandState, ResultMethod } from './command.js';
|
|
1
|
+
import type { AfterAction, AttachmentConstraint, AfterArgument, AfterCommand, AfterResult, Command, CommandMethod, CommandNodeHandle, CommandState, ResultMethod } from './command.js';
|
|
2
2
|
import type { ApplicationEnvironment, applicationEnvironment } from './environment.js';
|
|
3
3
|
import type { ExtensionValue } from './extension.js';
|
|
4
4
|
import type { GlobalsState } from './globals.js';
|
|
5
5
|
import type { CommandGraph } from './inspect.js';
|
|
6
|
-
import type { Plugin } from './plugin.js';
|
|
6
|
+
import type { BuiltPlugin, Plugin } from './plugin.js';
|
|
7
7
|
import type { RenderingPolicy } from './rendering.js';
|
|
8
|
-
import type { Action, ArgumentConfig, ArgumentValue, declaredTypes, DeclaredTypes, DefaultConstraint, GlobalNameConstraint,
|
|
9
|
-
import type { ViewOverride } from './view.js';
|
|
8
|
+
import type { Action, ArgumentConfig, ArgumentValue, declaredTypes, DeclaredTypes, DefaultConstraint, GlobalNameConstraint, GlobalOmissionConstraint, PerValueConstraint, NameConstraint, ExitCode, OptionConfig, OptionValue, ResultViews, ResultViewsOf, RowViews, RunOptions, ValidateOmittedConstraint } from './types.js';
|
|
9
|
+
import type { ViewContributions, ViewOverride } from './view.js';
|
|
10
10
|
/**
|
|
11
11
|
* Every authoring call an Application can publish, beside `run()` and `name`, which always remain.
|
|
12
12
|
* An Application's type state is a subset of these, and each call removes the names it invalidates.
|
|
@@ -22,6 +22,23 @@ export interface ApplicationOptions<Plugins extends readonly Plugin[] = readonly
|
|
|
22
22
|
description?: string;
|
|
23
23
|
version?: string;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Everything an Application holds beside its root declaration and its globals, each part checked by
|
|
27
|
+
* the constructor or the call that set it.
|
|
28
|
+
*/
|
|
29
|
+
interface ApplicationConfig {
|
|
30
|
+
/** Whether the application's own `command()` or `action()` has run, which closes `globalOption()`. */
|
|
31
|
+
composed: boolean;
|
|
32
|
+
/** Each installed plugin's view contributions, in installation order. */
|
|
33
|
+
contributors: readonly ViewContributions[];
|
|
34
|
+
facts: ApplicationFacts;
|
|
35
|
+
/** The parent that claimed each node the graph holds, so one value attaches at one point. */
|
|
36
|
+
owners: ReadonlyMap<CommandNodeHandle, string | null>;
|
|
37
|
+
plugins: readonly BuiltPlugin[];
|
|
38
|
+
rendering: RenderingPolicy;
|
|
39
|
+
/** The application's own view overrides. */
|
|
40
|
+
views: ViewContributions;
|
|
41
|
+
}
|
|
25
42
|
/**
|
|
26
43
|
* The Application holds the unnamed root's declaration state and applies the same transitions a
|
|
27
44
|
* Command does, so each declaration call has one typed implementation and no builder to recover.
|
|
@@ -30,19 +47,17 @@ declare class ApplicationBuilder<Args, Options, Globals, State extends Applicati
|
|
|
30
47
|
#private;
|
|
31
48
|
readonly [applicationEnvironment]: ApplicationEnvironment<Globals, Plugins>;
|
|
32
49
|
readonly [declaredTypes]: DeclaredTypes<Args, Options, Globals, Result>;
|
|
33
|
-
constructor(name: string,
|
|
34
|
-
|
|
35
|
-
views: unknown;
|
|
36
|
-
options?: unknown;
|
|
37
|
-
plugins: Plugins | undefined;
|
|
50
|
+
constructor(name: string, declared: {
|
|
51
|
+
config: ApplicationConfig;
|
|
38
52
|
globals: GlobalsState<Globals>;
|
|
53
|
+
root: CommandState<Args, Options, Globals>;
|
|
39
54
|
});
|
|
40
55
|
get name(): string;
|
|
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<
|
|
56
|
+
argument<const Name extends string, const Config extends ArgumentConfig>(name: Name, config: Config & NameConstraint<Name> & NoInfer<DefaultConstraint<Config>> & NoInfer<PerValueConstraint<Config>> & NoInfer<ValidateOmittedConstraint<Config>>): Application<Args & Record<Name, ArgumentValue<Config>>, Options, Globals, AfterArgument<State>, Plugins, Result>;
|
|
57
|
+
option<const Name extends string, const Config extends OptionConfig>(name: Name, config: Config & NameConstraint<Name> & GlobalNameConstraint<Name, Globals> & NoInfer<DefaultConstraint<Config>> & NoInfer<PerValueConstraint<Config>> & NoInfer<ValidateOmittedConstraint<Config>>): Application<Args, Options & Record<Name, OptionValue<Config>>, Globals, State, Plugins, Result>;
|
|
43
58
|
globalOption<const Name extends string, const Config extends OptionConfig>(name: Name, config: Config & NameConstraint<Name> & (Name extends keyof Options ? {
|
|
44
59
|
'This option name is already declared as a local option': Name;
|
|
45
|
-
} : unknown) & NoInfer<DefaultConstraint<Config>> & NoInfer<
|
|
60
|
+
} : unknown) & NoInfer<DefaultConstraint<Config>> & NoInfer<PerValueConstraint<Config>> & NoInfer<ValidateOmittedConstraint<Config>> & NoInfer<GlobalOmissionConstraint<Config>>): Application<Args, Options, Globals & Record<Name, OptionValue<Config>>, State, Plugins, Result>;
|
|
46
61
|
/** Registering the action closes input authoring; extension configuration remains available. */
|
|
47
62
|
action(handler: Action<Args, Globals & Options, Result>): Application<Args, Options, Globals, AfterAction, Plugins, Result>;
|
|
48
63
|
/** A child arrives in any type state, because its own action is the call that finished it. */
|
|
@@ -69,27 +84,26 @@ declare class ApplicationBuilder<Args, Options, Globals, State extends Applicati
|
|
|
69
84
|
default?: string;
|
|
70
85
|
}): Application<Args, Options, Globals, State, Plugins, Result>;
|
|
71
86
|
extend(...values: readonly ExtensionValue<'command'>[]): Application<Args, Options, Globals, State, Plugins, Result>;
|
|
87
|
+
/** The globals table the root's options and every joining subtree meet. */
|
|
88
|
+
private table;
|
|
72
89
|
/**
|
|
73
|
-
* Root declaration calls preserve the Application configuration
|
|
74
|
-
* travels through this call: each method names its transition in its return type,
|
|
75
|
-
* wrapper publishes the same runtime value in exactly that state.
|
|
90
|
+
* Root declaration calls preserve the Application configuration, with the parts a call changed.
|
|
91
|
+
* The next state travels through this call: each method names its transition in its return type,
|
|
92
|
+
* and the wrapper publishes the same runtime value in exactly that state.
|
|
76
93
|
*/
|
|
77
94
|
private derive;
|
|
78
95
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
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.
|
|
96
|
+
* The graph build, which applies the rules no earlier moment could know: the root's
|
|
97
|
+
* finished-Command rules and every lifecycle hook's contribution. The application's own overrides
|
|
98
|
+
* are published first, so a build fault reports through them. The merged registry is published
|
|
99
|
+
* once the build has succeeded, so a build fault never resolves through a plugin's overrides.
|
|
86
100
|
*/
|
|
87
101
|
private prepare;
|
|
88
102
|
/**
|
|
89
|
-
* The built graph as plain, frozen data. It
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
103
|
+
* The built graph as plain, frozen data. It builds the graph as `run()` does and throws
|
|
104
|
+
* `DeclarationError` for the same build faults. Validating a declared default through its schema
|
|
105
|
+
* can be asynchronous, so that one rule stays in `run()`. Nothing is cached: each call builds the
|
|
106
|
+
* graph anew.
|
|
93
107
|
*/
|
|
94
108
|
inspect(): CommandGraph;
|
|
95
109
|
run(options?: RunOptions): Promise<ExitCode>;
|
|
@@ -107,11 +121,10 @@ interface ApplicationConstructor {
|
|
|
107
121
|
new (name: string): Application<{}, {}, {}, ApplicationMethod, readonly []>;
|
|
108
122
|
new <const Plugins extends readonly Plugin[] = readonly []>(name: string, options: ApplicationOptions<Plugins>): Application<{}, {}, {}, ApplicationMethod, Plugins>;
|
|
109
123
|
}
|
|
110
|
-
/** The
|
|
111
|
-
interface
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
version: unknown;
|
|
124
|
+
/** The core facts one Application declares, validated at construction and reported by `inspect()`. */
|
|
125
|
+
interface ApplicationFacts {
|
|
126
|
+
description: string | undefined;
|
|
127
|
+
version: string;
|
|
115
128
|
}
|
|
116
129
|
export declare const Application: ApplicationConstructor;
|
|
117
130
|
export {};
|
package/dist/application.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { runInvocation } from './chain.js';
|
|
2
|
-
import {
|
|
2
|
+
import { attachToRoot, childNode, buildGraph, checkDeclaredOptions, collectInputs, declareAction, declareExtensions, declareArgument, declareOption, declareResult, declareResultViews, freshState, isPortableName, layerOf, portableNameCorrection, } from './command.js';
|
|
3
3
|
import { DeclarationError, InternalError, reasonOf, toFailure } from './errors.js';
|
|
4
|
+
import { storeCommandLayers } from './extension.js';
|
|
4
5
|
import { checkDescription, checkNoListingFacts, checkVersion, isPlainObject } from './facts.js';
|
|
5
|
-
import { declareGlobalOption, emptyGlobals } from './globals.js';
|
|
6
|
+
import { declareGlobalOption, emptyGlobals, globalTable } from './globals.js';
|
|
6
7
|
import { captureHost } from './host.js';
|
|
7
8
|
import { inspectGraph } from './inspect.js';
|
|
8
9
|
import { coreViews } from './lanes.js';
|
|
9
10
|
import { Output, reportPlainly } from './output.js';
|
|
10
|
-
import {
|
|
11
|
+
import { installPlugins, ownedSignals, pluginSentence } from './plugin.js';
|
|
11
12
|
import { renderingPolicy } from './rendering.js';
|
|
12
13
|
import { bracketRun, cancellationCode, isCancellationEcho } from './signals.js';
|
|
13
14
|
import { captureConfig, checkDeclarations, prepareInputs } from './validation.js';
|
|
@@ -64,24 +65,13 @@ function checkSignal(signal) {
|
|
|
64
65
|
class ApplicationBuilder {
|
|
65
66
|
#name;
|
|
66
67
|
#root;
|
|
67
|
-
// The override list the constructor read out of the options slot, unexamined until build.
|
|
68
|
-
#views;
|
|
69
|
-
#plugins;
|
|
70
68
|
#globals;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// `inspect()` and `run()`.
|
|
74
|
-
#options;
|
|
75
|
-
// The facts the constructor read out of that slot, unexamined until build.
|
|
76
|
-
#declared;
|
|
77
|
-
constructor(name, root, config) {
|
|
78
|
-
this.#declared = config.declared;
|
|
79
|
-
this.#views = config.views;
|
|
69
|
+
#config;
|
|
70
|
+
constructor(name, declared) {
|
|
80
71
|
this.#name = name;
|
|
81
|
-
this.#
|
|
82
|
-
this.#
|
|
83
|
-
this.#
|
|
84
|
-
this.#root = root;
|
|
72
|
+
this.#config = declared.config;
|
|
73
|
+
this.#globals = declared.globals;
|
|
74
|
+
this.#root = declared.root;
|
|
85
75
|
}
|
|
86
76
|
get name() {
|
|
87
77
|
return this.#name;
|
|
@@ -100,29 +90,38 @@ class ApplicationBuilder {
|
|
|
100
90
|
kind: 'option',
|
|
101
91
|
name,
|
|
102
92
|
};
|
|
103
|
-
return this.derive(declareOption(this.#root, input));
|
|
93
|
+
return this.derive(declareOption(this.#root, input, this.table()));
|
|
104
94
|
}
|
|
105
95
|
globalOption(name, config) {
|
|
96
|
+
// The plugins' Commands attach at construction, so only the application's own calls close it.
|
|
97
|
+
if (this.#config.composed) {
|
|
98
|
+
throw new DeclarationError(`The Application declares global option "${name}" after command() or action(). Declare global options before attaching Commands or registering an action.`);
|
|
99
|
+
}
|
|
106
100
|
const input = {
|
|
107
101
|
config: captureConfig(config),
|
|
108
102
|
kind: 'option',
|
|
109
103
|
name,
|
|
110
104
|
};
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
});
|
|
105
|
+
const descriptors = new Map(this.#root.descriptors);
|
|
106
|
+
const globals = declareGlobalOption(this.#globals, input, descriptors);
|
|
107
|
+
const root = { ...this.#root, descriptors };
|
|
108
|
+
checkDeclaredOptions(root, globalTable(globals.inputs, this.#config.plugins));
|
|
109
|
+
checkDeclarations([input]);
|
|
110
|
+
return new ApplicationBuilder(this.#name, { config: this.#config, globals, root });
|
|
118
111
|
}
|
|
119
112
|
/** Registering the action closes input authoring; extension configuration remains available. */
|
|
120
113
|
action(handler) {
|
|
121
|
-
return this.derive(declareAction(this.#root, handler));
|
|
114
|
+
return this.derive(declareAction(this.#root, handler), { composed: true });
|
|
122
115
|
}
|
|
123
116
|
/** A child arrives in any type state, because its own action is the call that finished it. */
|
|
124
117
|
command(child) {
|
|
125
|
-
|
|
118
|
+
const scope = {
|
|
119
|
+
descriptors: new Map(this.#root.descriptors),
|
|
120
|
+
owners: new Map(this.#config.owners),
|
|
121
|
+
table: this.table(),
|
|
122
|
+
};
|
|
123
|
+
const root = attachToRoot(this.#root, childNode(null, child), scope);
|
|
124
|
+
return this.derive(root, { composed: true, owners: scope.owners });
|
|
126
125
|
}
|
|
127
126
|
/**
|
|
128
127
|
* The value the root action produces for its consumer. The type argument is stated by the
|
|
@@ -142,50 +141,38 @@ class ApplicationBuilder {
|
|
|
142
141
|
extend(...values) {
|
|
143
142
|
return this.derive(declareExtensions(this.#root, values));
|
|
144
143
|
}
|
|
144
|
+
/** The globals table the root's options and every joining subtree meet. */
|
|
145
|
+
table() {
|
|
146
|
+
return globalTable(this.#globals.inputs, this.#config.plugins);
|
|
147
|
+
}
|
|
145
148
|
/**
|
|
146
|
-
* Root declaration calls preserve the Application configuration
|
|
147
|
-
* travels through this call: each method names its transition in its return type,
|
|
148
|
-
* wrapper publishes the same runtime value in exactly that state.
|
|
149
|
+
* Root declaration calls preserve the Application configuration, with the parts a call changed.
|
|
150
|
+
* The next state travels through this call: each method names its transition in its return type,
|
|
151
|
+
* and the wrapper publishes the same runtime value in exactly that state.
|
|
149
152
|
*/
|
|
150
|
-
derive(root) {
|
|
151
|
-
return new ApplicationBuilder(this.#name,
|
|
152
|
-
declared: this.#declared,
|
|
153
|
-
globals: this.#globals,
|
|
154
|
-
options: this.#options,
|
|
155
|
-
plugins: this.#plugins,
|
|
156
|
-
views: this.#views,
|
|
157
|
-
});
|
|
153
|
+
derive(root, changed = {}) {
|
|
154
|
+
return new ApplicationBuilder(this.#name, { config: { ...this.#config, ...changed }, globals: this.#globals, root });
|
|
158
155
|
}
|
|
159
156
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
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.
|
|
157
|
+
* The graph build, which applies the rules no earlier moment could know: the root's
|
|
158
|
+
* finished-Command rules and every lifecycle hook's contribution. The application's own overrides
|
|
159
|
+
* are published first, so a build fault reports through them. The merged registry is published
|
|
160
|
+
* once the build has succeeded, so a build fault never resolves through a plugin's overrides.
|
|
167
161
|
*/
|
|
168
162
|
prepare(stage) {
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
stage.
|
|
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);
|
|
163
|
+
const { contributors, facts, plugins, rendering, views } = this.#config;
|
|
164
|
+
stage.views([views]);
|
|
165
|
+
stage.rendering(rendering);
|
|
177
166
|
stage.plugins(plugins);
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
checkDeclarations([...graph.globals.inputs, ...collectInputs(graph.root)]);
|
|
181
|
-
stage.views([application, ...contributors]);
|
|
167
|
+
const graph = buildGraph(this.#root, this.#globals, plugins);
|
|
168
|
+
stage.views([views, ...contributors]);
|
|
182
169
|
return { facts, graph, plugins };
|
|
183
170
|
}
|
|
184
171
|
/**
|
|
185
|
-
* The built graph as plain, frozen data. It
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
172
|
+
* The built graph as plain, frozen data. It builds the graph as `run()` does and throws
|
|
173
|
+
* `DeclarationError` for the same build faults. Validating a declared default through its schema
|
|
174
|
+
* can be asynchronous, so that one rule stays in `run()`. Nothing is cached: each call builds the
|
|
175
|
+
* graph anew.
|
|
189
176
|
*/
|
|
190
177
|
inspect() {
|
|
191
178
|
const built = this.prepare({
|
|
@@ -231,8 +218,7 @@ class ApplicationBuilder {
|
|
|
231
218
|
const host = captureHost(overrides, stderr);
|
|
232
219
|
const invocationOutput = new Output(host, controller.signal);
|
|
233
220
|
output = invocationOutput;
|
|
234
|
-
// The
|
|
235
|
-
// A faulty rendering declaration then reports through the view the application listed.
|
|
221
|
+
// The constructor validated the declared policy, which the build hands over after the overrides.
|
|
236
222
|
let policy = {};
|
|
237
223
|
signals = bracketRun(controller, checkSignal(options?.signal));
|
|
238
224
|
const built = this.prepare({
|
|
@@ -272,6 +258,7 @@ class ApplicationBuilder {
|
|
|
272
258
|
invocationOutput.useRoute(path);
|
|
273
259
|
},
|
|
274
260
|
signal: controller.signal,
|
|
261
|
+
sourceOut: output.sourceOut,
|
|
275
262
|
style: output.style,
|
|
276
263
|
});
|
|
277
264
|
}
|
|
@@ -366,58 +353,83 @@ class ApplicationBuilder {
|
|
|
366
353
|
}
|
|
367
354
|
}
|
|
368
355
|
/** Reject obsolete wiring before silently losing options that invocations depend on. */
|
|
369
|
-
function checkOptions(options
|
|
370
|
-
if (options
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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);
|
|
356
|
+
function checkOptions(options) {
|
|
357
|
+
if (options === undefined) {
|
|
358
|
+
return { description: undefined, version: checkVersion(undefined) };
|
|
359
|
+
}
|
|
360
|
+
if (!isPlainObject(options)) {
|
|
361
|
+
throw new DeclarationError('The Application options must be an object. Supply an Application options object.');
|
|
362
|
+
}
|
|
363
|
+
if ('globals' in options) {
|
|
364
|
+
throw new DeclarationError('The Application options contain globals. Declare them with globalOption(name, config).');
|
|
365
|
+
}
|
|
366
|
+
if ('failures' in options) {
|
|
367
|
+
throw new DeclarationError('The Application options contain failures. Declare view overrides under views with override(key, view).');
|
|
383
368
|
}
|
|
369
|
+
// The root is every page's entry point, so it carries neither listing fact.
|
|
370
|
+
// A key that may not be there is a fault of the slot, so it answers with the slot's shape.
|
|
371
|
+
checkNoListingFacts('The Application', options);
|
|
384
372
|
return {
|
|
385
|
-
description: checkDescription('The Application',
|
|
386
|
-
version: checkVersion(
|
|
373
|
+
description: checkDescription('The Application', options.description),
|
|
374
|
+
version: checkVersion(options.version),
|
|
387
375
|
};
|
|
388
376
|
}
|
|
377
|
+
/**
|
|
378
|
+
* Every rule `new Application(name, options)` applies, in the order it reads the slot: the
|
|
379
|
+
* application's own view overrides, the rendering policy, the options slot and its facts, the
|
|
380
|
+
* installed list and every rule between two plugins, the root's extension values, and then each
|
|
381
|
+
* plugin's Commands, which attach to the root first, in installation order and list order.
|
|
382
|
+
*/
|
|
383
|
+
function declareApplication(options) {
|
|
384
|
+
const slot = isPlainObject(options) ? options : undefined;
|
|
385
|
+
const identities = viewIdentities(coreViews);
|
|
386
|
+
const views = buildViews({ declares: false, sentence: 'The Application' }, slot?.views, identities);
|
|
387
|
+
const rendering = renderingPolicy(slot?.rendering);
|
|
388
|
+
const facts = checkOptions(options);
|
|
389
|
+
const installed = installPlugins(slot?.plugins ?? []);
|
|
390
|
+
const { plugins } = installed;
|
|
391
|
+
const contributors = plugins.map((entry) => buildViews({ declares: true, sentence: pluginSentence(entry.identity) }, entry.views, identities));
|
|
392
|
+
const table = globalTable([], plugins);
|
|
393
|
+
const descriptors = installed.descriptors;
|
|
394
|
+
const extensions = storeCommandLayers({
|
|
395
|
+
descriptors,
|
|
396
|
+
layers: [slot?.extensions],
|
|
397
|
+
subject: layerOf(null),
|
|
398
|
+
});
|
|
399
|
+
// The Application checks its own facts, so the root carries none.
|
|
400
|
+
// Its diagnostics name the Application rather than the root Command.
|
|
401
|
+
let root = freshState({
|
|
402
|
+
descriptors,
|
|
403
|
+
extensions,
|
|
404
|
+
facts: { deprecated: undefined, description: undefined, hidden: false },
|
|
405
|
+
name: null,
|
|
406
|
+
});
|
|
407
|
+
const owners = new Map();
|
|
408
|
+
for (const command of plugins.flatMap((entry) => entry.commands)) {
|
|
409
|
+
root = attachToRoot(root, command.node, {
|
|
410
|
+
descriptors: new Map(root.descriptors),
|
|
411
|
+
owners,
|
|
412
|
+
table,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
config: { composed: false, contributors, facts, owners, plugins, rendering, views },
|
|
417
|
+
globals: emptyGlobals(),
|
|
418
|
+
root,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
/** The application name is typed as a command at the prompt, so it answers to the portable rule. */
|
|
422
|
+
function checkApplicationName(name) {
|
|
423
|
+
if (!isPortableName(name)) {
|
|
424
|
+
throw new DeclarationError(`Application name "${String(name)}" is invalid. ${portableNameCorrection}`);
|
|
425
|
+
}
|
|
426
|
+
return name;
|
|
427
|
+
}
|
|
389
428
|
/** Constructor inference preserves the installed plugin tuple; globals start empty. */
|
|
390
429
|
class ApplicationDeclaration extends ApplicationBuilder {
|
|
391
430
|
constructor(name, options) {
|
|
392
|
-
// The
|
|
393
|
-
|
|
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
|
-
});
|
|
431
|
+
// The arguments evaluate in order, so the name is checked before any option is read.
|
|
432
|
+
super(checkApplicationName(name), declareApplication(options));
|
|
421
433
|
}
|
|
422
434
|
}
|
|
423
435
|
export const Application = ApplicationDeclaration;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** The two keys the binding rules read, whichever scope declared the option. */
|
|
2
|
+
interface BindingConfig {
|
|
3
|
+
readonly env?: unknown;
|
|
4
|
+
readonly multiple?: unknown;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The environment binding one option declares, or `undefined` when it declares none. A multiple
|
|
8
|
+
* option takes its list from the configuration source, so it cannot bind, and a bound name must be
|
|
9
|
+
* a variable name. `sentence` names the declaration at the start of the diagnostic.
|
|
10
|
+
*/
|
|
11
|
+
export declare function checkEnvBinding(sentence: string, config: BindingConfig): string | undefined;
|
|
12
|
+
/** An argument is identified by its place among bare tokens, so no variable can stand in for it. */
|
|
13
|
+
export declare function checkNoArgumentBinding(sentence: string, config: object): void;
|
|
14
|
+
/** One option bound to a variable, with the phrase a duplicate-variable diagnostic names it by. */
|
|
15
|
+
export interface BoundOption {
|
|
16
|
+
readonly site: string;
|
|
17
|
+
readonly variable: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The variables one scope binds, each to the phrase of the option that binds it. Within one
|
|
21
|
+
* invocation's scope a variable binds one option, so a second binder is a declaration error that
|
|
22
|
+
* names the first binder, in scope order, and then the second. `held` is what an enclosing scope
|
|
23
|
+
* already binds, such as the globals table under a Command's own options.
|
|
24
|
+
*/
|
|
25
|
+
export declare function claimVariables(bound: readonly BoundOption[], held?: ReadonlyMap<string, string>): ReadonlyMap<string, string>;
|
|
26
|
+
export {};
|
package/dist/bindings.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { DeclarationError } from './errors.js';
|
|
2
|
+
/** A variable name: a letter or an underscore, then letters, digits, or underscores. */
|
|
3
|
+
const variableName = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
4
|
+
/**
|
|
5
|
+
* The environment binding one option declares, or `undefined` when it declares none. A multiple
|
|
6
|
+
* option takes its list from the configuration source, so it cannot bind, and a bound name must be
|
|
7
|
+
* a variable name. `sentence` names the declaration at the start of the diagnostic.
|
|
8
|
+
*/
|
|
9
|
+
export function checkEnvBinding(sentence, config) {
|
|
10
|
+
const env = config.env;
|
|
11
|
+
if (env === undefined) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
if (config.multiple === true) {
|
|
15
|
+
throw new DeclarationError(`${sentence} is a multiple option and declares env. Remove env; a list comes from the configuration source.`);
|
|
16
|
+
}
|
|
17
|
+
if (typeof env !== 'string' || !variableName.test(env)) {
|
|
18
|
+
const quoted = typeof env === 'string' ? ` "${env}"` : '';
|
|
19
|
+
throw new DeclarationError(`${sentence} env${quoted} is not a variable name. Use a letter or an underscore, then letters, digits, or underscores.`);
|
|
20
|
+
}
|
|
21
|
+
return env;
|
|
22
|
+
}
|
|
23
|
+
/** An argument is identified by its place among bare tokens, so no variable can stand in for it. */
|
|
24
|
+
export function checkNoArgumentBinding(sentence, config) {
|
|
25
|
+
if ('env' in config) {
|
|
26
|
+
throw new DeclarationError(`${sentence} declares env, which applies to options alone. Remove it.`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The variables one scope binds, each to the phrase of the option that binds it. Within one
|
|
31
|
+
* invocation's scope a variable binds one option, so a second binder is a declaration error that
|
|
32
|
+
* names the first binder, in scope order, and then the second. `held` is what an enclosing scope
|
|
33
|
+
* already binds, such as the globals table under a Command's own options.
|
|
34
|
+
*/
|
|
35
|
+
export function claimVariables(bound, held = new Map()) {
|
|
36
|
+
const claimed = new Map(held);
|
|
37
|
+
for (const { site, variable } of bound) {
|
|
38
|
+
const owner = claimed.get(variable);
|
|
39
|
+
if (owner !== undefined) {
|
|
40
|
+
throw new DeclarationError(`Variable "${variable}" is bound by ${owner} and ${site}. Bind each variable to one option.`);
|
|
41
|
+
}
|
|
42
|
+
claimed.set(variable, site);
|
|
43
|
+
}
|
|
44
|
+
return claimed;
|
|
45
|
+
}
|
package/dist/chain.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { BuiltGraph } from './command.js';
|
|
2
2
|
import type { LoomError } from './errors.js';
|
|
3
3
|
import type { CommandGraph, CommandNode } from './inspect.js';
|
|
4
|
-
import type { BuiltPlugin, PluginOptions, PluginOptionValues } from './plugin.js';
|
|
4
|
+
import type { BuiltPlugin, PluginOptions, PluginOptionSpellings, PluginOptionValues } from './plugin.js';
|
|
5
5
|
import type { ContextualStyle } from './style.js';
|
|
6
6
|
import type { ActionChannel, Host, OpenResult, Out, Request, ResultBinding } from './types.js';
|
|
7
7
|
import type { DefaultValues } from './validation.js';
|
|
@@ -13,14 +13,17 @@ type ChainOutcome = 'cancelled' | 'dispatched' | 'taken-over';
|
|
|
13
13
|
/**
|
|
14
14
|
* What one middleware receives. `graph` is the frozen graph `inspect()` returns, built once for the
|
|
15
15
|
* run, and `command` is the routed node inside it. `options` holds this plugin's own option values
|
|
16
|
-
* and never another plugin's or the application's globals
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
16
|
+
* and never another plugin's or the application's globals, and `spellings` holds the spelling that
|
|
17
|
+
* supplied each of them as a token, which a filled or defaulted option never has. `request` is the
|
|
18
|
+
* routed Command's invocation, parsed and validated ahead of the chain, and `null` while core holds
|
|
19
|
+
* a fault and on a group. `view` names the view the result renders through: it reads as the
|
|
20
|
+
* declaration's default until a middleware assigns one, and as `null` on a Command that declares
|
|
21
|
+
* none. The last assignment before the dispatch boundary wins, and one made after it changes
|
|
22
|
+
* nothing.
|
|
21
23
|
*/
|
|
22
24
|
interface MiddlewareContext<Options extends PluginOptions = PluginOptions> {
|
|
23
25
|
readonly options: PluginOptionValues<Options>;
|
|
26
|
+
readonly spellings: PluginOptionSpellings<Options>;
|
|
24
27
|
readonly graph: CommandGraph;
|
|
25
28
|
readonly command: CommandNode;
|
|
26
29
|
readonly request: Request | null;
|
|
@@ -49,6 +52,8 @@ interface Invocation {
|
|
|
49
52
|
* receives the channel the results lane builds for the Command that was routed.
|
|
50
53
|
*/
|
|
51
54
|
out: Out<OpenResult>;
|
|
55
|
+
/** The channel a configuration source receives, whose results call names the source. */
|
|
56
|
+
sourceOut: Out<OpenResult>;
|
|
52
57
|
plugins: readonly BuiltPlugin[];
|
|
53
58
|
/** A fault reported after the primary outcome, which turns a would-be 0 into 1. */
|
|
54
59
|
report: (fault: LoomError) => void;
|