@ai-agent-forge/plugin-test-runner 0.85.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.
Files changed (52) hide show
  1. package/README.md +44 -0
  2. package/agent-forge.json +12 -0
  3. package/dist/capability.d.ts +5 -0
  4. package/dist/capability.d.ts.map +1 -0
  5. package/dist/capability.js +138 -0
  6. package/dist/capability.js.map +1 -0
  7. package/dist/entry.d.ts +13 -0
  8. package/dist/entry.d.ts.map +1 -0
  9. package/dist/entry.js +14 -0
  10. package/dist/entry.js.map +1 -0
  11. package/dist/index.d.ts +12 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +10 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/node-adapter.d.ts +24 -0
  16. package/dist/node-adapter.d.ts.map +1 -0
  17. package/dist/node-adapter.js +448 -0
  18. package/dist/node-adapter.js.map +1 -0
  19. package/dist/node-jest-json-report.d.ts +12 -0
  20. package/dist/node-jest-json-report.d.ts.map +1 -0
  21. package/dist/node-jest-json-report.js +52 -0
  22. package/dist/node-jest-json-report.js.map +1 -0
  23. package/dist/node-jest-runner.d.ts +26 -0
  24. package/dist/node-jest-runner.d.ts.map +1 -0
  25. package/dist/node-jest-runner.js +51 -0
  26. package/dist/node-jest-runner.js.map +1 -0
  27. package/dist/node-junit-xml-report.d.ts +11 -0
  28. package/dist/node-junit-xml-report.d.ts.map +1 -0
  29. package/dist/node-junit-xml-report.js +76 -0
  30. package/dist/node-junit-xml-report.js.map +1 -0
  31. package/dist/node-pytest-runner.d.ts +34 -0
  32. package/dist/node-pytest-runner.d.ts.map +1 -0
  33. package/dist/node-pytest-runner.js +111 -0
  34. package/dist/node-pytest-runner.js.map +1 -0
  35. package/dist/node-test-process.d.ts +24 -0
  36. package/dist/node-test-process.d.ts.map +1 -0
  37. package/dist/node-test-process.js +89 -0
  38. package/dist/node-test-process.js.map +1 -0
  39. package/dist/node-vitest-runner.d.ts +24 -0
  40. package/dist/node-vitest-runner.d.ts.map +1 -0
  41. package/dist/node-vitest-runner.js +49 -0
  42. package/dist/node-vitest-runner.js.map +1 -0
  43. package/dist/shell-utils.d.ts +15 -0
  44. package/dist/shell-utils.d.ts.map +1 -0
  45. package/dist/shell-utils.js +43 -0
  46. package/dist/shell-utils.js.map +1 -0
  47. package/dist/types.d.ts +93 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +5 -0
  50. package/dist/types.js.map +1 -0
  51. package/package.json +55 -0
  52. package/plugin.json +9 -0
@@ -0,0 +1,448 @@
1
+ import { projectPluginContextV1, validatePluginErrorV1 } from "@agent-forge/plugin-sdk";
2
+ import { sanitizeBinaryOutput } from "./shell-utils.js";
3
+ import { TEST_RUNNER_HOST_ADAPTER_ID_V1, TEST_RUNNER_HOST_ADAPTER_VERSION_V1, } from "./types.js";
4
+ const DEFAULT_TIMEOUT_MS_V1 = 30_000;
5
+ const MAX_TIMEOUT_MS_V1 = 120_000;
6
+ const MAX_MAX_FAILURES_V1 = 1_000;
7
+ const MAX_COMMAND_ID_CHARS_V1 = 256;
8
+ const MAX_COMMAND_ARGS_V1 = 128;
9
+ const MAX_COMMAND_ARG_CHARS_V1 = 2048;
10
+ const MAX_TEST_COUNT_V1 = 10_000_000;
11
+ const MAX_HOST_FAILURES_V1 = 4096;
12
+ export const TEST_RUNNER_MAX_FAILURES_V1 = 64;
13
+ export const TEST_RUNNER_MAX_FAILURE_MESSAGE_CHARS_V1 = 512;
14
+ const MAX_HOST_FAILURE_MESSAGE_CHARS_V1 = 8192;
15
+ const MAX_FILE_IDENTIFIER_CHARS_V1 = 512;
16
+ const MAX_ERROR_CODE_CHARS_V1 = 128;
17
+ const MAX_ERROR_MESSAGE_CHARS_V1 = 512;
18
+ const MAX_OPERATION_ID_CHARS_V1 = 256;
19
+ const MAX_REPORT_ARTIFACT_REF_CHARS_V1 = 256;
20
+ export const TEST_RUNNER_MAX_OPAQUE_REF_CHARS_V1 = 256;
21
+ const FRAMEWORKS_V1 = new Set(["vitest", "jest", "node-test", "pytest"]);
22
+ const OPAQUE_REF_V1 = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/u;
23
+ const FILE_IDENTIFIER_V1 = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/u;
24
+ const SAFE_ERROR_CODE_V1 = /^[a-z][a-z0-9_]{0,127}$/u;
25
+ function isRecord(value) {
26
+ return value !== null && typeof value === "object" && !Array.isArray(value);
27
+ }
28
+ function hasOnlyKeys(value, allowed) {
29
+ return Object.keys(value).every((key) => allowed.includes(key));
30
+ }
31
+ function isSafeOpaqueRef(value) {
32
+ return value.length <= TEST_RUNNER_MAX_OPAQUE_REF_CHARS_V1 && OPAQUE_REF_V1.test(value);
33
+ }
34
+ function isSafeFileIdentifier(value) {
35
+ return FILE_IDENTIFIER_V1.test(value) && !value.split("/").some((segment) => segment === "." || segment === "..");
36
+ }
37
+ /** Removes credentials and absolute host paths before text crosses the model boundary. */
38
+ export function sanitizeTestRunnerTextV1(value, maximum = TEST_RUNNER_MAX_FAILURE_MESSAGE_CHARS_V1) {
39
+ let text = sanitizeBinaryOutput(value);
40
+ text = text
41
+ .replace(/((?:api[-_ ]?key|access[-_ ]?token|auth(?:orization)?|bearer|password|passwd|secret|token)\s*[:=]\s*)([^\s,;]+)/giu, "$1[redacted-secret]")
42
+ .replace(/(?:[A-Za-z]:[\\/]|\\\\|\/(?:home|Users|private|tmp|var|workspace)\/)[^\s"'<>]*/gu, "[redacted-path]");
43
+ return text.length <= maximum ? text : text.slice(0, maximum);
44
+ }
45
+ function validPositiveInteger(value, maximum) {
46
+ return Number.isSafeInteger(value) && value >= 1 && value <= maximum;
47
+ }
48
+ function invalidInput(operationId, message) {
49
+ return {
50
+ version: 1,
51
+ operationId,
52
+ status: "failed",
53
+ summary: emptySummary(),
54
+ failures: [],
55
+ failuresTruncated: false,
56
+ error: { version: 1, code: "invalid_input", message },
57
+ };
58
+ }
59
+ function adapterFailure(operationId) {
60
+ return {
61
+ version: 1,
62
+ operationId,
63
+ status: "failed",
64
+ summary: emptySummary(),
65
+ failures: [],
66
+ failuresTruncated: false,
67
+ error: { version: 1, code: "adapter_failed", message: "Test runner adapter returned an invalid result" },
68
+ };
69
+ }
70
+ function unsupported(operationId) {
71
+ return {
72
+ version: 1,
73
+ operationId,
74
+ status: "unsupported",
75
+ summary: emptySummary(),
76
+ failures: [],
77
+ failuresTruncated: false,
78
+ error: { version: 1, code: "test_runner_unavailable", message: "No test runner is bound by this host" },
79
+ };
80
+ }
81
+ function cancelled(operationId) {
82
+ return {
83
+ version: 1,
84
+ operationId,
85
+ status: "cancelled",
86
+ summary: emptySummary(),
87
+ failures: [],
88
+ failuresTruncated: false,
89
+ error: { version: 1, code: "cancelled", message: "Test run was cancelled" },
90
+ };
91
+ }
92
+ function timedOut(operationId) {
93
+ return {
94
+ version: 1,
95
+ operationId,
96
+ status: "failed",
97
+ summary: emptySummary(),
98
+ failures: [],
99
+ failuresTruncated: false,
100
+ error: { version: 1, code: "timeout", message: "Test run timed out" },
101
+ };
102
+ }
103
+ function emptySummary() {
104
+ return { total: 0, passed: 0, failed: 0, skipped: 0, flaky: 0 };
105
+ }
106
+ function validateRequest(request) {
107
+ if (request.version !== 1)
108
+ return "version must be 1";
109
+ if (typeof request.operationId !== "string" ||
110
+ request.operationId.trim() === "" ||
111
+ request.operationId.length > MAX_OPERATION_ID_CHARS_V1)
112
+ return "operationId is invalid";
113
+ if (typeof request.workspaceRef !== "string" || !isSafeOpaqueRef(request.workspaceRef))
114
+ return "workspaceRef must be a safe opaque host handle";
115
+ if (typeof request.framework !== "string" || !FRAMEWORKS_V1.has(request.framework))
116
+ return "framework is invalid";
117
+ if (typeof request.target !== "string" ||
118
+ request.target.trim() === "" ||
119
+ request.target.length > 1024 ||
120
+ /[\u0000\r\n]/u.test(request.target))
121
+ return "target is invalid";
122
+ if (request.timeoutMs !== undefined && !validPositiveInteger(request.timeoutMs, MAX_TIMEOUT_MS_V1))
123
+ return `timeoutMs must be a positive integer at most ${MAX_TIMEOUT_MS_V1}`;
124
+ if (request.maxFailures !== undefined && !validPositiveInteger(request.maxFailures, MAX_MAX_FAILURES_V1))
125
+ return `maxFailures must be a positive integer at most ${MAX_MAX_FAILURES_V1}`;
126
+ return undefined;
127
+ }
128
+ function validCommand(value) {
129
+ if (!isRecord(value) || !hasOnlyKeys(value, ["commandId", "args"]))
130
+ return false;
131
+ return (typeof value.commandId === "string" &&
132
+ value.commandId.length > 0 &&
133
+ value.commandId.length <= MAX_COMMAND_ID_CHARS_V1 &&
134
+ isSafeOpaqueRef(value.commandId) &&
135
+ Array.isArray(value.args) &&
136
+ value.args.length <= MAX_COMMAND_ARGS_V1 &&
137
+ value.args.every((arg) => typeof arg === "string" && arg.length <= MAX_COMMAND_ARG_CHARS_V1 && !/[\u0000\r\n]/u.test(arg)));
138
+ }
139
+ function validateSummary(value) {
140
+ if (!isRecord(value) || !hasOnlyKeys(value, ["total", "passed", "failed", "skipped", "flaky"]))
141
+ return undefined;
142
+ const total = value.total;
143
+ const passed = value.passed;
144
+ const failed = value.failed;
145
+ const skipped = value.skipped;
146
+ const flaky = value.flaky;
147
+ const isCount = (item) => Number.isSafeInteger(item) && item >= 0;
148
+ if (!isCount(total) ||
149
+ !isCount(passed) ||
150
+ !isCount(failed) ||
151
+ !isCount(skipped) ||
152
+ !isCount(flaky) ||
153
+ total > MAX_TEST_COUNT_V1 ||
154
+ passed > MAX_TEST_COUNT_V1 ||
155
+ failed > MAX_TEST_COUNT_V1 ||
156
+ skipped > MAX_TEST_COUNT_V1 ||
157
+ flaky > MAX_TEST_COUNT_V1 ||
158
+ passed + failed + skipped > total ||
159
+ flaky > total)
160
+ return undefined;
161
+ return {
162
+ total,
163
+ passed,
164
+ failed,
165
+ skipped,
166
+ flaky,
167
+ };
168
+ }
169
+ function projectFailure(value) {
170
+ if (!isRecord(value) || !hasOnlyKeys(value, ["file", "line", "column", "message"]))
171
+ return undefined;
172
+ if (typeof value.file !== "string" ||
173
+ value.file.length > MAX_FILE_IDENTIFIER_CHARS_V1 ||
174
+ !isSafeFileIdentifier(value.file) ||
175
+ typeof value.message !== "string" ||
176
+ value.message.trim() === "" ||
177
+ value.message.length > MAX_HOST_FAILURE_MESSAGE_CHARS_V1 ||
178
+ (value.line !== undefined && !validPositiveInteger(value.line, MAX_TEST_COUNT_V1)) ||
179
+ (value.column !== undefined && !validPositiveInteger(value.column, MAX_TEST_COUNT_V1)))
180
+ return undefined;
181
+ return {
182
+ file: value.file,
183
+ ...(value.line === undefined ? {} : { line: value.line }),
184
+ ...(value.column === undefined ? {} : { column: value.column }),
185
+ message: sanitizeTestRunnerTextV1(value.message),
186
+ };
187
+ }
188
+ function projectError(value) {
189
+ if (!isRecord(value) || !hasOnlyKeys(value, ["version", "code", "message", "causeCode"]))
190
+ return undefined;
191
+ try {
192
+ const error = validatePluginErrorV1(value, "Test runner error");
193
+ if (error.code.length > MAX_ERROR_CODE_CHARS_V1 ||
194
+ !SAFE_ERROR_CODE_V1.test(error.code) ||
195
+ error.message.length > MAX_HOST_FAILURE_MESSAGE_CHARS_V1 ||
196
+ (error.causeCode !== undefined &&
197
+ (error.causeCode.length > MAX_ERROR_CODE_CHARS_V1 || !SAFE_ERROR_CODE_V1.test(error.causeCode))))
198
+ return undefined;
199
+ return {
200
+ version: 1,
201
+ code: error.code,
202
+ message: sanitizeTestRunnerTextV1(error.message, MAX_ERROR_MESSAGE_CHARS_V1),
203
+ ...(error.causeCode === undefined ? {} : { causeCode: error.causeCode }),
204
+ };
205
+ }
206
+ catch {
207
+ return undefined;
208
+ }
209
+ }
210
+ /** Strictly projects arbitrary adapter output into the bounded public test DTO. */
211
+ export function projectTestRunResultV1(value, operationId) {
212
+ if (!isRecord(value) ||
213
+ !hasOnlyKeys(value, [
214
+ "version",
215
+ "operationId",
216
+ "status",
217
+ "summary",
218
+ "failures",
219
+ "failuresTruncated",
220
+ "exitCode",
221
+ "reportArtifactRef",
222
+ "error",
223
+ ]) ||
224
+ value.version !== 1 ||
225
+ value.operationId !== operationId ||
226
+ typeof value.status !== "string" ||
227
+ !["succeeded", "failed", "cancelled", "needs_user", "unsupported"].includes(value.status) ||
228
+ !Array.isArray(value.failures) ||
229
+ value.failures.length > TEST_RUNNER_MAX_FAILURES_V1 ||
230
+ typeof value.failuresTruncated !== "boolean")
231
+ return undefined;
232
+ const summary = validateSummary(value.summary);
233
+ if (summary === undefined)
234
+ return undefined;
235
+ const failures = value.failures.map(projectFailure);
236
+ if (failures.some((failure) => failure === undefined))
237
+ return undefined;
238
+ const exitCode = value.exitCode;
239
+ if (exitCode !== undefined &&
240
+ (typeof exitCode !== "number" || !Number.isSafeInteger(exitCode) || exitCode < 0 || exitCode > 255))
241
+ return undefined;
242
+ if (value.reportArtifactRef !== undefined &&
243
+ (typeof value.reportArtifactRef !== "string" ||
244
+ value.reportArtifactRef.length > MAX_REPORT_ARTIFACT_REF_CHARS_V1 ||
245
+ !isSafeOpaqueRef(value.reportArtifactRef)))
246
+ return undefined;
247
+ const error = value.error === undefined ? undefined : projectError(value.error);
248
+ if ((value.status === "succeeded" && error !== undefined) || (value.status !== "succeeded" && error === undefined))
249
+ return undefined;
250
+ if (value.status === "succeeded" && (summary.failed !== 0 || exitCode !== 0))
251
+ return undefined;
252
+ const projected = {
253
+ version: 1,
254
+ operationId,
255
+ status: value.status,
256
+ summary,
257
+ failures: failures,
258
+ failuresTruncated: value.failuresTruncated,
259
+ ...(exitCode === undefined ? {} : { exitCode }),
260
+ ...(value.reportArtifactRef === undefined ? {} : { reportArtifactRef: value.reportArtifactRef }),
261
+ ...(error === undefined ? {} : { error }),
262
+ };
263
+ return contextBoundResult(projected) ?? adapterFailure(operationId);
264
+ }
265
+ function completeResult(operationId, value, maxFailures) {
266
+ if (!isRecord(value) || !hasOnlyKeys(value, ["status", "exitCode", "summary", "failures", "reportArtifactRef"]))
267
+ return undefined;
268
+ if (value.status === "unsupported")
269
+ return unsupported(operationId);
270
+ if (value.status !== "completed")
271
+ return undefined;
272
+ if (!Number.isSafeInteger(value.exitCode) || value.exitCode < 0 || value.exitCode > 255)
273
+ return undefined;
274
+ const summary = validateSummary(value.summary);
275
+ if (summary === undefined)
276
+ return undefined;
277
+ if (value.failures !== undefined && (!Array.isArray(value.failures) || value.failures.length > MAX_HOST_FAILURES_V1))
278
+ return undefined;
279
+ const rawFailures = value.failures ?? [];
280
+ if (rawFailures.length > summary.failed)
281
+ return undefined;
282
+ const failureLimit = Math.min(maxFailures, TEST_RUNNER_MAX_FAILURES_V1);
283
+ const failures = rawFailures.slice(0, failureLimit).map(projectFailure);
284
+ if (failures.some((failure) => failure === undefined))
285
+ return undefined;
286
+ if (value.reportArtifactRef !== undefined &&
287
+ (typeof value.reportArtifactRef !== "string" ||
288
+ value.reportArtifactRef.length > MAX_REPORT_ARTIFACT_REF_CHARS_V1 ||
289
+ !isSafeOpaqueRef(value.reportArtifactRef)))
290
+ return undefined;
291
+ const succeeded = value.exitCode === 0 && summary.failed === 0;
292
+ const projected = {
293
+ version: 1,
294
+ operationId,
295
+ status: succeeded ? "succeeded" : "failed",
296
+ summary,
297
+ failures: failures,
298
+ failuresTruncated: rawFailures.length > failureLimit || summary.failed > failures.length,
299
+ exitCode: value.exitCode,
300
+ ...(value.reportArtifactRef === undefined ? {} : { reportArtifactRef: value.reportArtifactRef }),
301
+ ...(succeeded
302
+ ? {}
303
+ : {
304
+ error: {
305
+ version: 1,
306
+ code: summary.failed > 0 ? "test_failures" : "exit_nonzero",
307
+ message: summary.failed > 0 ? "One or more tests failed" : "Test runner exited with a non-zero status",
308
+ },
309
+ }),
310
+ };
311
+ return contextBoundResult(projected) ?? adapterFailure(operationId);
312
+ }
313
+ function contextBoundResult(result) {
314
+ const outputArtifactRef = result.reportArtifactRef;
315
+ for (let failureCount = result.failures.length; failureCount >= 0; failureCount--) {
316
+ const failures = result.failures.slice(0, failureCount);
317
+ const projection = projectPluginContextV1({
318
+ version: 1,
319
+ operationId: result.operationId,
320
+ items: [
321
+ { ...result.summary },
322
+ ...failures.map((failure) => ({ ...failure })),
323
+ ],
324
+ ...(outputArtifactRef === undefined ? {} : { outputArtifactRef }),
325
+ truncated: result.failuresTruncated || failureCount < result.failures.length,
326
+ }, { maxOutputBytes: 64 * 1024, maxInlineItems: 65, maxCellChars: 512 });
327
+ if (projection.status === "succeeded") {
328
+ const candidate = {
329
+ ...result,
330
+ failures,
331
+ failuresTruncated: result.failuresTruncated || failureCount < result.failures.length,
332
+ };
333
+ if (new TextEncoder().encode(JSON.stringify(candidate)).byteLength <= 64 * 1024)
334
+ return candidate;
335
+ }
336
+ }
337
+ return undefined;
338
+ }
339
+ const CANCELLATION_V1 = Symbol("test-runner-cancelled");
340
+ const TIMEOUT_V1 = Symbol("test-runner-timeout");
341
+ async function runBounded(runner, command, timeoutMs, signal) {
342
+ const controller = new AbortController();
343
+ let stop;
344
+ const abort = () => {
345
+ stop ??= CANCELLATION_V1;
346
+ controller.abort();
347
+ };
348
+ if (signal?.aborted)
349
+ return CANCELLATION_V1;
350
+ signal?.addEventListener("abort", abort, { once: true });
351
+ const timeout = setTimeout(() => {
352
+ stop ??= TIMEOUT_V1;
353
+ controller.abort();
354
+ }, timeoutMs);
355
+ const completed = Promise.resolve()
356
+ .then(() => runner.run({ command, timeoutMs, signal: controller.signal }))
357
+ .then((value) => value, () => undefined);
358
+ const stopped = new Promise((resolve) => {
359
+ const observe = () => {
360
+ if (stop !== undefined)
361
+ resolve(stop);
362
+ };
363
+ controller.signal.addEventListener("abort", observe, { once: true });
364
+ });
365
+ try {
366
+ return await Promise.race([completed, stopped]);
367
+ }
368
+ finally {
369
+ clearTimeout(timeout);
370
+ signal?.removeEventListener("abort", abort);
371
+ }
372
+ }
373
+ /**
374
+ * Creates a Node host adapter without a shell/cwd/environment surface. A host
375
+ * must inject both resolver and runner for real execution; the default is a
376
+ * deterministic `unsupported` result rather than a synthetic passing run.
377
+ */
378
+ export function createNodeTestRunnerAdapterV1(options = {}) {
379
+ const defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS_V1;
380
+ if (!validPositiveInteger(defaultTimeoutMs, MAX_TIMEOUT_MS_V1))
381
+ throw new Error(`defaultTimeoutMs must be a positive integer at most ${MAX_TIMEOUT_MS_V1}`);
382
+ const resolver = options.commandResolver;
383
+ const runner = options.runner;
384
+ return {
385
+ adapterId: TEST_RUNNER_HOST_ADAPTER_ID_V1,
386
+ adapterVersion: TEST_RUNNER_HOST_ADAPTER_VERSION_V1,
387
+ async run(request, signal) {
388
+ const operationId = typeof request.operationId === "string" && request.operationId.trim() !== ""
389
+ ? request.operationId
390
+ : "invalid-test-operation";
391
+ const invalid = validateRequest(request);
392
+ if (invalid !== undefined)
393
+ return invalidInput(operationId, invalid);
394
+ if (signal?.aborted)
395
+ return cancelled(operationId);
396
+ if (resolver === undefined || runner === undefined)
397
+ return unsupported(operationId);
398
+ let command;
399
+ try {
400
+ command = await resolver.resolve(request, signal);
401
+ }
402
+ catch {
403
+ return adapterFailure(operationId);
404
+ }
405
+ if (signal?.aborted)
406
+ return cancelled(operationId);
407
+ if (command === undefined)
408
+ return unsupported(operationId);
409
+ if (!validCommand(command))
410
+ return adapterFailure(operationId);
411
+ const rawResult = await runBounded(runner, command, request.timeoutMs ?? defaultTimeoutMs, signal);
412
+ if (rawResult === CANCELLATION_V1)
413
+ return cancelled(operationId);
414
+ if (rawResult === TIMEOUT_V1)
415
+ return timedOut(operationId);
416
+ if (rawResult === undefined)
417
+ return adapterFailure(operationId);
418
+ return (completeResult(operationId, rawResult, request.maxFailures ?? TEST_RUNNER_MAX_FAILURES_V1) ??
419
+ adapterFailure(operationId));
420
+ },
421
+ };
422
+ }
423
+ /** Deterministic test-only fixture adapter; it never starts a process. */
424
+ export function createDeterministicTestRunnerAdapterV1(fixtures) {
425
+ const fixtureByKey = new Map();
426
+ for (const [index, fixture] of fixtures.entries()) {
427
+ const key = `${fixture.workspaceRef}\u0000${fixture.framework}\u0000${fixture.target}`;
428
+ if (fixtureByKey.has(key))
429
+ throw new Error("Deterministic test runner fixtures must be unique");
430
+ fixtureByKey.set(key, { commandId: `fixture-${index + 1}`, fixture });
431
+ }
432
+ return createNodeTestRunnerAdapterV1({
433
+ commandResolver: {
434
+ async resolve(request) {
435
+ const key = `${request.workspaceRef}\u0000${request.framework}\u0000${request.target}`;
436
+ const fixture = fixtureByKey.get(key);
437
+ return fixture === undefined ? undefined : { commandId: fixture.commandId, args: [] };
438
+ },
439
+ },
440
+ runner: {
441
+ async run(input) {
442
+ const fixture = [...fixtureByKey.values()].find((candidate) => candidate.commandId === input.command.commandId);
443
+ return fixture?.fixture.result ?? { status: "unsupported" };
444
+ },
445
+ },
446
+ });
447
+ }
448
+ //# sourceMappingURL=node-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-adapter.js","sourceRoot":"","sources":["../src/node-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { JsonValue } from \"@agent-forge/plugin-sdk\";\nimport { projectPluginContextV1, validatePluginErrorV1 } from \"@agent-forge/plugin-sdk\";\nimport { sanitizeBinaryOutput } from \"./shell-utils.ts\";\nimport {\n\tTEST_RUNNER_HOST_ADAPTER_ID_V1,\n\tTEST_RUNNER_HOST_ADAPTER_VERSION_V1,\n\ttype TestRunFailureV1,\n\ttype TestRunnerAdapterV1,\n\ttype TestRunnerCommandResolverV1,\n\ttype TestRunnerCommandResultV1,\n\ttype TestRunnerCommandRunnerV1,\n\ttype TestRunnerDeterministicFixtureV1,\n\ttype TestRunRequestV1,\n\ttype TestRunResultV1,\n\ttype TestRunSummaryV1,\n} from \"./types.ts\";\n\nconst DEFAULT_TIMEOUT_MS_V1 = 30_000;\nconst MAX_TIMEOUT_MS_V1 = 120_000;\nconst MAX_MAX_FAILURES_V1 = 1_000;\nconst MAX_COMMAND_ID_CHARS_V1 = 256;\nconst MAX_COMMAND_ARGS_V1 = 128;\nconst MAX_COMMAND_ARG_CHARS_V1 = 2048;\nconst MAX_TEST_COUNT_V1 = 10_000_000;\nconst MAX_HOST_FAILURES_V1 = 4096;\nexport const TEST_RUNNER_MAX_FAILURES_V1 = 64;\nexport const TEST_RUNNER_MAX_FAILURE_MESSAGE_CHARS_V1 = 512;\nconst MAX_HOST_FAILURE_MESSAGE_CHARS_V1 = 8192;\nconst MAX_FILE_IDENTIFIER_CHARS_V1 = 512;\nconst MAX_ERROR_CODE_CHARS_V1 = 128;\nconst MAX_ERROR_MESSAGE_CHARS_V1 = 512;\nconst MAX_OPERATION_ID_CHARS_V1 = 256;\nconst MAX_REPORT_ARTIFACT_REF_CHARS_V1 = 256;\nexport const TEST_RUNNER_MAX_OPAQUE_REF_CHARS_V1 = 256;\n\nconst FRAMEWORKS_V1 = new Set([\"vitest\", \"jest\", \"node-test\", \"pytest\"]);\nconst OPAQUE_REF_V1 = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/u;\nconst FILE_IDENTIFIER_V1 = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/u;\nconst SAFE_ERROR_CODE_V1 = /^[a-z][a-z0-9_]{0,127}$/u;\n\nexport interface NodeTestRunnerAdapterOptionsV1 {\n\t/** Resolves a request to host-owned command identity and argv. */\n\treadonly commandResolver?: TestRunnerCommandResolverV1;\n\t/** Runs a resolved command without accepting cwd, environment, or shell input. */\n\treadonly runner?: TestRunnerCommandRunnerV1;\n\treadonly defaultTimeoutMs?: number;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction hasOnlyKeys(value: Record<string, unknown>, allowed: readonly string[]): boolean {\n\treturn Object.keys(value).every((key) => allowed.includes(key));\n}\n\nfunction isSafeOpaqueRef(value: string): boolean {\n\treturn value.length <= TEST_RUNNER_MAX_OPAQUE_REF_CHARS_V1 && OPAQUE_REF_V1.test(value);\n}\n\nfunction isSafeFileIdentifier(value: string): boolean {\n\treturn FILE_IDENTIFIER_V1.test(value) && !value.split(\"/\").some((segment) => segment === \".\" || segment === \"..\");\n}\n\n/** Removes credentials and absolute host paths before text crosses the model boundary. */\nexport function sanitizeTestRunnerTextV1(value: string, maximum = TEST_RUNNER_MAX_FAILURE_MESSAGE_CHARS_V1): string {\n\tlet text = sanitizeBinaryOutput(value);\n\ttext = text\n\t\t.replace(\n\t\t\t/((?:api[-_ ]?key|access[-_ ]?token|auth(?:orization)?|bearer|password|passwd|secret|token)\\s*[:=]\\s*)([^\\s,;]+)/giu,\n\t\t\t\"$1[redacted-secret]\",\n\t\t)\n\t\t.replace(/(?:[A-Za-z]:[\\\\/]|\\\\\\\\|\\/(?:home|Users|private|tmp|var|workspace)\\/)[^\\s\"'<>]*/gu, \"[redacted-path]\");\n\treturn text.length <= maximum ? text : text.slice(0, maximum);\n}\n\nfunction validPositiveInteger(value: unknown, maximum: number): value is number {\n\treturn Number.isSafeInteger(value) && (value as number) >= 1 && (value as number) <= maximum;\n}\n\nfunction invalidInput(operationId: string, message: string): TestRunResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"failed\",\n\t\tsummary: emptySummary(),\n\t\tfailures: [],\n\t\tfailuresTruncated: false,\n\t\terror: { version: 1, code: \"invalid_input\", message },\n\t};\n}\n\nfunction adapterFailure(operationId: string): TestRunResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"failed\",\n\t\tsummary: emptySummary(),\n\t\tfailures: [],\n\t\tfailuresTruncated: false,\n\t\terror: { version: 1, code: \"adapter_failed\", message: \"Test runner adapter returned an invalid result\" },\n\t};\n}\n\nfunction unsupported(operationId: string): TestRunResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"unsupported\",\n\t\tsummary: emptySummary(),\n\t\tfailures: [],\n\t\tfailuresTruncated: false,\n\t\terror: { version: 1, code: \"test_runner_unavailable\", message: \"No test runner is bound by this host\" },\n\t};\n}\n\nfunction cancelled(operationId: string): TestRunResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"cancelled\",\n\t\tsummary: emptySummary(),\n\t\tfailures: [],\n\t\tfailuresTruncated: false,\n\t\terror: { version: 1, code: \"cancelled\", message: \"Test run was cancelled\" },\n\t};\n}\n\nfunction timedOut(operationId: string): TestRunResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"failed\",\n\t\tsummary: emptySummary(),\n\t\tfailures: [],\n\t\tfailuresTruncated: false,\n\t\terror: { version: 1, code: \"timeout\", message: \"Test run timed out\" },\n\t};\n}\n\nfunction emptySummary(): TestRunSummaryV1 {\n\treturn { total: 0, passed: 0, failed: 0, skipped: 0, flaky: 0 };\n}\n\nfunction validateRequest(request: TestRunRequestV1): string | undefined {\n\tif (request.version !== 1) return \"version must be 1\";\n\tif (\n\t\ttypeof request.operationId !== \"string\" ||\n\t\trequest.operationId.trim() === \"\" ||\n\t\trequest.operationId.length > MAX_OPERATION_ID_CHARS_V1\n\t)\n\t\treturn \"operationId is invalid\";\n\tif (typeof request.workspaceRef !== \"string\" || !isSafeOpaqueRef(request.workspaceRef))\n\t\treturn \"workspaceRef must be a safe opaque host handle\";\n\tif (typeof request.framework !== \"string\" || !FRAMEWORKS_V1.has(request.framework)) return \"framework is invalid\";\n\tif (\n\t\ttypeof request.target !== \"string\" ||\n\t\trequest.target.trim() === \"\" ||\n\t\trequest.target.length > 1024 ||\n\t\t/[\\u0000\\r\\n]/u.test(request.target)\n\t)\n\t\treturn \"target is invalid\";\n\tif (request.timeoutMs !== undefined && !validPositiveInteger(request.timeoutMs, MAX_TIMEOUT_MS_V1))\n\t\treturn `timeoutMs must be a positive integer at most ${MAX_TIMEOUT_MS_V1}`;\n\tif (request.maxFailures !== undefined && !validPositiveInteger(request.maxFailures, MAX_MAX_FAILURES_V1))\n\t\treturn `maxFailures must be a positive integer at most ${MAX_MAX_FAILURES_V1}`;\n\treturn undefined;\n}\n\nfunction validCommand(value: unknown): value is { readonly commandId: string; readonly args: readonly string[] } {\n\tif (!isRecord(value) || !hasOnlyKeys(value, [\"commandId\", \"args\"])) return false;\n\treturn (\n\t\ttypeof value.commandId === \"string\" &&\n\t\tvalue.commandId.length > 0 &&\n\t\tvalue.commandId.length <= MAX_COMMAND_ID_CHARS_V1 &&\n\t\tisSafeOpaqueRef(value.commandId) &&\n\t\tArray.isArray(value.args) &&\n\t\tvalue.args.length <= MAX_COMMAND_ARGS_V1 &&\n\t\tvalue.args.every(\n\t\t\t(arg) => typeof arg === \"string\" && arg.length <= MAX_COMMAND_ARG_CHARS_V1 && !/[\\u0000\\r\\n]/u.test(arg),\n\t\t)\n\t);\n}\n\nfunction validateSummary(value: unknown): TestRunSummaryV1 | undefined {\n\tif (!isRecord(value) || !hasOnlyKeys(value, [\"total\", \"passed\", \"failed\", \"skipped\", \"flaky\"])) return undefined;\n\tconst total = value.total;\n\tconst passed = value.passed;\n\tconst failed = value.failed;\n\tconst skipped = value.skipped;\n\tconst flaky = value.flaky;\n\tconst isCount = (item: unknown): item is number => Number.isSafeInteger(item) && (item as number) >= 0;\n\tif (\n\t\t!isCount(total) ||\n\t\t!isCount(passed) ||\n\t\t!isCount(failed) ||\n\t\t!isCount(skipped) ||\n\t\t!isCount(flaky) ||\n\t\ttotal > MAX_TEST_COUNT_V1 ||\n\t\tpassed > MAX_TEST_COUNT_V1 ||\n\t\tfailed > MAX_TEST_COUNT_V1 ||\n\t\tskipped > MAX_TEST_COUNT_V1 ||\n\t\tflaky > MAX_TEST_COUNT_V1 ||\n\t\tpassed + failed + skipped > total ||\n\t\tflaky > total\n\t)\n\t\treturn undefined;\n\treturn {\n\t\ttotal,\n\t\tpassed,\n\t\tfailed,\n\t\tskipped,\n\t\tflaky,\n\t};\n}\n\nfunction projectFailure(value: unknown): TestRunFailureV1 | undefined {\n\tif (!isRecord(value) || !hasOnlyKeys(value, [\"file\", \"line\", \"column\", \"message\"])) return undefined;\n\tif (\n\t\ttypeof value.file !== \"string\" ||\n\t\tvalue.file.length > MAX_FILE_IDENTIFIER_CHARS_V1 ||\n\t\t!isSafeFileIdentifier(value.file) ||\n\t\ttypeof value.message !== \"string\" ||\n\t\tvalue.message.trim() === \"\" ||\n\t\tvalue.message.length > MAX_HOST_FAILURE_MESSAGE_CHARS_V1 ||\n\t\t(value.line !== undefined && !validPositiveInteger(value.line, MAX_TEST_COUNT_V1)) ||\n\t\t(value.column !== undefined && !validPositiveInteger(value.column, MAX_TEST_COUNT_V1))\n\t)\n\t\treturn undefined;\n\treturn {\n\t\tfile: value.file,\n\t\t...(value.line === undefined ? {} : { line: value.line }),\n\t\t...(value.column === undefined ? {} : { column: value.column }),\n\t\tmessage: sanitizeTestRunnerTextV1(value.message),\n\t};\n}\n\nfunction projectError(value: unknown): TestRunResultV1[\"error\"] | undefined {\n\tif (!isRecord(value) || !hasOnlyKeys(value, [\"version\", \"code\", \"message\", \"causeCode\"])) return undefined;\n\ttry {\n\t\tconst error = validatePluginErrorV1(value, \"Test runner error\");\n\t\tif (\n\t\t\terror.code.length > MAX_ERROR_CODE_CHARS_V1 ||\n\t\t\t!SAFE_ERROR_CODE_V1.test(error.code) ||\n\t\t\terror.message.length > MAX_HOST_FAILURE_MESSAGE_CHARS_V1 ||\n\t\t\t(error.causeCode !== undefined &&\n\t\t\t\t(error.causeCode.length > MAX_ERROR_CODE_CHARS_V1 || !SAFE_ERROR_CODE_V1.test(error.causeCode)))\n\t\t)\n\t\t\treturn undefined;\n\t\treturn {\n\t\t\tversion: 1,\n\t\t\tcode: error.code,\n\t\t\tmessage: sanitizeTestRunnerTextV1(error.message, MAX_ERROR_MESSAGE_CHARS_V1),\n\t\t\t...(error.causeCode === undefined ? {} : { causeCode: error.causeCode }),\n\t\t};\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** Strictly projects arbitrary adapter output into the bounded public test DTO. */\nexport function projectTestRunResultV1(value: unknown, operationId: string): TestRunResultV1 | undefined {\n\tif (\n\t\t!isRecord(value) ||\n\t\t!hasOnlyKeys(value, [\n\t\t\t\"version\",\n\t\t\t\"operationId\",\n\t\t\t\"status\",\n\t\t\t\"summary\",\n\t\t\t\"failures\",\n\t\t\t\"failuresTruncated\",\n\t\t\t\"exitCode\",\n\t\t\t\"reportArtifactRef\",\n\t\t\t\"error\",\n\t\t]) ||\n\t\tvalue.version !== 1 ||\n\t\tvalue.operationId !== operationId ||\n\t\ttypeof value.status !== \"string\" ||\n\t\t![\"succeeded\", \"failed\", \"cancelled\", \"needs_user\", \"unsupported\"].includes(value.status) ||\n\t\t!Array.isArray(value.failures) ||\n\t\tvalue.failures.length > TEST_RUNNER_MAX_FAILURES_V1 ||\n\t\ttypeof value.failuresTruncated !== \"boolean\"\n\t)\n\t\treturn undefined;\n\tconst summary = validateSummary(value.summary);\n\tif (summary === undefined) return undefined;\n\tconst failures = value.failures.map(projectFailure);\n\tif (failures.some((failure) => failure === undefined)) return undefined;\n\tconst exitCode = value.exitCode;\n\tif (\n\t\texitCode !== undefined &&\n\t\t(typeof exitCode !== \"number\" || !Number.isSafeInteger(exitCode) || exitCode < 0 || exitCode > 255)\n\t)\n\t\treturn undefined;\n\tif (\n\t\tvalue.reportArtifactRef !== undefined &&\n\t\t(typeof value.reportArtifactRef !== \"string\" ||\n\t\t\tvalue.reportArtifactRef.length > MAX_REPORT_ARTIFACT_REF_CHARS_V1 ||\n\t\t\t!isSafeOpaqueRef(value.reportArtifactRef))\n\t)\n\t\treturn undefined;\n\tconst error = value.error === undefined ? undefined : projectError(value.error);\n\tif ((value.status === \"succeeded\" && error !== undefined) || (value.status !== \"succeeded\" && error === undefined))\n\t\treturn undefined;\n\tif (value.status === \"succeeded\" && (summary.failed !== 0 || exitCode !== 0)) return undefined;\n\tconst projected: TestRunResultV1 = {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: value.status as TestRunResultV1[\"status\"],\n\t\tsummary,\n\t\tfailures: failures as TestRunFailureV1[],\n\t\tfailuresTruncated: value.failuresTruncated,\n\t\t...(exitCode === undefined ? {} : { exitCode }),\n\t\t...(value.reportArtifactRef === undefined ? {} : { reportArtifactRef: value.reportArtifactRef }),\n\t\t...(error === undefined ? {} : { error }),\n\t};\n\treturn contextBoundResult(projected) ?? adapterFailure(operationId);\n}\n\nfunction completeResult(\n\toperationId: string,\n\tvalue: TestRunnerCommandResultV1,\n\tmaxFailures: number,\n): TestRunResultV1 | undefined {\n\tif (!isRecord(value) || !hasOnlyKeys(value, [\"status\", \"exitCode\", \"summary\", \"failures\", \"reportArtifactRef\"]))\n\t\treturn undefined;\n\tif (value.status === \"unsupported\") return unsupported(operationId);\n\tif (value.status !== \"completed\") return undefined;\n\tif (!Number.isSafeInteger(value.exitCode) || value.exitCode < 0 || value.exitCode > 255) return undefined;\n\tconst summary = validateSummary(value.summary);\n\tif (summary === undefined) return undefined;\n\tif (value.failures !== undefined && (!Array.isArray(value.failures) || value.failures.length > MAX_HOST_FAILURES_V1))\n\t\treturn undefined;\n\tconst rawFailures = value.failures ?? [];\n\tif (rawFailures.length > summary.failed) return undefined;\n\tconst failureLimit = Math.min(maxFailures, TEST_RUNNER_MAX_FAILURES_V1);\n\tconst failures = rawFailures.slice(0, failureLimit).map(projectFailure);\n\tif (failures.some((failure) => failure === undefined)) return undefined;\n\tif (\n\t\tvalue.reportArtifactRef !== undefined &&\n\t\t(typeof value.reportArtifactRef !== \"string\" ||\n\t\t\tvalue.reportArtifactRef.length > MAX_REPORT_ARTIFACT_REF_CHARS_V1 ||\n\t\t\t!isSafeOpaqueRef(value.reportArtifactRef))\n\t)\n\t\treturn undefined;\n\tconst succeeded = value.exitCode === 0 && summary.failed === 0;\n\tconst projected: TestRunResultV1 = {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: succeeded ? \"succeeded\" : \"failed\",\n\t\tsummary,\n\t\tfailures: failures as TestRunFailureV1[],\n\t\tfailuresTruncated: rawFailures.length > failureLimit || summary.failed > failures.length,\n\t\texitCode: value.exitCode,\n\t\t...(value.reportArtifactRef === undefined ? {} : { reportArtifactRef: value.reportArtifactRef }),\n\t\t...(succeeded\n\t\t\t? {}\n\t\t\t: {\n\t\t\t\t\terror: {\n\t\t\t\t\t\tversion: 1 as const,\n\t\t\t\t\t\tcode: summary.failed > 0 ? \"test_failures\" : \"exit_nonzero\",\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\tsummary.failed > 0 ? \"One or more tests failed\" : \"Test runner exited with a non-zero status\",\n\t\t\t\t\t},\n\t\t\t\t}),\n\t};\n\treturn contextBoundResult(projected) ?? adapterFailure(operationId);\n}\n\nfunction contextBoundResult(result: TestRunResultV1): TestRunResultV1 | undefined {\n\tconst outputArtifactRef = result.reportArtifactRef;\n\tfor (let failureCount = result.failures.length; failureCount >= 0; failureCount--) {\n\t\tconst failures = result.failures.slice(0, failureCount);\n\t\tconst projection = projectPluginContextV1(\n\t\t\t{\n\t\t\t\tversion: 1,\n\t\t\t\toperationId: result.operationId,\n\t\t\t\titems: [\n\t\t\t\t\t{ ...result.summary } as unknown as JsonValue,\n\t\t\t\t\t...failures.map((failure) => ({ ...failure }) as unknown as JsonValue),\n\t\t\t\t],\n\t\t\t\t...(outputArtifactRef === undefined ? {} : { outputArtifactRef }),\n\t\t\t\ttruncated: result.failuresTruncated || failureCount < result.failures.length,\n\t\t\t},\n\t\t\t{ maxOutputBytes: 64 * 1024, maxInlineItems: 65, maxCellChars: 512 },\n\t\t);\n\t\tif (projection.status === \"succeeded\") {\n\t\t\tconst candidate: TestRunResultV1 = {\n\t\t\t\t...result,\n\t\t\t\tfailures,\n\t\t\t\tfailuresTruncated: result.failuresTruncated || failureCount < result.failures.length,\n\t\t\t};\n\t\t\tif (new TextEncoder().encode(JSON.stringify(candidate)).byteLength <= 64 * 1024) return candidate;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nconst CANCELLATION_V1 = Symbol(\"test-runner-cancelled\");\nconst TIMEOUT_V1 = Symbol(\"test-runner-timeout\");\n\nasync function runBounded(\n\trunner: TestRunnerCommandRunnerV1,\n\tcommand: { readonly commandId: string; readonly args: readonly string[] },\n\ttimeoutMs: number,\n\tsignal: AbortSignal | undefined,\n): Promise<TestRunnerCommandResultV1 | typeof CANCELLATION_V1 | typeof TIMEOUT_V1 | undefined> {\n\tconst controller = new AbortController();\n\tlet stop: typeof CANCELLATION_V1 | typeof TIMEOUT_V1 | undefined;\n\tconst abort = () => {\n\t\tstop ??= CANCELLATION_V1;\n\t\tcontroller.abort();\n\t};\n\tif (signal?.aborted) return CANCELLATION_V1;\n\tsignal?.addEventListener(\"abort\", abort, { once: true });\n\tconst timeout = setTimeout(() => {\n\t\tstop ??= TIMEOUT_V1;\n\t\tcontroller.abort();\n\t}, timeoutMs);\n\tconst completed = Promise.resolve()\n\t\t.then(() => runner.run({ command, timeoutMs, signal: controller.signal }))\n\t\t.then(\n\t\t\t(value) => value,\n\t\t\t() => undefined,\n\t\t);\n\tconst stopped = new Promise<typeof CANCELLATION_V1 | typeof TIMEOUT_V1>((resolve) => {\n\t\tconst observe = () => {\n\t\t\tif (stop !== undefined) resolve(stop);\n\t\t};\n\t\tcontroller.signal.addEventListener(\"abort\", observe, { once: true });\n\t});\n\ttry {\n\t\treturn await Promise.race([completed, stopped]);\n\t} finally {\n\t\tclearTimeout(timeout);\n\t\tsignal?.removeEventListener(\"abort\", abort);\n\t}\n}\n\n/**\n * Creates a Node host adapter without a shell/cwd/environment surface. A host\n * must inject both resolver and runner for real execution; the default is a\n * deterministic `unsupported` result rather than a synthetic passing run.\n */\nexport function createNodeTestRunnerAdapterV1(options: NodeTestRunnerAdapterOptionsV1 = {}): TestRunnerAdapterV1 {\n\tconst defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS_V1;\n\tif (!validPositiveInteger(defaultTimeoutMs, MAX_TIMEOUT_MS_V1))\n\t\tthrow new Error(`defaultTimeoutMs must be a positive integer at most ${MAX_TIMEOUT_MS_V1}`);\n\tconst resolver = options.commandResolver;\n\tconst runner = options.runner;\n\treturn {\n\t\tadapterId: TEST_RUNNER_HOST_ADAPTER_ID_V1,\n\t\tadapterVersion: TEST_RUNNER_HOST_ADAPTER_VERSION_V1,\n\t\tasync run(request, signal) {\n\t\t\tconst operationId =\n\t\t\t\ttypeof request.operationId === \"string\" && request.operationId.trim() !== \"\"\n\t\t\t\t\t? request.operationId\n\t\t\t\t\t: \"invalid-test-operation\";\n\t\t\tconst invalid = validateRequest(request);\n\t\t\tif (invalid !== undefined) return invalidInput(operationId, invalid);\n\t\t\tif (signal?.aborted) return cancelled(operationId);\n\t\t\tif (resolver === undefined || runner === undefined) return unsupported(operationId);\n\t\t\tlet command: Awaited<ReturnType<TestRunnerCommandResolverV1[\"resolve\"]>>;\n\t\t\ttry {\n\t\t\t\tcommand = await resolver.resolve(request, signal);\n\t\t\t} catch {\n\t\t\t\treturn adapterFailure(operationId);\n\t\t\t}\n\t\t\tif (signal?.aborted) return cancelled(operationId);\n\t\t\tif (command === undefined) return unsupported(operationId);\n\t\t\tif (!validCommand(command)) return adapterFailure(operationId);\n\t\t\tconst rawResult = await runBounded(runner, command, request.timeoutMs ?? defaultTimeoutMs, signal);\n\t\t\tif (rawResult === CANCELLATION_V1) return cancelled(operationId);\n\t\t\tif (rawResult === TIMEOUT_V1) return timedOut(operationId);\n\t\t\tif (rawResult === undefined) return adapterFailure(operationId);\n\t\t\treturn (\n\t\t\t\tcompleteResult(operationId, rawResult, request.maxFailures ?? TEST_RUNNER_MAX_FAILURES_V1) ??\n\t\t\t\tadapterFailure(operationId)\n\t\t\t);\n\t\t},\n\t};\n}\n\n/** Deterministic test-only fixture adapter; it never starts a process. */\nexport function createDeterministicTestRunnerAdapterV1(\n\tfixtures: readonly TestRunnerDeterministicFixtureV1[],\n): TestRunnerAdapterV1 {\n\tconst fixtureByKey = new Map<\n\t\tstring,\n\t\t{ readonly commandId: string; readonly fixture: TestRunnerDeterministicFixtureV1 }\n\t>();\n\tfor (const [index, fixture] of fixtures.entries()) {\n\t\tconst key = `${fixture.workspaceRef}\\u0000${fixture.framework}\\u0000${fixture.target}`;\n\t\tif (fixtureByKey.has(key)) throw new Error(\"Deterministic test runner fixtures must be unique\");\n\t\tfixtureByKey.set(key, { commandId: `fixture-${index + 1}`, fixture });\n\t}\n\treturn createNodeTestRunnerAdapterV1({\n\t\tcommandResolver: {\n\t\t\tasync resolve(request) {\n\t\t\t\tconst key = `${request.workspaceRef}\\u0000${request.framework}\\u0000${request.target}`;\n\t\t\t\tconst fixture = fixtureByKey.get(key);\n\t\t\t\treturn fixture === undefined ? undefined : { commandId: fixture.commandId, args: [] };\n\t\t\t},\n\t\t},\n\t\trunner: {\n\t\t\tasync run(input) {\n\t\t\t\tconst fixture = [...fixtureByKey.values()].find(\n\t\t\t\t\t(candidate) => candidate.commandId === input.command.commandId,\n\t\t\t\t);\n\t\t\t\treturn fixture?.fixture.result ?? { status: \"unsupported\" };\n\t\t\t},\n\t\t},\n\t});\n}\n"]}
@@ -0,0 +1,12 @@
1
+ import type { TestRunFailureV1, TestRunSummaryV1 } from "./types.ts";
2
+ /**
3
+ * Parses the Jest JSON report shape (the one Vitest's `--reporter=json` and
4
+ * Jest's `--json` both emit) into a bounded projection. Failure file paths are
5
+ * converted to forward-slash identifiers and messages are sanitized; absolute
6
+ * host paths never cross.
7
+ */
8
+ export declare function parseJestJsonReportV1(raw: string): {
9
+ readonly summary: TestRunSummaryV1;
10
+ readonly failures: readonly TestRunFailureV1[];
11
+ } | undefined;
12
+ //# sourceMappingURL=node-jest-json-report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-jest-json-report.d.ts","sourceRoot":"","sources":["../src/node-jest-json-report.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AA2BrE;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAC9C;IACA,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC9C,GACD,SAAS,CAsCX","sourcesContent":["import { sanitizeTestRunnerTextV1 } from \"./node-adapter.ts\";\nimport type { TestRunFailureV1, TestRunSummaryV1 } from \"./types.ts\";\n\nconst MAX_TEST_FAILURES_V1 = 256;\n\ninterface JestJsonAssertionV1 {\n\tfullName?: string;\n\tstatus?: string;\n\tlocation?: { line?: number; column?: number };\n\tfailureMessages?: string[];\n}\n\ninterface JestJsonReportV1 {\n\tnumTotalTests?: number;\n\tnumPassedTests?: number;\n\tnumFailedTests?: number;\n\tnumPendingTests?: number;\n\tnumTodoTests?: number;\n\ttestResults?: {\n\t\tname?: string;\n\t\tassertionResults?: JestJsonAssertionV1[];\n\t}[];\n}\n\nfunction boundedCount(value: unknown): number {\n\treturn typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0 ? value : 0;\n}\n\n/**\n * Parses the Jest JSON report shape (the one Vitest's `--reporter=json` and\n * Jest's `--json` both emit) into a bounded projection. Failure file paths are\n * converted to forward-slash identifiers and messages are sanitized; absolute\n * host paths never cross.\n */\nexport function parseJestJsonReportV1(raw: string):\n\t| {\n\t\t\treadonly summary: TestRunSummaryV1;\n\t\t\treadonly failures: readonly TestRunFailureV1[];\n\t }\n\t| undefined {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw.trim());\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) return undefined;\n\tconst report = parsed as JestJsonReportV1;\n\tconst summary: TestRunSummaryV1 = {\n\t\ttotal: boundedCount(report.numTotalTests),\n\t\tpassed: boundedCount(report.numPassedTests),\n\t\tfailed: boundedCount(report.numFailedTests),\n\t\tskipped: boundedCount(report.numPendingTests) + boundedCount(report.numTodoTests),\n\t\tflaky: 0,\n\t};\n\tconst failures: TestRunFailureV1[] = [];\n\tfor (const suite of report.testResults ?? []) {\n\t\tif (!Array.isArray(suite.assertionResults)) continue;\n\t\tconst file =\n\t\t\ttypeof suite.name === \"string\" && suite.name.trim() !== \"\"\n\t\t\t\t? suite.name.replaceAll(\"\\\\\", \"/\")\n\t\t\t\t: \"unknown-test-file\";\n\t\tfor (const assertion of suite.assertionResults) {\n\t\t\tif (assertion.status !== \"failed\" || failures.length >= MAX_TEST_FAILURES_V1) continue;\n\t\t\tfailures.push({\n\t\t\t\tfile,\n\t\t\t\t...(Number.isSafeInteger(assertion.location?.line) ? { line: assertion.location!.line } : {}),\n\t\t\t\t...(Number.isSafeInteger(assertion.location?.column) ? { column: assertion.location!.column } : {}),\n\t\t\t\tmessage: sanitizeTestRunnerTextV1(\n\t\t\t\t\ttypeof assertion.failureMessages?.[0] === \"string\"\n\t\t\t\t\t\t? assertion.failureMessages[0]!\n\t\t\t\t\t\t: (assertion.fullName ?? \"Test failed\"),\n\t\t\t\t),\n\t\t\t});\n\t\t}\n\t}\n\treturn { summary, failures };\n}\n"]}
@@ -0,0 +1,52 @@
1
+ import { sanitizeTestRunnerTextV1 } from "./node-adapter.js";
2
+ const MAX_TEST_FAILURES_V1 = 256;
3
+ function boundedCount(value) {
4
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : 0;
5
+ }
6
+ /**
7
+ * Parses the Jest JSON report shape (the one Vitest's `--reporter=json` and
8
+ * Jest's `--json` both emit) into a bounded projection. Failure file paths are
9
+ * converted to forward-slash identifiers and messages are sanitized; absolute
10
+ * host paths never cross.
11
+ */
12
+ export function parseJestJsonReportV1(raw) {
13
+ let parsed;
14
+ try {
15
+ parsed = JSON.parse(raw.trim());
16
+ }
17
+ catch {
18
+ return undefined;
19
+ }
20
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
21
+ return undefined;
22
+ const report = parsed;
23
+ const summary = {
24
+ total: boundedCount(report.numTotalTests),
25
+ passed: boundedCount(report.numPassedTests),
26
+ failed: boundedCount(report.numFailedTests),
27
+ skipped: boundedCount(report.numPendingTests) + boundedCount(report.numTodoTests),
28
+ flaky: 0,
29
+ };
30
+ const failures = [];
31
+ for (const suite of report.testResults ?? []) {
32
+ if (!Array.isArray(suite.assertionResults))
33
+ continue;
34
+ const file = typeof suite.name === "string" && suite.name.trim() !== ""
35
+ ? suite.name.replaceAll("\\", "/")
36
+ : "unknown-test-file";
37
+ for (const assertion of suite.assertionResults) {
38
+ if (assertion.status !== "failed" || failures.length >= MAX_TEST_FAILURES_V1)
39
+ continue;
40
+ failures.push({
41
+ file,
42
+ ...(Number.isSafeInteger(assertion.location?.line) ? { line: assertion.location.line } : {}),
43
+ ...(Number.isSafeInteger(assertion.location?.column) ? { column: assertion.location.column } : {}),
44
+ message: sanitizeTestRunnerTextV1(typeof assertion.failureMessages?.[0] === "string"
45
+ ? assertion.failureMessages[0]
46
+ : (assertion.fullName ?? "Test failed")),
47
+ });
48
+ }
49
+ }
50
+ return { summary, failures };
51
+ }
52
+ //# sourceMappingURL=node-jest-json-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-jest-json-report.js","sourceRoot":"","sources":["../src/node-jest-json-report.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAG7D,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAqBjC,SAAS,YAAY,CAAC,KAAc,EAAU;IAC7C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAAA,CAC1F;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAW,EAKpC;IACZ,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACJ,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,SAAS,CAAC;IAC7F,MAAM,MAAM,GAAG,MAA0B,CAAC;IAC1C,MAAM,OAAO,GAAqB;QACjC,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC,aAAa,CAAC;QACzC,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC,cAAc,CAAC;QAC3C,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC,cAAc,CAAC;QAC3C,OAAO,EAAE,YAAY,CAAC,MAAM,CAAC,eAAe,CAAC,GAAG,YAAY,CAAC,MAAM,CAAC,YAAY,CAAC;QACjF,KAAK,EAAE,CAAC;KACR,CAAC;IACF,MAAM,QAAQ,GAAuB,EAAE,CAAC;IACxC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC;QAC9C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC;YAAE,SAAS;QACrD,MAAM,IAAI,GACT,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;YACzD,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC;YAClC,CAAC,CAAC,mBAAmB,CAAC;QACxB,KAAK,MAAM,SAAS,IAAI,KAAK,CAAC,gBAAgB,EAAE,CAAC;YAChD,IAAI,SAAS,CAAC,MAAM,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,IAAI,oBAAoB;gBAAE,SAAS;YACvF,QAAQ,CAAC,IAAI,CAAC;gBACb,IAAI;gBACJ,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,QAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC7F,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,QAAS,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACnG,OAAO,EAAE,wBAAwB,CAChC,OAAO,SAAS,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,KAAK,QAAQ;oBACjD,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC,CAAE;oBAC/B,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,IAAI,aAAa,CAAC,CACxC;aACD,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAAA,CAC7B","sourcesContent":["import { sanitizeTestRunnerTextV1 } from \"./node-adapter.ts\";\nimport type { TestRunFailureV1, TestRunSummaryV1 } from \"./types.ts\";\n\nconst MAX_TEST_FAILURES_V1 = 256;\n\ninterface JestJsonAssertionV1 {\n\tfullName?: string;\n\tstatus?: string;\n\tlocation?: { line?: number; column?: number };\n\tfailureMessages?: string[];\n}\n\ninterface JestJsonReportV1 {\n\tnumTotalTests?: number;\n\tnumPassedTests?: number;\n\tnumFailedTests?: number;\n\tnumPendingTests?: number;\n\tnumTodoTests?: number;\n\ttestResults?: {\n\t\tname?: string;\n\t\tassertionResults?: JestJsonAssertionV1[];\n\t}[];\n}\n\nfunction boundedCount(value: unknown): number {\n\treturn typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0 ? value : 0;\n}\n\n/**\n * Parses the Jest JSON report shape (the one Vitest's `--reporter=json` and\n * Jest's `--json` both emit) into a bounded projection. Failure file paths are\n * converted to forward-slash identifiers and messages are sanitized; absolute\n * host paths never cross.\n */\nexport function parseJestJsonReportV1(raw: string):\n\t| {\n\t\t\treadonly summary: TestRunSummaryV1;\n\t\t\treadonly failures: readonly TestRunFailureV1[];\n\t }\n\t| undefined {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw.trim());\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) return undefined;\n\tconst report = parsed as JestJsonReportV1;\n\tconst summary: TestRunSummaryV1 = {\n\t\ttotal: boundedCount(report.numTotalTests),\n\t\tpassed: boundedCount(report.numPassedTests),\n\t\tfailed: boundedCount(report.numFailedTests),\n\t\tskipped: boundedCount(report.numPendingTests) + boundedCount(report.numTodoTests),\n\t\tflaky: 0,\n\t};\n\tconst failures: TestRunFailureV1[] = [];\n\tfor (const suite of report.testResults ?? []) {\n\t\tif (!Array.isArray(suite.assertionResults)) continue;\n\t\tconst file =\n\t\t\ttypeof suite.name === \"string\" && suite.name.trim() !== \"\"\n\t\t\t\t? suite.name.replaceAll(\"\\\\\", \"/\")\n\t\t\t\t: \"unknown-test-file\";\n\t\tfor (const assertion of suite.assertionResults) {\n\t\t\tif (assertion.status !== \"failed\" || failures.length >= MAX_TEST_FAILURES_V1) continue;\n\t\t\tfailures.push({\n\t\t\t\tfile,\n\t\t\t\t...(Number.isSafeInteger(assertion.location?.line) ? { line: assertion.location!.line } : {}),\n\t\t\t\t...(Number.isSafeInteger(assertion.location?.column) ? { column: assertion.location!.column } : {}),\n\t\t\t\tmessage: sanitizeTestRunnerTextV1(\n\t\t\t\t\ttypeof assertion.failureMessages?.[0] === \"string\"\n\t\t\t\t\t\t? assertion.failureMessages[0]!\n\t\t\t\t\t\t: (assertion.fullName ?? \"Test failed\"),\n\t\t\t\t),\n\t\t\t});\n\t\t}\n\t}\n\treturn { summary, failures };\n}\n"]}
@@ -0,0 +1,26 @@
1
+ import type { TestRunnerCommandResolverV1, TestRunnerCommandRunnerV1 } from "./types.ts";
2
+ export interface JestTestRunnerOptionsV1 {
3
+ /** Host-owned workspace directory the runner executes in. Never returned in a DTO. */
4
+ readonly workspaceDirectory: string;
5
+ /** Injectable runner override for deterministic tests; the real spawn is used when absent. */
6
+ readonly runner?: TestRunnerCommandRunnerV1;
7
+ readonly defaultTimeoutMs?: number;
8
+ }
9
+ /** Resolves the workspace-local Jest entry; undefined keeps the default wiring fail-closed. */
10
+ export declare function resolveJestEntryV1(workspaceDirectory: string): string | undefined;
11
+ /**
12
+ * Host-owned resolver: only the jest framework is supported in this slice;
13
+ * other frameworks resolve to undefined so the adapter reports unsupported.
14
+ * The target becomes a Jest test-path pattern argument after a bounded
15
+ * identifier check.
16
+ */
17
+ export declare function createJestTestRunnerCommandResolverV1(options: {
18
+ readonly workspaceDirectory: string;
19
+ }): TestRunnerCommandResolverV1;
20
+ /**
21
+ * Real Jest-backed test-runner adapter over one workspace directory. Jest
22
+ * availability is probed per resolution; a workspace without a local Jest
23
+ * install resolves to `unsupported` instead of a synthetic result.
24
+ */
25
+ export declare function createJestRealTestRunnerAdapterV1(options: JestTestRunnerOptionsV1): import("./types.ts").TestRunnerAdapterV1;
26
+ //# sourceMappingURL=node-jest-runner.d.ts.map