initiative-plugin-sdk 2.0.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/README.md +656 -0
- package/dist/build.d.ts +52 -0
- package/dist/build.js +229 -0
- package/dist/build.js.map +1 -0
- package/dist/cli.d.ts +24 -0
- package/dist/cli.js +160 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +288 -0
- package/dist/client.js +539 -0
- package/dist/client.js.map +1 -0
- package/dist/contract.d.ts +1247 -0
- package/dist/contract.js +167 -0
- package/dist/contract.js.map +1 -0
- package/dist/define.d.ts +336 -0
- package/dist/define.js +120 -0
- package/dist/define.js.map +1 -0
- package/dist/dev.d.ts +28 -0
- package/dist/dev.js +147 -0
- package/dist/dev.js.map +1 -0
- package/dist/expression.d.ts +28 -0
- package/dist/expression.js +67 -0
- package/dist/expression.js.map +1 -0
- package/dist/init.d.ts +14 -0
- package/dist/init.js +52 -0
- package/dist/init.js.map +1 -0
- package/dist/keys.d.ts +85 -0
- package/dist/keys.js +109 -0
- package/dist/keys.js.map +1 -0
- package/dist/manifest.d.ts +8 -0
- package/dist/manifest.js +8 -0
- package/dist/manifest.js.map +1 -0
- package/dist/pack.d.ts +35 -0
- package/dist/pack.js +84 -0
- package/dist/pack.js.map +1 -0
- package/dist/plugin-api.generated.d.ts +4263 -0
- package/dist/plugin-api.generated.js +1019 -0
- package/dist/plugin-api.generated.js.map +1 -0
- package/dist/server.d.ts +75 -0
- package/dist/server.js +408 -0
- package/dist/server.js.map +1 -0
- package/dist/testing.d.ts +116 -0
- package/dist/testing.js +386 -0
- package/dist/testing.js.map +1 -0
- package/dist/tokens.d.ts +161 -0
- package/dist/tokens.js +215 -0
- package/dist/tokens.js.map +1 -0
- package/dist/validate.d.ts +86 -0
- package/dist/validate.js +997 -0
- package/dist/validate.js.map +1 -0
- package/dist/widget.d.ts +235 -0
- package/dist/widget.js +26 -0
- package/dist/widget.js.map +1 -0
- package/examples/minimal/README.md +48 -0
- package/examples/minimal/assets/avatar.png +0 -0
- package/examples/minimal/package.json +22 -0
- package/examples/minimal/src/plugin.ts +77 -0
- package/examples/minimal/src/widgets/open-issues.ts +11 -0
- package/examples/minimal/test/fixtures/issues-statistics.json +9 -0
- package/examples/minimal/test/issue-counts.test.ts +48 -0
- package/examples/minimal/tsconfig.json +14 -0
- package/manifest.contract.json +1886 -0
- package/package.json +92 -0
- package/schemas/plugin-manifest.json +1747 -0
package/dist/dev.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `initiative-plugin dev --initiative <url> --api-key <key>`: a declarative plug-in
|
|
3
|
+
* on your own deployment, uploaded again each time its source changes.
|
|
4
|
+
*
|
|
5
|
+
* Each pass builds the plug-in, packs its listing file (`pack.ts`) and uploads it
|
|
6
|
+
* with its picture, as the owner whose personal API key is given: the
|
|
7
|
+
* deployment publishes it as its own plug-in, on the shelf straight away. The
|
|
8
|
+
* upload is versioned `<listing version>-dev.<digest of the file>`, since a
|
|
9
|
+
* deployment publishes each version once; communities that installed the plug-in
|
|
10
|
+
* move to it from its Update button, or on their own if they follow updates.
|
|
11
|
+
*
|
|
12
|
+
* It watches the directory holding the plug-in's definition, and builds again a
|
|
13
|
+
* moment after the last change there.
|
|
14
|
+
*/
|
|
15
|
+
export interface DevOptions {
|
|
16
|
+
/** The plug-in's package directory. */
|
|
17
|
+
root: string;
|
|
18
|
+
/** The module whose default export is the plug-in's definition, relative to `root`. */
|
|
19
|
+
plugin: string;
|
|
20
|
+
/** The deployment's address, such as `https://initiative.example.com`. */
|
|
21
|
+
initiative: string;
|
|
22
|
+
/** A personal API key of an account that may configure the deployment. */
|
|
23
|
+
apiKey: string;
|
|
24
|
+
/** Stops watching; `dev` then answers 0. */
|
|
25
|
+
signal?: AbortSignal;
|
|
26
|
+
}
|
|
27
|
+
/** Upload the plug-in, then again on each change. Answers the process's exit code. */
|
|
28
|
+
export declare function dev(options: DevOptions): Promise<number>;
|
package/dist/dev.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `initiative-plugin dev --initiative <url> --api-key <key>`: a declarative plug-in
|
|
3
|
+
* on your own deployment, uploaded again each time its source changes.
|
|
4
|
+
*
|
|
5
|
+
* Each pass builds the plug-in, packs its listing file (`pack.ts`) and uploads it
|
|
6
|
+
* with its picture, as the owner whose personal API key is given: the
|
|
7
|
+
* deployment publishes it as its own plug-in, on the shelf straight away. The
|
|
8
|
+
* upload is versioned `<listing version>-dev.<digest of the file>`, since a
|
|
9
|
+
* deployment publishes each version once; communities that installed the plug-in
|
|
10
|
+
* move to it from its Update button, or on their own if they follow updates.
|
|
11
|
+
*
|
|
12
|
+
* It watches the directory holding the plug-in's definition, and builds again a
|
|
13
|
+
* moment after the last change there.
|
|
14
|
+
*/
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { watch } from "node:fs";
|
|
17
|
+
import { basename, dirname, relative, resolve } from "node:path";
|
|
18
|
+
import { bundler, compile } from "./build.js";
|
|
19
|
+
import { listingFile } from "./pack.js";
|
|
20
|
+
/** How long the source must be still before it is built again, in milliseconds. */
|
|
21
|
+
const SETTLE_MS = 200;
|
|
22
|
+
/** Upload the plug-in, then again on each change. Answers the process's exit code. */
|
|
23
|
+
export async function dev(options) {
|
|
24
|
+
const esbuild = await bundler("dev");
|
|
25
|
+
if (!esbuild)
|
|
26
|
+
return 1;
|
|
27
|
+
const root = resolve(options.root);
|
|
28
|
+
const base = options.initiative.replace(/\/+$/, "");
|
|
29
|
+
let uploaded = "";
|
|
30
|
+
/** Send one request as the key's owner; null when the deployment could not be reached. */
|
|
31
|
+
const send = async (path, body) => {
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch(`${base}${path}`, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: {
|
|
36
|
+
authorization: `Bearer ${options.apiKey}`,
|
|
37
|
+
...(body instanceof FormData ? {} : { "content-type": "application/json" }),
|
|
38
|
+
},
|
|
39
|
+
body: body instanceof FormData ? body : JSON.stringify(body),
|
|
40
|
+
});
|
|
41
|
+
return { status: response.status, text: (await response.text()).trim() };
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
process.stderr.write(`could not reach ${base}: ${error.message}\n`);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
/** A refusal of the key itself ends the run: nothing later would be accepted either. */
|
|
49
|
+
const refusedKey = (status) => status === 401 || status === 403;
|
|
50
|
+
/** One pass. Answers an exit code to stop with, or null to keep watching. */
|
|
51
|
+
const upload = async () => {
|
|
52
|
+
let packed;
|
|
53
|
+
try {
|
|
54
|
+
const compiled = await compile(esbuild, root, options.plugin);
|
|
55
|
+
if (compiled.problems) {
|
|
56
|
+
for (const problem of compiled.problems)
|
|
57
|
+
process.stderr.write(`${problem}\n`);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
if (!compiled.plugin.hosts) {
|
|
61
|
+
process.stderr.write("dev uploads a declarative plug-in; pack a container plug-in's listing and register its service on the deployment\n");
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
packed = listingFile(compiled.plugin, compiled.manifest, root);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
process.stderr.write(`${error.message}\n`);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const { listing, avatar } = packed;
|
|
71
|
+
const digest = createHash("sha256").update(JSON.stringify(listing)).digest("hex").slice(0, 8);
|
|
72
|
+
const version = `${listing.version}-dev.${digest}`;
|
|
73
|
+
const name = `${listing.public_id} ${version}`;
|
|
74
|
+
if (digest === uploaded) {
|
|
75
|
+
process.stdout.write(`${name} is already uploaded\n`);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
if (avatar) {
|
|
79
|
+
const form = new FormData();
|
|
80
|
+
form.append("file", new Blob([new Uint8Array(avatar)]), basename(String(listing.avatar_url)));
|
|
81
|
+
const answer = await send("/api/v1/marketplace/local/media", form);
|
|
82
|
+
if (!answer)
|
|
83
|
+
return null;
|
|
84
|
+
if (answer.status !== 201) {
|
|
85
|
+
process.stderr.write(`the deployment refused the picture: ${answer.status} ${answer.text}\n`);
|
|
86
|
+
return refusedKey(answer.status) ? 1 : null;
|
|
87
|
+
}
|
|
88
|
+
listing.avatar_url = JSON.parse(answer.text).path;
|
|
89
|
+
}
|
|
90
|
+
const answer = await send("/api/v1/marketplace/local/upload", { manifest: { ...listing, version } });
|
|
91
|
+
if (!answer)
|
|
92
|
+
return null;
|
|
93
|
+
if (answer.status !== 201) {
|
|
94
|
+
process.stderr.write(`the deployment refused ${name}: ${answer.status} ${answer.text}\n`);
|
|
95
|
+
return refusedKey(answer.status) ? 1 : null;
|
|
96
|
+
}
|
|
97
|
+
uploaded = digest;
|
|
98
|
+
process.stdout.write(`uploaded ${name} (uid ${listing.uid}): ${answer.status} ${answer.text}\n`);
|
|
99
|
+
return null;
|
|
100
|
+
};
|
|
101
|
+
const first = await upload();
|
|
102
|
+
if (first !== null)
|
|
103
|
+
return first;
|
|
104
|
+
if (options.signal?.aborted)
|
|
105
|
+
return 0;
|
|
106
|
+
const source = dirname(resolve(root, options.plugin));
|
|
107
|
+
process.stdout.write(`watching ${relative(process.cwd(), source) || "."} for changes\n`);
|
|
108
|
+
return new Promise((done) => {
|
|
109
|
+
const watcher = watch(source, { recursive: true });
|
|
110
|
+
let timer;
|
|
111
|
+
let running = false;
|
|
112
|
+
let changed = false;
|
|
113
|
+
const finish = (code) => {
|
|
114
|
+
clearTimeout(timer);
|
|
115
|
+
watcher.close();
|
|
116
|
+
options.signal?.removeEventListener("abort", stop);
|
|
117
|
+
done(code);
|
|
118
|
+
};
|
|
119
|
+
const stop = () => finish(0);
|
|
120
|
+
const settle = () => {
|
|
121
|
+
clearTimeout(timer);
|
|
122
|
+
timer = setTimeout(pass, SETTLE_MS);
|
|
123
|
+
};
|
|
124
|
+
const pass = async () => {
|
|
125
|
+
if (running) {
|
|
126
|
+
changed = true;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
running = true;
|
|
130
|
+
const code = await upload();
|
|
131
|
+
running = false;
|
|
132
|
+
if (code !== null)
|
|
133
|
+
finish(code);
|
|
134
|
+
else if (changed) {
|
|
135
|
+
changed = false;
|
|
136
|
+
settle();
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
watcher.on("change", settle);
|
|
140
|
+
watcher.on("error", (error) => {
|
|
141
|
+
process.stderr.write(`stopped watching ${source}: ${error.message}\n`);
|
|
142
|
+
finish(1);
|
|
143
|
+
});
|
|
144
|
+
options.signal?.addEventListener("abort", stop);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=dev.js.map
|
package/dist/dev.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.js","sourceRoot":"","sources":["../src/dev.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSONata, as Initiative evaluates a declarative plug-in's expressions.
|
|
3
|
+
*
|
|
4
|
+
* Standard JSONata, the reference implementation, with no functions added.
|
|
5
|
+
* Every evaluation is bounded by three of the contract's caps, which
|
|
6
|
+
* Initiative reads from the same contract: `expressionTimeMs`,
|
|
7
|
+
* `expressionDepth` (levels of nesting, function calls included) and
|
|
8
|
+
* `expressionOutputBytes` (the answer, as JSON). Passing one is a failure,
|
|
9
|
+
* as an expression that raises an error is.
|
|
10
|
+
*/
|
|
11
|
+
import jsonata from "jsonata";
|
|
12
|
+
/** An expression that does not parse, failed, or passed one of its bounds. */
|
|
13
|
+
export declare class ExpressionError extends Error {
|
|
14
|
+
/** Where in the expression, in characters, when the failure names a place. */
|
|
15
|
+
readonly position: number | undefined;
|
|
16
|
+
constructor(message: string, position?: number);
|
|
17
|
+
}
|
|
18
|
+
/** An expression's syntax tree. Throws {@link ExpressionError} when it does not parse. */
|
|
19
|
+
export declare function parseExpression(text: string): jsonata.ExprNode;
|
|
20
|
+
/**
|
|
21
|
+
* Evaluate one expression over `input`, within the contract's bounds. The
|
|
22
|
+
* answer comes back as plain JSON, or undefined when the expression answers
|
|
23
|
+
* nothing. `now` fixes what `$now()` and `$millis()` answer; absent, they
|
|
24
|
+
* answer the moment of evaluation.
|
|
25
|
+
*/
|
|
26
|
+
export declare function evaluate(text: string, input: unknown, options?: {
|
|
27
|
+
now?: Date | string | number;
|
|
28
|
+
}): Promise<unknown>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSONata, as Initiative evaluates a declarative plug-in's expressions.
|
|
3
|
+
*
|
|
4
|
+
* Standard JSONata, the reference implementation, with no functions added.
|
|
5
|
+
* Every evaluation is bounded by three of the contract's caps, which
|
|
6
|
+
* Initiative reads from the same contract: `expressionTimeMs`,
|
|
7
|
+
* `expressionDepth` (levels of nesting, function calls included) and
|
|
8
|
+
* `expressionOutputBytes` (the answer, as JSON). Passing one is a failure,
|
|
9
|
+
* as an expression that raises an error is.
|
|
10
|
+
*/
|
|
11
|
+
import jsonata from "jsonata";
|
|
12
|
+
import { CAPS } from "./contract.js";
|
|
13
|
+
/** An expression that does not parse, failed, or passed one of its bounds. */
|
|
14
|
+
export class ExpressionError extends Error {
|
|
15
|
+
/** Where in the expression, in characters, when the failure names a place. */
|
|
16
|
+
position;
|
|
17
|
+
constructor(message, position) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "ExpressionError";
|
|
20
|
+
this.position = position;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function failure(error) {
|
|
24
|
+
const { message, position } = (error ?? {});
|
|
25
|
+
return new ExpressionError(typeof message === "string" ? message : String(error), typeof position === "number" ? position : undefined);
|
|
26
|
+
}
|
|
27
|
+
/** An expression's syntax tree. Throws {@link ExpressionError} when it does not parse. */
|
|
28
|
+
export function parseExpression(text) {
|
|
29
|
+
try {
|
|
30
|
+
return jsonata(text).ast();
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw failure(error);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** `$now()` and `$millis()` answering one instant, as every expression of one call sees it. */
|
|
37
|
+
async function clock(millis) {
|
|
38
|
+
return {
|
|
39
|
+
now: await jsonata(`function($picture, $timezone) { $fromMillis(${millis}, $picture, $timezone) }`).evaluate({}),
|
|
40
|
+
millis: await jsonata(`function() { ${millis} }`).evaluate({}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Evaluate one expression over `input`, within the contract's bounds. The
|
|
45
|
+
* answer comes back as plain JSON, or undefined when the expression answers
|
|
46
|
+
* nothing. `now` fixes what `$now()` and `$millis()` answer; absent, they
|
|
47
|
+
* answer the moment of evaluation.
|
|
48
|
+
*/
|
|
49
|
+
export async function evaluate(text, input, options = {}) {
|
|
50
|
+
let answer;
|
|
51
|
+
try {
|
|
52
|
+
const expression = jsonata(text, { timeout: CAPS.expressionTimeMs, stack: CAPS.expressionDepth });
|
|
53
|
+
const bindings = options.now === undefined ? undefined : await clock(new Date(options.now).getTime());
|
|
54
|
+
answer = await expression.evaluate(input, bindings);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
throw failure(error);
|
|
58
|
+
}
|
|
59
|
+
const json = answer === undefined ? undefined : JSON.stringify(answer);
|
|
60
|
+
if (json === undefined)
|
|
61
|
+
return undefined;
|
|
62
|
+
if (Buffer.byteLength(json, "utf-8") > CAPS.expressionOutputBytes) {
|
|
63
|
+
throw new ExpressionError(`the answer is over ${CAPS.expressionOutputBytes} bytes`);
|
|
64
|
+
}
|
|
65
|
+
return JSON.parse(json);
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=expression.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expression.js","sourceRoot":"","sources":["../src/expression.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,8EAA8E;AAC9E,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACxC,8EAA8E;IACrE,QAAQ,CAAqB;IAEtC,YAAY,OAAe,EAAE,QAAiB;QAC5C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAED,SAAS,OAAO,CAAC,KAAc;IAC7B,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE,CAA8C,CAAC;IACzF,OAAO,IAAI,eAAe,CACxB,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EACrD,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CACpD,CAAC;AACJ,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,+FAA+F;AAC/F,KAAK,UAAU,KAAK,CAAC,MAAc;IACjC,OAAO;QACL,GAAG,EAAE,MAAM,OAAO,CAAC,+CAA+C,MAAM,0BAA0B,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChH,MAAM,EAAE,MAAM,OAAO,CAAC,gBAAgB,MAAM,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;KAC/D,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAY,EACZ,KAAc,EACd,OAAO,GAAqC,EAAE;IAE9C,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC;QAClG,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACtG,MAAM,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACtD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACvE,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACzC,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAClE,MAAM,IAAI,eAAe,CAAC,sBAAsB,IAAI,CAAC,qBAAqB,QAAQ,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
package/dist/init.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `initiative-plugin init [dir] [--example minimal]`: a new plug-in, copied from one
|
|
3
|
+
* of the SDK's examples, with a uid of its own and its package named after its
|
|
4
|
+
* directory.
|
|
5
|
+
*/
|
|
6
|
+
/** A fresh catalog uid, in Crockford base32. Mint once, write it into the plug-in, never change it. */
|
|
7
|
+
export declare function mintUid(): string;
|
|
8
|
+
export interface InitOptions {
|
|
9
|
+
/** The new plug-in's directory: absent or empty. */
|
|
10
|
+
dir: string;
|
|
11
|
+
example: string;
|
|
12
|
+
}
|
|
13
|
+
/** Copy the example. Answers the process's exit code. */
|
|
14
|
+
export declare function init(options: InitOptions): number;
|
package/dist/init.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `initiative-plugin init [dir] [--example minimal]`: a new plug-in, copied from one
|
|
3
|
+
* of the SDK's examples, with a uid of its own and its package named after its
|
|
4
|
+
* directory.
|
|
5
|
+
*/
|
|
6
|
+
import { randomInt } from "node:crypto";
|
|
7
|
+
import { cpSync, existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { basename, join, relative, resolve } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { CAPS, CHARSETS } from "./contract.js";
|
|
11
|
+
/** Where the examples ship, beside `dist` in the package and `src` in the repo. */
|
|
12
|
+
const EXAMPLES = fileURLToPath(new URL("../examples/", import.meta.url));
|
|
13
|
+
/** A fresh catalog uid, in Crockford base32. Mint once, write it into the plug-in, never change it. */
|
|
14
|
+
export function mintUid() {
|
|
15
|
+
let uid = "";
|
|
16
|
+
for (let index = 0; index < CAPS.uidLength; index += 1)
|
|
17
|
+
uid += CHARSETS.uid[randomInt(CHARSETS.uid.length)];
|
|
18
|
+
return uid;
|
|
19
|
+
}
|
|
20
|
+
/** Copy the example. Answers the process's exit code. */
|
|
21
|
+
export function init(options) {
|
|
22
|
+
const examples = readdirSync(EXAMPLES);
|
|
23
|
+
if (!examples.includes(options.example)) {
|
|
24
|
+
process.stderr.write(`there is no example ${options.example}; there is ${examples.join(", ")}\n`);
|
|
25
|
+
return 2;
|
|
26
|
+
}
|
|
27
|
+
const dir = resolve(options.dir);
|
|
28
|
+
if (existsSync(dir) && readdirSync(dir).length > 0) {
|
|
29
|
+
process.stderr.write(`${dir} is not empty; choose a new directory\n`);
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
cpSync(join(EXAMPLES, options.example), dir, {
|
|
33
|
+
recursive: true,
|
|
34
|
+
filter: (path) => !/[\\/]node_modules$/.test(path),
|
|
35
|
+
});
|
|
36
|
+
const uid = mintUid();
|
|
37
|
+
const pluginPath = join(dir, "src", "plugin.ts");
|
|
38
|
+
writeFileSync(pluginPath, readFileSync(pluginPath, "utf-8").replace(/uid: "[0-9A-Z]+"/, `uid: "${uid}"`));
|
|
39
|
+
const packagePath = join(dir, "package.json");
|
|
40
|
+
const manifest = JSON.parse(readFileSync(packagePath, "utf-8"));
|
|
41
|
+
// npm's rule for a package name: lowercase, and no character a URL would escape.
|
|
42
|
+
const name = basename(dir).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+/, "") || "plugin";
|
|
43
|
+
writeFileSync(packagePath, `${JSON.stringify({ ...manifest, name }, null, 2)}\n`);
|
|
44
|
+
const where = relative(process.cwd(), dir) || ".";
|
|
45
|
+
process.stdout.write([
|
|
46
|
+
`created ${where} from the ${options.example} example, uid ${uid}`,
|
|
47
|
+
`next: cd ${where} && npm install && npm test`,
|
|
48
|
+
"",
|
|
49
|
+
].join("\n"));
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=init.js.map
|
package/dist/init.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"}
|
package/dist/keys.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Your plug-in's signing keys.
|
|
3
|
+
*
|
|
4
|
+
* A plug-in proves who it is to Initiative by signing a short JWT with its own
|
|
5
|
+
* private key (`private_key_jwt`, RFC 7523). The deployment's operator
|
|
6
|
+
* registers the matching public keys — a JWKS — against your plug-in's public id,
|
|
7
|
+
* and the private key never leaves your plug-in.
|
|
8
|
+
*
|
|
9
|
+
* Two algorithms are supported, and the deployment picks the one to check by
|
|
10
|
+
* the registered key's type: `RS256` for an RSA key, `ES256` for a P-256 key.
|
|
11
|
+
*
|
|
12
|
+
* Every key carries a `kid`. Each JWT names the `kid` it was signed with, so
|
|
13
|
+
* rotating is: generate a new key, have the operator register a JWKS holding
|
|
14
|
+
* both public keys, switch your plug-in to the new one, then drop the old entry.
|
|
15
|
+
*/
|
|
16
|
+
import { type KeyObject } from "node:crypto";
|
|
17
|
+
/** The algorithms a plug-in key may use. */
|
|
18
|
+
export type PluginKeyAlgorithm = "RS256" | "ES256";
|
|
19
|
+
/** One public key, as a JWK. */
|
|
20
|
+
export interface PublicJwk {
|
|
21
|
+
kty: "RSA" | "EC";
|
|
22
|
+
kid: string;
|
|
23
|
+
alg: PluginKeyAlgorithm;
|
|
24
|
+
use: "sig";
|
|
25
|
+
n?: string;
|
|
26
|
+
e?: string;
|
|
27
|
+
crv?: "P-256";
|
|
28
|
+
x?: string;
|
|
29
|
+
y?: string;
|
|
30
|
+
}
|
|
31
|
+
/** A JSON Web Key Set: what the operator registers for your plug-in. */
|
|
32
|
+
export interface Jwks {
|
|
33
|
+
keys: PublicJwk[];
|
|
34
|
+
}
|
|
35
|
+
/** A private key ready to sign with. */
|
|
36
|
+
export interface PluginSigningKey {
|
|
37
|
+
key: KeyObject;
|
|
38
|
+
kid: string;
|
|
39
|
+
alg: PluginKeyAlgorithm;
|
|
40
|
+
}
|
|
41
|
+
/** What {@link generatePluginKeys} returns. */
|
|
42
|
+
export interface GeneratedPluginKeys {
|
|
43
|
+
/** PKCS#8 PEM. Keep it secret, and out of source control. */
|
|
44
|
+
privateKeyPem: string;
|
|
45
|
+
/** The public half, to register with the deployment. */
|
|
46
|
+
jwks: Jwks;
|
|
47
|
+
kid: string;
|
|
48
|
+
alg: PluginKeyAlgorithm;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Generate a new signing key.
|
|
52
|
+
*
|
|
53
|
+
* `kid` defaults to the key's RFC 7638 thumbprint, so the same key always gets
|
|
54
|
+
* the same id and two keys never share one.
|
|
55
|
+
*/
|
|
56
|
+
export declare function generatePluginKeys(options?: {
|
|
57
|
+
alg?: PluginKeyAlgorithm;
|
|
58
|
+
kid?: string;
|
|
59
|
+
}): GeneratedPluginKeys;
|
|
60
|
+
/**
|
|
61
|
+
* Load a PEM private key for signing, under the `kid` it was registered with,
|
|
62
|
+
* or its RFC 7638 thumbprint when none is given.
|
|
63
|
+
*
|
|
64
|
+
* The algorithm follows from the key: an RSA key signs `RS256`, a P-256 key
|
|
65
|
+
* signs `ES256`, and any other key is refused.
|
|
66
|
+
*/
|
|
67
|
+
export declare function loadPrivateKey(pem: string, kid?: string): PluginSigningKey;
|
|
68
|
+
/** The JWKS for one signing key: its public half, with its `kid`. */
|
|
69
|
+
export declare function publicJwks(signing: PluginSigningKey): Jwks;
|
|
70
|
+
/** The algorithm a key signs with, from its type. */
|
|
71
|
+
export declare function algorithmOf(key: KeyObject): PluginKeyAlgorithm;
|
|
72
|
+
/**
|
|
73
|
+
* Sign a compact JWT with a plug-in key. `typ` goes in the header; a plug-in's own
|
|
74
|
+
* assertions are plain `JWT`.
|
|
75
|
+
*
|
|
76
|
+
* ES256 signatures are written in the JOSE form (`r || s`, 64 bytes), which is
|
|
77
|
+
* what `ieee-p1363` produces.
|
|
78
|
+
*/
|
|
79
|
+
export declare function signJwt(signing: PluginSigningKey, claims: Record<string, unknown>, typ?: string): string;
|
|
80
|
+
/**
|
|
81
|
+
* A public key's fingerprint: its RFC 7638 SHA-256 thumbprint, base64url
|
|
82
|
+
* without padding. The plug-in logs it at start, and Initiative shows the same
|
|
83
|
+
* value for the key it reads from the plug-in.
|
|
84
|
+
*/
|
|
85
|
+
export declare function jwkThumbprint(jwk: PublicJwk): string;
|
package/dist/keys.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Your plug-in's signing keys.
|
|
3
|
+
*
|
|
4
|
+
* A plug-in proves who it is to Initiative by signing a short JWT with its own
|
|
5
|
+
* private key (`private_key_jwt`, RFC 7523). The deployment's operator
|
|
6
|
+
* registers the matching public keys — a JWKS — against your plug-in's public id,
|
|
7
|
+
* and the private key never leaves your plug-in.
|
|
8
|
+
*
|
|
9
|
+
* Two algorithms are supported, and the deployment picks the one to check by
|
|
10
|
+
* the registered key's type: `RS256` for an RSA key, `ES256` for a P-256 key.
|
|
11
|
+
*
|
|
12
|
+
* Every key carries a `kid`. Each JWT names the `kid` it was signed with, so
|
|
13
|
+
* rotating is: generate a new key, have the operator register a JWKS holding
|
|
14
|
+
* both public keys, switch your plug-in to the new one, then drop the old entry.
|
|
15
|
+
*/
|
|
16
|
+
import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, sign as cryptoSign, } from "node:crypto";
|
|
17
|
+
/**
|
|
18
|
+
* Generate a new signing key.
|
|
19
|
+
*
|
|
20
|
+
* `kid` defaults to the key's RFC 7638 thumbprint, so the same key always gets
|
|
21
|
+
* the same id and two keys never share one.
|
|
22
|
+
*/
|
|
23
|
+
export function generatePluginKeys(options = {}) {
|
|
24
|
+
const alg = options.alg ?? "RS256";
|
|
25
|
+
const { privateKey } = alg === "RS256"
|
|
26
|
+
? generateKeyPairSync("rsa", { modulusLength: 2048 })
|
|
27
|
+
: alg === "ES256"
|
|
28
|
+
? generateKeyPairSync("ec", { namedCurve: "P-256" })
|
|
29
|
+
: unsupported(alg);
|
|
30
|
+
const kid = options.kid ?? thumbprint(privateKey);
|
|
31
|
+
return {
|
|
32
|
+
privateKeyPem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
|
|
33
|
+
jwks: publicJwks({ key: privateKey, kid, alg }),
|
|
34
|
+
kid,
|
|
35
|
+
alg,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Load a PEM private key for signing, under the `kid` it was registered with,
|
|
40
|
+
* or its RFC 7638 thumbprint when none is given.
|
|
41
|
+
*
|
|
42
|
+
* The algorithm follows from the key: an RSA key signs `RS256`, a P-256 key
|
|
43
|
+
* signs `ES256`, and any other key is refused.
|
|
44
|
+
*/
|
|
45
|
+
export function loadPrivateKey(pem, kid) {
|
|
46
|
+
const key = createPrivateKey(pem);
|
|
47
|
+
return { key, kid: kid || thumbprint(key), alg: algorithmOf(key) };
|
|
48
|
+
}
|
|
49
|
+
/** The JWKS for one signing key: its public half, with its `kid`. */
|
|
50
|
+
export function publicJwks(signing) {
|
|
51
|
+
const jwk = createPublicKey(signing.key).export({ format: "jwk" });
|
|
52
|
+
const entry = signing.alg === "RS256"
|
|
53
|
+
? { kty: "RSA", kid: signing.kid, alg: "RS256", use: "sig", n: jwk.n, e: jwk.e }
|
|
54
|
+
: {
|
|
55
|
+
kty: "EC",
|
|
56
|
+
kid: signing.kid,
|
|
57
|
+
alg: "ES256",
|
|
58
|
+
use: "sig",
|
|
59
|
+
crv: "P-256",
|
|
60
|
+
x: jwk.x,
|
|
61
|
+
y: jwk.y,
|
|
62
|
+
};
|
|
63
|
+
return { keys: [entry] };
|
|
64
|
+
}
|
|
65
|
+
/** The algorithm a key signs with, from its type. */
|
|
66
|
+
export function algorithmOf(key) {
|
|
67
|
+
if (key.asymmetricKeyType === "rsa")
|
|
68
|
+
return "RS256";
|
|
69
|
+
if (key.asymmetricKeyType === "ec" && key.asymmetricKeyDetails?.namedCurve === "prime256v1") {
|
|
70
|
+
return "ES256";
|
|
71
|
+
}
|
|
72
|
+
throw new TypeError(`unsupported key type ${key.asymmetricKeyType ?? "unknown"}: use an RSA or P-256 key`);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Sign a compact JWT with a plug-in key. `typ` goes in the header; a plug-in's own
|
|
76
|
+
* assertions are plain `JWT`.
|
|
77
|
+
*
|
|
78
|
+
* ES256 signatures are written in the JOSE form (`r || s`, 64 bytes), which is
|
|
79
|
+
* what `ieee-p1363` produces.
|
|
80
|
+
*/
|
|
81
|
+
export function signJwt(signing, claims, typ = "JWT") {
|
|
82
|
+
const header = { alg: signing.alg, kid: signing.kid, typ };
|
|
83
|
+
const input = `${base64url(JSON.stringify(header))}.${base64url(JSON.stringify(claims))}`;
|
|
84
|
+
const signature = signing.alg === "ES256"
|
|
85
|
+
? cryptoSign("sha256", Buffer.from(input), { key: signing.key, dsaEncoding: "ieee-p1363" })
|
|
86
|
+
: cryptoSign("sha256", Buffer.from(input), signing.key);
|
|
87
|
+
return `${input}.${signature.toString("base64url")}`;
|
|
88
|
+
}
|
|
89
|
+
function base64url(text) {
|
|
90
|
+
return Buffer.from(text, "utf-8").toString("base64url");
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* A public key's fingerprint: its RFC 7638 SHA-256 thumbprint, base64url
|
|
94
|
+
* without padding. The plug-in logs it at start, and Initiative shows the same
|
|
95
|
+
* value for the key it reads from the plug-in.
|
|
96
|
+
*/
|
|
97
|
+
export function jwkThumbprint(jwk) {
|
|
98
|
+
const members = jwk.kty === "RSA"
|
|
99
|
+
? { e: jwk.e, kty: jwk.kty, n: jwk.n }
|
|
100
|
+
: { crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y };
|
|
101
|
+
return createHash("sha256").update(JSON.stringify(members)).digest("base64url");
|
|
102
|
+
}
|
|
103
|
+
function thumbprint(privateKey) {
|
|
104
|
+
return jwkThumbprint(createPublicKey(privateKey).export({ format: "jwk" }));
|
|
105
|
+
}
|
|
106
|
+
function unsupported(alg) {
|
|
107
|
+
throw new TypeError(`unsupported algorithm ${alg}: use RS256 or ES256`);
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=keys.js.map
|
package/dist/keys.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `initiative-plugin-sdk/manifest`: the contract as types, the plug-in's one
|
|
3
|
+
* definition, and checking a manifest before a deployment does.
|
|
4
|
+
*/
|
|
5
|
+
export * from "./contract.js";
|
|
6
|
+
export { definePlugin, defineEndpoint, type Actor, type AfterConnectAnswer, type AfterConnectCall, type PluginContext, type PluginDefinition, type Call, type CallableEndpoint, type DashboardDeclaration, type EmittedEndpoint, type EndpointCall, type EndpointDeclaration, type Handoff, type Hooks, type ListingDeclaration, type Outcome, type Params, type ParamSpec, type ParamValue, type Result, type ReturnSpec, type RevokeCall, type ScheduleCall, type ScheduleDeclaration, type SurfaceCall, type SurfaceDeclaration, type WebhookCall, type WidgetDeclaration, } from "./define.js";
|
|
7
|
+
export { MANIFEST_PATH, manifestSchema, validateDocument, validateManifest, type PluginDocument, type ValidationProblem, } from "./validate.js";
|
|
8
|
+
export type { Jwks, PublicJwk } from "./keys.js";
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `initiative-plugin-sdk/manifest`: the contract as types, the plug-in's one
|
|
3
|
+
* definition, and checking a manifest before a deployment does.
|
|
4
|
+
*/
|
|
5
|
+
export * from "./contract.js";
|
|
6
|
+
export { definePlugin, defineEndpoint, } from "./define.js";
|
|
7
|
+
export { MANIFEST_PATH, manifestSchema, validateDocument, validateManifest, } from "./validate.js";
|
|
8
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,eAAe,CAAC;AAE9B,OAAO,EACL,YAAY,EACZ,cAAc,GA4Bf,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,gBAAgB,GAGjB,MAAM,eAAe,CAAC"}
|
package/dist/pack.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `initiative-plugin pack`: the plug-in as one listing file, the file a self-hosted
|
|
3
|
+
* deployment publishes as its own plug-in.
|
|
4
|
+
*
|
|
5
|
+
* The listing file is the shape a deployment's listing upload
|
|
6
|
+
* (`POST /api/v1/marketplace/local/upload`, as `{"manifest": <file>}`) and its
|
|
7
|
+
* catalog directory (`MARKETPLACE_EXTRA_CATALOG_DIR`) read: what the catalogue
|
|
8
|
+
* shows, this version's manifest as its `definition`, and its registration.
|
|
9
|
+
* The manifest is the one `build` makes, checked the same way.
|
|
10
|
+
*
|
|
11
|
+
* The listing's picture is named by the path the deployment keeps it under,
|
|
12
|
+
* which is the SHA-256 of its bytes, so it is uploaded beside the file
|
|
13
|
+
* (`POST /api/v1/marketplace/local/media`). A deployment keeps PNG, JPEG, GIF
|
|
14
|
+
* and WebP; any other picture is left out, and the listing shows the
|
|
15
|
+
* deployment's default mark.
|
|
16
|
+
*/
|
|
17
|
+
import type { Manifest } from "./contract.js";
|
|
18
|
+
import type { AnyPlugin } from "./define.js";
|
|
19
|
+
export interface PackOptions {
|
|
20
|
+
/** The plug-in's package directory. */
|
|
21
|
+
root: string;
|
|
22
|
+
/** The module whose default export is the plug-in's definition, relative to `root`. */
|
|
23
|
+
plugin: string;
|
|
24
|
+
/** Where to write the listing file, relative to `root`. Default: `<publicId>-<version>.json`. */
|
|
25
|
+
out?: string;
|
|
26
|
+
}
|
|
27
|
+
/** The listing file, and the picture to upload beside it (null when it names none). */
|
|
28
|
+
export interface Packed {
|
|
29
|
+
listing: Record<string, unknown>;
|
|
30
|
+
avatar: Buffer | null;
|
|
31
|
+
}
|
|
32
|
+
/** The listing file for a plug-in whose manifest `compile` made. */
|
|
33
|
+
export declare function listingFile(plugin: AnyPlugin, manifest: Manifest, root: string): Packed;
|
|
34
|
+
/** Write the plug-in's listing file. Answers the process's exit code. */
|
|
35
|
+
export declare function pack(options: PackOptions): Promise<number>;
|