@sous-io/plugin-api 0.0.1 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +96 -2
- package/dist/command-variables.d.ts +13 -0
- package/dist/command-variables.js +28 -0
- package/dist/config.d.ts +170 -0
- package/dist/config.js +1 -0
- package/dist/context.d.ts +159 -0
- package/dist/context.js +1 -0
- package/dist/entities.d.ts +60 -0
- package/dist/entities.js +1 -0
- package/dist/errors.d.ts +111 -0
- package/dist/errors.js +138 -0
- package/dist/events.d.ts +90 -0
- package/dist/events.js +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +14 -0
- package/dist/intents.d.ts +151 -0
- package/dist/intents.js +1 -0
- package/dist/json.d.ts +84 -0
- package/dist/json.js +7 -0
- package/dist/node.d.ts +81 -0
- package/dist/node.js +1 -0
- package/dist/outputs.d.ts +193 -0
- package/dist/outputs.js +286 -0
- package/dist/plugin.d.ts +144 -0
- package/dist/plugin.js +18 -0
- package/dist/prompter.d.ts +66 -0
- package/dist/prompter.js +32 -0
- package/dist/registry.d.ts +91 -0
- package/dist/registry.js +1 -0
- package/dist/report-styles.d.ts +220 -0
- package/dist/report-styles.js +60 -0
- package/dist/reporter.d.ts +270 -0
- package/dist/reporter.js +388 -0
- package/dist/schema.d.ts +131 -0
- package/dist/schema.js +211 -0
- package/dist/services.d.ts +68 -0
- package/dist/services.js +1 -0
- package/dist/timing.d.ts +31 -0
- package/dist/timing.js +20 -0
- package/package.json +33 -5
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { BuildOptions, BuiltConfig } from "./config.js";
|
|
2
|
+
import type { EntityReader } from "./entities.js";
|
|
3
|
+
import type { EventFor, EventPayload } from "./events.js";
|
|
4
|
+
import type { IntentCatalog, IntentRequest, SubmitOptions } from "./intents.js";
|
|
5
|
+
import type { JsonObject } from "./json.js";
|
|
6
|
+
import type { Prompter } from "./prompter.js";
|
|
7
|
+
import type { IntentInput, IntentName, IntentReturn } from "./registry.js";
|
|
8
|
+
import type { Reporter } from "./reporter.js";
|
|
9
|
+
import type { ServiceReader } from "./services.js";
|
|
10
|
+
import type { Timings } from "./timing.js";
|
|
11
|
+
/**
|
|
12
|
+
* The project a sous process serves: the one found from its working directory. Its root is the
|
|
13
|
+
* directory holding the `.sous/` directory the config file was found in.
|
|
14
|
+
*/
|
|
15
|
+
export interface ProjectInfo {
|
|
16
|
+
/** The project root. */
|
|
17
|
+
readonly root: string;
|
|
18
|
+
/** The `.sous/` directory. */
|
|
19
|
+
readonly sousDir: string;
|
|
20
|
+
/** The config file, when one was found. */
|
|
21
|
+
readonly configPath: string | undefined;
|
|
22
|
+
/** The `conf.d` directory. */
|
|
23
|
+
readonly confDir: string;
|
|
24
|
+
}
|
|
25
|
+
/** The environment variables sous was started with. Read them here, never from `process.env`. */
|
|
26
|
+
export type Environment = Readonly<Record<string, string | undefined>>;
|
|
27
|
+
/** The application the engine runs: sous itself, or another tool built on the engine. */
|
|
28
|
+
export interface AppInfo {
|
|
29
|
+
readonly name: string;
|
|
30
|
+
readonly version: string;
|
|
31
|
+
}
|
|
32
|
+
/** The chain a context belongs to. */
|
|
33
|
+
export interface ChainInfo {
|
|
34
|
+
/** Counts chains in this process. */
|
|
35
|
+
readonly id: number;
|
|
36
|
+
/** The intent the chain was submitted with. */
|
|
37
|
+
readonly intent: string;
|
|
38
|
+
readonly parallel: boolean;
|
|
39
|
+
}
|
|
40
|
+
/** One loaded plugin, as front ends and `plugins show` see it. */
|
|
41
|
+
export interface PluginInfo {
|
|
42
|
+
readonly name: string;
|
|
43
|
+
readonly version: string | undefined;
|
|
44
|
+
/** Whether it is active in the current config. */
|
|
45
|
+
readonly active: boolean;
|
|
46
|
+
readonly builtIn: boolean;
|
|
47
|
+
/** The plugin that brought it in, or `plugins.load`. */
|
|
48
|
+
readonly loadedBy: string | undefined;
|
|
49
|
+
/** What it registered, for `plugins show`. */
|
|
50
|
+
readonly registrations: ReadonlyArray<{
|
|
51
|
+
readonly kind: string;
|
|
52
|
+
readonly name: string;
|
|
53
|
+
}>;
|
|
54
|
+
/** Its options from `plugins.options` in the current config, if any. */
|
|
55
|
+
readonly options: JsonObject | undefined;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The engine, as plugins see it: what front ends and background intents use.
|
|
59
|
+
*/
|
|
60
|
+
export interface EngineHandle {
|
|
61
|
+
/** The config as of the latest rebuild. */
|
|
62
|
+
readonly config: BuiltConfig;
|
|
63
|
+
/** The application name and version. */
|
|
64
|
+
readonly app: AppInfo;
|
|
65
|
+
/** The intents front ends generate commands and tools from. */
|
|
66
|
+
readonly intents: IntentCatalog;
|
|
67
|
+
/** Every timing so far (`--timings`). */
|
|
68
|
+
readonly timings: Timings;
|
|
69
|
+
/** Where engine-level messages go (budget warnings, build warnings). */
|
|
70
|
+
readonly reporter: Reporter;
|
|
71
|
+
/**
|
|
72
|
+
* The error of the latest config build, when it failed (`undefined` once one succeeds). The
|
|
73
|
+
* config is then the partial one that build reached; only intents that declare
|
|
74
|
+
* `toleratesConfigErrors` run, and a front end should report this error for anything else.
|
|
75
|
+
*/
|
|
76
|
+
readonly configError: unknown;
|
|
77
|
+
/** Sets where engine-level messages go; messages kept so far are sent there first. */
|
|
78
|
+
setReporter(reporter: Reporter): void;
|
|
79
|
+
/** Submits a chain; resolves with the intent's outputs. */
|
|
80
|
+
submit<N extends IntentName>(request: IntentRequest<N>, options?: SubmitOptions): Promise<IntentReturn<N>>;
|
|
81
|
+
/** Builds a separate "what if" config, never replacing the real one. */
|
|
82
|
+
buildConfig(options?: BuildOptions): Promise<BuiltConfig>;
|
|
83
|
+
/** Every loaded plugin. */
|
|
84
|
+
plugins(): PluginInfo[];
|
|
85
|
+
/** Resolves when every background intent has returned. */
|
|
86
|
+
whenBackgroundIntentsReturn(): Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* What a handler, a listener or an entity method gets (`ctx`). Inside a chain, `run` starts another
|
|
90
|
+
* intent as part of the same chain.
|
|
91
|
+
*/
|
|
92
|
+
export interface Context {
|
|
93
|
+
/** The plugin whose code was given this context. */
|
|
94
|
+
readonly plugin: string;
|
|
95
|
+
/** The config this chain started with (for a background intent, the latest). */
|
|
96
|
+
readonly config: BuiltConfig;
|
|
97
|
+
readonly entities: EntityReader;
|
|
98
|
+
readonly services: ServiceReader;
|
|
99
|
+
/** Where this chain's output goes: the front end that submitted it. */
|
|
100
|
+
readonly reporter: Reporter;
|
|
101
|
+
/** Where this chain's questions go: the front end that submitted it. */
|
|
102
|
+
readonly prompter: Prompter;
|
|
103
|
+
readonly project: ProjectInfo | undefined;
|
|
104
|
+
readonly env: Environment;
|
|
105
|
+
/** The front end's global argument values (`debug`, `logLevel`, plugin-added ones). */
|
|
106
|
+
readonly globals: Readonly<Record<string, unknown>>;
|
|
107
|
+
/**
|
|
108
|
+
* The working directory the front end gave the chain: the CLI gives the process's, the MCP
|
|
109
|
+
* server the project root. Use it instead of `process.cwd()`. Default: the engine's.
|
|
110
|
+
*/
|
|
111
|
+
readonly cwd: string;
|
|
112
|
+
/** The chain, or `undefined` outside one (a background intent). */
|
|
113
|
+
readonly chain: ChainInfo | undefined;
|
|
114
|
+
readonly engine: EngineHandle;
|
|
115
|
+
/** This plugin's own options from `plugins.options`. */
|
|
116
|
+
options<T = JsonObject>(): T | undefined;
|
|
117
|
+
/**
|
|
118
|
+
* Runs an intent as part of this chain, immediately, and returns its outputs; `toPlain()` gives
|
|
119
|
+
* its result as a plain object. Outside a chain it submits a new chain instead.
|
|
120
|
+
*/
|
|
121
|
+
run<N extends IntentName>(intent: N, input?: IntentInput<N> | Record<string, unknown>): Promise<IntentReturn<N>>;
|
|
122
|
+
/** Fires an event and waits for every listener. */
|
|
123
|
+
emit<N extends string>(name: N, payload?: EventPayload<N>): Promise<EventFor<N>>;
|
|
124
|
+
/** Says this chain changed the config, so it is rebuilt before the next chain starts. */
|
|
125
|
+
configChanged(): void;
|
|
126
|
+
/**
|
|
127
|
+
* Rebuilds the config now, for the rest of this chain: later `ctx.config`, `ctx.services`
|
|
128
|
+
* (`config` services are made again), `ctx.entities` and `ctx.run` calls see the new one. The
|
|
129
|
+
* new config becomes the engine's, and `config.built` fires, as for any rebuild. Resolves with
|
|
130
|
+
* it; a failed build rejects, and the chain keeps its old config.
|
|
131
|
+
*
|
|
132
|
+
* Only a series chain may do this: a parallel chain, a background intent, a `config.built`
|
|
133
|
+
* listener or code outside any chain gets a `SousError`.
|
|
134
|
+
*
|
|
135
|
+
* ```ts
|
|
136
|
+
* async handle(input, ctx) {
|
|
137
|
+
* await writeSubscription(ctx, input); // changes a config file
|
|
138
|
+
* await ctx.refreshConfig(); // the build below sees it
|
|
139
|
+
*
|
|
140
|
+
* return ctx.run("build", {}); // the build's outputs become this intent's
|
|
141
|
+
* }
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
refreshConfig(options?: RefreshConfigOptions): Promise<BuiltConfig>;
|
|
145
|
+
}
|
|
146
|
+
/** Options for `ctx.refreshConfig()`. */
|
|
147
|
+
export interface RefreshConfigOptions {
|
|
148
|
+
/**
|
|
149
|
+
* Where to look for the project before rebuilding, for an intent that has just set one up
|
|
150
|
+
* (`sous init`). The engine takes the project found at or above this directory when it differs
|
|
151
|
+
* from the one it serves; without a project there, nothing changes.
|
|
152
|
+
*
|
|
153
|
+
* ```ts
|
|
154
|
+
* await writeProjectFiles(root);
|
|
155
|
+
* await ctx.refreshConfig({ locateProjectFrom: root }); // the build below serves `root`
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
locateProjectFrom?: string;
|
|
159
|
+
}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ProjectInfo } from "./context.js";
|
|
2
|
+
import type { PluginDefinition } from "./plugin.js";
|
|
3
|
+
import type { EntityKind, EntityType } from "./registry.js";
|
|
4
|
+
import type { SousSchema } from "./schema.js";
|
|
5
|
+
/** Options for defining an entity kind. */
|
|
6
|
+
export interface EntityKindOptions {
|
|
7
|
+
/** What the kind is, for `plugins show`. */
|
|
8
|
+
description?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* One entity, with where it came from.
|
|
12
|
+
*
|
|
13
|
+
* The engine compares entities by id, source and a hash of their fields that are not functions,
|
|
14
|
+
* so a contributor that read an entity re-runs when it appears, disappears or its data changes.
|
|
15
|
+
*/
|
|
16
|
+
export interface EntityRecord<E = unknown> {
|
|
17
|
+
readonly kind: string;
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly entry: E;
|
|
20
|
+
/** The plugin that added it. */
|
|
21
|
+
readonly plugin: string;
|
|
22
|
+
/** The contributor that added it, when the config created it. */
|
|
23
|
+
readonly contributor?: string;
|
|
24
|
+
}
|
|
25
|
+
/** Reading entities, after the config is built. */
|
|
26
|
+
export interface EntityReader {
|
|
27
|
+
/** One entity, or `undefined`. */
|
|
28
|
+
get<K extends EntityKind>(kind: K, id: string): EntityType<K> | undefined;
|
|
29
|
+
/** Every entity of a kind, in the order they were added. */
|
|
30
|
+
list<K extends EntityKind>(kind: K): EntityType<K>[];
|
|
31
|
+
/** Whether an entity exists. */
|
|
32
|
+
has(kind: string, id: string): boolean;
|
|
33
|
+
/** Every entity of a kind, with where each came from. */
|
|
34
|
+
describe<K extends EntityKind>(kind: K): EntityRecord<EntityType<K>>[];
|
|
35
|
+
/** Every kind defined so far. */
|
|
36
|
+
kinds(): string[];
|
|
37
|
+
}
|
|
38
|
+
/** A `config-key` entity: a key the config may hold, with its schema and defaults. */
|
|
39
|
+
export interface ConfigKeyEntity {
|
|
40
|
+
/** The key, as a path: `diagrams`, or `tools.claude` for a key inside another plugin's. */
|
|
41
|
+
id: string;
|
|
42
|
+
/** Checks the value and fills in its defaults when the finished config is checked. */
|
|
43
|
+
schema: SousSchema;
|
|
44
|
+
/** What the key is for. */
|
|
45
|
+
description?: string;
|
|
46
|
+
}
|
|
47
|
+
/** What a plugin locator is told. */
|
|
48
|
+
export interface PluginLocateContext {
|
|
49
|
+
readonly project: ProjectInfo | undefined;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A `plugin-locator` entity: turns a specifier from `plugins.load` into a module to import, such as
|
|
53
|
+
* a recipe's downloaded bundle, or a Yarn Plug'n'Play location. Locators are asked in plugin order;
|
|
54
|
+
* the first answer wins, and when none answers, Node's own resolution from the project is used.
|
|
55
|
+
*/
|
|
56
|
+
export interface PluginLocatorEntity {
|
|
57
|
+
id: string;
|
|
58
|
+
/** A path or URL to import, a plugin definition, or `undefined` to pass. */
|
|
59
|
+
locate(specifier: string, context: PluginLocateContext): string | URL | PluginDefinition | undefined | Promise<string | URL | PluginDefinition | undefined>;
|
|
60
|
+
}
|
package/dist/entities.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error classes every plugin throws and every front end recognizes.
|
|
3
|
+
*
|
|
4
|
+
* A front end turns an error into a message and an exit code. Errors are recognized by their
|
|
5
|
+
* `sousErrorKind` as well as by class, so two copies of this package never confuse a front end.
|
|
6
|
+
*/
|
|
7
|
+
/** The kind of a sous error, one per class. */
|
|
8
|
+
export type SousErrorKind = "sous" | "config" | "input" | "non-interactive" | "cancelled" | "loop" | "plugin" | "conflict" | "not-found" | "budget";
|
|
9
|
+
/** Options every sous error accepts. */
|
|
10
|
+
export interface SousErrorOptions {
|
|
11
|
+
/** The error that caused this one. */
|
|
12
|
+
cause?: unknown;
|
|
13
|
+
/** More lines that explain it or say how to fix it, shown under the message. */
|
|
14
|
+
details?: readonly string[];
|
|
15
|
+
/** The exit code a CLI should use. Default 1. */
|
|
16
|
+
exitCode?: number;
|
|
17
|
+
}
|
|
18
|
+
/** The base of every error sous and its plugins throw on purpose. */
|
|
19
|
+
export declare class SousError extends Error {
|
|
20
|
+
/** Identifies sous errors across copies of this package. */
|
|
21
|
+
readonly sousErrorKind: SousErrorKind;
|
|
22
|
+
/** More lines that explain the error or say how to fix it. */
|
|
23
|
+
readonly details: readonly string[];
|
|
24
|
+
/** The exit code a CLI should use. */
|
|
25
|
+
readonly exitCode: number;
|
|
26
|
+
constructor(message: string, options?: SousErrorOptions);
|
|
27
|
+
}
|
|
28
|
+
/** The config could not be built, or is not valid. */
|
|
29
|
+
export declare class ConfigError extends SousError {
|
|
30
|
+
readonly sousErrorKind: SousErrorKind;
|
|
31
|
+
}
|
|
32
|
+
/** One problem with an intent's input, or a config value: where, and what. */
|
|
33
|
+
export interface InputIssue {
|
|
34
|
+
/** Where, as a path string (`ref`, `targets[2].id`); `""` for the value itself. */
|
|
35
|
+
path: string;
|
|
36
|
+
message: string;
|
|
37
|
+
}
|
|
38
|
+
/** An intent's input did not match its schema. */
|
|
39
|
+
export declare class InputError extends SousError {
|
|
40
|
+
readonly sousErrorKind: SousErrorKind;
|
|
41
|
+
/** The intent whose input was wrong. */
|
|
42
|
+
readonly intent: string;
|
|
43
|
+
/** Every problem found. */
|
|
44
|
+
readonly issues: readonly InputIssue[];
|
|
45
|
+
constructor(intent: string, issues: readonly InputIssue[], options?: SousErrorOptions);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A question could not be asked, because nothing can answer it in this run. The message
|
|
49
|
+
* names the question and the flag or argument that answers it ahead of time.
|
|
50
|
+
*/
|
|
51
|
+
export declare class NonInteractiveError extends SousError {
|
|
52
|
+
readonly sousErrorKind: SousErrorKind;
|
|
53
|
+
/** The question that could not be asked. */
|
|
54
|
+
readonly question: string;
|
|
55
|
+
/** The flag or argument that answers it, such as `--yes`. */
|
|
56
|
+
readonly answeredBy: string | undefined;
|
|
57
|
+
constructor(question: string, answeredBy: string | undefined, options?: SousErrorOptions);
|
|
58
|
+
}
|
|
59
|
+
/** A listener cancelled an intent or an event on purpose; `reason` says why. */
|
|
60
|
+
export declare class CancelledError extends SousError {
|
|
61
|
+
readonly sousErrorKind: SousErrorKind;
|
|
62
|
+
/** What was cancelled: an intent or an event name. */
|
|
63
|
+
readonly target: string;
|
|
64
|
+
/** The reason the listener gave. */
|
|
65
|
+
readonly reason: string;
|
|
66
|
+
/** The plugin whose listener cancelled it. */
|
|
67
|
+
readonly cancelledBy: string | undefined;
|
|
68
|
+
constructor(target: string, reason: string, cancelledBy?: string, options?: SousErrorOptions);
|
|
69
|
+
}
|
|
70
|
+
/** The engine found a loop: the config did not settle, or calls nested past a limit. */
|
|
71
|
+
export declare class LoopError extends SousError {
|
|
72
|
+
readonly sousErrorKind: SousErrorKind;
|
|
73
|
+
/** Which limit was passed, as named under the `limits` config key. */
|
|
74
|
+
readonly limit: string;
|
|
75
|
+
constructor(limit: string, message: string, options?: SousErrorOptions);
|
|
76
|
+
}
|
|
77
|
+
/** A plugin could not be found, loaded or registered, or a plugin it depends on is missing. */
|
|
78
|
+
export declare class PluginError extends SousError {
|
|
79
|
+
readonly sousErrorKind: SousErrorKind;
|
|
80
|
+
/** The plugin, by name or by the specifier it was asked for under. */
|
|
81
|
+
readonly plugin: string;
|
|
82
|
+
constructor(plugin: string, message: string, options?: SousErrorOptions);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Two plugins claim the same thing (a service replacement, an intent, a CLI command) and the
|
|
86
|
+
* config does not pick one. The message names both and the config key that picks one.
|
|
87
|
+
*/
|
|
88
|
+
export declare class ConflictError extends SousError {
|
|
89
|
+
readonly sousErrorKind: SousErrorKind;
|
|
90
|
+
/** What is claimed twice. */
|
|
91
|
+
readonly subject: string;
|
|
92
|
+
/** The plugins claiming it. */
|
|
93
|
+
readonly plugins: readonly string[];
|
|
94
|
+
constructor(subject: string, plugins: readonly string[], message: string, options?: SousErrorOptions);
|
|
95
|
+
}
|
|
96
|
+
/** No plugin provides the intent, service, entity or front end asked for. */
|
|
97
|
+
export declare class NotFoundError extends SousError {
|
|
98
|
+
readonly sousErrorKind: SousErrorKind;
|
|
99
|
+
/** What kind of thing was asked for: `intent`, `service`, `entity`, `front end`. */
|
|
100
|
+
readonly thing: string;
|
|
101
|
+
/** Its name or id. */
|
|
102
|
+
readonly id: string;
|
|
103
|
+
constructor(thing: string, id: string, message?: string, options?: SousErrorOptions);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Whether a value is a sous error, by class or by its `sousErrorKind` (so a second copy of this
|
|
107
|
+
* package is still recognized).
|
|
108
|
+
*/
|
|
109
|
+
export declare function isSousError(value: unknown): value is SousError;
|
|
110
|
+
/** Whether a value is a sous error of one kind. */
|
|
111
|
+
export declare function isSousErrorOfKind(value: unknown, kind: SousErrorKind): value is SousError;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error classes every plugin throws and every front end recognizes.
|
|
3
|
+
*
|
|
4
|
+
* A front end turns an error into a message and an exit code. Errors are recognized by their
|
|
5
|
+
* `sousErrorKind` as well as by class, so two copies of this package never confuse a front end.
|
|
6
|
+
*/
|
|
7
|
+
/** The base of every error sous and its plugins throw on purpose. */
|
|
8
|
+
export class SousError extends Error {
|
|
9
|
+
/** Identifies sous errors across copies of this package. */
|
|
10
|
+
sousErrorKind = "sous";
|
|
11
|
+
/** More lines that explain the error or say how to fix it. */
|
|
12
|
+
details;
|
|
13
|
+
/** The exit code a CLI should use. */
|
|
14
|
+
exitCode;
|
|
15
|
+
constructor(message, options = {}) {
|
|
16
|
+
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
17
|
+
this.name = new.target.name;
|
|
18
|
+
this.details = options.details ?? [];
|
|
19
|
+
this.exitCode = options.exitCode ?? 1;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** The config could not be built, or is not valid. */
|
|
23
|
+
export class ConfigError extends SousError {
|
|
24
|
+
sousErrorKind = "config";
|
|
25
|
+
}
|
|
26
|
+
/** An intent's input did not match its schema. */
|
|
27
|
+
export class InputError extends SousError {
|
|
28
|
+
sousErrorKind = "input";
|
|
29
|
+
/** The intent whose input was wrong. */
|
|
30
|
+
intent;
|
|
31
|
+
/** Every problem found. */
|
|
32
|
+
issues;
|
|
33
|
+
constructor(intent, issues, options = {}) {
|
|
34
|
+
super(`The input to "${intent}" is not valid.`, {
|
|
35
|
+
exitCode: 2,
|
|
36
|
+
...options,
|
|
37
|
+
details: options.details ?? issues.map((issue) => (issue.path ? `${issue.path}: ${issue.message}` : issue.message)),
|
|
38
|
+
});
|
|
39
|
+
this.intent = intent;
|
|
40
|
+
this.issues = issues;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A question could not be asked, because nothing can answer it in this run. The message
|
|
45
|
+
* names the question and the flag or argument that answers it ahead of time.
|
|
46
|
+
*/
|
|
47
|
+
export class NonInteractiveError extends SousError {
|
|
48
|
+
sousErrorKind = "non-interactive";
|
|
49
|
+
/** The question that could not be asked. */
|
|
50
|
+
question;
|
|
51
|
+
/** The flag or argument that answers it, such as `--yes`. */
|
|
52
|
+
answeredBy;
|
|
53
|
+
constructor(question, answeredBy, options = {}) {
|
|
54
|
+
const fix = answeredBy ? ` Pass ${answeredBy} to answer it ahead of time.` : "";
|
|
55
|
+
// Exit code 1, as before the refactor: only a command line the CLI could not parse exits 2.
|
|
56
|
+
super(`sous needed to ask "${question}" but cannot ask questions in this run.${fix}`, options);
|
|
57
|
+
this.question = question;
|
|
58
|
+
this.answeredBy = answeredBy;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** A listener cancelled an intent or an event on purpose; `reason` says why. */
|
|
62
|
+
export class CancelledError extends SousError {
|
|
63
|
+
sousErrorKind = "cancelled";
|
|
64
|
+
/** What was cancelled: an intent or an event name. */
|
|
65
|
+
target;
|
|
66
|
+
/** The reason the listener gave. */
|
|
67
|
+
reason;
|
|
68
|
+
/** The plugin whose listener cancelled it. */
|
|
69
|
+
cancelledBy;
|
|
70
|
+
constructor(target, reason, cancelledBy, options = {}) {
|
|
71
|
+
const by = cancelledBy ? ` (cancelled by ${cancelledBy})` : "";
|
|
72
|
+
super(`"${target}" was cancelled: ${reason}${by}`, options);
|
|
73
|
+
this.target = target;
|
|
74
|
+
this.reason = reason;
|
|
75
|
+
this.cancelledBy = cancelledBy;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** The engine found a loop: the config did not settle, or calls nested past a limit. */
|
|
79
|
+
export class LoopError extends SousError {
|
|
80
|
+
sousErrorKind = "loop";
|
|
81
|
+
/** Which limit was passed, as named under the `limits` config key. */
|
|
82
|
+
limit;
|
|
83
|
+
constructor(limit, message, options = {}) {
|
|
84
|
+
super(message, options);
|
|
85
|
+
this.limit = limit;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** A plugin could not be found, loaded or registered, or a plugin it depends on is missing. */
|
|
89
|
+
export class PluginError extends SousError {
|
|
90
|
+
sousErrorKind = "plugin";
|
|
91
|
+
/** The plugin, by name or by the specifier it was asked for under. */
|
|
92
|
+
plugin;
|
|
93
|
+
constructor(plugin, message, options = {}) {
|
|
94
|
+
super(message, options);
|
|
95
|
+
this.plugin = plugin;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Two plugins claim the same thing (a service replacement, an intent, a CLI command) and the
|
|
100
|
+
* config does not pick one. The message names both and the config key that picks one.
|
|
101
|
+
*/
|
|
102
|
+
export class ConflictError extends SousError {
|
|
103
|
+
sousErrorKind = "conflict";
|
|
104
|
+
/** What is claimed twice. */
|
|
105
|
+
subject;
|
|
106
|
+
/** The plugins claiming it. */
|
|
107
|
+
plugins;
|
|
108
|
+
constructor(subject, plugins, message, options = {}) {
|
|
109
|
+
super(message, options);
|
|
110
|
+
this.subject = subject;
|
|
111
|
+
this.plugins = plugins;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** No plugin provides the intent, service, entity or front end asked for. */
|
|
115
|
+
export class NotFoundError extends SousError {
|
|
116
|
+
sousErrorKind = "not-found";
|
|
117
|
+
/** What kind of thing was asked for: `intent`, `service`, `entity`, `front end`. */
|
|
118
|
+
thing;
|
|
119
|
+
/** Its name or id. */
|
|
120
|
+
id;
|
|
121
|
+
constructor(thing, id, message, options = {}) {
|
|
122
|
+
super(message ?? `No plugin provides the ${thing} "${id}".`, options);
|
|
123
|
+
this.thing = thing;
|
|
124
|
+
this.id = id;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Whether a value is a sous error, by class or by its `sousErrorKind` (so a second copy of this
|
|
129
|
+
* package is still recognized).
|
|
130
|
+
*/
|
|
131
|
+
export function isSousError(value) {
|
|
132
|
+
return (value instanceof SousError ||
|
|
133
|
+
(value instanceof Error && typeof value.sousErrorKind === "string"));
|
|
134
|
+
}
|
|
135
|
+
/** Whether a value is a sous error of one kind. */
|
|
136
|
+
export function isSousErrorOfKind(value, kind) {
|
|
137
|
+
return isSousError(value) && value.sousErrorKind === kind;
|
|
138
|
+
}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Context } from "./context.js";
|
|
2
|
+
import type { BuiltConfig } from "./config.js";
|
|
3
|
+
import type { CoreEvents, IntentInput, IntentReturn, SousEvents, SousIntents } from "./registry.js";
|
|
4
|
+
/** What every event has. */
|
|
5
|
+
export interface EventBase {
|
|
6
|
+
/** The event's name: `build.before`, `diagrams.rendered`. */
|
|
7
|
+
readonly name: string;
|
|
8
|
+
/** Shared with later listeners, and from an intent's `before` event to its `after` event. */
|
|
9
|
+
readonly data: Map<string, unknown>;
|
|
10
|
+
/** Stops the intent (or tells the emitter of a custom event to stop); the reason is reported. */
|
|
11
|
+
cancel(reason: string): void;
|
|
12
|
+
/** Whether a listener cancelled it. */
|
|
13
|
+
readonly cancelled: boolean;
|
|
14
|
+
/** The reason given to {@link cancel}. */
|
|
15
|
+
readonly cancelReason: string | undefined;
|
|
16
|
+
/** The plugin whose listener cancelled it. */
|
|
17
|
+
readonly cancelledBy: string | undefined;
|
|
18
|
+
/** Later listeners don't see this event. An intent still runs. */
|
|
19
|
+
stopPropagation(): void;
|
|
20
|
+
/** Whether a listener stopped propagation. */
|
|
21
|
+
readonly propagationStopped: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** A custom event, fired with `emit`. */
|
|
24
|
+
export interface CustomEvent<P = unknown> extends EventBase {
|
|
25
|
+
readonly kind: "custom";
|
|
26
|
+
payload: P;
|
|
27
|
+
}
|
|
28
|
+
/** `<intent>.before`: fired before the handler runs. Listeners may change `input`. */
|
|
29
|
+
export interface IntentBeforeEvent<I extends string = string, In = IntentInput<I>> extends EventBase {
|
|
30
|
+
readonly kind: "before";
|
|
31
|
+
readonly intent: I;
|
|
32
|
+
input: In;
|
|
33
|
+
}
|
|
34
|
+
/** `<intent>.after`: fired after the handler returned. Listeners may replace `result`, its outputs. */
|
|
35
|
+
export interface IntentAfterEvent<I extends string = string, In = IntentInput<I>, R = IntentReturn<I>> extends EventBase {
|
|
36
|
+
readonly kind: "after";
|
|
37
|
+
readonly intent: I;
|
|
38
|
+
readonly input: In;
|
|
39
|
+
result: R;
|
|
40
|
+
}
|
|
41
|
+
/** `<intent>.error`: fired when the handler threw. A listener may turn the failure into outputs. */
|
|
42
|
+
export interface IntentErrorEvent<I extends string = string, In = IntentInput<I>, R = IntentReturn<I>> extends EventBase {
|
|
43
|
+
readonly kind: "error";
|
|
44
|
+
readonly intent: I;
|
|
45
|
+
readonly input: In;
|
|
46
|
+
readonly error: unknown;
|
|
47
|
+
/** Turns the failure into these outputs; the intent then succeeds and `after` fires. */
|
|
48
|
+
recover(result: R): void;
|
|
49
|
+
/** Whether a listener recovered. */
|
|
50
|
+
readonly recovered: boolean;
|
|
51
|
+
}
|
|
52
|
+
/** Any event, for regular expression listeners, which narrow by `event.name` or `event.kind`. */
|
|
53
|
+
export type AnyEvent = CustomEvent<unknown> | IntentBeforeEvent | IntentAfterEvent | IntentErrorEvent;
|
|
54
|
+
/** The payload of `config.built`. */
|
|
55
|
+
export interface ConfigBuiltPayload {
|
|
56
|
+
/** The config that was just built. */
|
|
57
|
+
config: BuiltConfig;
|
|
58
|
+
}
|
|
59
|
+
/** Every custom event TypeScript knows about. */
|
|
60
|
+
export type AllEvents = CoreEvents & SousEvents;
|
|
61
|
+
type KnownIntent = keyof SousIntents & string;
|
|
62
|
+
/** Every event name TypeScript knows about: custom events, and every intent's three events. */
|
|
63
|
+
export type KnownEventName = (keyof AllEvents & string) | `${KnownIntent}.${"before" | "after" | "error"}`;
|
|
64
|
+
type CustomOrAny<N extends string> = N extends keyof AllEvents ? CustomEvent<AllEvents[N]> : AnyEvent;
|
|
65
|
+
/**
|
|
66
|
+
* The event object a listener for one exact name receives. An intent's three events are typed from
|
|
67
|
+
* `SousIntents`; for an intent no plugin declared, the input and result are `unknown`.
|
|
68
|
+
*/
|
|
69
|
+
export type EventFor<N extends string> = N extends keyof AllEvents ? CustomEvent<AllEvents[N]> : N extends `${infer I}.before` ? I extends KnownIntent ? IntentBeforeEvent<I> : IntentBeforeEvent<I, unknown> : N extends `${infer I}.after` ? I extends KnownIntent ? IntentAfterEvent<I> : IntentAfterEvent<I, unknown, unknown> : N extends `${infer I}.error` ? I extends KnownIntent ? IntentErrorEvent<I> : IntentErrorEvent<I, unknown, unknown> : CustomOrAny<N>;
|
|
70
|
+
/** Turns a wildcard pattern into a template literal type: `build.*` becomes `build.${string}`. */
|
|
71
|
+
export type PatternTemplate<P extends string> = P extends `${infer A}*${infer B}` ? `${A}${string}${PatternTemplate<B>}` : P;
|
|
72
|
+
/** The event names a pattern matches, among those TypeScript knows. */
|
|
73
|
+
export type MatchingEventNames<P extends string> = Extract<KnownEventName, PatternTemplate<P>>;
|
|
74
|
+
/**
|
|
75
|
+
* The event a listener receives for a pattern: an exact name gives that event, a wildcard the union
|
|
76
|
+
* of every matching known event, and a regular expression {@link AnyEvent}.
|
|
77
|
+
*/
|
|
78
|
+
export type EventForPattern<P extends EventPattern> = P extends RegExp ? AnyEvent : P extends `${string}*${string}` ? [MatchingEventNames<P & string>] extends [never] ? AnyEvent : EventFor<MatchingEventNames<P & string>> : EventFor<P & string>;
|
|
79
|
+
/** What a listener subscribes to: an exact name, a wildcard (`build.*`, `*.before`) or a regular expression. */
|
|
80
|
+
export type EventPattern = string | RegExp;
|
|
81
|
+
/** A listener. It gets a `ctx` like a handler's, so it can run intents, read the config and report. */
|
|
82
|
+
export type Listener<E = AnyEvent> = (event: E, ctx: Context) => void | Promise<void>;
|
|
83
|
+
/** Options for `on` and `once`. */
|
|
84
|
+
export interface ListenerOptions {
|
|
85
|
+
/** Higher runs first. Default `0`. */
|
|
86
|
+
priority?: number;
|
|
87
|
+
}
|
|
88
|
+
/** The payload type of a custom event. */
|
|
89
|
+
export type EventPayload<N extends string> = N extends keyof AllEvents ? AllEvents[N] : unknown;
|
|
90
|
+
export {};
|
package/dist/events.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The plugin interface of sous: the one promise sous makes outside its
|
|
3
|
+
* monorepo. It holds `definePlugin`, the interfaces plugins add their types to, every public
|
|
4
|
+
* type, the `Reporter` and `Prompter` interfaces and the error classes. Everything else a plugin
|
|
5
|
+
* uses is handed to it in `register(sous)` and in each `ctx`.
|
|
6
|
+
*/
|
|
7
|
+
export type * from "./config.js";
|
|
8
|
+
export type * from "./context.js";
|
|
9
|
+
export type * from "./entities.js";
|
|
10
|
+
export type * from "./events.js";
|
|
11
|
+
export type * from "./intents.js";
|
|
12
|
+
export type * from "./json.js";
|
|
13
|
+
export type * from "./node.js";
|
|
14
|
+
export type * from "./registry.js";
|
|
15
|
+
export type * from "./services.js";
|
|
16
|
+
export { definePlugin } from "./plugin.js";
|
|
17
|
+
export type * from "./plugin.js";
|
|
18
|
+
export { CancelledError, ConfigError, ConflictError, InputError, isSousError, isSousErrorOfKind, LoopError, NonInteractiveError, NotFoundError, PluginError, SousError, } from "./errors.js";
|
|
19
|
+
export type { InputIssue, SousErrorKind, SousErrorOptions } from "./errors.js";
|
|
20
|
+
export { nonInteractivePrompter, scriptedPrompter } from "./prompter.js";
|
|
21
|
+
export type * from "./prompter.js";
|
|
22
|
+
export { BaseReporter, BufferedReporter, createReporter, isLevelShown, isLogLevel, LOG_LEVELS, errorChain, LogLevel, messageText, nullReporter, ReportEntryStyle, Reporting, } from "./reporter.js";
|
|
23
|
+
export type * from "./reporter.js";
|
|
24
|
+
export { commandVariables } from "./command-variables.js";
|
|
25
|
+
export { DataValue, IntentOutput, IntentOutputs, ItemList, Payload, Table, TextBlock, Tree, VariableSet } from "./outputs.js";
|
|
26
|
+
export type * from "./outputs.js";
|
|
27
|
+
export { formatIssuePath, isSchema, jsonSchema, toJsonSchema, validateWith, validateWithSync } from "./schema.js";
|
|
28
|
+
export type * from "./schema.js";
|
|
29
|
+
export { TIMING_CATEGORIES } from "./timing.js";
|
|
30
|
+
export type * from "./timing.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The plugin interface of sous: the one promise sous makes outside its
|
|
3
|
+
* monorepo. It holds `definePlugin`, the interfaces plugins add their types to, every public
|
|
4
|
+
* type, the `Reporter` and `Prompter` interfaces and the error classes. Everything else a plugin
|
|
5
|
+
* uses is handed to it in `register(sous)` and in each `ctx`.
|
|
6
|
+
*/
|
|
7
|
+
export { definePlugin } from "./plugin.js";
|
|
8
|
+
export { CancelledError, ConfigError, ConflictError, InputError, isSousError, isSousErrorOfKind, LoopError, NonInteractiveError, NotFoundError, PluginError, SousError, } from "./errors.js";
|
|
9
|
+
export { nonInteractivePrompter, scriptedPrompter } from "./prompter.js";
|
|
10
|
+
export { BaseReporter, BufferedReporter, createReporter, isLevelShown, isLogLevel, LOG_LEVELS, errorChain, LogLevel, messageText, nullReporter, ReportEntryStyle, Reporting, } from "./reporter.js";
|
|
11
|
+
export { commandVariables } from "./command-variables.js";
|
|
12
|
+
export { DataValue, IntentOutput, IntentOutputs, ItemList, Payload, Table, TextBlock, Tree, VariableSet } from "./outputs.js";
|
|
13
|
+
export { formatIssuePath, isSchema, jsonSchema, toJsonSchema, validateWith, validateWithSync } from "./schema.js";
|
|
14
|
+
export { TIMING_CATEGORIES } from "./timing.js";
|