spur-board 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +21 -4
- package/dist/{client-Ln76BdDz.mjs → client-D4cveOHQ.mjs} +67 -14
- package/dist/client.mjs +2 -2
- package/dist/config-B1fVOLzp.mjs +229 -0
- package/dist/daemon.mjs +265 -105
- package/dist/mcp.mjs +52 -4
- package/dist/{src-BFgTh55w.mjs → src-g7u1onIP.mjs} +28 -10
- package/package.json +1 -1
- package/dist/config-B_6fA4Au.mjs +0 -111
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { B as SLACK_BOT_SCOPES,
|
|
2
|
-
import { a as
|
|
3
|
-
import { i as resolveDaemonAddress, n as
|
|
1
|
+
import { B as requiredSecretKeys, V as SLACK_BOT_SCOPES, j as ERRORS, s as OPERATIONS, z as configSchema } from "./src-g7u1onIP.mjs";
|
|
2
|
+
import { a as errorExitCode, i as CliError, n as requestOperation, o as renderCliError, r as requestTokenEnrollment, s as escapeTerminalControls } from "./client-D4cveOHQ.mjs";
|
|
3
|
+
import { a as memoryVerdictCache, c as loadEnrollmentConfig, i as fileVerdictCache, l as resolveDaemonAddress, n as cliPackageVersion, o as configFile, s as loadClientConfig, t as checkCatalog } from "./config-B1fVOLzp.mjs";
|
|
4
4
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
5
5
|
import { chmodSync, closeSync, constants, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { homedir, hostname, userInfo } from "node:os";
|
|
@@ -1082,8 +1082,25 @@ function createProgram(options = {}) {
|
|
|
1082
1082
|
const config = options.clientConfig ?? loadClientConfig(env);
|
|
1083
1083
|
const key = op.kind === "mutation" ? keyOption || randomUUID() : void 0;
|
|
1084
1084
|
if (key) io.stderr(`key: ${escapeTerminalControls(key)}\n`);
|
|
1085
|
+
let instance;
|
|
1086
|
+
let outcome;
|
|
1085
1087
|
try {
|
|
1086
|
-
|
|
1088
|
+
outcome = { value: await (options.sendOperation ?? requestOperation)(op, input, key, config, (seen) => {
|
|
1089
|
+
instance = seen;
|
|
1090
|
+
}) };
|
|
1091
|
+
} catch (error) {
|
|
1092
|
+
outcome = { error };
|
|
1093
|
+
}
|
|
1094
|
+
const reportedMismatch = "error" in outcome && outcome.error instanceof CliError && outcome.error.code === "version_mismatch";
|
|
1095
|
+
if (instance !== void 0 && !reportedMismatch) {
|
|
1096
|
+
const cliVersion = cliPackageVersion();
|
|
1097
|
+
const cache = cliVersion === void 0 ? memoryVerdictCache() : fileVerdictCache(join(dirname(configFile(env, options.home ?? homedir())), "version-check.json"), cliVersion);
|
|
1098
|
+
const verdict = await checkCatalog(config, instance, cache);
|
|
1099
|
+
if (verdict !== void 0 && !verdict.matches) io.stderr(`warning: the daemon catalog ${verdict.serverVersion.slice(0, 12)} differs from this CLI's ${verdict.clientVersion.slice(0, 12)}. ${ERRORS.version_mismatch.hint}\n`);
|
|
1100
|
+
}
|
|
1101
|
+
try {
|
|
1102
|
+
if ("error" in outcome) throw outcome.error;
|
|
1103
|
+
const result = outcome.value;
|
|
1087
1104
|
io.stdout(`${json || !io.isTTY ? JSON.stringify(result) : formatHuman(result)}\n`);
|
|
1088
1105
|
} catch (error) {
|
|
1089
1106
|
if (error instanceof CliError) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { M as errorContextSchema, N as errorEnvelopeSchema, P as successEnvelopeSchema, j as ERRORS, l as renderJsonError } from "./src-
|
|
1
|
+
import { M as errorContextSchema, N as errorEnvelopeSchema, P as successEnvelopeSchema, j as ERRORS, l as renderJsonError } from "./src-g7u1onIP.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { request } from "node:http";
|
|
@@ -84,9 +84,15 @@ function decodeResponse(output, response, raw) {
|
|
|
84
84
|
}
|
|
85
85
|
throw problemError(response.statusCode ?? 0, response.headers["content-type"], body);
|
|
86
86
|
}
|
|
87
|
-
|
|
87
|
+
const instanceHeader = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
88
|
+
/** The daemon instance a response came from, when it sends a well-formed one. */
|
|
89
|
+
function responseInstance(response) {
|
|
90
|
+
const value = response.headers["x-board-instance"];
|
|
91
|
+
return typeof value === "string" && instanceHeader.test(value) ? value : void 0;
|
|
92
|
+
}
|
|
93
|
+
function send({ method, path, body, config, anonymous, decode, observe }) {
|
|
88
94
|
const target = new URL(path, config.address);
|
|
89
|
-
const payload = JSON.stringify(body);
|
|
95
|
+
const payload = method === "POST" ? JSON.stringify(body) : void 0;
|
|
90
96
|
const transport = target.protocol === "https:" ? request$1 : request;
|
|
91
97
|
return new Promise((resolve, reject) => {
|
|
92
98
|
let connected = false;
|
|
@@ -100,15 +106,19 @@ async function post(path, body, config, output, anonymous) {
|
|
|
100
106
|
else resolve(result.value);
|
|
101
107
|
};
|
|
102
108
|
const req = transport(target, {
|
|
103
|
-
method
|
|
109
|
+
method,
|
|
104
110
|
agent: false,
|
|
105
111
|
headers: {
|
|
106
|
-
"content-type": "application/json",
|
|
107
112
|
accept: "application/json, application/problem+json",
|
|
108
|
-
|
|
113
|
+
...payload === void 0 ? {} : {
|
|
114
|
+
"content-type": "application/json",
|
|
115
|
+
"content-length": Buffer.byteLength(payload)
|
|
116
|
+
},
|
|
109
117
|
...anonymous ? {} : config.session ? { "x-board-session": config.session } : { authorization: `Bearer ${config.token ?? ""}` }
|
|
110
118
|
}
|
|
111
119
|
}, (response) => {
|
|
120
|
+
const instance = responseInstance(response);
|
|
121
|
+
if (instance !== void 0) observe?.(instance);
|
|
112
122
|
const chunks = [];
|
|
113
123
|
response.on("data", (chunk) => {
|
|
114
124
|
chunks.push(chunk);
|
|
@@ -118,7 +128,7 @@ async function post(path, body, config, output, anonymous) {
|
|
|
118
128
|
});
|
|
119
129
|
response.once("end", () => {
|
|
120
130
|
try {
|
|
121
|
-
settle({ value:
|
|
131
|
+
settle({ value: decode(response, Buffer.concat(chunks).toString()) });
|
|
122
132
|
} catch (error) {
|
|
123
133
|
settle({ error: error instanceof CliError ? error : new CliError("outcome_unknown") });
|
|
124
134
|
}
|
|
@@ -142,12 +152,48 @@ async function post(path, body, config, output, anonymous) {
|
|
|
142
152
|
});
|
|
143
153
|
}
|
|
144
154
|
/** Shared daemon HTTP client. The CLI owns output; this module owns transport and wire parsing. */
|
|
145
|
-
async function requestOperation(op, input, key, config) {
|
|
155
|
+
async function requestOperation(op, input, key, config, observe) {
|
|
146
156
|
if (!config.session && !config.token) throw new CliError("config");
|
|
147
|
-
return
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
157
|
+
return send({
|
|
158
|
+
method: "POST",
|
|
159
|
+
path: `/v1/ops/${encodeURIComponent(op.id)}`,
|
|
160
|
+
body: {
|
|
161
|
+
input,
|
|
162
|
+
...key ? { key } : {}
|
|
163
|
+
},
|
|
164
|
+
config,
|
|
165
|
+
anonymous: false,
|
|
166
|
+
decode: (response, raw) => decodeResponse(op.output, response, raw),
|
|
167
|
+
observe
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
const schemaVersion = z.looseObject({ version: z.string().regex(/^[0-9a-f]{64}$/) });
|
|
171
|
+
/**
|
|
172
|
+
* The daemon's catalog version and instance. `/v1/schema` answers with the bare snapshot, not a
|
|
173
|
+
* success envelope.
|
|
174
|
+
*/
|
|
175
|
+
function fetchCatalogVersion(config) {
|
|
176
|
+
return send({
|
|
177
|
+
method: "GET",
|
|
178
|
+
path: "/v1/schema",
|
|
179
|
+
config,
|
|
180
|
+
anonymous: true,
|
|
181
|
+
decode: (response, raw) => {
|
|
182
|
+
let body;
|
|
183
|
+
try {
|
|
184
|
+
body = JSON.parse(raw);
|
|
185
|
+
} catch {
|
|
186
|
+
throw new CliError("outcome_unknown");
|
|
187
|
+
}
|
|
188
|
+
if (response.statusCode !== 200) throw problemError(response.statusCode ?? 0, response.headers["content-type"], body);
|
|
189
|
+
const parsed = schemaVersion.safeParse(body);
|
|
190
|
+
if (!parsed.success) throw new CliError("outcome_unknown");
|
|
191
|
+
return {
|
|
192
|
+
version: parsed.data.version,
|
|
193
|
+
instance: responseInstance(response)
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
});
|
|
151
197
|
}
|
|
152
198
|
const enrollmentOutput = z.strictObject({
|
|
153
199
|
name: z.string(),
|
|
@@ -156,7 +202,14 @@ const enrollmentOutput = z.strictObject({
|
|
|
156
202
|
});
|
|
157
203
|
/** Bootstrap sends a hash and no authorization header. */
|
|
158
204
|
function requestTokenEnrollment(input, config) {
|
|
159
|
-
return
|
|
205
|
+
return send({
|
|
206
|
+
method: "POST",
|
|
207
|
+
path: "/v1/tokens/requests",
|
|
208
|
+
body: input,
|
|
209
|
+
config,
|
|
210
|
+
anonymous: true,
|
|
211
|
+
decode: (response, raw) => decodeResponse(enrollmentOutput, response, raw)
|
|
212
|
+
});
|
|
160
213
|
}
|
|
161
214
|
//#endregion
|
|
162
|
-
export {
|
|
215
|
+
export { errorExitCode as a, CliError as i, requestOperation as n, renderCliError as o, requestTokenEnrollment as r, escapeTerminalControls as s, fetchCatalogVersion as t };
|
package/dist/client.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as requestTokenEnrollment, t as
|
|
2
|
-
export { requestOperation, requestTokenEnrollment };
|
|
1
|
+
import { n as requestOperation, r as requestTokenEnrollment, t as fetchCatalogVersion } from "./client-D4cveOHQ.mjs";
|
|
2
|
+
export { fetchCatalogVersion, requestOperation, requestTokenEnrollment };
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { a as buildCatalogSnapshot, s as OPERATIONS, z as configSchema } from "./src-g7u1onIP.mjs";
|
|
2
|
+
import { i as CliError, t as fetchCatalogVersion } from "./client-D4cveOHQ.mjs";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import YAML from "yaml";
|
|
9
|
+
//#region src/config.ts
|
|
10
|
+
const CONNECT_TIMEOUT_MS = 2e3;
|
|
11
|
+
const RESPONSE_TIMEOUT_MS = 3e4;
|
|
12
|
+
function configFile(env, home) {
|
|
13
|
+
const boardHome = env["BOARD_HOME"];
|
|
14
|
+
if (boardHome) return join(resolve(boardHome), "config.yaml");
|
|
15
|
+
const xdg = env["XDG_CONFIG_HOME"];
|
|
16
|
+
return join(xdg && isAbsolute(xdg) ? xdg : join(home, ".config"), "spur-board", "config.yaml");
|
|
17
|
+
}
|
|
18
|
+
function resolveDaemonAddress(env = process.env, home = homedir(), configuredPort) {
|
|
19
|
+
const rawAddress = env["BOARD_DAEMON_URL"];
|
|
20
|
+
if (rawAddress === void 0) {
|
|
21
|
+
let port = configuredPort ?? 4311;
|
|
22
|
+
if (configuredPort === void 0) try {
|
|
23
|
+
const parsed = YAML.parseDocument(readFileSync(configFile(env, home), "utf8"));
|
|
24
|
+
if (parsed.errors.length === 0) {
|
|
25
|
+
const config = configSchema.safeParse(parsed.toJS());
|
|
26
|
+
if (config.success) port = config.data.http.port;
|
|
27
|
+
}
|
|
28
|
+
} catch {}
|
|
29
|
+
if (port === 0) throw new CliError("config", void 0, void 0, "http.port: 0 assigns a runtime port that clients cannot discover. Set BOARD_DAEMON_URL to the daemon listening address.");
|
|
30
|
+
return new URL(`http://127.0.0.1:${String(port)}`);
|
|
31
|
+
}
|
|
32
|
+
let address;
|
|
33
|
+
try {
|
|
34
|
+
address = new URL(rawAddress);
|
|
35
|
+
} catch {
|
|
36
|
+
throw new CliError("config", { environmentVariable: "BOARD_DAEMON_URL" });
|
|
37
|
+
}
|
|
38
|
+
if (!["http:", "https:"].includes(address.protocol) || address.username || address.password) throw new CliError("config", { environmentVariable: "BOARD_DAEMON_URL" });
|
|
39
|
+
return address;
|
|
40
|
+
}
|
|
41
|
+
function tokenInFolder(folder, warn) {
|
|
42
|
+
const directory = join(folder, ".spur-board");
|
|
43
|
+
const file = join(directory, "token");
|
|
44
|
+
try {
|
|
45
|
+
const owner = process.getuid?.();
|
|
46
|
+
const directoryInfo = lstatSync(directory);
|
|
47
|
+
if (!directoryInfo.isDirectory() || directoryInfo.uid !== owner || (directoryInfo.mode & 63) !== 0) throw new Error("unsafe token directory");
|
|
48
|
+
const descriptor = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
49
|
+
let value;
|
|
50
|
+
try {
|
|
51
|
+
const fileInfo = fstatSync(descriptor);
|
|
52
|
+
if (!fileInfo.isFile() || fileInfo.uid !== owner || (fileInfo.mode & 63) !== 0) throw new Error("unsafe token file");
|
|
53
|
+
value = readFileSync(descriptor, "utf8").trimEnd();
|
|
54
|
+
} finally {
|
|
55
|
+
closeSync(descriptor);
|
|
56
|
+
}
|
|
57
|
+
if (value === "") throw new Error("empty token file");
|
|
58
|
+
return value;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT") return;
|
|
61
|
+
warn("Ignoring an unsafe .spur-board/token candidate.\n");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function findTokenFile(cwd, home, warn = (message) => {
|
|
66
|
+
process.stderr.write(message);
|
|
67
|
+
}) {
|
|
68
|
+
const current = realpathSync(cwd);
|
|
69
|
+
const boundary = realpathSync(home);
|
|
70
|
+
if (current === boundary) return void 0;
|
|
71
|
+
const toCurrent = relative(boundary, current);
|
|
72
|
+
const outsideHome = toCurrent === ".." || toCurrent.startsWith(`..${sep}`) || isAbsolute(toCurrent);
|
|
73
|
+
let folder = current;
|
|
74
|
+
while (folder !== boundary) {
|
|
75
|
+
if (outsideHome) {
|
|
76
|
+
const info = statSync(folder);
|
|
77
|
+
if (info.uid !== process.getuid?.() || (info.mode & 2) !== 0) break;
|
|
78
|
+
}
|
|
79
|
+
const token = tokenInFolder(folder, warn);
|
|
80
|
+
if (token !== void 0) return token;
|
|
81
|
+
const parent = dirname(folder);
|
|
82
|
+
if (parent === folder) break;
|
|
83
|
+
folder = parent;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function loadClientConfig(env = process.env, paths = {
|
|
87
|
+
cwd: process.cwd(),
|
|
88
|
+
home: homedir()
|
|
89
|
+
}) {
|
|
90
|
+
const address = resolveDaemonAddress(env, paths.home);
|
|
91
|
+
const session = env["SPUR_SESSION"] || void 0;
|
|
92
|
+
const token = session ? void 0 : env["BOARD_TOKEN"] === void 0 ? findTokenFile(paths.cwd, paths.home) : env["BOARD_TOKEN"];
|
|
93
|
+
if (!session && !token) throw new CliError("config", { environmentVariable: "BOARD_TOKEN" });
|
|
94
|
+
return {
|
|
95
|
+
address,
|
|
96
|
+
session,
|
|
97
|
+
token,
|
|
98
|
+
connectTimeoutMs: CONNECT_TIMEOUT_MS,
|
|
99
|
+
responseTimeoutMs: RESPONSE_TIMEOUT_MS
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** Enrollment sends only a hash and works before any operator credential exists. */
|
|
103
|
+
function loadEnrollmentConfig(env = process.env) {
|
|
104
|
+
return {
|
|
105
|
+
address: resolveDaemonAddress(env),
|
|
106
|
+
session: void 0,
|
|
107
|
+
token: void 0,
|
|
108
|
+
connectTimeoutMs: CONNECT_TIMEOUT_MS,
|
|
109
|
+
responseTimeoutMs: RESPONSE_TIMEOUT_MS
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/version.ts
|
|
114
|
+
let clientVersion;
|
|
115
|
+
/** The catalog version this client was built with; the snapshot is built once per process. */
|
|
116
|
+
function clientCatalogVersion() {
|
|
117
|
+
if (clientVersion === void 0) {
|
|
118
|
+
const pending = buildCatalogSnapshot(OPERATIONS).then((snapshot) => snapshot.version);
|
|
119
|
+
clientVersion = pending;
|
|
120
|
+
pending.catch(() => {
|
|
121
|
+
if (clientVersion === pending) clientVersion = void 0;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return clientVersion;
|
|
125
|
+
}
|
|
126
|
+
/** The CLI package version, or `undefined` when its manifest cannot be read. */
|
|
127
|
+
function cliPackageVersion() {
|
|
128
|
+
try {
|
|
129
|
+
const version = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"))?.version;
|
|
130
|
+
return typeof version === "string" && version ? version : void 0;
|
|
131
|
+
} catch {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Compares the client catalog with the daemon's; rejects when the daemon cannot answer. */
|
|
136
|
+
async function compareCatalogs(config) {
|
|
137
|
+
const [clientVersion, served] = await Promise.all([clientCatalogVersion(), fetchCatalogVersion(config)]);
|
|
138
|
+
return {
|
|
139
|
+
clientVersion,
|
|
140
|
+
serverVersion: served.version,
|
|
141
|
+
matches: clientVersion === served.version,
|
|
142
|
+
instance: served.instance
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Compares the client catalog with the daemon instance's catalog once per cache entry. Any
|
|
147
|
+
* failure returns `undefined`, so the caller carries on unchanged.
|
|
148
|
+
*/
|
|
149
|
+
function checkCatalog(config, instance, cache) {
|
|
150
|
+
return cache.check(config.address, instance, () => compareCatalogs(config).catch(() => void 0));
|
|
151
|
+
}
|
|
152
|
+
/** In-process cache that shares an in-flight check between overlapping callers. */
|
|
153
|
+
function memoryVerdictCache() {
|
|
154
|
+
const entries = /* @__PURE__ */ new Map();
|
|
155
|
+
const keyOf = (address, instance) => `${address.href} ${instance}`;
|
|
156
|
+
return {
|
|
157
|
+
check(address, instance, run) {
|
|
158
|
+
const key = keyOf(address, instance);
|
|
159
|
+
const held = entries.get(key);
|
|
160
|
+
if (held !== void 0) return held;
|
|
161
|
+
const pending = run();
|
|
162
|
+
entries.set(key, pending);
|
|
163
|
+
pending.then((verdict) => {
|
|
164
|
+
if (verdict === void 0 && entries.get(key) === pending) entries.delete(key);
|
|
165
|
+
});
|
|
166
|
+
return pending;
|
|
167
|
+
},
|
|
168
|
+
remember(address, instance, verdict) {
|
|
169
|
+
entries.set(keyOf(address, instance), Promise.resolve(verdict));
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const fileEntrySchema = z.object({
|
|
174
|
+
cliPackageVersion: z.string(),
|
|
175
|
+
address: z.string(),
|
|
176
|
+
instance: z.string(),
|
|
177
|
+
clientVersion: z.string(),
|
|
178
|
+
serverVersion: z.string()
|
|
179
|
+
});
|
|
180
|
+
const fileSchema = z.object({ entries: z.array(fileEntrySchema) });
|
|
181
|
+
function readEntries(path) {
|
|
182
|
+
try {
|
|
183
|
+
const parsed = fileSchema.safeParse(JSON.parse(readFileSync(path, "utf8")));
|
|
184
|
+
return parsed.success ? parsed.data.entries : [];
|
|
185
|
+
} catch {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function writeEntries(path, entries) {
|
|
190
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
191
|
+
try {
|
|
192
|
+
mkdirSync(dirname(path), {
|
|
193
|
+
recursive: true,
|
|
194
|
+
mode: 448
|
|
195
|
+
});
|
|
196
|
+
writeFileSync(temporary, `${JSON.stringify({ entries })}\n`, { mode: 384 });
|
|
197
|
+
renameSync(temporary, path);
|
|
198
|
+
} catch {
|
|
199
|
+
try {
|
|
200
|
+
unlinkSync(temporary);
|
|
201
|
+
} catch {}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/** Verdicts kept across CLI invocations, keyed by CLI package version, address and instance. */
|
|
205
|
+
function fileVerdictCache(path, cliVersion) {
|
|
206
|
+
return { async check(address, instance, run) {
|
|
207
|
+
const matchesKey = (entry) => entry.cliPackageVersion === cliVersion && entry.address === address.href && entry.instance === instance;
|
|
208
|
+
const held = readEntries(path).find(matchesKey);
|
|
209
|
+
if (held !== void 0) return {
|
|
210
|
+
clientVersion: held.clientVersion,
|
|
211
|
+
serverVersion: held.serverVersion,
|
|
212
|
+
matches: held.clientVersion === held.serverVersion
|
|
213
|
+
};
|
|
214
|
+
const verdict = await run();
|
|
215
|
+
if (verdict !== void 0) {
|
|
216
|
+
const entry = {
|
|
217
|
+
cliPackageVersion: cliVersion,
|
|
218
|
+
address: address.href,
|
|
219
|
+
instance,
|
|
220
|
+
clientVersion: verdict.clientVersion,
|
|
221
|
+
serverVersion: verdict.serverVersion
|
|
222
|
+
};
|
|
223
|
+
writeEntries(path, [...readEntries(path).filter((item) => !matchesKey(item)), entry].slice(-16));
|
|
224
|
+
}
|
|
225
|
+
return verdict;
|
|
226
|
+
} };
|
|
227
|
+
}
|
|
228
|
+
//#endregion
|
|
229
|
+
export { memoryVerdictCache as a, loadEnrollmentConfig as c, fileVerdictCache as i, resolveDaemonAddress as l, cliPackageVersion as n, configFile as o, compareCatalogs as r, loadClientConfig as s, checkCatalog as t };
|