@elench/testkit 0.1.79 → 0.1.81

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 (47) hide show
  1. package/README.md +50 -35
  2. package/lib/cli/args.mjs +2 -14
  3. package/lib/cli/args.test.mjs +1 -17
  4. package/lib/cli/command-helpers.mjs +1 -20
  5. package/lib/cli/entrypoint.mjs +0 -4
  6. package/lib/cli/presentation/colors.mjs +1 -1
  7. package/lib/cli/presentation/failure-presentation.mjs +31 -0
  8. package/lib/cli/presentation/run-reporter.mjs +63 -93
  9. package/lib/cli/presentation/run-reporter.test.mjs +137 -26
  10. package/lib/cli/presentation/summary-box.mjs +45 -0
  11. package/lib/cli/presentation/summary-box.test.mjs +43 -0
  12. package/lib/cli/presentation/terminal-layout.mjs +43 -0
  13. package/lib/cli/presentation/terminal-layout.test.mjs +23 -0
  14. package/lib/cli/viewer.mjs +18 -19
  15. package/lib/config/index.mjs +6 -6
  16. package/lib/config/runtime.mjs +8 -8
  17. package/lib/config-api/index.d.ts +4 -4
  18. package/lib/package.test.mjs +4 -4
  19. package/lib/{known-failures → regressions}/github.mjs +39 -77
  20. package/lib/regressions/github.test.mjs +324 -0
  21. package/lib/regressions/index.d.ts +189 -0
  22. package/lib/{known-failures → regressions}/index.mjs +90 -93
  23. package/lib/{known-failures → regressions}/index.test.mjs +37 -48
  24. package/lib/runner/formatting.mjs +105 -103
  25. package/lib/runner/formatting.test.mjs +94 -131
  26. package/lib/runner/metadata.mjs +1 -1
  27. package/lib/runner/orchestrator.mjs +7 -8
  28. package/lib/runner/regressions.mjs +304 -0
  29. package/lib/runner/{triage.test.mjs → regressions.test.mjs} +50 -36
  30. package/lib/runner/reporting.mjs +2 -2
  31. package/lib/runner/reporting.test.mjs +2 -2
  32. package/lib/runner/run-finalization.mjs +18 -30
  33. package/lib/runner/template-steps.mjs +2 -2
  34. package/lib/runner/worker-loop.mjs +1 -0
  35. package/node_modules/@elench/next-analysis/package.json +1 -1
  36. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  37. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  38. package/node_modules/@elench/ts-analysis/package.json +1 -1
  39. package/package.json +12 -9
  40. package/lib/cli/commands/known-failures/render.mjs +0 -19
  41. package/lib/cli/commands/known-failures/validate.mjs +0 -20
  42. package/lib/cli/known-failures.mjs +0 -164
  43. package/lib/known-failures/github.test.mjs +0 -512
  44. package/lib/known-failures/index.d.ts +0 -192
  45. package/lib/runner/triage.mjs +0 -221
  46. /package/lib/{known-failures → regressions}/github-cache.mjs +0 -0
  47. /package/lib/{known-failures → regressions}/github-transport.mjs +0 -0
@@ -1,27 +1,24 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { normalizeKnownFailuresDocument } from "../known-failures/index.mjs";
3
- import { applyKnownFailuresToArtifacts } from "./triage.mjs";
2
+ import { normalizeRegressionCatalogDocument } from "../regressions/index.mjs";
3
+ import { applyRegressionAnalysisToArtifacts } from "./regressions.mjs";
4
4
 
5
- describe("runner triage", () => {
6
- it("matches exact failure keys and enriches both artifacts", () => {
7
- const knownFailures = normalizeKnownFailuresDocument({
5
+ describe("runner regressions", () => {
6
+ it("classifies known failed regressions and enriches both artifacts", () => {
7
+ const regressionCatalog = normalizeRegressionCatalogDocument({
8
8
  schemaVersion: 1,
9
9
  issueRepo: "acme/repo",
10
10
  entries: [
11
11
  {
12
12
  id: "bad-message",
13
- title: "Bad message bug",
14
13
  classification: "product_bug",
15
- state: "open",
16
14
  issue: {
17
15
  repo: "acme/repo",
18
16
  number: 12,
19
- url: "https://github.com/acme/repo/issues/12",
20
17
  },
21
- description: "The API returns the wrong message.",
22
- whyFailing: "The endpoint payload is wrong.",
23
- lastReviewedAt: "2026-04-27",
24
- matches: [
18
+ summary: "API returns the wrong message",
19
+ cause: "The endpoint payload is wrong.",
20
+ lastReviewedAt: "2026-05-04",
21
+ fingerprints: [
25
22
  {
26
23
  service: "api",
27
24
  type: "int",
@@ -80,36 +77,49 @@ describe("runner triage", () => {
80
77
  ],
81
78
  };
82
79
 
83
- const enriched = applyKnownFailuresToArtifacts(runArtifact, statusArtifact, knownFailures);
84
- expect(enriched.statusArtifact.tests[0].triage).toMatchObject({
85
- status: "known_failure",
80
+ const enriched = applyRegressionAnalysisToArtifacts(
81
+ runArtifact,
82
+ statusArtifact,
83
+ regressionCatalog,
84
+ {
85
+ entries: [
86
+ {
87
+ id: "bad-message",
88
+ status: "open_and_failing",
89
+ github: {
90
+ state: "OPEN",
91
+ url: "https://github.com/acme/repo/issues/12",
92
+ cached: false,
93
+ },
94
+ findings: [],
95
+ },
96
+ ],
97
+ findings: [],
98
+ }
99
+ );
100
+
101
+ expect(enriched.statusArtifact.tests[0].diagnosis).toMatchObject({
102
+ status: "known_regression",
86
103
  classifications: ["product_bug"],
87
104
  });
88
- expect(enriched.runArtifact.services[0].suites[0].files[0].triage.entries[0]).toMatchObject({
105
+ expect(enriched.runArtifact.services[0].suites[0].files[0].diagnosis.entries[0]).toMatchObject({
89
106
  id: "bad-message",
90
107
  issue: {
91
108
  number: 12,
92
109
  },
93
110
  });
94
- expect(enriched.statusArtifact.triageSummary).toEqual({
95
- failed: {
96
- total: 1,
97
- known: 1,
98
- untriaged: 0,
99
- byClassification: {
100
- product_bug: 1,
101
- },
102
- },
103
- entries: {
104
- total: 1,
105
- matchedByFailedTests: 1,
106
- unmatched: 0,
107
- },
111
+ expect(enriched.statusArtifact.regressions.summary).toEqual({
112
+ newRegressions: 0,
113
+ knownRegressions: 1,
114
+ fixedKnownRegressions: 0,
115
+ catalogStale: 0,
116
+ catalogSyncUnavailable: false,
117
+ usedStaleCache: false,
108
118
  });
109
119
  });
110
120
 
111
- it("marks unmatched failed tests as untriaged", () => {
112
- const enriched = applyKnownFailuresToArtifacts(
121
+ it("marks unmatched failed tests as new regressions and emits drafts", () => {
122
+ const enriched = applyRegressionAnalysisToArtifacts(
113
123
  {
114
124
  services: [
115
125
  {
@@ -140,15 +150,19 @@ describe("runner triage", () => {
140
150
  },
141
151
  ],
142
152
  },
143
- normalizeKnownFailuresDocument({
153
+ normalizeRegressionCatalogDocument({
144
154
  schemaVersion: 1,
145
155
  entries: [],
146
- })
156
+ }),
157
+ null
147
158
  );
148
159
 
149
- expect(enriched.statusArtifact.tests[0].triage).toEqual({
150
- status: "untriaged",
160
+ expect(enriched.statusArtifact.tests[0].diagnosis).toEqual({
161
+ status: "new_regression",
162
+ classifications: [],
151
163
  entries: [],
152
164
  });
165
+ expect(enriched.statusArtifact.regressions.summary.newRegressions).toBe(1);
166
+ expect(enriched.statusArtifact.regressions.drafts.newRegressions).toHaveLength(1);
153
167
  });
154
168
  });
@@ -80,7 +80,7 @@ export function buildStatusArtifact({
80
80
  scope.serviceFilter === null;
81
81
 
82
82
  return {
83
- schemaVersion: 6,
83
+ schemaVersion: 7,
84
84
  source: "testkit",
85
85
  notice: "Generated file. Do not edit manually.",
86
86
  product: {
@@ -134,7 +134,7 @@ export function buildRunArtifact({
134
134
  const dbBackend = summarizeDbBackend(results);
135
135
 
136
136
  return {
137
- schemaVersion: 8,
137
+ schemaVersion: 9,
138
138
  source: "testkit",
139
139
  generatedAt: new Date(finishedAt).toISOString(),
140
140
  product: {
@@ -79,7 +79,7 @@ describe("runner reporting", () => {
79
79
  });
80
80
 
81
81
  expect(artifact.product.name).toBe("my-product");
82
- expect(artifact.schemaVersion).toBe(8);
82
+ expect(artifact.schemaVersion).toBe(9);
83
83
  expect(artifact.run).toMatchObject({
84
84
  workers: 2,
85
85
  fileTimeoutSeconds: 60,
@@ -233,7 +233,7 @@ describe("runner reporting", () => {
233
233
  });
234
234
 
235
235
  expect(status).toEqual({
236
- schemaVersion: 6,
236
+ schemaVersion: 7,
237
237
  source: "testkit",
238
238
  notice: "Generated file. Do not edit manually.",
239
239
  product: {
@@ -1,11 +1,11 @@
1
1
  import { buildRunArtifact, buildStatusArtifact } from "./reporting.mjs";
2
- import { applyKnownFailureIssueValidationToArtifacts, applyKnownFailuresToArtifacts } from "./triage.mjs";
2
+ import { applyRegressionAnalysisToArtifacts } from "./regressions.mjs";
3
3
  import { writeRunArtifact, writeStatusArtifact } from "./artifacts.mjs";
4
4
  import { summarizeDbBackend } from "./results.mjs";
5
5
  import { formatError } from "./formatting.mjs";
6
6
  import { uploadTelemetryArtifact } from "../telemetry/index.mjs";
7
7
  import { loadHistory, saveHistory, updateHistoryFromRunArtifact } from "../history/index.mjs";
8
- import { shouldFailKnownFailureIssueValidation, validateKnownFailureIssues } from "../known-failures/github.mjs";
8
+ import { shouldFailRegressionSync, validateRegressionIssues } from "../regressions/github.mjs";
9
9
 
10
10
  export async function finalizeRunArtifacts({
11
11
  productDir,
@@ -20,8 +20,8 @@ export async function finalizeRunArtifacts({
20
20
  metadata,
21
21
  logRegistry,
22
22
  setupRegistry,
23
- knownFailures,
24
- issueValidationConfig,
23
+ regressionCatalog,
24
+ regressionSyncConfig,
25
25
  telemetry,
26
26
  reporter,
27
27
  writeStatus,
@@ -60,24 +60,19 @@ export async function finalizeRunArtifacts({
60
60
  metadata,
61
61
  })
62
62
  : null;
63
- const enrichedArtifacts = applyKnownFailuresToArtifacts(runArtifact, statusArtifact, knownFailures);
64
- const knownFailureIssueValidation = await validateKnownFailureIssues({
63
+ const regressionSync = await validateRegressionIssues({
65
64
  productDir,
66
- document: knownFailures,
67
- runArtifact: enrichedArtifacts.runArtifact,
68
- statusArtifact: enrichedArtifacts.statusArtifact,
69
- config: issueValidationConfig,
65
+ document: regressionCatalog,
66
+ runArtifact,
67
+ statusArtifact,
68
+ config: regressionSyncConfig,
70
69
  gitMetadata: metadata.git,
71
70
  });
72
- applyKnownFailureIssueValidationToArtifacts(
73
- enrichedArtifacts.runArtifact,
74
- enrichedArtifacts.statusArtifact,
75
- knownFailureIssueValidation
76
- );
77
- attachKnownFailureIssueValidation(
78
- enrichedArtifacts.runArtifact,
79
- enrichedArtifacts.statusArtifact,
80
- knownFailureIssueValidation
71
+ const enrichedArtifacts = applyRegressionAnalysisToArtifacts(
72
+ runArtifact,
73
+ statusArtifact,
74
+ regressionCatalog,
75
+ regressionSync
81
76
  );
82
77
 
83
78
  writeRunArtifact(productDir, enrichedArtifacts.runArtifact);
@@ -91,14 +86,15 @@ export async function finalizeRunArtifacts({
91
86
  );
92
87
  saveHistory(productDir, nextHistory);
93
88
 
94
- reporter?.runSummary?.(results, finishedAt - startedAt, knownFailureIssueValidation);
89
+ reporter?.runSummary?.(results, finishedAt - startedAt, enrichedArtifacts.regressionReport);
95
90
  await reportTelemetry(telemetry, enrichedArtifacts.runArtifact, reporter);
96
91
 
97
92
  return {
98
93
  runArtifact: enrichedArtifacts.runArtifact,
99
94
  statusArtifact: enrichedArtifacts.statusArtifact,
100
- knownFailureIssueValidation,
101
- shouldFailIssueValidation: shouldFailKnownFailureIssueValidation(knownFailureIssueValidation),
95
+ regressionReport: enrichedArtifacts.regressionReport,
96
+ regressionSync,
97
+ shouldFailRegressionSync: shouldFailRegressionSync(regressionSync),
102
98
  };
103
99
  }
104
100
 
@@ -122,11 +118,3 @@ async function reportTelemetry(telemetry, artifact, reporter = null) {
122
118
  reporter?.telemetry?.(`Telemetry: upload failed (${formatError(error)})`);
123
119
  }
124
120
  }
125
-
126
- function attachKnownFailureIssueValidation(runArtifact, statusArtifact, validation) {
127
- if (!validation) return;
128
- runArtifact.knownFailuresIssueValidation = validation;
129
- if (statusArtifact) {
130
- statusArtifact.knownFailuresIssueValidation = validation;
131
- }
132
- }
@@ -35,7 +35,7 @@ const PLAYWRIGHT_ENTRY = path.join(PACKAGE_ROOT, "lib", "playwright", "index.mjs
35
35
  const RUNTIME_ENTRY = path.join(PACKAGE_ROOT, "lib", "runtime", "index.mjs");
36
36
  const VITEST_ENTRY = path.join(PACKAGE_ROOT, "lib", "vitest", "index.mjs");
37
37
  const DISCOVERY_ENTRY = path.join(PACKAGE_ROOT, "lib", "discovery", "index.mjs");
38
- const KNOWN_FAILURES_ENTRY = path.join(PACKAGE_ROOT, "lib", "known-failures", "index.mjs");
38
+ const REGRESSIONS_ENTRY = path.join(PACKAGE_ROOT, "lib", "regressions", "index.mjs");
39
39
  const MODULE_RUNNER_ENTRY = path.join(
40
40
  PACKAGE_ROOT,
41
41
  "lib",
@@ -263,7 +263,7 @@ function resolvePackageSubpath(specifier) {
263
263
  if (subpath === "/runtime") return RUNTIME_ENTRY;
264
264
  if (subpath === "/vitest") return VITEST_ENTRY;
265
265
  if (subpath === "/discovery") return DISCOVERY_ENTRY;
266
- if (subpath === "/known-failures") return KNOWN_FAILURES_ENTRY;
266
+ if (subpath === "/regressions") return REGRESSIONS_ENTRY;
267
267
 
268
268
  throw new Error(`Unsupported @elench/testkit import "${specifier}" while loading template step`);
269
269
  }
@@ -81,6 +81,7 @@ export async function runWorker(
81
81
  } catch (error) {
82
82
  const message = formatError(error);
83
83
  errors.push(message);
84
+ reporter?.runtimeError?.(task, message);
84
85
  recordGraphError(trackers, { targetNames: lease?.context?.targetNames || [task.targetName] }, message);
85
86
  await runtimeManager.release(lease, {
86
87
  invalidate: lease !== null,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/next-analysis",
3
- "version": "0.1.79",
3
+ "version": "0.1.81",
4
4
  "description": "SWC-backed Next.js source analysis primitives for Erench tools",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/testkit-bridge",
3
- "version": "0.1.79",
3
+ "version": "0.1.81",
4
4
  "description": "Browser bridge helpers for testkit",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,7 +22,7 @@
22
22
  "typecheck": "tsc -p tsconfig.json --noEmit"
23
23
  },
24
24
  "dependencies": {
25
- "@elench/testkit-protocol": "0.1.79"
25
+ "@elench/testkit-protocol": "0.1.81"
26
26
  },
27
27
  "private": false
28
28
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/testkit-protocol",
3
- "version": "0.1.79",
3
+ "version": "0.1.81",
4
4
  "description": "Shared browser protocol for testkit bridge and extension consumers",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/ts-analysis",
3
- "version": "0.1.79",
3
+ "version": "0.1.81",
4
4
  "description": "TypeScript compiler-backed source analysis primitives for Erench tools",
5
5
  "type": "module",
6
6
  "exports": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/testkit",
3
- "version": "0.1.79",
3
+ "version": "0.1.81",
4
4
  "description": "CLI for discovering and running local HTTP, DAL, and Playwright test suites",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -40,9 +40,9 @@
40
40
  "types": "./lib/discovery/index.d.ts",
41
41
  "default": "./lib/discovery/index.mjs"
42
42
  },
43
- "./known-failures": {
44
- "types": "./lib/known-failures/index.d.ts",
45
- "default": "./lib/known-failures/index.mjs"
43
+ "./regressions": {
44
+ "types": "./lib/regressions/index.d.ts",
45
+ "default": "./lib/regressions/index.mjs"
46
46
  },
47
47
  "./package.json": "./package.json"
48
48
  },
@@ -81,10 +81,10 @@
81
81
  },
82
82
  "dependencies": {
83
83
  "@babel/code-frame": "^7.29.0",
84
- "@elench/next-analysis": "0.1.79",
85
- "@elench/testkit-bridge": "0.1.79",
86
- "@elench/testkit-protocol": "0.1.79",
87
- "@elench/ts-analysis": "0.1.79",
84
+ "@elench/next-analysis": "0.1.81",
85
+ "@elench/testkit-bridge": "0.1.81",
86
+ "@elench/testkit-protocol": "0.1.81",
87
+ "@elench/ts-analysis": "0.1.81",
88
88
  "@oclif/core": "^4.10.6",
89
89
  "esbuild": "^0.25.11",
90
90
  "execa": "^9.5.0",
@@ -92,7 +92,10 @@
92
92
  "ink": "^7.0.1",
93
93
  "picocolors": "^1.1.1",
94
94
  "react": "^19.2.5",
95
- "typescript": "^5.9.3"
95
+ "string-width": "^8.1.0",
96
+ "strip-ansi": "^7.1.2",
97
+ "typescript": "^5.9.3",
98
+ "wrap-ansi": "^10.0.0"
96
99
  },
97
100
  "engines": {
98
101
  "node": ">=18"
@@ -1,19 +0,0 @@
1
- import { Command } from "@oclif/core";
2
- import { makeKnownFailuresFlags } from "../../command-helpers.mjs";
3
- import { runKnownFailuresRenderCommand } from "../../known-failures.mjs";
4
-
5
- export default class KnownFailuresRenderCommand extends Command {
6
- static summary = "Render known failures markdown";
7
-
8
- static enableJsonFlag = true;
9
-
10
- static flags = makeKnownFailuresFlags();
11
-
12
- async run() {
13
- const { flags } = await this.parse(KnownFailuresRenderCommand);
14
- await runKnownFailuresRenderCommand({
15
- ...flags,
16
- });
17
- return { ok: true };
18
- }
19
- }
@@ -1,20 +0,0 @@
1
- import { Command } from "@oclif/core";
2
- import { makeKnownFailuresFlags } from "../../command-helpers.mjs";
3
- import { runKnownFailuresValidateCommand } from "../../known-failures.mjs";
4
-
5
- export default class KnownFailuresValidateCommand extends Command {
6
- static summary = "Validate known failures against the latest status artifact";
7
-
8
- static enableJsonFlag = true;
9
-
10
- static flags = makeKnownFailuresFlags();
11
-
12
- async run() {
13
- const { flags } = await this.parse(KnownFailuresValidateCommand);
14
- await runKnownFailuresValidateCommand({
15
- ...flags,
16
- issueMode: flags["issue-mode"] || null,
17
- });
18
- return { ok: true };
19
- }
20
- }
@@ -1,164 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { resolveProductDir } from "../config/index.mjs";
4
- import { loadTestkitConfig } from "../config/config-loader.mjs";
5
- import {
6
- buildKnownFailureIssueValidationSummaryLines,
7
- normalizeKnownFailureIssueValidationConfig,
8
- shouldFailKnownFailureIssueValidation,
9
- validateKnownFailureIssues,
10
- } from "../known-failures/github.mjs";
11
- import {
12
- loadKnownFailuresDocument,
13
- renderKnownFailuresMarkdown,
14
- validateKnownFailuresDocument,
15
- } from "../known-failures/index.mjs";
16
- import { collectGitMetadata } from "../runner/metadata.mjs";
17
-
18
- const DEFAULT_ISSUE_VALIDATION = {
19
- provider: "github",
20
- mode: "error",
21
- cacheTtlSeconds: 900,
22
- };
23
-
24
- export async function runKnownFailuresRenderCommand(options = {}) {
25
- const context = await resolveKnownFailuresContext(options);
26
- const document = loadKnownFailuresDocument(context.knownFailuresPath, context.knownFailuresRelativePath);
27
- const markdown = renderKnownFailuresMarkdown(document);
28
-
29
- if (options.output) {
30
- const outputPath = path.resolve(context.productDir, options.output);
31
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
32
- fs.writeFileSync(outputPath, markdown);
33
- console.log(`Wrote ${path.relative(context.productDir, outputPath)}`);
34
- return;
35
- }
36
-
37
- process.stdout.write(markdown);
38
- }
39
-
40
- export async function runKnownFailuresValidateCommand(options = {}) {
41
- const context = await resolveKnownFailuresContext(options);
42
- const document = loadKnownFailuresDocument(context.knownFailuresPath, context.knownFailuresRelativePath);
43
- const statusArtifact = context.statusArtifactPath
44
- ? readJsonIfExists(context.statusArtifactPath)
45
- : undefined;
46
-
47
- const documentValidation = validateKnownFailuresDocument(document, {
48
- productDir: context.productDir,
49
- statusArtifact,
50
- });
51
-
52
- const issueValidation = await validateKnownFailureIssues({
53
- productDir: context.productDir,
54
- document,
55
- statusArtifact,
56
- config: context.issueValidationConfig,
57
- gitMetadata: collectGitMetadata(context.productDir),
58
- });
59
-
60
- const issueErrors = collectIssueMessages(issueValidation, "error");
61
- const issueWarnings = collectIssueMessages(issueValidation, "warning");
62
- const hasErrors =
63
- documentValidation.errors.length > 0 ||
64
- shouldFailKnownFailureIssueValidation(issueValidation) ||
65
- issueErrors.length > 0;
66
-
67
- if (hasErrors) {
68
- console.error("Known failures validation failed:");
69
- for (const error of documentValidation.errors) {
70
- console.error(`- ${error}`);
71
- }
72
- for (const error of issueErrors) {
73
- console.error(`- ${error}`);
74
- }
75
- if (documentValidation.warnings.length > 0 || issueWarnings.length > 0) {
76
- console.error("");
77
- for (const warning of documentValidation.warnings) {
78
- console.error(`! ${warning}`);
79
- }
80
- for (const warning of issueWarnings) {
81
- console.error(`! ${warning}`);
82
- }
83
- }
84
- for (const line of buildKnownFailureIssueValidationSummaryLines(issueValidation)) {
85
- console.error(line);
86
- }
87
- process.exitCode = 1;
88
- return;
89
- }
90
-
91
- console.log(
92
- `Known failures valid: ${documentValidation.stats.entries} entries, ${documentValidation.stats.matches} matches`
93
- );
94
- if (documentValidation.stats.failedTests > 0) {
95
- console.log(
96
- `Status coverage: ${documentValidation.stats.triagedFailedTests}/${documentValidation.stats.failedTests} failed tests triaged`
97
- );
98
- }
99
- for (const line of buildKnownFailureIssueValidationSummaryLines(issueValidation)) {
100
- console.log(line);
101
- }
102
- for (const warning of [...documentValidation.warnings, ...issueWarnings]) {
103
- console.warn(`! ${warning}`);
104
- }
105
- }
106
-
107
- async function resolveKnownFailuresContext(options) {
108
- const productDir = resolveProductDir(process.cwd(), options.dir);
109
- const { config } = await loadTestkitConfig(productDir);
110
- const reporting = config?.reporting || {};
111
-
112
- const knownFailuresRelativePath = normalizeOptionalString(options.input)
113
- || normalizeOptionalString(reporting.knownFailuresFile);
114
- if (!knownFailuresRelativePath) {
115
- throw new Error(
116
- "Known failures file not configured. Set reporting.knownFailuresFile in testkit.config.ts or pass --input."
117
- );
118
- }
119
-
120
- const statusRelativePath = normalizeOptionalString(options.status) || "testkit.status.json";
121
- const configuredIssueValidation = normalizeKnownFailureIssueValidationConfig(reporting.issueValidation)
122
- || DEFAULT_ISSUE_VALIDATION;
123
- const issueMode = normalizeOptionalString(options.issueMode) || "error";
124
-
125
- return {
126
- productDir,
127
- knownFailuresRelativePath,
128
- knownFailuresPath: path.resolve(productDir, knownFailuresRelativePath),
129
- statusArtifactPath: statusRelativePath ? path.resolve(productDir, statusRelativePath) : null,
130
- issueValidationConfig: {
131
- ...configuredIssueValidation,
132
- mode: issueMode,
133
- },
134
- };
135
- }
136
-
137
- function collectIssueMessages(validation, severity) {
138
- if (!validation) return [];
139
-
140
- const messages = [
141
- ...validation.findings
142
- .filter((finding) => finding.severity === severity)
143
- .map((finding) => finding.message),
144
- ];
145
- for (const entry of validation.entries) {
146
- for (const finding of entry.findings) {
147
- if (finding.severity === severity) {
148
- messages.push(finding.message);
149
- }
150
- }
151
- }
152
- return [...new Set(messages)];
153
- }
154
-
155
- function normalizeOptionalString(value) {
156
- if (typeof value !== "string") return null;
157
- const normalized = value.trim();
158
- return normalized.length > 0 ? normalized : null;
159
- }
160
-
161
- function readJsonIfExists(filePath) {
162
- if (!fs.existsSync(filePath)) return undefined;
163
- return JSON.parse(fs.readFileSync(filePath, "utf8"));
164
- }