assertloom 0.0.0-stage → 0.2.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 (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +393 -2
  3. package/dist/ai/anthropic.d.ts +11 -0
  4. package/dist/ai/anthropic.js +68 -0
  5. package/dist/ai/anthropic.js.map +1 -0
  6. package/dist/ai/index.d.ts +12 -0
  7. package/dist/ai/index.js +23 -0
  8. package/dist/ai/index.js.map +1 -0
  9. package/dist/ai/openai.d.ts +9 -0
  10. package/dist/ai/openai.js +57 -0
  11. package/dist/ai/openai.js.map +1 -0
  12. package/dist/ai/openaiCompatible.d.ts +13 -0
  13. package/dist/ai/openaiCompatible.js +62 -0
  14. package/dist/ai/openaiCompatible.js.map +1 -0
  15. package/dist/ai/pricing.d.ts +38 -0
  16. package/dist/ai/pricing.js +105 -0
  17. package/dist/ai/pricing.js.map +1 -0
  18. package/dist/ai/prompts.d.ts +27 -0
  19. package/dist/ai/prompts.js +89 -0
  20. package/dist/ai/prompts.js.map +1 -0
  21. package/dist/ai/provider.d.ts +26 -0
  22. package/dist/ai/provider.js +4 -0
  23. package/dist/ai/provider.js.map +1 -0
  24. package/dist/ai/redact.d.ts +5 -0
  25. package/dist/ai/redact.js +31 -0
  26. package/dist/ai/redact.js.map +1 -0
  27. package/dist/analyzer/dependencies.d.ts +16 -0
  28. package/dist/analyzer/dependencies.js +168 -0
  29. package/dist/analyzer/dependencies.js.map +1 -0
  30. package/dist/analyzer/format.d.ts +3 -0
  31. package/dist/analyzer/format.js +14 -0
  32. package/dist/analyzer/format.js.map +1 -0
  33. package/dist/analyzer/functions.d.ts +4 -0
  34. package/dist/analyzer/functions.js +324 -0
  35. package/dist/analyzer/functions.js.map +1 -0
  36. package/dist/analyzer/index.d.ts +5 -0
  37. package/dist/analyzer/index.js +29 -0
  38. package/dist/analyzer/index.js.map +1 -0
  39. package/dist/analyzer/parser.d.ts +11 -0
  40. package/dist/analyzer/parser.js +38 -0
  41. package/dist/analyzer/parser.js.map +1 -0
  42. package/dist/analyzer/usage.d.ts +16 -0
  43. package/dist/analyzer/usage.js +159 -0
  44. package/dist/analyzer/usage.js.map +1 -0
  45. package/dist/ci/report.d.ts +88 -0
  46. package/dist/ci/report.js +139 -0
  47. package/dist/ci/report.js.map +1 -0
  48. package/dist/ci/run.d.ts +70 -0
  49. package/dist/ci/run.js +198 -0
  50. package/dist/ci/run.js.map +1 -0
  51. package/dist/ci/select.d.ts +58 -0
  52. package/dist/ci/select.js +238 -0
  53. package/dist/ci/select.js.map +1 -0
  54. package/dist/cli/commands/analyze.d.ts +2 -0
  55. package/dist/cli/commands/analyze.js +49 -0
  56. package/dist/cli/commands/analyze.js.map +1 -0
  57. package/dist/cli/commands/ci.d.ts +37 -0
  58. package/dist/cli/commands/ci.js +258 -0
  59. package/dist/cli/commands/ci.js.map +1 -0
  60. package/dist/cli/commands/generate.d.ts +3 -0
  61. package/dist/cli/commands/generate.js +201 -0
  62. package/dist/cli/commands/generate.js.map +1 -0
  63. package/dist/cli/commands/run.d.ts +4 -0
  64. package/dist/cli/commands/run.js +53 -0
  65. package/dist/cli/commands/run.js.map +1 -0
  66. package/dist/cli/index.d.ts +2 -0
  67. package/dist/cli/index.js +16 -0
  68. package/dist/cli/index.js.map +1 -0
  69. package/dist/cli/program.d.ts +2 -0
  70. package/dist/cli/program.js +20 -0
  71. package/dist/cli/program.js.map +1 -0
  72. package/dist/cli/report.d.ts +3 -0
  73. package/dist/cli/report.js +36 -0
  74. package/dist/cli/report.js.map +1 -0
  75. package/dist/cli/validateFile.d.ts +5 -0
  76. package/dist/cli/validateFile.js +43 -0
  77. package/dist/cli/validateFile.js.map +1 -0
  78. package/dist/errors.d.ts +4 -0
  79. package/dist/errors.js +8 -0
  80. package/dist/errors.js.map +1 -0
  81. package/dist/generator/diff.d.ts +5 -0
  82. package/dist/generator/diff.js +56 -0
  83. package/dist/generator/diff.js.map +1 -0
  84. package/dist/generator/importPlan.d.ts +15 -0
  85. package/dist/generator/importPlan.js +62 -0
  86. package/dist/generator/importPlan.js.map +1 -0
  87. package/dist/generator/index.d.ts +5 -0
  88. package/dist/generator/index.js +6 -0
  89. package/dist/generator/index.js.map +1 -0
  90. package/dist/generator/mocks.d.ts +48 -0
  91. package/dist/generator/mocks.js +356 -0
  92. package/dist/generator/mocks.js.map +1 -0
  93. package/dist/generator/schema.d.ts +40 -0
  94. package/dist/generator/schema.js +15 -0
  95. package/dist/generator/schema.js.map +1 -0
  96. package/dist/generator/testGenerator.d.ts +36 -0
  97. package/dist/generator/testGenerator.js +85 -0
  98. package/dist/generator/testGenerator.js.map +1 -0
  99. package/dist/generator/testWriter.d.ts +22 -0
  100. package/dist/generator/testWriter.js +93 -0
  101. package/dist/generator/testWriter.js.map +1 -0
  102. package/dist/generator/validate.d.ts +31 -0
  103. package/dist/generator/validate.js +180 -0
  104. package/dist/generator/validate.js.map +1 -0
  105. package/dist/repair/classifyFailure.d.ts +11 -0
  106. package/dist/repair/classifyFailure.js +26 -0
  107. package/dist/repair/classifyFailure.js.map +1 -0
  108. package/dist/repair/index.d.ts +2 -0
  109. package/dist/repair/index.js +3 -0
  110. package/dist/repair/index.js.map +1 -0
  111. package/dist/repair/repairLoop.d.ts +74 -0
  112. package/dist/repair/repairLoop.js +219 -0
  113. package/dist/repair/repairLoop.js.map +1 -0
  114. package/dist/repair/schema.d.ts +17 -0
  115. package/dist/repair/schema.js +14 -0
  116. package/dist/repair/schema.js.map +1 -0
  117. package/dist/runner/coverage.d.ts +5 -0
  118. package/dist/runner/coverage.js +42 -0
  119. package/dist/runner/coverage.js.map +1 -0
  120. package/dist/runner/index.d.ts +5 -0
  121. package/dist/runner/index.js +5 -0
  122. package/dist/runner/index.js.map +1 -0
  123. package/dist/runner/jestConfig.d.ts +11 -0
  124. package/dist/runner/jestConfig.js +103 -0
  125. package/dist/runner/jestConfig.js.map +1 -0
  126. package/dist/runner/jestRunner.d.ts +17 -0
  127. package/dist/runner/jestRunner.js +154 -0
  128. package/dist/runner/jestRunner.js.map +1 -0
  129. package/dist/runner/networkGuard.d.ts +4 -0
  130. package/dist/runner/networkGuard.js +55 -0
  131. package/dist/runner/networkGuard.js.map +1 -0
  132. package/dist/runner/project.d.ts +30 -0
  133. package/dist/runner/project.js +86 -0
  134. package/dist/runner/project.js.map +1 -0
  135. package/dist/runner/results.d.ts +32 -0
  136. package/dist/runner/results.js +58 -0
  137. package/dist/runner/results.js.map +1 -0
  138. package/dist/runner/types.d.ts +39 -0
  139. package/dist/runner/types.js +2 -0
  140. package/dist/runner/types.js.map +1 -0
  141. package/dist/runner/vitest.d.ts +24 -0
  142. package/dist/runner/vitest.js +105 -0
  143. package/dist/runner/vitest.js.map +1 -0
  144. package/dist/types/index.d.ts +96 -0
  145. package/dist/types/index.js +2 -0
  146. package/dist/types/index.js.map +1 -0
  147. package/package.json +67 -4
@@ -0,0 +1,219 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { promptTokens } from "../ai/pricing.js";
5
+ import { buildRepairPrompt, REPAIR_SYSTEM_PROMPT } from "../ai/prompts.js";
6
+ import { checkBody, renderTestFile } from "../generator/index.js";
7
+ import { runJest } from "../runner/index.js";
8
+ import { classifyTestFailure } from "./classifyFailure.js";
9
+ import { RepairSchema } from "./schema.js";
10
+ export const MAX_REPAIR_ATTEMPTS = 3;
11
+ const MAX_TARGETS_PER_ATTEMPT = 15;
12
+ const MAX_OUTPUT_TOKENS = 16_000;
13
+ export function summarize(run) {
14
+ return { status: run.status, passed: run.passed, failed: run.failed, skipped: run.skipped, coverage: run.coverage };
15
+ }
16
+ const key = (functionName, name) => `${functionName}\u0000${name}`;
17
+ function cloneSuites(suites) {
18
+ return suites.map((s) => ({ binding: s.binding, cases: s.cases.map((c) => ({ ...c })) }));
19
+ }
20
+ function outcomesByKey(run) {
21
+ return new Map(run.tests.map((t) => [key(t.suite, t.name), t]));
22
+ }
23
+ function findFailing(suites, run) {
24
+ const outcomes = outcomesByKey(run);
25
+ const failing = [];
26
+ suites.forEach((suite, suiteIndex) => suite.cases.forEach((testCase, caseIndex) => {
27
+ const outcome = outcomes.get(key(suite.binding.binding, testCase.name));
28
+ if (testCase.skipReason || outcome?.status !== "failed")
29
+ return;
30
+ failing.push({ suiteIndex, caseIndex, outcome, ...classifyTestFailure(outcome) });
31
+ }));
32
+ return failing;
33
+ }
34
+ function firstLine(text) {
35
+ return text.split("\n").find((line) => line.trim())?.trim() ?? "";
36
+ }
37
+ function activeCaseCount(suites) {
38
+ return suites.reduce((n, s) => n + s.cases.filter((c) => !c.skipReason).length, 0);
39
+ }
40
+ function sha256(text) {
41
+ return createHash("sha256").update(text).digest("hex");
42
+ }
43
+ /**
44
+ * Repairs failing generated tests in bounded rounds. Only the test file is ever written;
45
+ * a round that breaks a previously passing test is reverted and ends the loop.
46
+ */
47
+ export async function repairTests(options) {
48
+ if (path.resolve(options.testPath) === path.resolve(options.sourcePath)) {
49
+ throw new Error("Refusing to repair: the test path is the source file.");
50
+ }
51
+ const maxAttempts = Math.max(0, Math.min(options.maxAttempts ?? MAX_REPAIR_ATTEMPTS, MAX_REPAIR_ATTEMPTS));
52
+ const runTests = options.runTests ?? ((testPath) => runJest({ testPath, sourcePath: options.sourcePath, allowNetwork: options.allowNetwork }));
53
+ const originalSource = await readFile(options.sourcePath, "utf8");
54
+ const originalHash = sha256(originalSource);
55
+ const render = (suites) => renderTestFile({
56
+ sourceFile: options.analysis.file,
57
+ preamble: options.preamble,
58
+ suites,
59
+ extension: options.extension,
60
+ });
61
+ let suites = cloneSuites(options.suites);
62
+ let content = render(suites);
63
+ let run = options.initialRun;
64
+ const attempts = [];
65
+ const usage = { inputTokens: 0, outputTokens: 0 };
66
+ let stopReason;
67
+ while (!stopReason && attempts.length < maxAttempts) {
68
+ if (run.infra) {
69
+ stopReason = "unrepairable";
70
+ break;
71
+ }
72
+ const failing = findFailing(suites, run);
73
+ if (failing.length === 0) {
74
+ stopReason = "all-passing";
75
+ break;
76
+ }
77
+ const targets = failing.filter((f) => f.repairable).slice(0, MAX_TARGETS_PER_ATTEMPT);
78
+ if (targets.length === 0) {
79
+ stopReason = "unrepairable";
80
+ break;
81
+ }
82
+ const repairTargets = targets.map((t) => {
83
+ const suite = suites[t.suiteIndex];
84
+ const testCase = suite.cases[t.caseIndex];
85
+ return {
86
+ binding: suite.binding,
87
+ testName: testCase.name,
88
+ body: testCase.body,
89
+ failureCategory: t.category,
90
+ message: t.outcome.failure?.message ?? "",
91
+ };
92
+ });
93
+ const prompt = buildRepairPrompt({
94
+ analysis: options.analysis,
95
+ code: options.promptSource,
96
+ targets: repairTargets,
97
+ attempt: attempts.length + 1,
98
+ mockSummary: options.mockSummary,
99
+ });
100
+ if (options.budget && !options.budget.allows({ inputTokens: promptTokens(REPAIR_SYSTEM_PROMPT, prompt), outputTokens: MAX_OUTPUT_TOKENS })) {
101
+ stopReason = "budget";
102
+ break;
103
+ }
104
+ const attempt = {
105
+ attempt: attempts.length + 1,
106
+ before: summarize(run),
107
+ requested: targets.length,
108
+ changes: [],
109
+ invalid: [],
110
+ regressed: false,
111
+ usage: { inputTokens: 0, outputTokens: 0 },
112
+ };
113
+ attempts.push(attempt);
114
+ const response = await options.provider.generateStructured({
115
+ system: REPAIR_SYSTEM_PROMPT,
116
+ prompt,
117
+ schema: RepairSchema,
118
+ schemaName: "assertloom_repairs",
119
+ maxOutputTokens: MAX_OUTPUT_TOKENS,
120
+ });
121
+ options.budget?.add(response.usage);
122
+ attempt.usage = response.usage;
123
+ usage.inputTokens += response.usage.inputTokens;
124
+ usage.outputTokens += response.usage.outputTokens;
125
+ const proposals = new Map(response.data.repairs.map((r) => [key(r.functionName, r.name), r]));
126
+ const candidate = cloneSuites(suites);
127
+ const removals = [];
128
+ for (const target of targets) {
129
+ const suite = candidate[target.suiteIndex];
130
+ const testCase = suite.cases[target.caseIndex];
131
+ const functionName = suite.binding.binding;
132
+ const proposal = proposals.get(key(functionName, testCase.name));
133
+ const invalid = (reason) => attempt.invalid.push({ functionName, name: testCase.name, reason });
134
+ if (!proposal) {
135
+ invalid("no repair returned");
136
+ continue;
137
+ }
138
+ if (proposal.action === "fix") {
139
+ const check = checkBody(proposal.body, options.extension, new Set(suites.map((s) => s.binding.binding)));
140
+ if ("error" in check) {
141
+ invalid(`proposed body ${check.error}`);
142
+ continue;
143
+ }
144
+ if (proposal.body.trim() === testCase.body.trim()) {
145
+ invalid("proposed body is unchanged");
146
+ continue;
147
+ }
148
+ testCase.body = proposal.body.trim();
149
+ testCase.async = check.async;
150
+ }
151
+ else if (proposal.action === "skip") {
152
+ testCase.skipReason = `suspected bug in the source: ${proposal.reason}`;
153
+ }
154
+ else {
155
+ removals.push({ suiteIndex: target.suiteIndex, caseIndex: target.caseIndex });
156
+ }
157
+ attempt.changes.push({ functionName, name: testCase.name, action: proposal.action, reason: proposal.reason });
158
+ }
159
+ for (const { suiteIndex, caseIndex } of removals.sort((a, b) => b.caseIndex - a.caseIndex)) {
160
+ candidate[suiteIndex].cases.splice(caseIndex, 1);
161
+ }
162
+ const nonEmpty = candidate.filter((s) => s.cases.length > 0);
163
+ if (attempt.changes.length === 0 || activeCaseCount(nonEmpty) === 0) {
164
+ stopReason = "no-valid-repairs";
165
+ break;
166
+ }
167
+ const candidateContent = render(nonEmpty);
168
+ await writeFile(options.testPath, candidateContent, "utf8");
169
+ const candidateRun = await runTests(options.testPath);
170
+ attempt.after = summarize(candidateRun);
171
+ const before = outcomesByKey(run);
172
+ const after = outcomesByKey(candidateRun);
173
+ const brokeAPassingTest = [...before].some(([testKey, outcome]) => outcome.status === "passed" && after.get(testKey)?.status === "failed");
174
+ if (candidateRun.infra || brokeAPassingTest) {
175
+ attempt.regressed = true;
176
+ await writeFile(options.testPath, content, "utf8");
177
+ stopReason = "regressed";
178
+ break;
179
+ }
180
+ suites = nonEmpty;
181
+ content = candidateContent;
182
+ run = candidateRun;
183
+ }
184
+ const gaveUp = [];
185
+ if (!run.infra) {
186
+ const remaining = findFailing(suites, run);
187
+ if (remaining.length > 0) {
188
+ for (const f of remaining) {
189
+ const testCase = suites[f.suiteIndex].cases[f.caseIndex];
190
+ const reason = f.category === "mock"
191
+ ? "opens a real network connection; mock the dependency (--mock <module>) or rerun with --allow-network"
192
+ : `still failing after repair (${f.category}): ${firstLine(f.outcome.failure?.message ?? "")}`;
193
+ testCase.skipReason = reason;
194
+ gaveUp.push({ functionName: suites[f.suiteIndex].binding.binding, name: testCase.name, action: "gave-up", reason });
195
+ }
196
+ content = render(suites);
197
+ await writeFile(options.testPath, content, "utf8");
198
+ run = await runTests(options.testPath);
199
+ }
200
+ }
201
+ stopReason ??= findFailing(suites, run).length || gaveUp.length ? "max-attempts" : "all-passing";
202
+ const currentSource = await readFile(options.sourcePath, "utf8");
203
+ const sourceUnchanged = sha256(currentSource) === originalHash;
204
+ if (!sourceUnchanged)
205
+ await writeFile(options.sourcePath, originalSource, "utf8");
206
+ return {
207
+ suites,
208
+ content,
209
+ run,
210
+ initial: summarize(options.initialRun),
211
+ final: summarize(run),
212
+ attempts,
213
+ gaveUp,
214
+ stopReason,
215
+ sourceUnchanged,
216
+ usage,
217
+ };
218
+ }
219
+ //# sourceMappingURL=repairLoop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repairLoop.js","sourceRoot":"","sources":["../../src/repair/repairLoop.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { z } from "zod";
2
+ export declare const REPAIR_ACTIONS: readonly ["fix", "skip", "remove"];
3
+ export declare const RepairSchema: z.ZodObject<{
4
+ repairs: z.ZodArray<z.ZodObject<{
5
+ functionName: z.ZodString;
6
+ name: z.ZodString;
7
+ action: z.ZodEnum<{
8
+ fix: "fix";
9
+ remove: "remove";
10
+ skip: "skip";
11
+ }>;
12
+ body: z.ZodString;
13
+ reason: z.ZodString;
14
+ }, z.core.$strip>>;
15
+ }, z.core.$strip>;
16
+ export type RepairResponse = z.infer<typeof RepairSchema>;
17
+ export type RepairAction = (typeof REPAIR_ACTIONS)[number];
@@ -0,0 +1,14 @@
1
+ import { z } from "zod";
2
+ export const REPAIR_ACTIONS = ["fix", "skip", "remove"];
3
+ export const RepairSchema = z.object({
4
+ repairs: z.array(z.object({
5
+ functionName: z.string().describe("Binding name of the function, exactly as given"),
6
+ name: z.string().describe("Test name, exactly as given"),
7
+ action: z
8
+ .enum(REPAIR_ACTIONS)
9
+ .describe("fix: rewrite the body; skip: the test is right and the source looks buggy; remove: the test cannot be made reliable"),
10
+ body: z.string().describe("The corrected test body for fix; an empty string otherwise"),
11
+ reason: z.string().describe("One sentence explaining the failure and the change"),
12
+ })),
13
+ });
14
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/repair/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAC;AAEjE,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,OAAO,EAAE,CAAC,CAAC,KAAK,CACd,CAAC,CAAC,MAAM,CAAC;QACP,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gDAAgD,CAAC;QACnF,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,6BAA6B,CAAC;QACxD,MAAM,EAAE,CAAC;aACN,IAAI,CAAC,cAAc,CAAC;aACpB,QAAQ,CAAC,qHAAqH,CAAC;QAClI,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,4DAA4D,CAAC;QACvF,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,oDAAoD,CAAC;KAClF,CAAC,CACH;CACF,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { CoverageMetrics } from "./types.js";
2
+ /** Resolves symlinks and Windows short names (`/var` → `/private/var`, `RUNNER~1` → `runneradmin`). */
3
+ export declare function realPath(file: string): string;
4
+ /** Reads coverage for one source file from Jest's `coverage-summary.json`. */
5
+ export declare function readCoverage(summaryPath: string, sourcePath: string): Promise<CoverageMetrics | undefined>;
@@ -0,0 +1,42 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ function percent(metric) {
5
+ if (!metric || metric.total === 0 || typeof metric.pct !== "number")
6
+ return null;
7
+ return metric.pct;
8
+ }
9
+ /** Resolves symlinks and Windows short names (`/var` → `/private/var`, `RUNNER~1` → `runneradmin`). */
10
+ export function realPath(file) {
11
+ try {
12
+ return realpathSync.native(path.resolve(file));
13
+ }
14
+ catch {
15
+ return path.resolve(file);
16
+ }
17
+ }
18
+ function samePath(a, b) {
19
+ const normalize = (p) => (process.platform === "win32" ? realPath(p).toLowerCase() : realPath(p));
20
+ return normalize(a) === normalize(b);
21
+ }
22
+ /** Reads coverage for one source file from Jest's `coverage-summary.json`. */
23
+ export async function readCoverage(summaryPath, sourcePath) {
24
+ let summary;
25
+ try {
26
+ summary = JSON.parse(await readFile(summaryPath, "utf8"));
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ const key = Object.keys(summary).find((file) => file !== "total" && samePath(file, sourcePath));
32
+ if (!key)
33
+ return undefined;
34
+ const entry = summary[key];
35
+ return {
36
+ lines: percent(entry.lines),
37
+ statements: percent(entry.statements),
38
+ functions: percent(entry.functions),
39
+ branches: percent(entry.branches),
40
+ };
41
+ }
42
+ //# sourceMappingURL=coverage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../src/runner/coverage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAU7B,SAAS,OAAO,CAAC,MAA0B;IACzC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK,KAAK,CAAC,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACjF,OAAO,MAAM,CAAC,GAAG,CAAC;AACpB,CAAC;AAED,uGAAuG;AACvG,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS,EAAE,CAAS;IACpC,MAAM,SAAS,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1G,OAAO,SAAS,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC;AACvC,CAAC;AAED,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,WAAmB,EAAE,UAAkB;IACxE,IAAI,OAAwB,CAAC;IAC7B,IAAI,CAAC;QACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAoB,CAAC;IAC/E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;IAChG,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAE3B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3B,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;QAC3B,UAAU,EAAE,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC;QACrC,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC;QACnC,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC;KAClC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { runJest, sanitizedEnv, DEFAULT_RUN_TIMEOUT_MS, type RunOptions } from "./jestRunner.js";
2
+ export { detectJestSetup, findProjectRoot } from "./project.js";
3
+ export { detectFramework, detectVitestSetup } from "./vitest.js";
4
+ export { NETWORK_BLOCKED_CODE, NETWORK_BLOCKED_MESSAGE } from "./networkGuard.js";
5
+ export type { CoverageMetrics, InfraFailure, RunResult, TestOutcome } from "./types.js";
@@ -0,0 +1,5 @@
1
+ export { runJest, sanitizedEnv, DEFAULT_RUN_TIMEOUT_MS } from "./jestRunner.js";
2
+ export { detectJestSetup, findProjectRoot } from "./project.js";
3
+ export { detectFramework, detectVitestSetup } from "./vitest.js";
4
+ export { NETWORK_BLOCKED_CODE, NETWORK_BLOCKED_MESSAGE } from "./networkGuard.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runner/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAmB,MAAM,iBAAiB,CAAC;AACjG,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { JestSetup } from "./project.js";
2
+ export interface RunFiles {
3
+ testPath: string;
4
+ sourcePath: string;
5
+ coverageDir: string;
6
+ }
7
+ /** Matches exactly this test file by its full path, on any OS path separator. */
8
+ export declare function testRegexFor(testPath: string): string;
9
+ export declare function defaultJestConfig(root: string, files: RunFiles): Record<string, unknown>;
10
+ /** Writes the Jest config for one run into `runDir` and returns its path. */
11
+ export declare function writeRunConfig(setup: JestSetup, files: RunFiles, runDir: string): Promise<string>;
@@ -0,0 +1,103 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ const require = createRequire(import.meta.url);
6
+ function escapeRegex(text) {
7
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8
+ }
9
+ /** Matches exactly this test file by its full path, on any OS path separator. */
10
+ export function testRegexFor(testPath) {
11
+ return `^${testPath.split(/[\\/]/).map(escapeRegex).join("[\\\\/]")}$`;
12
+ }
13
+ function coverageGlob(rootDir, sourcePath) {
14
+ return path.relative(rootDir, sourcePath).replaceAll("\\", "/");
15
+ }
16
+ export function defaultJestConfig(root, files) {
17
+ const babelOptions = {
18
+ babelrc: false,
19
+ configFile: false,
20
+ presets: [require.resolve("@babel/preset-typescript"), [require.resolve("@babel/preset-react"), { runtime: "automatic" }]],
21
+ plugins: [require.resolve("@babel/plugin-transform-modules-commonjs")],
22
+ };
23
+ return {
24
+ rootDir: root,
25
+ roots: [...new Set([path.dirname(files.testPath), path.dirname(files.sourcePath)])],
26
+ testEnvironment: "node",
27
+ testRegex: testRegexFor(files.testPath),
28
+ transform: { "\\.[cm]?[jt]sx?$": [require.resolve("babel-jest"), babelOptions] },
29
+ moduleFileExtensions: ["ts", "tsx", "mts", "cts", "js", "jsx", "mjs", "cjs", "json", "node"],
30
+ moduleNameMapper: { "^(\\.{1,2}/.*)\\.js$": "$1" },
31
+ collectCoverageFrom: [coverageGlob(root, files.sourcePath)],
32
+ coverageProvider: "v8",
33
+ coverageReporters: ["json-summary"],
34
+ coverageDirectory: files.coverageDir,
35
+ };
36
+ }
37
+ /** Loads the project's own config at run time and overrides only test selection and coverage. */
38
+ function wrapperModule(baseConfigExpression, baseDir, files) {
39
+ const overrides = {
40
+ testRegex: testRegexFor(files.testPath),
41
+ coverageProvider: "v8",
42
+ coverageReporters: ["json-summary"],
43
+ coverageDirectory: files.coverageDir,
44
+ };
45
+ return `import path from "node:path";
46
+
47
+ const baseDir = ${JSON.stringify(baseDir)};
48
+ const sourcePath = ${JSON.stringify(files.sourcePath)};
49
+ const testPath = ${JSON.stringify(files.testPath)};
50
+
51
+ const ignores = (pattern, rootDir) => {
52
+ try {
53
+ // Jest rewrites "/" in these patterns to the OS separator, so check both spellings of the path.
54
+ const regex = new RegExp(pattern.replaceAll("<rootDir>", rootDir));
55
+ return regex.test(testPath) || regex.test(testPath.split(path.sep).join("/"));
56
+ } catch {
57
+ return false;
58
+ }
59
+ };
60
+
61
+ export default async () => {
62
+ let config = ${baseConfigExpression};
63
+ if (typeof config === "function") config = await config();
64
+ config = { ...config };
65
+ if (config.projects) throw new Error("Assertloom: Jest \`projects\` configurations are not supported yet.");
66
+ delete config.testMatch;
67
+ const rootDir = config.rootDir ? path.resolve(baseDir, config.rootDir) : baseDir;
68
+ if (Array.isArray(config.roots)) config.roots = [...config.roots, path.dirname(testPath)];
69
+ if (Array.isArray(config.testPathIgnorePatterns)) {
70
+ config.testPathIgnorePatterns = config.testPathIgnorePatterns.filter((p) => !ignores(p, rootDir));
71
+ }
72
+ return {
73
+ ...config,
74
+ rootDir,
75
+ ...${JSON.stringify(overrides)},
76
+ collectCoverageFrom: [path.relative(rootDir, sourcePath).split(path.sep).join("/")],
77
+ };
78
+ };
79
+ `;
80
+ }
81
+ /** Writes the Jest config for one run into `runDir` and returns its path. */
82
+ export async function writeRunConfig(setup, files, runDir) {
83
+ if (setup.config.kind === "default") {
84
+ const file = path.join(runDir, "jest.config.json");
85
+ await writeFile(file, JSON.stringify(defaultJestConfig(setup.root, files), null, 2));
86
+ return file;
87
+ }
88
+ const baseDir = setup.config.kind === "file" ? path.dirname(setup.config.path) : setup.root;
89
+ let baseConfigExpression;
90
+ if (setup.config.kind === "package-json") {
91
+ baseConfigExpression = JSON.stringify(setup.config.config);
92
+ }
93
+ else if (setup.config.path.endsWith(".json")) {
94
+ baseConfigExpression = JSON.stringify(JSON.parse(await readFile(setup.config.path, "utf8")));
95
+ }
96
+ else {
97
+ baseConfigExpression = `(await import(${JSON.stringify(pathToFileURL(setup.config.path).href)})).default`;
98
+ }
99
+ const file = path.join(runDir, "jest.config.mjs");
100
+ await writeFile(file, wrapperModule(baseConfigExpression, baseDir, files));
101
+ return file;
102
+ }
103
+ //# sourceMappingURL=jestConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jestConfig.js","sourceRoot":"","sources":["../../src/runner/jestConfig.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { RunResult } from "./types.js";
2
+ import type { TestFramework } from "../types/index.js";
3
+ export declare const DEFAULT_RUN_TIMEOUT_MS = 120000;
4
+ export declare const DEFAULT_TEST_TIMEOUT_MS = 5000;
5
+ export interface RunOptions {
6
+ testPath: string;
7
+ sourcePath: string;
8
+ timeoutMs?: number;
9
+ testTimeoutMs?: number;
10
+ /** Let tests open network connections. Off by default. */
11
+ allowNetwork?: boolean;
12
+ /** Default: jest. */
13
+ framework?: TestFramework;
14
+ }
15
+ /** Environment for untrusted test code: drops anything that looks like a credential. */
16
+ export declare function sanitizedEnv(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
17
+ export declare function runJest(options: RunOptions): Promise<RunResult>;
@@ -0,0 +1,154 @@
1
+ import { spawn } from "node:child_process";
2
+ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { readCoverage, realPath } from "./coverage.js";
6
+ import { writeRunConfig } from "./jestConfig.js";
7
+ import { writeNetworkGuard } from "./networkGuard.js";
8
+ import { detectJestSetup, jestCanStartIn } from "./project.js";
9
+ import { cleanMessage, parseJestReport } from "./results.js";
10
+ import { describeVitestSetup, detectVitestSetup, vitestArgs, writeVitestRunConfig } from "./vitest.js";
11
+ export const DEFAULT_RUN_TIMEOUT_MS = 120_000;
12
+ export const DEFAULT_TEST_TIMEOUT_MS = 5_000;
13
+ const OUTPUT_TAIL_CHARS = 8_000;
14
+ const SECRET_ENV = /KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH|SESSION|COOKIE/i;
15
+ /** Environment for untrusted test code: drops anything that looks like a credential. */
16
+ export function sanitizedEnv(env = process.env) {
17
+ const clean = {};
18
+ for (const [name, value] of Object.entries(env)) {
19
+ if (!SECRET_ENV.test(name))
20
+ clean[name] = value;
21
+ }
22
+ return { ...clean, NODE_ENV: "test", CI: "true", FORCE_COLOR: "0" };
23
+ }
24
+ function describeSetup(setup) {
25
+ const jest = setup.jestSource === "project" ? "project Jest" : "bundled Jest";
26
+ const config = setup.config.kind === "default"
27
+ ? "Assertloom default config"
28
+ : setup.config.kind === "file"
29
+ ? path.basename(setup.config.path)
30
+ : `"jest" in package.json`;
31
+ return `${jest}, ${config}`;
32
+ }
33
+ function runProcess(command, args, options) {
34
+ return new Promise((resolve, reject) => {
35
+ const child = spawn(command, args, { cwd: options.cwd, env: options.env, windowsHide: true });
36
+ let output = "";
37
+ let timedOut = false;
38
+ const append = (chunk) => {
39
+ output = (output + chunk).slice(-OUTPUT_TAIL_CHARS);
40
+ };
41
+ child.stdout.setEncoding("utf8");
42
+ child.stderr.setEncoding("utf8");
43
+ child.stdout.on("data", append);
44
+ child.stderr.on("data", append);
45
+ const timer = setTimeout(() => {
46
+ timedOut = true;
47
+ child.kill("SIGKILL");
48
+ }, options.timeoutMs);
49
+ child.on("error", (error) => {
50
+ clearTimeout(timer);
51
+ reject(error);
52
+ });
53
+ child.on("close", (code) => {
54
+ clearTimeout(timer);
55
+ resolve({ code, timedOut, output });
56
+ });
57
+ });
58
+ }
59
+ export async function runJest(options) {
60
+ // Jest reports real paths, so configure it with them too.
61
+ const testPath = realPath(options.testPath);
62
+ const sourcePath = realPath(options.sourcePath);
63
+ const timeoutMs = options.timeoutMs ?? DEFAULT_RUN_TIMEOUT_MS;
64
+ const testTimeoutMs = options.testTimeoutMs ?? DEFAULT_TEST_TIMEOUT_MS;
65
+ const vitest = options.framework === "vitest" ? detectVitestSetup(sourcePath) : undefined;
66
+ const jest = vitest ? undefined : detectJestSetup(sourcePath);
67
+ const originalSource = await readFile(sourcePath);
68
+ const runDir = await mkdtemp(path.join(os.tmpdir(), "assertloom-run-"));
69
+ const started = Date.now();
70
+ const network = options.allowNetwork ? "network allowed" : "network blocked";
71
+ const described = vitest ? describeVitestSetup(vitest) : describeSetup(jest);
72
+ const base = { tests: [], passed: 0, failed: 0, skipped: 0, total: 0, setup: `${described}, ${network}` };
73
+ let result;
74
+ try {
75
+ result = await execute();
76
+ return result;
77
+ }
78
+ finally {
79
+ await rm(runDir, { recursive: true, force: true });
80
+ const current = await readFile(sourcePath).catch(() => undefined);
81
+ if (!current?.equals(originalSource)) {
82
+ await writeFile(sourcePath, originalSource);
83
+ if (result)
84
+ result.sourceRestored = true;
85
+ }
86
+ }
87
+ async function execute() {
88
+ const coverageDir = path.join(runDir, "coverage");
89
+ const resultsPath = path.join(runDir, "results.json");
90
+ const files = { testPath, sourcePath, coverageDir };
91
+ const env = sanitizedEnv();
92
+ if (!options.allowNetwork) {
93
+ env.NODE_OPTIONS = [env.NODE_OPTIONS, await writeNetworkGuard(runDir)].filter(Boolean).join(" ");
94
+ }
95
+ let args;
96
+ let cwd;
97
+ if (vitest) {
98
+ args = vitestArgs(vitest, await writeVitestRunConfig(vitest, { ...files, resultsPath, testTimeoutMs }, runDir));
99
+ cwd = vitest.root;
100
+ }
101
+ else {
102
+ const setup = jest;
103
+ args = [
104
+ setup.jestBin,
105
+ "--config",
106
+ await writeRunConfig(setup, files, runDir),
107
+ "--runInBand",
108
+ "--ci",
109
+ "--no-watchman",
110
+ "--json",
111
+ "--outputFile",
112
+ resultsPath,
113
+ "--coverage",
114
+ // Open handles left by the code under test would otherwise keep Jest alive until the timeout.
115
+ "--forceExit",
116
+ `--testTimeout=${testTimeoutMs}`,
117
+ ];
118
+ // runDir holds the generated config, so Jest can always find one there.
119
+ cwd = jestCanStartIn(setup.root) ? setup.root : runDir;
120
+ }
121
+ const { timedOut, output } = await runProcess(process.execPath, args, { cwd, timeoutMs, env });
122
+ const durationMs = Date.now() - started;
123
+ let report;
124
+ try {
125
+ report = JSON.parse(await readFile(resultsPath, "utf8"));
126
+ }
127
+ catch {
128
+ if (timedOut) {
129
+ return { ...base, status: "error", durationMs, infra: { kind: "timeout", message: `Jest did not finish within ${timeoutMs / 1000}s.` } };
130
+ }
131
+ return { ...base, status: "error", durationMs, infra: { kind: "crash", message: cleanMessage(output) || "Jest exited without writing results." } };
132
+ }
133
+ const { tests, infra } = parseJestReport(report);
134
+ const passed = tests.filter((t) => t.status === "passed").length;
135
+ const failed = tests.filter((t) => t.status === "failed").length;
136
+ const skipped = tests.filter((t) => t.status === "skipped").length;
137
+ if (!infra && tests.length === 0) {
138
+ return { ...base, status: "error", durationMs, infra: { kind: "suite-error", message: cleanMessage(output) || "Jest found no tests to run." } };
139
+ }
140
+ return {
141
+ ...base,
142
+ status: infra ? "error" : failed > 0 ? "failed" : "passed",
143
+ tests,
144
+ passed,
145
+ failed,
146
+ skipped,
147
+ total: tests.length,
148
+ infra,
149
+ coverage: infra ? undefined : await readCoverage(path.join(coverageDir, "coverage-summary.json"), sourcePath),
150
+ durationMs,
151
+ };
152
+ }
153
+ }
154
+ //# sourceMappingURL=jestRunner.js.map