@vostride/agent-qa-plugin-kit 0.0.1 → 0.3.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.md +105 -0
- package/NOTICE.md +11 -0
- package/README.md +78 -0
- package/dist/index.cjs +814 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +241 -0
- package/dist/index.d.ts +241 -0
- package/dist/index.js +800 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.cjs +323 -0
- package/dist/protocol.cjs.map +1 -0
- package/dist/protocol.d.cts +259 -0
- package/dist/protocol.d.ts +259 -0
- package/dist/protocol.js +291 -0
- package/dist/protocol.js.map +1 -0
- package/dist/scripted-plugin.js +985 -0
- package/dist/scripted-plugin.js.map +1 -0
- package/dist/testing.cjs +973 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +236 -0
- package/dist/testing.d.ts +236 -0
- package/dist/testing.js +969 -0
- package/dist/testing.js.map +1 -0
- package/package.json +80 -3
|
@@ -0,0 +1,985 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, readFileSync, realpathSync, writeSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { format, inspect } from "node:util";
|
|
6
|
+
// src/protocol/messages.ts
|
|
7
|
+
const PLUGIN_METHODS = [
|
|
8
|
+
"initialize",
|
|
9
|
+
"check",
|
|
10
|
+
"acquire",
|
|
11
|
+
"uploadApp",
|
|
12
|
+
"started",
|
|
13
|
+
"end",
|
|
14
|
+
"release",
|
|
15
|
+
"shutdown"
|
|
16
|
+
];
|
|
17
|
+
const OPTIONAL_PLUGIN_METHODS = [
|
|
18
|
+
"check",
|
|
19
|
+
"uploadApp",
|
|
20
|
+
"started",
|
|
21
|
+
"end"
|
|
22
|
+
];
|
|
23
|
+
const PLUGIN_SERVES = [
|
|
24
|
+
"playwright",
|
|
25
|
+
"cdp",
|
|
26
|
+
"appium"
|
|
27
|
+
];
|
|
28
|
+
const PLUGIN_RUN_FACTS = [
|
|
29
|
+
"testName",
|
|
30
|
+
"runId",
|
|
31
|
+
"failure"
|
|
32
|
+
];
|
|
33
|
+
const PLUGIN_FAILURE_KINDS = [
|
|
34
|
+
"unreachable",
|
|
35
|
+
"auth",
|
|
36
|
+
"capacity",
|
|
37
|
+
"timeout",
|
|
38
|
+
"version",
|
|
39
|
+
"refused",
|
|
40
|
+
"ended",
|
|
41
|
+
"lost"
|
|
42
|
+
];
|
|
43
|
+
const PLUGIN_ERROR_CODES = {
|
|
44
|
+
parse: -32700,
|
|
45
|
+
invalidRequest: -32600,
|
|
46
|
+
methodNotFound: -32601,
|
|
47
|
+
invalidParams: -32602,
|
|
48
|
+
plugin: -32e3,
|
|
49
|
+
cancelled: -32800
|
|
50
|
+
};
|
|
51
|
+
const PROGRESS_METHOD = "$/progress";
|
|
52
|
+
const MAX_MESSAGE_BYTES = 1048576;
|
|
53
|
+
const JsonObjectSchema = z.record(z.string(), z.unknown());
|
|
54
|
+
const HandleSchema = z.json();
|
|
55
|
+
const RequestIdSchema = z.union([z.string(), z.number()]);
|
|
56
|
+
const InitializeParamsSchema = z.object({
|
|
57
|
+
protocolVersions: z.array(z.number().int()),
|
|
58
|
+
host: z.object({
|
|
59
|
+
pid: z.number().int(),
|
|
60
|
+
agentQaVersion: z.string(),
|
|
61
|
+
playwrightVersion: z.string(),
|
|
62
|
+
webdriverioVersion: z.string()
|
|
63
|
+
}),
|
|
64
|
+
entry: z.string(),
|
|
65
|
+
options: JsonObjectSchema,
|
|
66
|
+
runFacts: z.object({ runId: z.string().optional() }).optional()
|
|
67
|
+
});
|
|
68
|
+
z.object({
|
|
69
|
+
protocolVersion: z.number().int(),
|
|
70
|
+
plugin: z.object({
|
|
71
|
+
name: z.string(),
|
|
72
|
+
version: z.string()
|
|
73
|
+
}),
|
|
74
|
+
serves: z.array(z.enum(PLUGIN_SERVES)),
|
|
75
|
+
methods: z.array(z.enum(OPTIONAL_PLUGIN_METHODS))
|
|
76
|
+
});
|
|
77
|
+
const CheckParamsSchema = z.object({ remotes: z.array(z.object({
|
|
78
|
+
name: z.string(),
|
|
79
|
+
settings: JsonObjectSchema
|
|
80
|
+
})) });
|
|
81
|
+
const CheckResultSchema = z.object({
|
|
82
|
+
state: z.enum(["ok", "warn"]),
|
|
83
|
+
lines: z.array(z.string())
|
|
84
|
+
});
|
|
85
|
+
const WebSessionSchema = z.object({
|
|
86
|
+
platform: z.literal("web"),
|
|
87
|
+
engine: z.enum([
|
|
88
|
+
"chromium",
|
|
89
|
+
"firefox",
|
|
90
|
+
"webkit"
|
|
91
|
+
]),
|
|
92
|
+
context: z.enum(["new", "reuse"])
|
|
93
|
+
});
|
|
94
|
+
const DeviceSessionSchema = z.object({
|
|
95
|
+
platform: z.enum(["android", "ios"]),
|
|
96
|
+
capabilities: JsonObjectSchema
|
|
97
|
+
});
|
|
98
|
+
const AcquireParamsSchema = z.object({
|
|
99
|
+
remote: z.string(),
|
|
100
|
+
settings: JsonObjectSchema,
|
|
101
|
+
session: z.discriminatedUnion("platform", [WebSessionSchema, DeviceSessionSchema]),
|
|
102
|
+
run: z.object({ testName: z.string().optional() }).optional()
|
|
103
|
+
});
|
|
104
|
+
const AcquireResultSchema = z.object({
|
|
105
|
+
handle: HandleSchema,
|
|
106
|
+
protocol: z.enum(PLUGIN_SERVES),
|
|
107
|
+
url: z.string(),
|
|
108
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
109
|
+
capabilities: JsonObjectSchema.optional(),
|
|
110
|
+
mask: z.array(z.string()).optional(),
|
|
111
|
+
link: z.string().optional()
|
|
112
|
+
});
|
|
113
|
+
const UploadAppParamsSchema = z.object({
|
|
114
|
+
remote: z.string(),
|
|
115
|
+
settings: JsonObjectSchema,
|
|
116
|
+
platform: z.enum(["android", "ios"]),
|
|
117
|
+
path: z.string(),
|
|
118
|
+
sha256: z.string(),
|
|
119
|
+
size: z.number().int().nonnegative()
|
|
120
|
+
});
|
|
121
|
+
const UploadAppResultSchema = z.object({ ref: z.string() });
|
|
122
|
+
const StartedParamsSchema = z.object({
|
|
123
|
+
handle: HandleSchema,
|
|
124
|
+
sessionId: z.string().optional()
|
|
125
|
+
});
|
|
126
|
+
const StartedResultSchema = z.object({
|
|
127
|
+
link: z.string().optional(),
|
|
128
|
+
appiumVersion: z.string().optional()
|
|
129
|
+
});
|
|
130
|
+
const EndParamsSchema = z.object({
|
|
131
|
+
handle: HandleSchema,
|
|
132
|
+
outcome: z.enum([
|
|
133
|
+
"passed",
|
|
134
|
+
"failed",
|
|
135
|
+
"error",
|
|
136
|
+
"cancelled"
|
|
137
|
+
]),
|
|
138
|
+
reason: z.string().optional()
|
|
139
|
+
});
|
|
140
|
+
const ReleaseParamsSchema = z.object({ handle: HandleSchema });
|
|
141
|
+
const CancelParamsSchema = z.object({ id: RequestIdSchema });
|
|
142
|
+
z.object({
|
|
143
|
+
id: RequestIdSchema,
|
|
144
|
+
message: z.string(),
|
|
145
|
+
queued: z.number().int().nonnegative().optional(),
|
|
146
|
+
percent: z.number().min(0).max(100).optional()
|
|
147
|
+
});
|
|
148
|
+
z.object({
|
|
149
|
+
kind: z.enum(PLUGIN_FAILURE_KINDS),
|
|
150
|
+
path: z.string().optional(),
|
|
151
|
+
handle: HandleSchema.optional(),
|
|
152
|
+
supported: z.array(z.number().int()).optional(),
|
|
153
|
+
requested: z.array(z.number().int()).optional()
|
|
154
|
+
});
|
|
155
|
+
// src/define-plugin.ts
|
|
156
|
+
var PluginError = class extends Error {
|
|
157
|
+
kind;
|
|
158
|
+
path;
|
|
159
|
+
handle;
|
|
160
|
+
constructor(message, options) {
|
|
161
|
+
super(message);
|
|
162
|
+
this.name = "PluginError";
|
|
163
|
+
this.kind = options.kind;
|
|
164
|
+
this.path = options.path;
|
|
165
|
+
this.handle = options.handle;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
function definePlugin(spec) {
|
|
169
|
+
const methods = OPTIONAL_PLUGIN_METHODS.filter((method) => typeof spec[method] === "function");
|
|
170
|
+
return {
|
|
171
|
+
...spec,
|
|
172
|
+
methods
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// src/package-root.ts
|
|
176
|
+
function findPackageRoot(fromDir) {
|
|
177
|
+
let dir = fromDir;
|
|
178
|
+
for (;;) {
|
|
179
|
+
if (existsSync(join(dir, "package.json"))) return dir;
|
|
180
|
+
const parent = dirname(dir);
|
|
181
|
+
if (parent === dir) return void 0;
|
|
182
|
+
dir = parent;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function readPackageJson(packageRoot) {
|
|
186
|
+
const parsed = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
|
|
187
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
188
|
+
}
|
|
189
|
+
// src/protocol/manifest.ts
|
|
190
|
+
const PLUGIN_MANIFEST_FIELD = "agent-qa";
|
|
191
|
+
const PackagePathSchema = z.string().min(1);
|
|
192
|
+
const RemotePluginManifestSchema = z.strictObject({
|
|
193
|
+
kind: z.literal("remote"),
|
|
194
|
+
runs: z.literal("process"),
|
|
195
|
+
protocol: z.number().int().positive(),
|
|
196
|
+
entry: PackagePathSchema,
|
|
197
|
+
serves: z.array(z.enum(PLUGIN_SERVES)).nonempty(),
|
|
198
|
+
methods: z.array(z.enum(OPTIONAL_PLUGIN_METHODS)),
|
|
199
|
+
options: PackagePathSchema,
|
|
200
|
+
settings: PackagePathSchema,
|
|
201
|
+
runFacts: z.array(z.enum(PLUGIN_RUN_FACTS)).optional(),
|
|
202
|
+
skill: PackagePathSchema.optional()
|
|
203
|
+
});
|
|
204
|
+
const ModelAuthManifestSchema = z.strictObject({
|
|
205
|
+
kind: z.literal("model-auth"),
|
|
206
|
+
runs: z.literal("in-process"),
|
|
207
|
+
entry: PackagePathSchema,
|
|
208
|
+
options: PackagePathSchema.optional()
|
|
209
|
+
});
|
|
210
|
+
const ModelProviderManifestSchema = z.strictObject({
|
|
211
|
+
kind: z.literal("model-provider"),
|
|
212
|
+
runs: z.literal("in-process"),
|
|
213
|
+
entry: PackagePathSchema,
|
|
214
|
+
options: PackagePathSchema.optional()
|
|
215
|
+
});
|
|
216
|
+
const PluginManifestSchema = z.discriminatedUnion("kind", [
|
|
217
|
+
RemotePluginManifestSchema,
|
|
218
|
+
ModelAuthManifestSchema,
|
|
219
|
+
ModelProviderManifestSchema
|
|
220
|
+
]);
|
|
221
|
+
// src/protocol/framing.ts
|
|
222
|
+
function encodeMessage(message) {
|
|
223
|
+
return `${JSON.stringify(message)}\n`;
|
|
224
|
+
}
|
|
225
|
+
const NEWLINE = 10;
|
|
226
|
+
function createLineDecoder({ maxLineBytes, onLine, onOverflow }) {
|
|
227
|
+
const encoder = new TextEncoder();
|
|
228
|
+
const decoder = new TextDecoder();
|
|
229
|
+
let parts = [];
|
|
230
|
+
let size = 0;
|
|
231
|
+
let discarding = false;
|
|
232
|
+
const append = (segment) => {
|
|
233
|
+
if (discarding || segment.length === 0) return;
|
|
234
|
+
if (size + segment.length > maxLineBytes) {
|
|
235
|
+
discarding = true;
|
|
236
|
+
parts = [];
|
|
237
|
+
size = 0;
|
|
238
|
+
onOverflow();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
parts.push(new Uint8Array(segment));
|
|
242
|
+
size += segment.length;
|
|
243
|
+
};
|
|
244
|
+
const finishLine = () => {
|
|
245
|
+
const wasDiscarding = discarding;
|
|
246
|
+
const lineParts = parts;
|
|
247
|
+
const lineSize = size;
|
|
248
|
+
parts = [];
|
|
249
|
+
size = 0;
|
|
250
|
+
discarding = false;
|
|
251
|
+
if (wasDiscarding || lineSize === 0) return;
|
|
252
|
+
const bytes = new Uint8Array(lineSize);
|
|
253
|
+
let offset = 0;
|
|
254
|
+
for (const part of lineParts) {
|
|
255
|
+
bytes.set(part, offset);
|
|
256
|
+
offset += part.length;
|
|
257
|
+
}
|
|
258
|
+
const line = decoder.decode(bytes).replace(/\r$/, "");
|
|
259
|
+
if (line.trim() !== "") onLine(line);
|
|
260
|
+
};
|
|
261
|
+
return {
|
|
262
|
+
push(chunk) {
|
|
263
|
+
const bytes = typeof chunk === "string" ? encoder.encode(chunk) : chunk;
|
|
264
|
+
let start = 0;
|
|
265
|
+
while (start <= bytes.length) {
|
|
266
|
+
const newline = bytes.indexOf(NEWLINE, start);
|
|
267
|
+
if (newline === -1) {
|
|
268
|
+
append(bytes.subarray(start));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
append(bytes.subarray(start, newline));
|
|
272
|
+
finishLine();
|
|
273
|
+
start = newline + 1;
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
end() {
|
|
277
|
+
finishLine();
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
const RELEASE_ON_CLOSE_BOUND_MS = 3e4;
|
|
282
|
+
const RELEASE_ON_SIGTERM_BOUND_MS = 4e3;
|
|
283
|
+
const SUPPORTED_PROTOCOL_VERSIONS = [1];
|
|
284
|
+
var ProtocolFailure = class extends Error {
|
|
285
|
+
code;
|
|
286
|
+
data;
|
|
287
|
+
constructor(code, message, data) {
|
|
288
|
+
super(message);
|
|
289
|
+
this.code = code;
|
|
290
|
+
this.data = data;
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
function resolveIo(io) {
|
|
294
|
+
return {
|
|
295
|
+
stdin: io.stdin ?? process.stdin,
|
|
296
|
+
stdout: io.stdout ?? process.stdout,
|
|
297
|
+
exit: io.exit ?? ((code) => process.exit(code)),
|
|
298
|
+
entryPath: io.entryPath ?? process.argv[1],
|
|
299
|
+
cancelAnswerGraceMs: io.cancelAnswerGraceMs ?? 4e3,
|
|
300
|
+
hostPidPollMs: io.hostPidPollMs ?? 1e3
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function routeStrayOutputToStderr() {
|
|
304
|
+
const toLog = (...args) => {
|
|
305
|
+
process.stderr.write(`${format(...args)}\n`);
|
|
306
|
+
};
|
|
307
|
+
console.log = toLog;
|
|
308
|
+
console.info = toLog;
|
|
309
|
+
console.debug = toLog;
|
|
310
|
+
console.warn = toLog;
|
|
311
|
+
process.stdout.write = process.stderr.write.bind(process.stderr);
|
|
312
|
+
}
|
|
313
|
+
function hostGone(hostPid, startPpid) {
|
|
314
|
+
if (process.ppid !== startPpid) return true;
|
|
315
|
+
try {
|
|
316
|
+
process.kill(hostPid, 0);
|
|
317
|
+
return false;
|
|
318
|
+
} catch (error) {
|
|
319
|
+
return error.code === "ESRCH";
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function handleKey(handle) {
|
|
323
|
+
const canonical = (value) => {
|
|
324
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
325
|
+
if (value === null || typeof value !== "object") return value;
|
|
326
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical(value[key])]));
|
|
327
|
+
};
|
|
328
|
+
return JSON.stringify(canonical(handle));
|
|
329
|
+
}
|
|
330
|
+
function within(work, boundMs) {
|
|
331
|
+
let timer;
|
|
332
|
+
const bound = new Promise((resolve) => {
|
|
333
|
+
timer = setTimeout(() => resolve(void 0), boundMs);
|
|
334
|
+
});
|
|
335
|
+
return Promise.race([work, bound]).finally(() => clearTimeout(timer));
|
|
336
|
+
}
|
|
337
|
+
function firstLine(message) {
|
|
338
|
+
return message.split(/\r?\n/, 1)[0] ?? "";
|
|
339
|
+
}
|
|
340
|
+
function isRecord(value) {
|
|
341
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
342
|
+
}
|
|
343
|
+
function isRequestId(value) {
|
|
344
|
+
return typeof value === "string" || typeof value === "number";
|
|
345
|
+
}
|
|
346
|
+
function isPluginMethod(method) {
|
|
347
|
+
return PLUGIN_METHODS.includes(method);
|
|
348
|
+
}
|
|
349
|
+
function isOptionalMethod(method) {
|
|
350
|
+
return OPTIONAL_PLUGIN_METHODS.includes(method);
|
|
351
|
+
}
|
|
352
|
+
function issuePath(issue) {
|
|
353
|
+
return issue.path.map(String).join(".");
|
|
354
|
+
}
|
|
355
|
+
function parseParams(schema, params, method) {
|
|
356
|
+
const parsed = schema.safeParse(params ?? {});
|
|
357
|
+
if (parsed.success) return parsed.data;
|
|
358
|
+
const issue = parsed.error.issues[0];
|
|
359
|
+
const where = issue === void 0 ? "" : issuePath(issue);
|
|
360
|
+
throw new ProtocolFailure(PLUGIN_ERROR_CODES.invalidParams, `${method} params${where === "" ? "" : ` at ${where}`}: ${issue?.message ?? "invalid"}`);
|
|
361
|
+
}
|
|
362
|
+
function refuse(schema, value, prefix) {
|
|
363
|
+
const parsed = schema.safeParse(value);
|
|
364
|
+
if (parsed.success) return parsed.data;
|
|
365
|
+
const issue = parsed.error.issues[0];
|
|
366
|
+
const inner = issue === void 0 ? "" : issuePath(issue);
|
|
367
|
+
const path = inner === "" ? void 0 : `${prefix}.${inner}`;
|
|
368
|
+
throw new PluginError(`${path ?? prefix}: ${issue?.message ?? "invalid"}`, {
|
|
369
|
+
kind: "refused",
|
|
370
|
+
...path === void 0 ? {} : { path }
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
function checkedResult(schema, value, method) {
|
|
374
|
+
const parsed = schema.safeParse(value);
|
|
375
|
+
if (parsed.success) return value;
|
|
376
|
+
const issue = parsed.error.issues[0];
|
|
377
|
+
const where = issue === void 0 ? "" : issuePath(issue);
|
|
378
|
+
const handle = method === "acquire" && isRecord(value) && value.handle !== void 0 ? value.handle : void 0;
|
|
379
|
+
throw new PluginError(`${method} answered a malformed result${where === "" ? "" : ` at ${where}`}: ${issue?.message ?? "invalid"}`, {
|
|
380
|
+
kind: "refused",
|
|
381
|
+
...handle === void 0 ? {} : { handle }
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
function pluginIdentity(entryPath) {
|
|
385
|
+
const unknown = {
|
|
386
|
+
name: "unknown",
|
|
387
|
+
version: "unknown"
|
|
388
|
+
};
|
|
389
|
+
if (entryPath === void 0) return unknown;
|
|
390
|
+
try {
|
|
391
|
+
const root = findPackageRoot(dirname(realpathSync(entryPath)));
|
|
392
|
+
if (root === void 0) return unknown;
|
|
393
|
+
const pkg = readPackageJson(root);
|
|
394
|
+
return {
|
|
395
|
+
name: typeof pkg.name === "string" ? pkg.name : unknown.name,
|
|
396
|
+
version: typeof pkg.version === "string" ? pkg.version : unknown.version
|
|
397
|
+
};
|
|
398
|
+
} catch {
|
|
399
|
+
return unknown;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function runPlugin(definition, ioInput = {}) {
|
|
403
|
+
const io = resolveIo(ioInput);
|
|
404
|
+
const write = io.stdout.write.bind(io.stdout);
|
|
405
|
+
const ownsProcess = ioInput.stdout === void 0;
|
|
406
|
+
if (ownsProcess) {
|
|
407
|
+
routeStrayOutputToStderr();
|
|
408
|
+
process.stdout.on("error", () => {});
|
|
409
|
+
process.stderr.on("error", () => {});
|
|
410
|
+
process.on("SIGINT", () => {});
|
|
411
|
+
process.on("uncaughtException", (error) => {
|
|
412
|
+
try {
|
|
413
|
+
writeSync(2, `${inspect(error)}\n`);
|
|
414
|
+
} catch {}
|
|
415
|
+
process.exit(1);
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
const send = (message) => {
|
|
419
|
+
if (leaving) return;
|
|
420
|
+
write(encodeMessage(message));
|
|
421
|
+
};
|
|
422
|
+
const answer = (id, result) => send({
|
|
423
|
+
jsonrpc: "2.0",
|
|
424
|
+
id,
|
|
425
|
+
result
|
|
426
|
+
});
|
|
427
|
+
const fail = (id, code, message, data) => send({
|
|
428
|
+
jsonrpc: "2.0",
|
|
429
|
+
id,
|
|
430
|
+
error: {
|
|
431
|
+
code,
|
|
432
|
+
message: firstLine(message),
|
|
433
|
+
...data === void 0 ? {} : { data }
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
let options;
|
|
437
|
+
let runFacts;
|
|
438
|
+
let initialized = false;
|
|
439
|
+
let initializing = false;
|
|
440
|
+
let shutdownRan = false;
|
|
441
|
+
let leaving = false;
|
|
442
|
+
let hostPoll;
|
|
443
|
+
const startPpid = process.ppid;
|
|
444
|
+
const pending = /* @__PURE__ */ new Map();
|
|
445
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
446
|
+
const held = /* @__PURE__ */ new Map();
|
|
447
|
+
const releasing = /* @__PURE__ */ new Map();
|
|
448
|
+
const detachedContext = () => ({
|
|
449
|
+
signal: new AbortController().signal,
|
|
450
|
+
progress: () => {},
|
|
451
|
+
runFacts
|
|
452
|
+
});
|
|
453
|
+
const hold = (handle) => held.set(handleKey(handle), handle);
|
|
454
|
+
const releaseOnce = (handle, ctx) => {
|
|
455
|
+
const key = handleKey(handle);
|
|
456
|
+
const running = releasing.get(key);
|
|
457
|
+
if (running !== void 0) return running;
|
|
458
|
+
const work = (async () => {
|
|
459
|
+
try {
|
|
460
|
+
await definition.release({
|
|
461
|
+
handle,
|
|
462
|
+
options
|
|
463
|
+
}, ctx);
|
|
464
|
+
held.delete(key);
|
|
465
|
+
} finally {
|
|
466
|
+
releasing.delete(key);
|
|
467
|
+
}
|
|
468
|
+
})();
|
|
469
|
+
releasing.set(key, work);
|
|
470
|
+
return work;
|
|
471
|
+
};
|
|
472
|
+
const releaseHeld = (attempted) => Promise.allSettled([...held].filter(([key]) => !attempted.has(key)).map(([key, handle]) => {
|
|
473
|
+
attempted.add(key);
|
|
474
|
+
return releaseOnce(handle, detachedContext());
|
|
475
|
+
}));
|
|
476
|
+
const settings = (value) => refuse(definition.settings, value, "settings");
|
|
477
|
+
const leave = async (boundMs) => {
|
|
478
|
+
if (leaving) return;
|
|
479
|
+
leaving = true;
|
|
480
|
+
clearInterval(hostPoll);
|
|
481
|
+
const sequence = async () => {
|
|
482
|
+
if (!initialized) return;
|
|
483
|
+
for (const entry of pending.values()) if (entry.method !== "release" && entry.method !== "shutdown") entry.controller.abort();
|
|
484
|
+
const attempted = /* @__PURE__ */ new Set();
|
|
485
|
+
await Promise.allSettled([releaseHeld(attempted), ...inFlight]);
|
|
486
|
+
await releaseHeld(attempted);
|
|
487
|
+
if (shutdownRan) return;
|
|
488
|
+
shutdownRan = true;
|
|
489
|
+
await definition.shutdown?.({ options }, detachedContext());
|
|
490
|
+
};
|
|
491
|
+
await within(sequence().catch(() => {}), boundMs);
|
|
492
|
+
write("", () => io.exit(0));
|
|
493
|
+
};
|
|
494
|
+
const watchHost = (hostPid) => {
|
|
495
|
+
hostPoll = setInterval(() => {
|
|
496
|
+
if (hostGone(hostPid, startPpid)) leave(RELEASE_ON_CLOSE_BOUND_MS);
|
|
497
|
+
}, io.hostPidPollMs);
|
|
498
|
+
hostPoll.unref();
|
|
499
|
+
};
|
|
500
|
+
const initialize = async (params, ctx) => {
|
|
501
|
+
if (initialized || initializing) throw new ProtocolFailure(PLUGIN_ERROR_CODES.invalidRequest, "initialize was already called");
|
|
502
|
+
const input = parseParams(InitializeParamsSchema, params, "initialize");
|
|
503
|
+
const offered = input.protocolVersions.filter((version) => SUPPORTED_PROTOCOL_VERSIONS.includes(version));
|
|
504
|
+
if (offered.length === 0) throw new ProtocolFailure(PLUGIN_ERROR_CODES.plugin, `this plugin speaks protocol ${SUPPORTED_PROTOCOL_VERSIONS.join(", ")}, and the host offered ${input.protocolVersions.join(", ") || "none"}`, {
|
|
505
|
+
kind: "version",
|
|
506
|
+
supported: SUPPORTED_PROTOCOL_VERSIONS,
|
|
507
|
+
requested: input.protocolVersions
|
|
508
|
+
});
|
|
509
|
+
const parsedOptions = refuse(definition.options, input.options, "options");
|
|
510
|
+
initializing = true;
|
|
511
|
+
try {
|
|
512
|
+
await definition.initialize?.({
|
|
513
|
+
options: parsedOptions,
|
|
514
|
+
host: input.host,
|
|
515
|
+
entry: input.entry,
|
|
516
|
+
runFacts: input.runFacts
|
|
517
|
+
}, {
|
|
518
|
+
...ctx,
|
|
519
|
+
runFacts: input.runFacts
|
|
520
|
+
});
|
|
521
|
+
} finally {
|
|
522
|
+
initializing = false;
|
|
523
|
+
}
|
|
524
|
+
options = parsedOptions;
|
|
525
|
+
runFacts = input.runFacts;
|
|
526
|
+
initialized = true;
|
|
527
|
+
watchHost(input.host.pid);
|
|
528
|
+
return {
|
|
529
|
+
protocolVersion: Math.max(...offered),
|
|
530
|
+
plugin: pluginIdentity(io.entryPath),
|
|
531
|
+
serves: [...definition.serves],
|
|
532
|
+
methods: [...definition.methods]
|
|
533
|
+
};
|
|
534
|
+
};
|
|
535
|
+
const call = async (method, params, ctx) => {
|
|
536
|
+
if (method === "initialize") return initialize(params, ctx);
|
|
537
|
+
if (!isPluginMethod(method) || isOptionalMethod(method) && (!definition.methods.includes(method) || typeof definition[method] !== "function")) throw new ProtocolFailure(PLUGIN_ERROR_CODES.methodNotFound, `${method} is not a method of this plugin`);
|
|
538
|
+
if (!initialized) throw new ProtocolFailure(PLUGIN_ERROR_CODES.invalidRequest, `${method} came before initialize`);
|
|
539
|
+
const current = options;
|
|
540
|
+
switch (method) {
|
|
541
|
+
case "acquire": {
|
|
542
|
+
const input = parseParams(AcquireParamsSchema, params, method);
|
|
543
|
+
const result = await definition.acquire({
|
|
544
|
+
...input,
|
|
545
|
+
settings: settings(input.settings),
|
|
546
|
+
options: current
|
|
547
|
+
}, ctx);
|
|
548
|
+
return checkedResult(AcquireResultSchema, result, method);
|
|
549
|
+
}
|
|
550
|
+
case "release": {
|
|
551
|
+
const input = parseParams(ReleaseParamsSchema, params, method);
|
|
552
|
+
await releaseOnce(input.handle, ctx);
|
|
553
|
+
return {};
|
|
554
|
+
}
|
|
555
|
+
case "shutdown":
|
|
556
|
+
shutdownRan = true;
|
|
557
|
+
await releaseHeld(/* @__PURE__ */ new Set());
|
|
558
|
+
await definition.shutdown?.({ options: current }, ctx);
|
|
559
|
+
return {};
|
|
560
|
+
case "check": {
|
|
561
|
+
const remotes = parseParams(CheckParamsSchema, params, method).remotes.map((remote) => ({
|
|
562
|
+
name: remote.name,
|
|
563
|
+
settings: settings(remote.settings)
|
|
564
|
+
}));
|
|
565
|
+
const result = await definition.check?.({
|
|
566
|
+
remotes,
|
|
567
|
+
options: current
|
|
568
|
+
}, ctx);
|
|
569
|
+
return checkedResult(CheckResultSchema, result, method);
|
|
570
|
+
}
|
|
571
|
+
case "uploadApp": {
|
|
572
|
+
const input = parseParams(UploadAppParamsSchema, params, method);
|
|
573
|
+
const result = await definition.uploadApp?.({
|
|
574
|
+
...input,
|
|
575
|
+
settings: settings(input.settings),
|
|
576
|
+
options: current
|
|
577
|
+
}, ctx);
|
|
578
|
+
return checkedResult(UploadAppResultSchema, result, method);
|
|
579
|
+
}
|
|
580
|
+
case "started": {
|
|
581
|
+
const input = parseParams(StartedParamsSchema, params, method);
|
|
582
|
+
const result = await definition.started?.({
|
|
583
|
+
...input,
|
|
584
|
+
options: current
|
|
585
|
+
}, ctx);
|
|
586
|
+
return checkedResult(StartedResultSchema, result, method);
|
|
587
|
+
}
|
|
588
|
+
case "end": {
|
|
589
|
+
const input = parseParams(EndParamsSchema, params, method);
|
|
590
|
+
await definition.end?.({
|
|
591
|
+
...input,
|
|
592
|
+
options: current
|
|
593
|
+
}, ctx);
|
|
594
|
+
return {};
|
|
595
|
+
}
|
|
596
|
+
default: throw new ProtocolFailure(PLUGIN_ERROR_CODES.methodNotFound, `${method} is not a method of this plugin`);
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
const createdHandle = (method, outcome) => {
|
|
600
|
+
if (outcome.ok) {
|
|
601
|
+
if (method !== "acquire" || !isRecord(outcome.value)) return void 0;
|
|
602
|
+
return outcome.value.handle;
|
|
603
|
+
}
|
|
604
|
+
return outcome.error instanceof PluginError ? outcome.error.handle : void 0;
|
|
605
|
+
};
|
|
606
|
+
const answerFailure = (id, error) => {
|
|
607
|
+
if (error instanceof ProtocolFailure) fail(id, error.code, error.message, error.data);
|
|
608
|
+
else if (error instanceof PluginError) fail(id, PLUGIN_ERROR_CODES.plugin, error.message, {
|
|
609
|
+
kind: error.kind,
|
|
610
|
+
...error.path === void 0 ? {} : { path: error.path },
|
|
611
|
+
...error.handle === void 0 ? {} : { handle: error.handle }
|
|
612
|
+
});
|
|
613
|
+
else {
|
|
614
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
615
|
+
fail(id, PLUGIN_ERROR_CODES.plugin, message, { kind: "unreachable" });
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
const dispatch = async (id, method, params) => {
|
|
619
|
+
const entry = {
|
|
620
|
+
method,
|
|
621
|
+
controller: new AbortController(),
|
|
622
|
+
answered: false
|
|
623
|
+
};
|
|
624
|
+
pending.set(id, entry);
|
|
625
|
+
const ctx = {
|
|
626
|
+
signal: entry.controller.signal,
|
|
627
|
+
progress: (update) => {
|
|
628
|
+
if (entry.answered) return;
|
|
629
|
+
send({
|
|
630
|
+
jsonrpc: "2.0",
|
|
631
|
+
method: PROGRESS_METHOD,
|
|
632
|
+
params: {
|
|
633
|
+
id,
|
|
634
|
+
...update
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
},
|
|
638
|
+
runFacts
|
|
639
|
+
};
|
|
640
|
+
let outcome;
|
|
641
|
+
try {
|
|
642
|
+
outcome = {
|
|
643
|
+
ok: true,
|
|
644
|
+
value: await call(method, params, ctx)
|
|
645
|
+
};
|
|
646
|
+
} catch (error) {
|
|
647
|
+
outcome = {
|
|
648
|
+
ok: false,
|
|
649
|
+
error
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
clearTimeout(entry.graceTimer);
|
|
653
|
+
if (pending.get(id) === entry) pending.delete(id);
|
|
654
|
+
const handle = createdHandle(method, outcome);
|
|
655
|
+
const tracked = initialized ? handle : void 0;
|
|
656
|
+
if (tracked !== void 0) hold(tracked);
|
|
657
|
+
if (entry.answered) {
|
|
658
|
+
if (tracked !== void 0) await releaseOnce(tracked, detachedContext()).catch(() => {});
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
if (leaving) return;
|
|
662
|
+
entry.answered = true;
|
|
663
|
+
if (outcome.ok) answer(id, outcome.value);
|
|
664
|
+
else if (entry.controller.signal.aborted) {
|
|
665
|
+
const message = outcome.error instanceof Error ? outcome.error.message : String(outcome.error);
|
|
666
|
+
fail(id, PLUGIN_ERROR_CODES.cancelled, message, {
|
|
667
|
+
kind: "timeout",
|
|
668
|
+
...handle === void 0 ? {} : { handle }
|
|
669
|
+
});
|
|
670
|
+
} else answerFailure(id, outcome.error);
|
|
671
|
+
};
|
|
672
|
+
const cancel = (params) => {
|
|
673
|
+
const parsed = CancelParamsSchema.safeParse(params);
|
|
674
|
+
if (!parsed.success) return;
|
|
675
|
+
const { id } = parsed.data;
|
|
676
|
+
const entry = pending.get(id);
|
|
677
|
+
if (entry === void 0 || entry.controller.signal.aborted) return;
|
|
678
|
+
entry.controller.abort();
|
|
679
|
+
entry.graceTimer = setTimeout(() => {
|
|
680
|
+
if (entry.answered) return;
|
|
681
|
+
entry.answered = true;
|
|
682
|
+
fail(id, PLUGIN_ERROR_CODES.cancelled, `${entry.method} did not stop within ${io.cancelAnswerGraceMs} ms of its cancel`, { kind: "timeout" });
|
|
683
|
+
}, io.cancelAnswerGraceMs);
|
|
684
|
+
};
|
|
685
|
+
const handleLine = (line) => {
|
|
686
|
+
let message;
|
|
687
|
+
try {
|
|
688
|
+
message = JSON.parse(line);
|
|
689
|
+
} catch {
|
|
690
|
+
fail(null, PLUGIN_ERROR_CODES.parse, "the line is not JSON");
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
if (!isRecord(message)) {
|
|
694
|
+
fail(null, PLUGIN_ERROR_CODES.invalidRequest, "a message must be a JSON-RPC 2.0 object");
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
const id = isRequestId(message.id) ? message.id : null;
|
|
698
|
+
if (message.method === void 0 && ("result" in message || "error" in message)) return;
|
|
699
|
+
if (message.jsonrpc !== "2.0" || typeof message.method !== "string") {
|
|
700
|
+
fail(id, PLUGIN_ERROR_CODES.invalidRequest, "a request needs jsonrpc \"2.0\" and a method");
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
if (!("id" in message)) {
|
|
704
|
+
if (message.method === "$/cancel") cancel(message.params);
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
if (id === null) {
|
|
708
|
+
fail(null, PLUGIN_ERROR_CODES.invalidRequest, "a request id is a string or a number");
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (leaving) return;
|
|
712
|
+
const run = dispatch(id, message.method, message.params);
|
|
713
|
+
inFlight.add(run);
|
|
714
|
+
run.catch(() => {}).finally(() => inFlight.delete(run));
|
|
715
|
+
};
|
|
716
|
+
const decoder = createLineDecoder({
|
|
717
|
+
maxLineBytes: MAX_MESSAGE_BYTES,
|
|
718
|
+
onLine: handleLine,
|
|
719
|
+
onOverflow: () => fail(null, PLUGIN_ERROR_CODES.invalidRequest, `a message is longer than ${MAX_MESSAGE_BYTES} bytes`)
|
|
720
|
+
});
|
|
721
|
+
const stdinClosed = () => {
|
|
722
|
+
if (leaving) return;
|
|
723
|
+
decoder.end();
|
|
724
|
+
leave(RELEASE_ON_CLOSE_BOUND_MS);
|
|
725
|
+
};
|
|
726
|
+
if (ownsProcess) process.on("SIGTERM", () => void leave(RELEASE_ON_SIGTERM_BOUND_MS));
|
|
727
|
+
io.stdin.on("data", (chunk) => decoder.push(chunk));
|
|
728
|
+
io.stdin.on("end", stdinClosed);
|
|
729
|
+
io.stdin.on("error", stdinClosed);
|
|
730
|
+
}
|
|
731
|
+
// src/testing/scripted-plugin-main.ts
|
|
732
|
+
const ScriptStepSchema = z.union([
|
|
733
|
+
z.enum([
|
|
734
|
+
"ok",
|
|
735
|
+
"hold",
|
|
736
|
+
"crash",
|
|
737
|
+
"uncaught",
|
|
738
|
+
"noise",
|
|
739
|
+
"consoleLog",
|
|
740
|
+
"ignoreCancel"
|
|
741
|
+
]),
|
|
742
|
+
z.object({ error: z.object({
|
|
743
|
+
kind: z.enum(PLUGIN_FAILURE_KINDS),
|
|
744
|
+
message: z.string(),
|
|
745
|
+
path: z.string().optional()
|
|
746
|
+
}) }),
|
|
747
|
+
z.object({
|
|
748
|
+
delayMs: z.number().int().nonnegative(),
|
|
749
|
+
ignoreCancel: z.boolean().optional()
|
|
750
|
+
}),
|
|
751
|
+
z.object({ errorAfterCreate: z.object({
|
|
752
|
+
kind: z.enum(PLUGIN_FAILURE_KINDS),
|
|
753
|
+
message: z.string()
|
|
754
|
+
}) })
|
|
755
|
+
]);
|
|
756
|
+
const ScriptSchema = z.union([ScriptStepSchema, z.array(ScriptStepSchema).min(1)]);
|
|
757
|
+
const AnswerSchema = z.object({
|
|
758
|
+
protocol: z.enum(PLUGIN_SERVES),
|
|
759
|
+
url: z.string(),
|
|
760
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
761
|
+
capabilities: z.record(z.string(), z.unknown()).optional(),
|
|
762
|
+
mask: z.array(z.string()).optional(),
|
|
763
|
+
link: z.string().optional()
|
|
764
|
+
});
|
|
765
|
+
const ProgressSchema = z.object({
|
|
766
|
+
message: z.string(),
|
|
767
|
+
queued: z.number().int().nonnegative().optional(),
|
|
768
|
+
percent: z.number().min(0).max(100).optional()
|
|
769
|
+
});
|
|
770
|
+
const ScriptedOptionsSchema = z.looseObject({
|
|
771
|
+
ledgerFile: z.string().optional(),
|
|
772
|
+
recordProcess: z.boolean().optional(),
|
|
773
|
+
recordParams: z.boolean().optional(),
|
|
774
|
+
answer: AnswerSchema.optional(),
|
|
775
|
+
answerFile: z.string().optional(),
|
|
776
|
+
script: z.record(z.string(), ScriptSchema).optional(),
|
|
777
|
+
stderr: z.record(z.string(), z.string()).optional(),
|
|
778
|
+
progress: z.array(ProgressSchema).optional(),
|
|
779
|
+
progressByRemote: z.record(z.string(), z.array(ProgressSchema)).optional(),
|
|
780
|
+
progressEveryMs: z.number().int().positive().optional(),
|
|
781
|
+
started: StartedResultSchema.optional(),
|
|
782
|
+
upload: UploadAppResultSchema.optional(),
|
|
783
|
+
check: CheckResultSchema.optional()
|
|
784
|
+
});
|
|
785
|
+
function readOwnManifest() {
|
|
786
|
+
const entryPath = process.argv[1];
|
|
787
|
+
if (entryPath === void 0) throw new Error("the scripted plugin needs its entry path");
|
|
788
|
+
const root = findPackageRoot(dirname(realpathSync(entryPath)));
|
|
789
|
+
if (root === void 0) throw new Error(`no package.json above ${entryPath}`);
|
|
790
|
+
const field = readPackageJson(root)[PLUGIN_MANIFEST_FIELD];
|
|
791
|
+
const plugin = typeof field === "object" && field !== null ? Reflect.get(field, "plugin") : void 0;
|
|
792
|
+
const manifest = PluginManifestSchema.parse(plugin);
|
|
793
|
+
if (manifest.kind !== "remote") throw new Error("the scripted plugin is a remote plugin");
|
|
794
|
+
return manifest;
|
|
795
|
+
}
|
|
796
|
+
function ledger(options, entry) {
|
|
797
|
+
if (options.ledgerFile !== void 0) appendFileSync(options.ledgerFile, `${JSON.stringify(entry)}\n`);
|
|
798
|
+
}
|
|
799
|
+
function cancelled(method, handle) {
|
|
800
|
+
return new PluginError(`${method} was cancelled`, {
|
|
801
|
+
kind: "timeout",
|
|
802
|
+
...handle === void 0 ? {} : { handle }
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
const callsSoFar = /* @__PURE__ */ new Map();
|
|
806
|
+
function reportProgress(options, remote, ctx) {
|
|
807
|
+
const updates = options.progressByRemote?.[remote] ?? options.progress ?? [];
|
|
808
|
+
for (const update of updates) ctx.progress(update);
|
|
809
|
+
if (options.progressEveryMs === void 0 || updates.length === 0) return () => {};
|
|
810
|
+
let next = 0;
|
|
811
|
+
const timer = setInterval(() => {
|
|
812
|
+
const update = updates[next % updates.length];
|
|
813
|
+
next += 1;
|
|
814
|
+
if (update !== void 0) ctx.progress(update);
|
|
815
|
+
}, options.progressEveryMs);
|
|
816
|
+
return () => clearInterval(timer);
|
|
817
|
+
}
|
|
818
|
+
function stepFor(options, method) {
|
|
819
|
+
const planned = options.script?.[method] ?? "ok";
|
|
820
|
+
if (!Array.isArray(planned)) return planned;
|
|
821
|
+
const call = callsSoFar.get(method) ?? 0;
|
|
822
|
+
callsSoFar.set(method, call + 1);
|
|
823
|
+
return planned[Math.min(call, planned.length - 1)] ?? "ok";
|
|
824
|
+
}
|
|
825
|
+
async function follow(options, method, ctx, handle) {
|
|
826
|
+
const logged = options.stderr?.[method];
|
|
827
|
+
if (logged !== void 0) writeSync(2, `${logged}\n`);
|
|
828
|
+
const step = stepFor(options, method);
|
|
829
|
+
switch (step) {
|
|
830
|
+
case "ok": return;
|
|
831
|
+
case "hold":
|
|
832
|
+
await new Promise((resolve) => {
|
|
833
|
+
ctx.signal.addEventListener("abort", () => resolve(), { once: true });
|
|
834
|
+
});
|
|
835
|
+
throw cancelled(method, handle);
|
|
836
|
+
case "ignoreCancel":
|
|
837
|
+
await new Promise(() => {});
|
|
838
|
+
return;
|
|
839
|
+
case "crash": process.exit(1);
|
|
840
|
+
case "uncaught":
|
|
841
|
+
process.emitWarning("a dependency used a deprecated API", "DeprecationWarning", "DEP0000");
|
|
842
|
+
setTimeout(() => {
|
|
843
|
+
throw new Error(`${method} threw where nothing catches it`);
|
|
844
|
+
}, 10);
|
|
845
|
+
await new Promise(() => {});
|
|
846
|
+
return;
|
|
847
|
+
case "noise":
|
|
848
|
+
writeSync(1, "noise\n");
|
|
849
|
+
return;
|
|
850
|
+
case "consoleLog":
|
|
851
|
+
console.log(`${method} logged to the console`);
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
if ("delayMs" in step) {
|
|
855
|
+
try {
|
|
856
|
+
await setTimeout$1(step.delayMs, void 0, step.ignoreCancel ? {} : { signal: ctx.signal });
|
|
857
|
+
} catch {
|
|
858
|
+
throw cancelled(method, handle);
|
|
859
|
+
}
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
if ("errorAfterCreate" in step) throw new PluginError(step.errorAfterCreate.message, {
|
|
863
|
+
kind: step.errorAfterCreate.kind,
|
|
864
|
+
...handle === void 0 ? {} : { handle }
|
|
865
|
+
});
|
|
866
|
+
throw new PluginError(step.error.message, {
|
|
867
|
+
kind: step.error.kind,
|
|
868
|
+
...step.error.path === void 0 ? {} : { path: step.error.path }
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
function startScriptedPlugin(timing = {}) {
|
|
872
|
+
const manifest = readOwnManifest();
|
|
873
|
+
const declares = (method) => manifest.methods.includes(method);
|
|
874
|
+
let issued = 0;
|
|
875
|
+
runPlugin(definePlugin({
|
|
876
|
+
options: ScriptedOptionsSchema,
|
|
877
|
+
settings: z.looseObject({}),
|
|
878
|
+
serves: manifest.serves,
|
|
879
|
+
...manifest.runFacts === void 0 ? {} : { runFacts: manifest.runFacts },
|
|
880
|
+
async initialize({ options, host, runFacts }, ctx) {
|
|
881
|
+
ledger(options, {
|
|
882
|
+
method: "initialize",
|
|
883
|
+
...options.recordProcess === true ? {
|
|
884
|
+
execPath: process.execPath,
|
|
885
|
+
pid: process.pid,
|
|
886
|
+
envKeys: Object.keys(process.env),
|
|
887
|
+
options,
|
|
888
|
+
host,
|
|
889
|
+
...runFacts === void 0 ? {} : { runFacts }
|
|
890
|
+
} : {}
|
|
891
|
+
});
|
|
892
|
+
await follow(options, "initialize", ctx);
|
|
893
|
+
},
|
|
894
|
+
async acquire({ options, remote, session, run }, ctx) {
|
|
895
|
+
issued += 1;
|
|
896
|
+
const handle = { n: issued };
|
|
897
|
+
ledger(options, {
|
|
898
|
+
method: "acquire",
|
|
899
|
+
handle,
|
|
900
|
+
remote,
|
|
901
|
+
...options.recordParams === true ? {
|
|
902
|
+
session,
|
|
903
|
+
...run === void 0 ? {} : { run }
|
|
904
|
+
} : {}
|
|
905
|
+
});
|
|
906
|
+
const stopProgress = reportProgress(options, remote, ctx);
|
|
907
|
+
try {
|
|
908
|
+
await follow(options, "acquire", ctx, handle);
|
|
909
|
+
} finally {
|
|
910
|
+
stopProgress();
|
|
911
|
+
}
|
|
912
|
+
const answer = options.answerFile === void 0 ? options.answer : AnswerSchema.parse(JSON.parse(readFileSync(options.answerFile, "utf8")));
|
|
913
|
+
if (answer === void 0) throw new PluginError("the scripted plugin has no answer option", {
|
|
914
|
+
kind: "refused",
|
|
915
|
+
path: "answer"
|
|
916
|
+
});
|
|
917
|
+
return {
|
|
918
|
+
handle,
|
|
919
|
+
...answer
|
|
920
|
+
};
|
|
921
|
+
},
|
|
922
|
+
async release({ options, handle }, ctx) {
|
|
923
|
+
ledger(options, {
|
|
924
|
+
method: "release",
|
|
925
|
+
handle
|
|
926
|
+
});
|
|
927
|
+
await follow(options, "release", ctx);
|
|
928
|
+
},
|
|
929
|
+
async shutdown({ options }, ctx) {
|
|
930
|
+
ledger(options, { method: "shutdown" });
|
|
931
|
+
await follow(options, "shutdown", ctx);
|
|
932
|
+
},
|
|
933
|
+
...declares("check") ? { async check({ options }, ctx) {
|
|
934
|
+
ledger(options, { method: "check" });
|
|
935
|
+
await follow(options, "check", ctx);
|
|
936
|
+
return options.check ?? {
|
|
937
|
+
state: "ok",
|
|
938
|
+
lines: []
|
|
939
|
+
};
|
|
940
|
+
} } : {},
|
|
941
|
+
...declares("uploadApp") ? { async uploadApp({ options, remote, settings, platform, path, sha256, size }, ctx) {
|
|
942
|
+
ledger(options, {
|
|
943
|
+
method: "uploadApp",
|
|
944
|
+
remote,
|
|
945
|
+
...options.recordParams === true ? {
|
|
946
|
+
settings,
|
|
947
|
+
platform,
|
|
948
|
+
path,
|
|
949
|
+
sha256,
|
|
950
|
+
size
|
|
951
|
+
} : {}
|
|
952
|
+
});
|
|
953
|
+
const stopProgress = reportProgress(options, remote, ctx);
|
|
954
|
+
try {
|
|
955
|
+
await follow(options, "uploadApp", ctx);
|
|
956
|
+
} finally {
|
|
957
|
+
stopProgress();
|
|
958
|
+
}
|
|
959
|
+
return options.upload ?? { ref: "scripted-app" };
|
|
960
|
+
} } : {},
|
|
961
|
+
...declares("started") ? { async started({ options, handle, sessionId }, ctx) {
|
|
962
|
+
ledger(options, {
|
|
963
|
+
method: "started",
|
|
964
|
+
handle,
|
|
965
|
+
...options.recordParams === true && sessionId !== void 0 ? { sessionId } : {}
|
|
966
|
+
});
|
|
967
|
+
await follow(options, "started", ctx);
|
|
968
|
+
return options.started ?? {};
|
|
969
|
+
} } : {},
|
|
970
|
+
...declares("end") ? { async end({ options, handle, outcome, reason }, ctx) {
|
|
971
|
+
ledger(options, {
|
|
972
|
+
method: "end",
|
|
973
|
+
handle,
|
|
974
|
+
...options.recordParams === true ? {
|
|
975
|
+
outcome,
|
|
976
|
+
...reason === void 0 ? {} : { reason }
|
|
977
|
+
} : {}
|
|
978
|
+
});
|
|
979
|
+
await follow(options, "end", ctx);
|
|
980
|
+
} } : {}
|
|
981
|
+
}), timing);
|
|
982
|
+
}
|
|
983
|
+
export { startScriptedPlugin };
|
|
984
|
+
|
|
985
|
+
//# sourceMappingURL=scripted-plugin.js.map
|