@byokit/openclaw 0.14.0 → 0.14.4
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/CHANGELOG.md +38 -0
- package/README.md +12 -2
- package/dist/accounts.js +11 -5
- package/dist/approvals.js +1 -1
- package/dist/auth-status.js +1 -1
- package/dist/auth-store.js +2 -2
- package/dist/bridge.js +1 -1
- package/dist/browser/host.js +3 -3
- package/dist/browser/verify.js +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +7 -3
- package/dist/constants.d.ts +1 -1
- package/dist/day-usage.js +1 -1
- package/dist/device.js +7 -7
- package/dist/engine.d.ts +10 -0
- package/dist/engine.js +45 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.js +9 -8
- package/dist/keys.d.ts +14 -1
- package/dist/keys.js +56 -8
- package/dist/kit.d.ts +15 -1
- package/dist/kit.js +99 -31
- package/dist/link.js +5 -5
- package/dist/members.d.ts +3 -0
- package/dist/members.js +10 -0
- package/dist/migrate.js +3 -3
- package/dist/pick.js +1 -1
- package/dist/plugins.d.ts +17 -0
- package/dist/plugins.js +40 -0
- package/dist/routes.json +288 -288
- package/dist/runs.js +6 -6
- package/dist/signin.d.ts +5 -0
- package/dist/signin.js +55 -5
- package/dist/testing/browser.js +2 -2
- package/dist/testing/contract.js +3 -3
- package/dist/testing/fake-gateway.js +14 -8
- package/dist/testing/index.js +4 -4
- package/dist/testing/model-stub.d.ts +4 -0
- package/dist/testing/model-stub.js +8 -1
- package/dist/transport.d.ts +1 -1
- package/dist/transport.js +1 -1
- package/engine/plugins.json +150 -0
- package/package.json +6 -6
- package/policy/policy.mjs +43 -14
package/dist/kit.js
CHANGED
|
@@ -1,31 +1,33 @@
|
|
|
1
|
+
import { openCodeWorkspaces } from '@byokit/accounts/opencode-go';
|
|
1
2
|
import { readFileSync } from 'node:fs';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
4
5
|
import { isDeepStrictEqual } from 'node:util';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { PROTOCOL_VERSION, LIVE_CATALOGS } from
|
|
7
|
-
import { atomic } from
|
|
8
|
-
import { Approvals } from
|
|
9
|
-
import { Bridge } from
|
|
10
|
-
import { Engine, refusedOnly } from
|
|
11
|
-
import { AuthStoreSealSizeError } from
|
|
12
|
-
import { gatewayTransport } from
|
|
13
|
-
import { createMembers } from
|
|
14
|
-
import { createAccounts, memberOfAccount } from
|
|
15
|
-
import { createLocks } from
|
|
16
|
-
import { keyAgentId, KEY_PREFIX, MEMBER_ID } from
|
|
17
|
-
import { confirmRetainedLogin as confirmLogin, migrateRetainedLogin as migrateLogin } from
|
|
18
|
-
import { createRuns, signedInProviders } from
|
|
19
|
-
import { authStatus, createAuthStatus } from
|
|
20
|
-
import { outputSchema } from
|
|
21
|
-
import { createKeys } from
|
|
22
|
-
import { routes as routeTable } from
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
7
|
+
import { PROTOCOL_VERSION, LIVE_CATALOGS } from './constants.js';
|
|
8
|
+
import { atomic } from './engine-patches.js';
|
|
9
|
+
import { Approvals } from './approvals.js';
|
|
10
|
+
import { Bridge } from './bridge.js';
|
|
11
|
+
import { Engine, canonicalStateDir, refusedOnly } from './engine.js';
|
|
12
|
+
import { AuthStoreSealSizeError } from './auth-store.js';
|
|
13
|
+
import { gatewayTransport } from './transport.js';
|
|
14
|
+
import { createMembers } from './members.js';
|
|
15
|
+
import { createAccounts, memberOfAccount } from './accounts.js';
|
|
16
|
+
import { createLocks } from './locks.js';
|
|
17
|
+
import { keyAgentId, KEY_PREFIX, MEMBER_ID } from './members.js';
|
|
18
|
+
import { confirmRetainedLogin as confirmLogin, migrateRetainedLogin as migrateLogin } from './migrate.js';
|
|
19
|
+
import { createRuns, signedInProviders } from './runs.js';
|
|
20
|
+
import { authStatus, createAuthStatus } from './auth-status.js';
|
|
21
|
+
import { outputSchema } from './output.js';
|
|
22
|
+
import { createKeys } from './keys.js';
|
|
23
|
+
import { routes as routeTable } from './routes.js';
|
|
24
|
+
import { installedPlugins, pinnedSpec, recordPlugin, PluginInstallError } from './plugins.js';
|
|
25
|
+
import { providerOf } from './pick.js';
|
|
26
|
+
import { signIn as startSignIn, signOut as engineSignOut } from './signin.js';
|
|
27
|
+
import { reconcileConfig, browserToolPolicySafe, browserProfileAcknowledged, browserSessionMayRun, memoryLimited as configMemoryLimited, configKeyPath, readConfigKey, writeConfigKey } from './config.js';
|
|
28
|
+
import { createBrowserHost } from './browser/host.js';
|
|
29
|
+
import { fileSignInStore } from './browser/store.js';
|
|
30
|
+
import { launchBroker } from './browser/broker.js';
|
|
29
31
|
/** The engine's learning-mode config key (5.6 "Engine learning state"). */
|
|
30
32
|
const LEARNING_KEY = 'skills.workshop.autonomous.mode';
|
|
31
33
|
const RESTART_MS = 60_000;
|
|
@@ -436,6 +438,7 @@ export class OpenClawKit {
|
|
|
436
438
|
members;
|
|
437
439
|
locks = createLocks();
|
|
438
440
|
routeMap = new Map();
|
|
441
|
+
installs = Promise.resolve();
|
|
439
442
|
accountsCtl;
|
|
440
443
|
// One bridge and one approval surface per kit: listeners and run registrations survive reconnects (B3).
|
|
441
444
|
// Only the socket itself is per-connection. Approvals always reaches the live transport through this.request().
|
|
@@ -651,7 +654,7 @@ export class OpenClawKit {
|
|
|
651
654
|
|| (o.browser && ['browser', 'request_sign_in', 'exec', 'process', 'code_execution', 'terminal', 'read', 'write', 'edit', 'apply_patch', 'gateway'].includes(t.name)))
|
|
652
655
|
throw new Error(`invalid tool name: ${t.name}`);
|
|
653
656
|
}
|
|
654
|
-
this.o = o;
|
|
657
|
+
this.o = { ...o, stateDir: canonicalStateDir(o.stateDir) };
|
|
655
658
|
this.engine = new Engine({ ...o, pluginId: o.plugin?.id ?? 'byokit', tools: [...o.tools ?? [], ...(o.browser ? [{
|
|
656
659
|
name: 'request_sign_in', description: 'Ask the person to sign in to the current browser site.',
|
|
657
660
|
parameters: { type: 'object', additionalProperties: false, properties: {
|
|
@@ -668,10 +671,12 @@ export class OpenClawKit {
|
|
|
668
671
|
dropLane: (member, provider) => this.dropKeyLane(member, provider),
|
|
669
672
|
backfillLane: (member, model) => this.backfillKeyLane(member, model),
|
|
670
673
|
restarting: () => this.engineRestarting(),
|
|
674
|
+
routes: () => this.routes(),
|
|
671
675
|
request: (method, params, options) => this.request()(method, params, options),
|
|
672
676
|
ensure: (member) => this.ensureMember(member),
|
|
673
677
|
authConfig: () => this.authSnapshot(),
|
|
674
|
-
restarted: (authBefore, signal) => this.restarted(authBefore, signal)
|
|
678
|
+
restarted: (authBefore, signal) => this.restarted(authBefore, signal),
|
|
679
|
+
setProviderHeaders: (provider, headers) => this.patchConfig({ models: { providers: { [provider]: { headers: headers ?? null } } } }) });
|
|
675
680
|
const slot = {};
|
|
676
681
|
this.approvalsCtl = new Approvals({
|
|
677
682
|
request: (method, params, co) => this.request()(method, params, co),
|
|
@@ -960,10 +965,69 @@ export class OpenClawKit {
|
|
|
960
965
|
onDisconnect: (fn) => { this.signInDisconnects.add(fn); return () => { this.signInDisconnects.delete(fn); }; },
|
|
961
966
|
stopSignal: this.engine.stopSignal,
|
|
962
967
|
authConfig: () => this.authSnapshot(),
|
|
963
|
-
restarted: (authBefore, signal) => this.restarted(authBefore, signal)
|
|
968
|
+
restarted: (authBefore, signal) => this.restarted(authBefore, signal),
|
|
969
|
+
setProviderHeaders: (provider, headers) => this.patchConfig({ models: { providers: { [provider]: { headers: headers ?? null } } } }),
|
|
970
|
+
listWorkspaces: (key, signal) => this.o.listWorkspaces
|
|
971
|
+
? this.o.listWorkspaces(key, signal)
|
|
972
|
+
: openCodeWorkspaces(key, (input, init) => fetch(input, { ...init, signal: init?.signal ? AbortSignal.any([init.signal, signal]) : signal })) };
|
|
964
973
|
}
|
|
965
974
|
routes() {
|
|
966
|
-
return routeTable();
|
|
975
|
+
return routeTable({ plugins: installedPlugins(this.engine.root) });
|
|
976
|
+
}
|
|
977
|
+
/** B7 (5.7): install one external provider plugin a `needs_plugin` route names into this kit's own engine, at its
|
|
978
|
+
* pin; resolves once the restarted gateway lists it enabled at that version. Discovery never installs. Serialized:
|
|
979
|
+
* concurrent installs would race the allow-list update and double-spawn the installer into one engine tree. */
|
|
980
|
+
installPlugin(pluginId) {
|
|
981
|
+
const run = this.installs.then(() => this.runInstall(pluginId));
|
|
982
|
+
this.installs = run.catch(() => { });
|
|
983
|
+
return run;
|
|
984
|
+
}
|
|
985
|
+
async runInstall(pluginId) {
|
|
986
|
+
const pin = pinnedSpec(pluginId);
|
|
987
|
+
const version = pin.spec.slice(pin.spec.lastIndexOf('@') + 1);
|
|
988
|
+
const loaded = async () => {
|
|
989
|
+
const { plugins } = await this.request()('plugins.list', {});
|
|
990
|
+
return plugins.some((p) => p.id === pluginId && p.enabled && p.version === version);
|
|
991
|
+
};
|
|
992
|
+
if (!(await loaded())) {
|
|
993
|
+
// The installer enables a plugin only when the (restrictive once non-empty) allow list already names it.
|
|
994
|
+
const allow = (this.getConfigKey('plugins.allow') ?? []);
|
|
995
|
+
const added = !allow.includes(pluginId);
|
|
996
|
+
if (added)
|
|
997
|
+
this.setConfigKey('plugins.allow', [...allow, pluginId]);
|
|
998
|
+
let code;
|
|
999
|
+
let output;
|
|
1000
|
+
try {
|
|
1001
|
+
({ code, output } = await this.engine.installPlugin(pin.spec));
|
|
1002
|
+
}
|
|
1003
|
+
catch (error) {
|
|
1004
|
+
if (added)
|
|
1005
|
+
this.setConfigKey('plugins.allow', allow);
|
|
1006
|
+
throw error;
|
|
1007
|
+
}
|
|
1008
|
+
if (code !== 0) {
|
|
1009
|
+
if (added)
|
|
1010
|
+
this.setConfigKey('plugins.allow', allow);
|
|
1011
|
+
if (output.includes('pinned plugin integrity mismatch'))
|
|
1012
|
+
throw new PluginInstallError('integrity', `${pin.spec} does not match its pinned integrity; nothing was installed.`);
|
|
1013
|
+
if (output.includes('not a pinned plugin version'))
|
|
1014
|
+
throw new PluginInstallError('version', `The registry does not serve ${pin.spec} as pinned; nothing was installed.`);
|
|
1015
|
+
const reason = output.trim().split('\n').filter((line) => line.trim()).slice(-3).join(' ');
|
|
1016
|
+
throw new PluginInstallError('install', `${pin.spec} did not install: ${reason || `the installer exited ${code}`}`);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
// The running gateway hot-reloads the installer's config write without loading the plugin (verified live on
|
|
1020
|
+
// 2026.8.35); a safe restart (after in-flight requests) loads it, and the kit reconnects. Reads fail while it is down.
|
|
1021
|
+
if (!(await loaded()))
|
|
1022
|
+
await this.request()('gateway.restart.request', { reason: `byokit installed plugin ${pluginId}` });
|
|
1023
|
+
const deadline = Date.now() + RESTART_MS;
|
|
1024
|
+
while (!(await loaded().catch(() => false))) {
|
|
1025
|
+
if (Date.now() >= deadline)
|
|
1026
|
+
throw new PluginInstallError('install', `${pin.spec} installed, but the engine did not load it.`);
|
|
1027
|
+
await delay(500);
|
|
1028
|
+
}
|
|
1029
|
+
// Readiness (routes(), the keys gates) tracks this record, so it names the plugin only once the engine lists it loaded.
|
|
1030
|
+
recordPlugin(this.engine.root, pluginId);
|
|
967
1031
|
}
|
|
968
1032
|
async providerStatus(member) {
|
|
969
1033
|
const { agentId } = await this.ensureMember(member);
|
|
@@ -1068,7 +1132,10 @@ export class OpenClawKit {
|
|
|
1068
1132
|
return Promise.reject(new Error('gateway not ready'));
|
|
1069
1133
|
return this.accountsCtl.setDefaults(member, d);
|
|
1070
1134
|
}
|
|
1071
|
-
/** Save an explicitly supplied API key (billed per use) in this member's separate key agent.
|
|
1135
|
+
/** Save an explicitly supplied API key (billed per use) in this member's separate key agent.
|
|
1136
|
+
* `orgId` names the workspace a Go plan login acts for; it rides every probe and run as `x-opencode-org-id`,
|
|
1137
|
+
* and replacing the key rewrites it. Without it a Go key's workspaces are listed: one is used, several resolve
|
|
1138
|
+
* `needs_workspace` with the rows for the app's pick (add again with `orgId`). */
|
|
1072
1139
|
addKey(agentId, o) {
|
|
1073
1140
|
return this.keys.add(agentId, o);
|
|
1074
1141
|
}
|
|
@@ -1095,7 +1162,7 @@ export class OpenClawKit {
|
|
|
1095
1162
|
await this.accountsCtl.unkey(member, provider).catch((error) => this.o.log?.(`removeKey: the key lane entry is not dropped yet: ${error instanceof Error ? error.message : String(error)}`));
|
|
1096
1163
|
}
|
|
1097
1164
|
async backfillKeyLane(member, model) {
|
|
1098
|
-
const found = model === undefined ? [] :
|
|
1165
|
+
const found = model === undefined ? [] : this.routes().filter((r) => r.keyEntry && r.readiness === 'ready' && r.provider === providerOf(model));
|
|
1099
1166
|
await this.accountsCtl?.backfill(member, found).catch((error) => this.o.log?.(`key lane ${member}: not indexed yet: ${error instanceof Error ? error.message : String(error)}`));
|
|
1100
1167
|
}
|
|
1101
1168
|
async prepareKeyOwner(agentId) {
|
|
@@ -1137,7 +1204,7 @@ export class OpenClawKit {
|
|
|
1137
1204
|
return confirmLogin({ ...this.signInCtx(), seal: this.o.authSeal, log: this.o.log }, member, source);
|
|
1138
1205
|
}
|
|
1139
1206
|
// runs (5.8): the facade owns no run state; every call delegates to the O8 module
|
|
1140
|
-
// with this kit's live transport, member table and bridge (
|
|
1207
|
+
// with this kit's live transport, member table and bridge (spec item O9).
|
|
1141
1208
|
runs() {
|
|
1142
1209
|
return createRuns({
|
|
1143
1210
|
request: (method, params, o) => this.request()(method, params, o),
|
|
@@ -1230,7 +1297,8 @@ export class OpenClawKit {
|
|
|
1230
1297
|
const safe = reconcileConfig(response.config, { root: this.engine.root, stateDir: this.o.stateDir,
|
|
1231
1298
|
port: Number(readFileSync(join(this.engine.root, 'port'), 'utf8')), pluginId: this.o.plugin?.id ?? 'byokit',
|
|
1232
1299
|
pluginDir: join(this.engine.root, 'plugin'), policyPath: fileURLToPath(new URL('../policy/policy.mjs', import.meta.url)), app: patch,
|
|
1233
|
-
offered: this.o.offered, installPolicy: this.o.installPolicy,
|
|
1300
|
+
offered: this.o.offered, installPolicy: this.o.installPolicy, plugins: installedPlugins(this.engine.root),
|
|
1301
|
+
...(this.o.browser ? { browser: this.browserConfig() } : {}) });
|
|
1234
1302
|
if (this.o.browser && !browserToolPolicySafe(safe, this.toolNames()))
|
|
1235
1303
|
throw new Error('browser tool policy refused');
|
|
1236
1304
|
if (this.o.browser) {
|
package/dist/link.js
CHANGED
|
@@ -7,11 +7,11 @@ import { createServer } from 'node:http';
|
|
|
7
7
|
import { WebSocketServer } from 'ws';
|
|
8
8
|
import { PublicLinkError, } from '@byokit/pair';
|
|
9
9
|
import { reach } from '@byokit/discover';
|
|
10
|
-
import { ENGINE_VERSION } from
|
|
11
|
-
import { b64urlDecode, b64urlEncode, sealNotice } from
|
|
12
|
-
import { routeFor } from
|
|
13
|
-
import { outputSchema } from
|
|
14
|
-
import { stateWords, words } from
|
|
10
|
+
import { ENGINE_VERSION } from './constants.js';
|
|
11
|
+
import { b64urlDecode, b64urlEncode, sealNotice } from './notices.js';
|
|
12
|
+
import { routeFor } from './routes.js';
|
|
13
|
+
import { outputSchema } from './output.js';
|
|
14
|
+
import { stateWords, words } from './words.js';
|
|
15
15
|
const VIEW_OPS = new Set(['oc.state', 'oc.routes', 'oc.signin.view', 'oc.sessions', 'oc.approvals', 'oc.events',
|
|
16
16
|
'oc.browser.state', 'oc.browser.signins', 'oc.browser.thumb']);
|
|
17
17
|
// This kit's own version, read once from its package.json (beside src/ and dist/ alike).
|
package/dist/members.d.ts
CHANGED
|
@@ -6,6 +6,9 @@ export declare const BASE_AGENT = "byokit-base";
|
|
|
6
6
|
export declare const keyAgentId: (member: Member) => string;
|
|
7
7
|
/** Resolves once agents.list shows `id`, which agents.create answers before. */
|
|
8
8
|
export declare function untilListed(request: GatewayTransport['request'], id: string): Promise<void>;
|
|
9
|
+
/** Resolves once agents.list no longer shows `id`, which agents.delete answers before: the engine's roster
|
|
10
|
+
* hot-reload applies a beat after the delete answer, the same lag as after agents.create. */
|
|
11
|
+
export declare function untilUnlisted(request: GatewayTransport['request'], id: string): Promise<void>;
|
|
9
12
|
export declare function createMembers(ctx: {
|
|
10
13
|
request: GatewayTransport['request'];
|
|
11
14
|
root: string;
|
package/dist/members.js
CHANGED
|
@@ -23,6 +23,16 @@ export async function untilListed(request, id) {
|
|
|
23
23
|
await new Promise((resolve) => setTimeout(resolve, VISIBLE_POLL_MS));
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
+
/** Resolves once agents.list no longer shows `id`, which agents.delete answers before: the engine's roster
|
|
27
|
+
* hot-reload applies a beat after the delete answer, the same lag as after agents.create. */
|
|
28
|
+
export async function untilUnlisted(request, id) {
|
|
29
|
+
const deadline = Date.now() + VISIBLE_TIMEOUT_MS;
|
|
30
|
+
while (await listed(request, id)) {
|
|
31
|
+
if (Date.now() >= deadline)
|
|
32
|
+
throw new Error(`agent ${id} is still listed by agents.list after agents.delete`);
|
|
33
|
+
await new Promise((resolve) => setTimeout(resolve, VISIBLE_POLL_MS));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
26
36
|
export function createMembers(ctx) {
|
|
27
37
|
const cache = new Map();
|
|
28
38
|
return { ensure(member) {
|
package/dist/migrate.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// only when the Gateway itself reports every provider signed in.
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import { retireArchive } from
|
|
6
|
-
import { MEMBER_ID } from
|
|
7
|
-
import { providers } from
|
|
5
|
+
import { retireArchive } from './auth-store.js';
|
|
6
|
+
import { MEMBER_ID } from './members.js';
|
|
7
|
+
import { providers } from './signin.js';
|
|
8
8
|
const MOVED = '.moved-to-engine';
|
|
9
9
|
const MARKER = '.canonicalized';
|
|
10
10
|
const TRY_MS = 1_500;
|
package/dist/pick.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { words } from
|
|
1
|
+
import { words } from './words.js';
|
|
2
2
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
3
3
|
const finite = (n) => typeof n === 'number' && Number.isFinite(n);
|
|
4
4
|
/** The lowercased provider of a well-formed `provider/model` reference; undefined when the reference is malformed. */
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type PluginInstallFailure = 'unknown_plugin' | 'version' | 'integrity' | 'install';
|
|
2
|
+
export declare class PluginInstallError extends Error {
|
|
3
|
+
readonly code: PluginInstallFailure;
|
|
4
|
+
constructor(code: PluginInstallFailure, message: string);
|
|
5
|
+
}
|
|
6
|
+
type Pin = {
|
|
7
|
+
spec: string;
|
|
8
|
+
integrity: string;
|
|
9
|
+
};
|
|
10
|
+
/** engine/plugins.json: `{ "<plugin id>": { spec: '<package>@<engine version>', integrity } }`. */
|
|
11
|
+
export declare const pluginPins: () => Record<string, Pin>;
|
|
12
|
+
/** The exact spec to install for a plugin a route needs: its `install.npmSpec` at the engine pin, and pinned so. */
|
|
13
|
+
export declare function pinnedSpec(pluginId: string): Pin;
|
|
14
|
+
/** The external plugins this kit installed into its own engine. */
|
|
15
|
+
export declare function installedPlugins(root: string): string[];
|
|
16
|
+
export declare function recordPlugin(root: string, pluginId: string): void;
|
|
17
|
+
export {};
|
package/dist/plugins.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// External provider plugins (5.7, B7): the pins the kit installs at, and the ids it installed. Installing is the
|
|
2
|
+
// kit's explicit call; discovery only reads the record.
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { atomic } from './engine-patches.js';
|
|
7
|
+
import { ENGINE_VERSION } from './constants.js';
|
|
8
|
+
import { routes } from './routes.js';
|
|
9
|
+
export class PluginInstallError extends Error {
|
|
10
|
+
code;
|
|
11
|
+
constructor(code, message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = 'PluginInstallError';
|
|
14
|
+
this.code = code;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const kitDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
18
|
+
/** engine/plugins.json: `{ "<plugin id>": { spec: '<package>@<engine version>', integrity } }`. */
|
|
19
|
+
export const pluginPins = () => JSON.parse(readFileSync(join(kitDir, 'engine', 'plugins.json'), 'utf8'));
|
|
20
|
+
/** The exact spec to install for a plugin a route needs: its `install.npmSpec` at the engine pin, and pinned so. */
|
|
21
|
+
export function pinnedSpec(pluginId) {
|
|
22
|
+
const npmSpec = routes().find(route => route.needs?.plugin === pluginId && route.install?.npmSpec)?.install?.npmSpec;
|
|
23
|
+
if (!npmSpec)
|
|
24
|
+
throw new PluginInstallError('unknown_plugin', `No route needs a plugin named ${pluginId}.`);
|
|
25
|
+
const pin = pluginPins()[pluginId];
|
|
26
|
+
if (pin?.spec !== `${npmSpec}@${ENGINE_VERSION}`)
|
|
27
|
+
throw new PluginInstallError('version', `${pluginId} has no pin for engine ${ENGINE_VERSION}.`);
|
|
28
|
+
return pin;
|
|
29
|
+
}
|
|
30
|
+
const recordOf = (root) => join(root, 'installed-plugins.json');
|
|
31
|
+
/** The external plugins this kit installed into its own engine. */
|
|
32
|
+
export function installedPlugins(root) {
|
|
33
|
+
const path = recordOf(root);
|
|
34
|
+
return existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : [];
|
|
35
|
+
}
|
|
36
|
+
export function recordPlugin(root, pluginId) {
|
|
37
|
+
const installed = installedPlugins(root);
|
|
38
|
+
if (!installed.includes(pluginId))
|
|
39
|
+
atomic(recordOf(root), JSON.stringify([...installed, pluginId]) + '\n');
|
|
40
|
+
}
|