@monosmith/plugin-sdk 0.0.0-stage → 0.1.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/README.md +89 -2
- package/build-dir/build-dir.algorithm.d.ts +16 -0
- package/build-dir/build-dir.algorithm.js +26 -0
- package/build-dir/index.d.ts +1 -0
- package/build-dir/index.js +1 -0
- package/ci/ci-contribution.algorithm.d.ts +28 -0
- package/ci/ci-contribution.algorithm.js +19 -0
- package/ci/index.d.ts +2 -0
- package/ci/index.js +1 -0
- package/codec/encode-by-schema.algorithm.d.ts +12 -0
- package/codec/encode-by-schema.algorithm.js +105 -0
- package/codec/encode-error.error.d.ts +4 -0
- package/codec/encode-error.error.js +7 -0
- package/codec/encode-json-value.algorithm.d.ts +13 -0
- package/codec/encode-json-value.algorithm.js +51 -0
- package/codec/index.d.ts +6 -0
- package/codec/index.js +5 -0
- package/codec/message-parse.error.d.ts +4 -0
- package/codec/message-parse.error.js +7 -0
- package/codec/method-codec.algorithm.d.ts +8 -0
- package/codec/method-codec.algorithm.js +31 -0
- package/codec/normalize-by-schema.algorithm.d.ts +14 -0
- package/codec/normalize-by-schema.algorithm.js +103 -0
- package/codec/params-error.error.d.ts +4 -0
- package/codec/params-error.error.js +7 -0
- package/codec/schema-lookup.algorithm.d.ts +13 -0
- package/codec/schema-lookup.algorithm.js +56 -0
- package/codec/wire-message.mapper.d.ts +25 -0
- package/codec/wire-message.mapper.js +58 -0
- package/conformance/check-generation.use-case.d.ts +12 -0
- package/conformance/check-generation.use-case.js +91 -0
- package/conformance/check-plugin.use-case.d.ts +24 -0
- package/conformance/check-plugin.use-case.js +78 -0
- package/conformance/conformance-report.model.d.ts +22 -0
- package/conformance/conformance-report.model.js +23 -0
- package/conformance/find-manifests.algorithm.d.ts +7 -0
- package/conformance/find-manifests.algorithm.js +34 -0
- package/conformance/go-list.mapper.d.ts +4 -0
- package/conformance/go-list.mapper.js +8 -0
- package/conformance/index.d.ts +5 -0
- package/conformance/index.js +3 -0
- package/framing/frame-message.algorithm.d.ts +5 -0
- package/framing/frame-message.algorithm.js +8 -0
- package/framing/frame-reader.store.d.ts +15 -0
- package/framing/frame-reader.store.js +59 -0
- package/framing/framing.error.d.ts +4 -0
- package/framing/framing.error.js +7 -0
- package/framing/index.d.ts +3 -0
- package/framing/index.js +3 -0
- package/host/connect-plugin.use-case.d.ts +26 -0
- package/host/connect-plugin.use-case.js +44 -0
- package/host/host-options.contract.d.ts +28 -0
- package/host/host-options.contract.js +2 -0
- package/host/index.d.ts +7 -0
- package/host/index.js +4 -0
- package/host/plugin-client.client.d.ts +42 -0
- package/host/plugin-client.client.js +166 -0
- package/host/start-plugin.use-case.d.ts +10 -0
- package/host/start-plugin.use-case.js +57 -0
- package/index.d.ts +15 -0
- package/index.js +20 -0
- package/jsondoc/indent-raw-json.algorithm.d.ts +8 -0
- package/jsondoc/indent-raw-json.algorithm.js +60 -0
- package/jsondoc/index.d.ts +2 -0
- package/jsondoc/index.js +2 -0
- package/jsondoc/json-document.error.d.ts +4 -0
- package/jsondoc/json-document.error.js +7 -0
- package/jsondoc/json-document.model.d.ts +38 -0
- package/jsondoc/json-document.model.js +113 -0
- package/jsondoc/scan-json-object.algorithm.d.ts +17 -0
- package/jsondoc/scan-json-object.algorithm.js +132 -0
- package/package.json +34 -4
- package/plugin-refs/index.d.ts +1 -0
- package/plugin-refs/index.js +1 -0
- package/plugin-refs/plugin-ref.policy.d.ts +9 -0
- package/plugin-refs/plugin-ref.policy.js +22 -0
- package/plugin-roles/index.d.ts +1 -0
- package/plugin-roles/index.js +1 -0
- package/plugin-roles/plugin-role.policy.d.ts +16 -0
- package/plugin-roles/plugin-role.policy.js +30 -0
- package/projects/audit-settings.policy.d.ts +5 -0
- package/projects/audit-settings.policy.js +8 -0
- package/projects/index.d.ts +6 -0
- package/projects/index.js +6 -0
- package/projects/manifest-match.algorithm.d.ts +7 -0
- package/projects/manifest-match.algorithm.js +38 -0
- package/projects/project-kind.policy.d.ts +9 -0
- package/projects/project-kind.policy.js +28 -0
- package/projects/publishes.policy.d.ts +7 -0
- package/projects/publishes.policy.js +8 -0
- package/projects/set-toolchain.algorithm.d.ts +7 -0
- package/projects/set-toolchain.algorithm.js +13 -0
- package/projects/stack-plugins.algorithm.d.ts +3 -0
- package/projects/stack-plugins.algorithm.js +4 -0
- package/protocol/index.d.ts +4 -0
- package/protocol/index.js +3 -0
- package/protocol/protocol-schema.contract.d.ts +3 -0
- package/protocol/protocol-schema.contract.js +1925 -0
- package/protocol/protocol-types.contract.d.ts +1017 -0
- package/protocol/protocol-types.contract.js +36 -0
- package/protocol/rpc-error.error.d.ts +9 -0
- package/protocol/rpc-error.error.js +13 -0
- package/protocol/schema-document.contract.d.ts +55 -0
- package/protocol/schema-document.contract.js +1 -0
- package/scaffold/index.d.ts +3 -0
- package/scaffold/index.js +2 -0
- package/scaffold/scaffold-data.mapper.d.ts +15 -0
- package/scaffold/scaffold-data.mapper.js +24 -0
- package/scaffold/scaffold-project.use-case.d.ts +20 -0
- package/scaffold/scaffold-project.use-case.js +13 -0
- package/serve/capabilities-of.algorithm.d.ts +13 -0
- package/serve/capabilities-of.algorithm.js +28 -0
- package/serve/claim-stdout.use-case.d.ts +10 -0
- package/serve/claim-stdout.use-case.js +14 -0
- package/serve/dispatch-request.use-case.d.ts +14 -0
- package/serve/dispatch-request.use-case.js +56 -0
- package/serve/index.d.ts +4 -0
- package/serve/index.js +2 -0
- package/serve/initialize-plugin.use-case.d.ts +9 -0
- package/serve/initialize-plugin.use-case.js +16 -0
- package/serve/plugin-members.config.d.ts +16 -0
- package/serve/plugin-members.config.js +25 -0
- package/serve/plugin.contract.d.ts +65 -0
- package/serve/plugin.contract.js +1 -0
- package/serve/protocol-streams.contract.d.ts +13 -0
- package/serve/protocol-streams.contract.js +1 -0
- package/serve/request-outcome.contract.d.ts +9 -0
- package/serve/request-outcome.contract.js +1 -0
- package/serve/serve.use-case.d.ts +27 -0
- package/serve/serve.use-case.js +93 -0
- package/tasks/contribute-tasks.algorithm.d.ts +15 -0
- package/tasks/contribute-tasks.algorithm.js +22 -0
- package/tasks/index.d.ts +2 -0
- package/tasks/index.js +2 -0
- package/tasks/task.algorithm.d.ts +18 -0
- package/tasks/task.algorithm.js +23 -0
- package/templating/file-exists.error.d.ts +6 -0
- package/templating/file-exists.error.js +10 -0
- package/templating/index.d.ts +4 -0
- package/templating/index.js +4 -0
- package/templating/parse-template.algorithm.d.ts +11 -0
- package/templating/parse-template.algorithm.js +197 -0
- package/templating/render-template.algorithm.d.ts +7 -0
- package/templating/render-template.algorithm.js +87 -0
- package/templating/template-functions.algorithm.d.ts +10 -0
- package/templating/template-functions.algorithm.js +104 -0
- package/templating/template-syntax.contract.d.ts +33 -0
- package/templating/template-syntax.contract.js +1 -0
- package/templating/template.error.d.ts +4 -0
- package/templating/template.error.js +7 -0
- package/templating/tokenize-action.algorithm.d.ts +20 -0
- package/templating/tokenize-action.algorithm.js +57 -0
- package/templating/write-templates.use-case.d.ts +11 -0
- package/templating/write-templates.use-case.js +44 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// GENERATED by tools/generate-protocol from packages/plugin-sdk/schema/protocol.schema.json.
|
|
3
|
+
// Do not edit it: change the Go types in packages/plugin-sdk/protocol, regenerate the schema
|
|
4
|
+
// (`smith run schema -p plugin-sdk`), then run `smith run generate -p plugin-sdk-ts`.
|
|
5
|
+
/** The protocol major version this SDK speaks. A breaking change to the protocol raises it. */
|
|
6
|
+
export const PROTOCOL_VERSION = 1;
|
|
7
|
+
/** The largest message the framing accepts, in bytes. */
|
|
8
|
+
export const MAX_MESSAGE_BYTES = 67108864;
|
|
9
|
+
/** The error codes of the protocol: the JSON-RPC 2.0 ones, and `PluginFailure` for a failure the user must act on. */
|
|
10
|
+
export const ERROR_CODES = {
|
|
11
|
+
/** The message was not valid JSON. The response has a null id. */
|
|
12
|
+
ParseError: -32700,
|
|
13
|
+
/**
|
|
14
|
+
* The request is not valid, or arrived out of order: every request before initialize is refused
|
|
15
|
+
* with this code.
|
|
16
|
+
*/
|
|
17
|
+
InvalidRequest: -32600,
|
|
18
|
+
/**
|
|
19
|
+
* The plugin does not implement the method, or did not advertise the capability. The message is
|
|
20
|
+
* "method not supported: <method>".
|
|
21
|
+
*/
|
|
22
|
+
MethodNotFound: -32601,
|
|
23
|
+
/** The params do not have the shape the method expects. */
|
|
24
|
+
InvalidParams: -32602,
|
|
25
|
+
/**
|
|
26
|
+
* The plugin failed in a way that is not the user's to act on, for example a result that could
|
|
27
|
+
* not be encoded.
|
|
28
|
+
*/
|
|
29
|
+
InternalError: -32603,
|
|
30
|
+
/**
|
|
31
|
+
* The method ran and failed for a reason the user must act on (a missing toolchain, a bad
|
|
32
|
+
* manifest). The message is shown to the user as it is, so it should say what to do. An error a
|
|
33
|
+
* method of the Go SDK returns becomes this code.
|
|
34
|
+
*/
|
|
35
|
+
PluginFailure: -32000,
|
|
36
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A JSON-RPC 2.0 error object as an exception: what a host's call rejects
|
|
3
|
+
* with when the plugin answers an error, and what the serve loop turns an
|
|
4
|
+
* exception back into. `code` is one of {@link ERROR_CODES}.
|
|
5
|
+
*/
|
|
6
|
+
export declare class RpcError extends Error {
|
|
7
|
+
readonly code: number;
|
|
8
|
+
constructor(code: number, message: string);
|
|
9
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A JSON-RPC 2.0 error object as an exception: what a host's call rejects
|
|
3
|
+
* with when the plugin answers an error, and what the serve loop turns an
|
|
4
|
+
* exception back into. `code` is one of {@link ERROR_CODES}.
|
|
5
|
+
*/
|
|
6
|
+
export class RpcError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
constructor(code, message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = 'RpcError';
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape of `packages/plugin-sdk/schema/protocol.schema.json`, the JSON
|
|
3
|
+
* Schema (draft 2020-12) of the plugin protocol, as far as the SDK reads it:
|
|
4
|
+
* the types, the wire codec and the host's capability check all follow it.
|
|
5
|
+
*/
|
|
6
|
+
export interface SchemaNode {
|
|
7
|
+
'$ref'?: string;
|
|
8
|
+
'type'?: string | string[];
|
|
9
|
+
'properties'?: Record<string, SchemaNode>;
|
|
10
|
+
'required'?: string[];
|
|
11
|
+
'items'?: SchemaNode;
|
|
12
|
+
'additionalProperties'?: SchemaNode | boolean;
|
|
13
|
+
'enum'?: string[];
|
|
14
|
+
'anyOf'?: SchemaNode[];
|
|
15
|
+
'description'?: string;
|
|
16
|
+
'x-enumDescriptions'?: Record<string, string>;
|
|
17
|
+
}
|
|
18
|
+
/** One row of the methods table: what a method takes, gives and needs. */
|
|
19
|
+
export interface MethodRow {
|
|
20
|
+
description: string;
|
|
21
|
+
params: SchemaNode;
|
|
22
|
+
result: SchemaNode;
|
|
23
|
+
/** The capability a plugin advertises to be asked the method; `null` when it is always available. */
|
|
24
|
+
capability: string | null;
|
|
25
|
+
}
|
|
26
|
+
export interface ErrorCodeRow {
|
|
27
|
+
name: string;
|
|
28
|
+
code: number;
|
|
29
|
+
description: string;
|
|
30
|
+
}
|
|
31
|
+
export interface RoleRow {
|
|
32
|
+
name: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
/** `exclusive` (one per project), `additive` (any number) or `perSlot` (one per task name provided). */
|
|
35
|
+
cardinality: 'exclusive' | 'additive' | 'perSlot';
|
|
36
|
+
/** The kinds of plugin that play the role when the plugin names none. */
|
|
37
|
+
defaultFor: string[];
|
|
38
|
+
}
|
|
39
|
+
export interface SchemaDocument {
|
|
40
|
+
'$schema': string;
|
|
41
|
+
'$id': string;
|
|
42
|
+
'title': string;
|
|
43
|
+
'description': string;
|
|
44
|
+
'x-protocolVersion': number;
|
|
45
|
+
'x-transport': {
|
|
46
|
+
framing: string;
|
|
47
|
+
encoding: string;
|
|
48
|
+
jsonrpc: string;
|
|
49
|
+
maxMessageBytes: number;
|
|
50
|
+
};
|
|
51
|
+
'x-methods': Record<string, MethodRow>;
|
|
52
|
+
'x-errorCodes': ErrorCodeRow[];
|
|
53
|
+
'x-roles': RoleRow[];
|
|
54
|
+
'$defs': Record<string, SchemaNode>;
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { GenerateParams } from '../protocol/index.ts';
|
|
2
|
+
/**
|
|
3
|
+
* What a project template can say, from the `generate` params:
|
|
4
|
+
*
|
|
5
|
+
* | Field | Value |
|
|
6
|
+
* |---|---|
|
|
7
|
+
* | `Dir` | the project's directory, workspace-relative |
|
|
8
|
+
* | `Name` | the project's name |
|
|
9
|
+
* | `Kind` | `app`, `lib` or `package` |
|
|
10
|
+
* | `Stack` | the stack to record in the manifest (may be empty) |
|
|
11
|
+
* | `Workspace` | the workspace's name |
|
|
12
|
+
* | `Scope` | the npm scope, with its `@` (may be empty) |
|
|
13
|
+
* | `ModulePrefix` | the Go module path prefix (may be empty) |
|
|
14
|
+
*/
|
|
15
|
+
export declare function scaffoldData(params: GenerateParams): Record<string, string>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a project template can say, from the `generate` params:
|
|
3
|
+
*
|
|
4
|
+
* | Field | Value |
|
|
5
|
+
* |---|---|
|
|
6
|
+
* | `Dir` | the project's directory, workspace-relative |
|
|
7
|
+
* | `Name` | the project's name |
|
|
8
|
+
* | `Kind` | `app`, `lib` or `package` |
|
|
9
|
+
* | `Stack` | the stack to record in the manifest (may be empty) |
|
|
10
|
+
* | `Workspace` | the workspace's name |
|
|
11
|
+
* | `Scope` | the npm scope, with its `@` (may be empty) |
|
|
12
|
+
* | `ModulePrefix` | the Go module path prefix (may be empty) |
|
|
13
|
+
*/
|
|
14
|
+
export function scaffoldData(params) {
|
|
15
|
+
return {
|
|
16
|
+
Dir: params.directory,
|
|
17
|
+
Name: params.name,
|
|
18
|
+
Kind: params.kind,
|
|
19
|
+
Stack: params.stack ?? '',
|
|
20
|
+
Workspace: params.workspace.name,
|
|
21
|
+
Scope: params.workspace.scope ?? '',
|
|
22
|
+
ModulePrefix: params.workspace.modulePrefix ?? '',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { GenerateParams, GenerateResult } from '../protocol/index.ts';
|
|
2
|
+
export interface ScaffoldOptions {
|
|
3
|
+
/** The workspace root from `initialize`: the project's directory is relative to it. */
|
|
4
|
+
workspaceRoot: string;
|
|
5
|
+
/** The `generate` params. */
|
|
6
|
+
params: GenerateParams;
|
|
7
|
+
/** Target path to template text, with `[[ ]]` actions in both (`'[[.Dir]]/widget.json'`). */
|
|
8
|
+
files: Record<string, string>;
|
|
9
|
+
/** More fields for the templates, next to the ones {@link scaffoldData} lists. They win over them. */
|
|
10
|
+
data?: Record<string, unknown>;
|
|
11
|
+
/** Notes printed to the user after generation. */
|
|
12
|
+
notes?: string[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The answer to `generate` for a scaffold: renders the templates into the new
|
|
16
|
+
* project, never overwriting a file, and reports what it wrote. A language
|
|
17
|
+
* plugin that scaffolds must record `params.stack` in the manifest it writes
|
|
18
|
+
* (`[[if .Stack]]` in a template), so discovery reports it back.
|
|
19
|
+
*/
|
|
20
|
+
export declare function scaffoldProject(options: ScaffoldOptions): Promise<GenerateResult>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { writeTemplates } from '../templating/index.js';
|
|
2
|
+
import { scaffoldData } from './scaffold-data.mapper.js';
|
|
3
|
+
/**
|
|
4
|
+
* The answer to `generate` for a scaffold: renders the templates into the new
|
|
5
|
+
* project, never overwriting a file, and reports what it wrote. A language
|
|
6
|
+
* plugin that scaffolds must record `params.stack` in the manifest it writes
|
|
7
|
+
* (`[[if .Stack]]` in a template), so discovery reports it back.
|
|
8
|
+
*/
|
|
9
|
+
export async function scaffoldProject(options) {
|
|
10
|
+
const data = { ...scaffoldData(options.params), ...options.data };
|
|
11
|
+
const files = await writeTemplates(options.workspaceRoot, options.files, data);
|
|
12
|
+
return options.notes === undefined ? { files } : { files, notes: options.notes };
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Capabilities } from '../protocol/index.ts';
|
|
2
|
+
import type { SmithPlugin } from './plugin.contract.ts';
|
|
3
|
+
/** Whether the plugin implements a method of the protocol: it has every member the method needs, as functions. */
|
|
4
|
+
export declare function implementsMethod(plugin: SmithPlugin, method: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* The capabilities a plugin advertises, derived from the methods it
|
|
7
|
+
* implements: a plugin cannot advertise a method it lacks, or implement one
|
|
8
|
+
* it does not advertise. The capability of each method is read from the
|
|
9
|
+
* schema's methods table.
|
|
10
|
+
*/
|
|
11
|
+
export declare function capabilitiesOf(plugin: SmithPlugin): Capabilities;
|
|
12
|
+
/** Methods a plugin half-implements: a member of a pair (`install` without `uninstall`) that is not advertised for lack of the other. */
|
|
13
|
+
export declare function halfImplemented(plugin: SmithPlugin): string[];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { protocolSchema } from '../protocol/index.js';
|
|
2
|
+
import { PLUGIN_MEMBERS } from './plugin-members.config.js';
|
|
3
|
+
/** Whether the plugin implements a method of the protocol: it has every member the method needs, as functions. */
|
|
4
|
+
export function implementsMethod(plugin, method) {
|
|
5
|
+
const entry = Object.hasOwn(PLUGIN_MEMBERS, method) ? PLUGIN_MEMBERS[method] : undefined;
|
|
6
|
+
return entry !== undefined && entry.requires.every(member => typeof plugin[member] === 'function');
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The capabilities a plugin advertises, derived from the methods it
|
|
10
|
+
* implements: a plugin cannot advertise a method it lacks, or implement one
|
|
11
|
+
* it does not advertise. The capability of each method is read from the
|
|
12
|
+
* schema's methods table.
|
|
13
|
+
*/
|
|
14
|
+
export function capabilitiesOf(plugin) {
|
|
15
|
+
const capabilities = {};
|
|
16
|
+
const methods = Object.entries(protocolSchema['x-methods']);
|
|
17
|
+
for (const [method, row] of methods) {
|
|
18
|
+
if (row.capability !== null && implementsMethod(plugin, method))
|
|
19
|
+
capabilities[row.capability] = true;
|
|
20
|
+
}
|
|
21
|
+
return capabilities;
|
|
22
|
+
}
|
|
23
|
+
/** Methods a plugin half-implements: a member of a pair (`install` without `uninstall`) that is not advertised for lack of the other. */
|
|
24
|
+
export function halfImplemented(plugin) {
|
|
25
|
+
return Object.entries(PLUGIN_MEMBERS)
|
|
26
|
+
.filter(([method, entry]) => typeof plugin[entry.member] === 'function' && !implementsMethod(plugin, method))
|
|
27
|
+
.map(([, entry]) => entry.member);
|
|
28
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FrameSink } from './protocol-streams.contract.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Takes the process's standard output for the protocol and nothing else. A
|
|
4
|
+
* `console.log`, a library's `process.stdout.write` or a child process's
|
|
5
|
+
* inherited output would corrupt the stream the host reads, so from here on
|
|
6
|
+
* everything written to `process.stdout` goes to standard error, where the
|
|
7
|
+
* host shows it to the user. The returned sink is the only way to the real
|
|
8
|
+
* standard output.
|
|
9
|
+
*/
|
|
10
|
+
export declare function claimStdout(): FrameSink;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Takes the process's standard output for the protocol and nothing else. A
|
|
3
|
+
* `console.log`, a library's `process.stdout.write` or a child process's
|
|
4
|
+
* inherited output would corrupt the stream the host reads, so from here on
|
|
5
|
+
* everything written to `process.stdout` goes to standard error, where the
|
|
6
|
+
* host shows it to the user. The returned sink is the only way to the real
|
|
7
|
+
* standard output.
|
|
8
|
+
*/
|
|
9
|
+
export function claimStdout() {
|
|
10
|
+
const stdout = process.stdout;
|
|
11
|
+
const write = stdout.write.bind(stdout);
|
|
12
|
+
stdout.write = ((...args) => process.stderr.write(...args));
|
|
13
|
+
return { write };
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReceivedRequest } from '../codec/index.ts';
|
|
2
|
+
import type { SmithPlugin } from './plugin.contract.ts';
|
|
3
|
+
import type { RequestOutcome } from './request-outcome.contract.ts';
|
|
4
|
+
/** The text of an exception: its message, or what was thrown. */
|
|
5
|
+
export declare function messageOf(thrown: unknown): string;
|
|
6
|
+
/**
|
|
7
|
+
* Answers one request: `initialize` and `shutdown` here, every other method
|
|
8
|
+
* by the member of the plugin that implements it. The codes are the Go SDK's:
|
|
9
|
+
* MethodNotFound for a method the plugin does not implement, InvalidParams
|
|
10
|
+
* for params of the wrong shape, PluginFailure for an exception the method
|
|
11
|
+
* threw (its message is shown to the user), InternalError for a result that
|
|
12
|
+
* cannot be written as the schema describes.
|
|
13
|
+
*/
|
|
14
|
+
export declare function dispatchRequest(plugin: SmithPlugin, request: ReceivedRequest): Promise<RequestOutcome>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { EncodeError, ParamsError, decodeParams, encodeResult } from '../codec/index.js';
|
|
2
|
+
import { ERROR_CODES } from '../protocol/index.js';
|
|
3
|
+
import { implementsMethod } from './capabilities-of.algorithm.js';
|
|
4
|
+
import { initializePlugin } from './initialize-plugin.use-case.js';
|
|
5
|
+
import { PLUGIN_MEMBERS } from './plugin-members.config.js';
|
|
6
|
+
/** The text of an exception: its message, or what was thrown. */
|
|
7
|
+
export function messageOf(thrown) {
|
|
8
|
+
return thrown instanceof Error ? thrown.message : String(thrown);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Answers one request: `initialize` and `shutdown` here, every other method
|
|
12
|
+
* by the member of the plugin that implements it. The codes are the Go SDK's:
|
|
13
|
+
* MethodNotFound for a method the plugin does not implement, InvalidParams
|
|
14
|
+
* for params of the wrong shape, PluginFailure for an exception the method
|
|
15
|
+
* threw (its message is shown to the user), InternalError for a result that
|
|
16
|
+
* cannot be written as the schema describes.
|
|
17
|
+
*/
|
|
18
|
+
export async function dispatchRequest(plugin, request) {
|
|
19
|
+
if (request.method === 'shutdown')
|
|
20
|
+
return { ok: true, resultJson: '{}' };
|
|
21
|
+
let params;
|
|
22
|
+
if (request.method === 'initialize' || implementsMethod(plugin, request.method)) {
|
|
23
|
+
try {
|
|
24
|
+
params = decodeParams(request.method, request.params);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error instanceof ParamsError)
|
|
28
|
+
return { ok: false, code: ERROR_CODES.InvalidParams, message: error.message };
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
let result;
|
|
33
|
+
try {
|
|
34
|
+
if (request.method === 'initialize') {
|
|
35
|
+
result = await initializePlugin(plugin, params);
|
|
36
|
+
}
|
|
37
|
+
else if (implementsMethod(plugin, request.method)) {
|
|
38
|
+
const member = PLUGIN_MEMBERS[request.method].member;
|
|
39
|
+
result = await plugin[member].call(plugin, params);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
return { ok: false, code: ERROR_CODES.MethodNotFound, message: `method not supported: ${request.method}` };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
return { ok: false, code: ERROR_CODES.PluginFailure, message: messageOf(error) };
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
return { ok: true, resultJson: encodeResult(request.method, result) };
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error instanceof EncodeError)
|
|
53
|
+
return { ok: false, code: ERROR_CODES.InternalError, message: `${request.method}: ${error.message}` };
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
package/serve/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { serve, serveStreams } from './serve.use-case.ts';
|
|
2
|
+
export { capabilitiesOf, implementsMethod } from './capabilities-of.algorithm.ts';
|
|
3
|
+
export type { Awaitable, CIContributor, Contributor, DeclarationEditor, DependencyManager, Discoverer, DoctorContributor, Generator, LatestReader, Plugin, PluginDescription, Publisher, RegistryReader, Remover, SmithPlugin, Versioner, } from './plugin.contract.ts';
|
|
4
|
+
export type { FrameSink, ServeStreams } from './protocol-streams.contract.ts';
|
package/serve/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { InitializeParams, InitializeResult } from '../protocol/index.ts';
|
|
2
|
+
import type { SmithPlugin } from './plugin.contract.ts';
|
|
3
|
+
/**
|
|
4
|
+
* The handshake. It refuses a host that does not speak this SDK's protocol
|
|
5
|
+
* version (the message is the Go SDK's), asks the plugin to describe itself,
|
|
6
|
+
* and fills in the version it chose and the capabilities derived from the
|
|
7
|
+
* methods the plugin has, whatever `describe` returned for them.
|
|
8
|
+
*/
|
|
9
|
+
export declare function initializePlugin(plugin: SmithPlugin, params: InitializeParams): Promise<InitializeResult>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PROTOCOL_VERSION } from '../protocol/index.js';
|
|
2
|
+
import { capabilitiesOf } from './capabilities-of.algorithm.js';
|
|
3
|
+
/**
|
|
4
|
+
* The handshake. It refuses a host that does not speak this SDK's protocol
|
|
5
|
+
* version (the message is the Go SDK's), asks the plugin to describe itself,
|
|
6
|
+
* and fills in the version it chose and the capabilities derived from the
|
|
7
|
+
* methods the plugin has, whatever `describe` returned for them.
|
|
8
|
+
*/
|
|
9
|
+
export async function initializePlugin(plugin, params) {
|
|
10
|
+
if (!params.protocolVersions.includes(PROTOCOL_VERSION)) {
|
|
11
|
+
// The Go SDK formats the host's list with %v: [1 2].
|
|
12
|
+
throw new Error(`no common protocol version: host speaks [${params.protocolVersions.join(' ')}], plugin speaks ${PROTOCOL_VERSION}`);
|
|
13
|
+
}
|
|
14
|
+
const description = await plugin.describe(params);
|
|
15
|
+
return { ...description, protocolVersion: PROTOCOL_VERSION, capabilities: capabilitiesOf(plugin) };
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SmithPlugin } from './plugin.contract.ts';
|
|
2
|
+
type Member = Exclude<keyof SmithPlugin, 'describe'>;
|
|
3
|
+
/**
|
|
4
|
+
* Which member of a plugin answers each method of the protocol (other than
|
|
5
|
+
* `initialize` and `shutdown`, which the serve loop answers), and which members
|
|
6
|
+
* the plugin must have for the method to count as implemented. A plugin with
|
|
7
|
+
* `install` and no `uninstall` does not implement the `dependencies`
|
|
8
|
+
* capability, as in the Go SDK, where the interface needs both methods. A spec
|
|
9
|
+
* checks the table against the methods of the schema, so a method added to the
|
|
10
|
+
* protocol cannot be left out.
|
|
11
|
+
*/
|
|
12
|
+
export declare const PLUGIN_MEMBERS: Readonly<Record<string, {
|
|
13
|
+
member: Member;
|
|
14
|
+
requires: readonly Member[];
|
|
15
|
+
}>>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which member of a plugin answers each method of the protocol (other than
|
|
3
|
+
* `initialize` and `shutdown`, which the serve loop answers), and which members
|
|
4
|
+
* the plugin must have for the method to count as implemented. A plugin with
|
|
5
|
+
* `install` and no `uninstall` does not implement the `dependencies`
|
|
6
|
+
* capability, as in the Go SDK, where the interface needs both methods. A spec
|
|
7
|
+
* checks the table against the methods of the schema, so a method added to the
|
|
8
|
+
* protocol cannot be left out.
|
|
9
|
+
*/
|
|
10
|
+
export const PLUGIN_MEMBERS = {
|
|
11
|
+
'discover': { member: 'discover', requires: ['discover'] },
|
|
12
|
+
'generate': { member: 'generate', requires: ['generate'] },
|
|
13
|
+
'version/write': { member: 'writeVersion', requires: ['writeVersion'] },
|
|
14
|
+
'publish': { member: 'publish', requires: ['publish'] },
|
|
15
|
+
'ci': { member: 'ci', requires: ['ci'] },
|
|
16
|
+
'doctor': { member: 'doctor', requires: ['doctor'] },
|
|
17
|
+
'contribute': { member: 'contribute', requires: ['contribute'] },
|
|
18
|
+
'remove': { member: 'remove', requires: ['remove'] },
|
|
19
|
+
'published': { member: 'published', requires: ['published'] },
|
|
20
|
+
'dependencies/install': { member: 'install', requires: ['install', 'uninstall'] },
|
|
21
|
+
'dependencies/uninstall': { member: 'uninstall', requires: ['install', 'uninstall'] },
|
|
22
|
+
'dependencies/declared': { member: 'declared', requires: ['declared', 'rewrite'] },
|
|
23
|
+
'dependencies/rewrite': { member: 'rewrite', requires: ['declared', 'rewrite'] },
|
|
24
|
+
'dependencies/latest': { member: 'latest', requires: ['latest'] },
|
|
25
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { CIParams, CIResult, ContributeParams, ContributeResult, DeclaredParams, DeclaredResult, DependencyResult, DiscoverParams, DiscoverResult, DoctorParams, DoctorResult, GenerateParams, GenerateResult, InitializeParams, InitializeResult, InstallParams, LatestParams, LatestResult, PublishedParams, PublishedResult, PublishParams, PublishResult, RemoveParams, RemoveResult, RewriteParams, UninstallParams, VersionParams, VersionResult } from '../protocol/index.ts';
|
|
2
|
+
/** A result now or later: every method of a plugin may be `async`. */
|
|
3
|
+
export type Awaitable<T> = T | Promise<T>;
|
|
4
|
+
/** What a plugin says about itself. The serve loop fills in `protocolVersion` and `capabilities`. */
|
|
5
|
+
export type PluginDescription = Omit<InitializeResult, 'protocolVersion' | 'capabilities'>;
|
|
6
|
+
/** The one method every plugin has. */
|
|
7
|
+
export interface Plugin {
|
|
8
|
+
/** Answers `initialize`: the plugin's identity, kind, languages, manifests and roles. */
|
|
9
|
+
describe: (params: InitializeParams) => Awaitable<PluginDescription>;
|
|
10
|
+
}
|
|
11
|
+
/** `discover`: describes the projects behind manifests. */
|
|
12
|
+
export interface Discoverer {
|
|
13
|
+
discover: (params: DiscoverParams) => Awaitable<DiscoverResult>;
|
|
14
|
+
}
|
|
15
|
+
/** `generate`: scaffolds a project. */
|
|
16
|
+
export interface Generator {
|
|
17
|
+
generate: (params: GenerateParams) => Awaitable<GenerateResult>;
|
|
18
|
+
}
|
|
19
|
+
/** `version/write`: records a release version in a manifest. */
|
|
20
|
+
export interface Versioner {
|
|
21
|
+
writeVersion: (params: VersionParams) => Awaitable<VersionResult>;
|
|
22
|
+
}
|
|
23
|
+
/** `publish`: publishes a released project. */
|
|
24
|
+
export interface Publisher {
|
|
25
|
+
publish: (params: PublishParams) => Awaitable<PublishResult>;
|
|
26
|
+
}
|
|
27
|
+
/** `published`: the newest version a registry has of a project. */
|
|
28
|
+
export interface RegistryReader {
|
|
29
|
+
published: (params: PublishedParams) => Awaitable<PublishedResult>;
|
|
30
|
+
}
|
|
31
|
+
/** `ci`: what a pipeline needs for the plugin's projects. */
|
|
32
|
+
export interface CIContributor {
|
|
33
|
+
ci: (params: CIParams) => Awaitable<CIResult>;
|
|
34
|
+
}
|
|
35
|
+
/** `doctor`: checks the plugin's projects. */
|
|
36
|
+
export interface DoctorContributor {
|
|
37
|
+
doctor: (params: DoctorParams) => Awaitable<DoctorResult>;
|
|
38
|
+
}
|
|
39
|
+
/** `remove`: cleans up after a project is removed. */
|
|
40
|
+
export interface Remover {
|
|
41
|
+
remove: (params: RemoveParams) => Awaitable<RemoveResult>;
|
|
42
|
+
}
|
|
43
|
+
/** `dependencies/install` and `dependencies/uninstall`: both, or the capability is not advertised. */
|
|
44
|
+
export interface DependencyManager {
|
|
45
|
+
install: (params: InstallParams) => Awaitable<DependencyResult>;
|
|
46
|
+
uninstall: (params: UninstallParams) => Awaitable<DependencyResult>;
|
|
47
|
+
}
|
|
48
|
+
/** `dependencies/declared` and `dependencies/rewrite`: both, or the capability is not advertised. */
|
|
49
|
+
export interface DeclarationEditor {
|
|
50
|
+
declared: (params: DeclaredParams) => Awaitable<DeclaredResult>;
|
|
51
|
+
rewrite: (params: RewriteParams) => Awaitable<DependencyResult>;
|
|
52
|
+
}
|
|
53
|
+
/** `dependencies/latest`: the newest releases of dependencies and pinned tools. */
|
|
54
|
+
export interface LatestReader {
|
|
55
|
+
latest: (params: LatestParams) => Awaitable<LatestResult>;
|
|
56
|
+
}
|
|
57
|
+
/** `contribute`: a convention adds tasks to the projects it applies to. */
|
|
58
|
+
export interface Contributor {
|
|
59
|
+
contribute: (params: ContributeParams) => Awaitable<ContributeResult>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A plugin as {@link serve} takes it: `describe`, and the methods it implements.
|
|
63
|
+
* The capabilities it advertises are derived from which methods it has.
|
|
64
|
+
*/
|
|
65
|
+
export type SmithPlugin = Plugin & Partial<Discoverer & Generator & Versioner & Publisher & RegistryReader & CIContributor & DoctorContributor & Remover & DependencyManager & DeclarationEditor & LatestReader & Contributor>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Where the serve loop writes its frames: a Node `Writable` fits. */
|
|
2
|
+
export interface FrameSink {
|
|
3
|
+
write: (chunk: Buffer, callback: (error?: Error | null) => void) => unknown;
|
|
4
|
+
}
|
|
5
|
+
/** The streams {@link serveStreams} runs on. */
|
|
6
|
+
export interface ServeStreams {
|
|
7
|
+
/** The host's requests, as bytes (a Node `Readable` fits). */
|
|
8
|
+
input: AsyncIterable<Uint8Array>;
|
|
9
|
+
/** The plugin's responses. */
|
|
10
|
+
output: FrameSink;
|
|
11
|
+
/** Where a warning about the plugin goes. Default: standard error. */
|
|
12
|
+
log?: (message: string) => void;
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { SmithPlugin } from './plugin.contract.ts';
|
|
2
|
+
import type { ServeStreams } from './protocol-streams.contract.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Runs a plugin on the process's standard input and output, and exits when the
|
|
5
|
+
* host sends `shutdown` or closes the stream. Use it as the whole body of the
|
|
6
|
+
* plugin's entry point:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* await serve({ describe: () => ({ ... }), discover: (params) => ({ ... }) })
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Standard output carries only the protocol's frames: whatever else the plugin
|
|
13
|
+
* or a library writes there goes to standard error instead. A failure of the
|
|
14
|
+
* stream exits with status 1 after the reason is printed.
|
|
15
|
+
*/
|
|
16
|
+
export declare function serve(plugin: SmithPlugin): Promise<never>;
|
|
17
|
+
/**
|
|
18
|
+
* Answers requests from `input` on `output` until the host sends `shutdown`
|
|
19
|
+
* or the input ends, then resolves. This is the loop {@link serve} runs, on
|
|
20
|
+
* streams you choose: a test serves a plugin in-process over pipes with it.
|
|
21
|
+
*
|
|
22
|
+
* Semantics of the Go SDK's `serve.Serve`: requests are answered one at a time,
|
|
23
|
+
* in order; a request before `initialize` is refused (InvalidRequest);
|
|
24
|
+
* a notification (no `id`) gets no answer; an unreadable frame body is answered
|
|
25
|
+
* with ParseError and the loop goes on; an unreadable header ends it with an error.
|
|
26
|
+
*/
|
|
27
|
+
export declare function serveStreams(plugin: SmithPlugin, streams: ServeStreams): Promise<void>;
|