@elench/testkit 0.1.80 → 0.1.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -56
- package/lib/cli/args.mjs +2 -14
- package/lib/cli/args.test.mjs +1 -17
- package/lib/cli/command-helpers.mjs +1 -20
- package/lib/cli/entrypoint.mjs +0 -4
- package/lib/cli/presentation/colors.mjs +1 -1
- package/lib/cli/presentation/failure-presentation.mjs +4 -4
- package/lib/cli/presentation/run-reporter.mjs +23 -9
- package/lib/cli/presentation/run-reporter.test.mjs +12 -6
- package/lib/cli/presentation/summary-box.test.mjs +4 -4
- package/lib/cli/viewer.mjs +18 -19
- package/lib/config/index.mjs +6 -6
- package/lib/config/runtime.mjs +8 -8
- package/lib/config-api/auth-fixtures.mjs +762 -0
- package/lib/config-api/index.d.ts +96 -112
- package/lib/config-api/index.mjs +22 -12
- package/lib/config-api/index.test.mjs +61 -222
- package/lib/index.d.ts +29 -9
- package/lib/package.test.mjs +4 -4
- package/lib/{known-failures → regressions}/github.mjs +36 -78
- package/lib/regressions/github.test.mjs +324 -0
- package/lib/regressions/index.d.ts +189 -0
- package/lib/{known-failures → regressions}/index.mjs +90 -93
- package/lib/{known-failures → regressions}/index.test.mjs +37 -48
- package/lib/runner/formatting.mjs +49 -34
- package/lib/runner/formatting.test.mjs +16 -15
- package/lib/runner/metadata.mjs +1 -1
- package/lib/runner/orchestrator.mjs +7 -9
- package/lib/runner/regressions.mjs +304 -0
- package/lib/runner/{triage.test.mjs → regressions.test.mjs} +50 -36
- package/lib/runner/reporting.mjs +2 -2
- package/lib/runner/reporting.test.mjs +2 -2
- package/lib/runner/run-finalization.mjs +18 -30
- package/lib/runner/template-steps.mjs +2 -2
- package/lib/runtime/index.d.ts +50 -33
- package/lib/runtime/index.mjs +0 -1
- package/lib/runtime-src/k6/http-suite-runtime.js +147 -0
- package/lib/runtime-src/k6/http.js +80 -41
- package/lib/runtime-src/k6/scenario-suite.js +13 -110
- package/lib/runtime-src/k6/suite.js +13 -107
- package/node_modules/@elench/next-analysis/package.json +1 -1
- package/node_modules/@elench/testkit-bridge/package.json +2 -2
- package/node_modules/@elench/testkit-protocol/package.json +1 -1
- package/node_modules/@elench/ts-analysis/package.json +1 -1
- package/package.json +8 -8
- package/lib/cli/commands/known-failures/render.mjs +0 -19
- package/lib/cli/commands/known-failures/validate.mjs +0 -20
- package/lib/cli/known-failures.mjs +0 -164
- package/lib/config-api/profiles.mjs +0 -640
- package/lib/known-failures/github.test.mjs +0 -512
- package/lib/known-failures/index.d.ts +0 -192
- package/lib/runner/triage.mjs +0 -221
- /package/lib/{known-failures → regressions}/github-cache.mjs +0 -0
- /package/lib/{known-failures → regressions}/github-transport.mjs +0 -0
|
@@ -7,16 +7,21 @@ import {
|
|
|
7
7
|
startFailureCollection,
|
|
8
8
|
} from "./checks.js";
|
|
9
9
|
import {
|
|
10
|
-
createHttpClient,
|
|
11
10
|
emitHttpTraceCollectionArtifact,
|
|
12
|
-
getEnv,
|
|
13
11
|
startHttpTraceCollection,
|
|
14
12
|
} from "./http.js";
|
|
15
13
|
import {
|
|
16
14
|
clearRuntimeContext,
|
|
17
15
|
registerRuntimeContext,
|
|
18
|
-
resolveHttpProfile,
|
|
19
16
|
} from "../../config-api/runtime.mjs";
|
|
17
|
+
import {
|
|
18
|
+
buildRuntimeExceptionDetail,
|
|
19
|
+
createSuiteActors,
|
|
20
|
+
formatFatalSuiteError,
|
|
21
|
+
mergeProfileConfig,
|
|
22
|
+
normalizeSuiteArgs,
|
|
23
|
+
resolveRuntimeConfig,
|
|
24
|
+
} from "./http-suite-runtime.js";
|
|
20
25
|
|
|
21
26
|
export function defineHttpSuite(configOrRun, maybeRun) {
|
|
22
27
|
const { config, run } = normalizeSuiteArgs(configOrRun, maybeRun);
|
|
@@ -44,18 +49,18 @@ export function defineHttpSuite(configOrRun, maybeRun) {
|
|
|
44
49
|
}
|
|
45
50
|
},
|
|
46
51
|
exec(setupData) {
|
|
47
|
-
const resolved = resolveRuntimeConfig(config);
|
|
52
|
+
const resolved = resolveRuntimeConfig(config, setupData);
|
|
53
|
+
const actorContext = createSuiteActors(setupData, resolved.client, resolved.profileMeta);
|
|
48
54
|
startFailureCollection("exec");
|
|
49
55
|
startHttpTraceCollection("exec");
|
|
50
56
|
try {
|
|
51
57
|
registerRuntimeContext({ env: resolved.env, http: resolved.client.rawHttp || null });
|
|
52
58
|
return run({
|
|
59
|
+
actor: actorContext.actor,
|
|
60
|
+
actors: actorContext.actors,
|
|
53
61
|
env: resolved.env,
|
|
54
|
-
req: resolved.client
|
|
62
|
+
req: resolved.client,
|
|
55
63
|
rawReq: resolved.client.raw,
|
|
56
|
-
getWithHeaders: resolved.client.getWithHeaders,
|
|
57
|
-
setupData,
|
|
58
|
-
session: setupData,
|
|
59
64
|
});
|
|
60
65
|
} catch (error) {
|
|
61
66
|
recordFailureDetail(buildRuntimeExceptionDetail("exec", error));
|
|
@@ -69,102 +74,3 @@ export function defineHttpSuite(configOrRun, maybeRun) {
|
|
|
69
74
|
},
|
|
70
75
|
};
|
|
71
76
|
}
|
|
72
|
-
|
|
73
|
-
function normalizeSuiteArgs(configOrRun, maybeRun) {
|
|
74
|
-
if (typeof configOrRun === "function") {
|
|
75
|
-
return { config: {}, run: configOrRun };
|
|
76
|
-
}
|
|
77
|
-
if (typeof maybeRun !== "function") {
|
|
78
|
-
throw new Error("suite factory requires a run callback");
|
|
79
|
-
}
|
|
80
|
-
return { config: configOrRun || {}, run: maybeRun };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function callHeaders(builder, setupData, env) {
|
|
84
|
-
if (typeof builder !== "function") return {};
|
|
85
|
-
return builder(setupData, { env }) || {};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function mergeProfileConfig(config) {
|
|
89
|
-
if (!config?.profile) return config || {};
|
|
90
|
-
|
|
91
|
-
const profile = resolveHttpProfile(config.profile) || {};
|
|
92
|
-
return {
|
|
93
|
-
...profile,
|
|
94
|
-
...config,
|
|
95
|
-
auth: config.auth ?? profile.auth ?? null,
|
|
96
|
-
headers: config.headers ?? profile.headers,
|
|
97
|
-
rawHeaders: config.rawHeaders ?? profile.rawHeaders,
|
|
98
|
-
options: config.options ?? profile.options,
|
|
99
|
-
env: config.env ?? profile.env,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function resolveRuntimeConfig(config) {
|
|
104
|
-
const resolvedConfig = mergeProfileConfig(config);
|
|
105
|
-
const env = {
|
|
106
|
-
...(resolvedConfig.env || getEnv()),
|
|
107
|
-
rawEnv: __ENV,
|
|
108
|
-
};
|
|
109
|
-
const auth = resolvedConfig.auth || null;
|
|
110
|
-
const client = createHttpClient({
|
|
111
|
-
baseUrl: env.BASE,
|
|
112
|
-
routeHeaders: env.routeParams,
|
|
113
|
-
getHeaders(setupData) {
|
|
114
|
-
return {
|
|
115
|
-
...callHeaders(auth?.headers, setupData, env),
|
|
116
|
-
...callHeaders(resolvedConfig.headers, setupData, env),
|
|
117
|
-
};
|
|
118
|
-
},
|
|
119
|
-
getRawHeaders(setupData) {
|
|
120
|
-
return callHeaders(resolvedConfig.rawHeaders, setupData, env);
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
resolvedConfig,
|
|
126
|
-
env,
|
|
127
|
-
auth,
|
|
128
|
-
client,
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function formatFatalSuiteError(phase, error) {
|
|
133
|
-
if (error instanceof Error) {
|
|
134
|
-
return `Uncaught testkit suite error during ${phase}: ${error.message}`;
|
|
135
|
-
}
|
|
136
|
-
return `Uncaught testkit suite error during ${phase}: ${String(error)}`;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function buildRuntimeExceptionDetail(phase, error) {
|
|
140
|
-
const message =
|
|
141
|
-
error instanceof Error ? error.message : String(error);
|
|
142
|
-
const stack = error instanceof Error && typeof error.stack === "string" ? error.stack : "";
|
|
143
|
-
const location = extractLocationFromStack(stack);
|
|
144
|
-
return {
|
|
145
|
-
kind: "runtime-exception",
|
|
146
|
-
key: location ? `${location.path}:${location.line}:${location.column}` : `runtime-exception:${phase}:${message}`,
|
|
147
|
-
title: "Uncaught runtime exception",
|
|
148
|
-
message: `Uncaught testkit suite error during ${phase}: ${message}`,
|
|
149
|
-
location,
|
|
150
|
-
stack,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function extractLocationFromStack(stack) {
|
|
155
|
-
if (!stack) return null;
|
|
156
|
-
const matches = [...String(stack).matchAll(/(file:\/\/[^\s)]+|\/[^\s):]+):(\d+):(\d+)/g)].map((match) => ({
|
|
157
|
-
path: normalizeStackPath(match[1]),
|
|
158
|
-
line: Number(match[2]),
|
|
159
|
-
column: Number(match[3]),
|
|
160
|
-
}));
|
|
161
|
-
return matches[0] || null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function normalizeStackPath(rawPath) {
|
|
165
|
-
if (typeof rawPath !== "string") return rawPath;
|
|
166
|
-
if (rawPath.startsWith("file://")) {
|
|
167
|
-
return rawPath.slice("file://".length);
|
|
168
|
-
}
|
|
169
|
-
return rawPath;
|
|
170
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.82",
|
|
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.
|
|
25
|
+
"@elench/testkit-protocol": "0.1.82"
|
|
26
26
|
},
|
|
27
27
|
"private": false
|
|
28
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.82",
|
|
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
|
-
"./
|
|
44
|
-
"types": "./lib/
|
|
45
|
-
"default": "./lib/
|
|
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.
|
|
85
|
-
"@elench/testkit-bridge": "0.1.
|
|
86
|
-
"@elench/testkit-protocol": "0.1.
|
|
87
|
-
"@elench/ts-analysis": "0.1.
|
|
84
|
+
"@elench/next-analysis": "0.1.82",
|
|
85
|
+
"@elench/testkit-bridge": "0.1.82",
|
|
86
|
+
"@elench/testkit-protocol": "0.1.82",
|
|
87
|
+
"@elench/ts-analysis": "0.1.82",
|
|
88
88
|
"@oclif/core": "^4.10.6",
|
|
89
89
|
"esbuild": "^0.25.11",
|
|
90
90
|
"execa": "^9.5.0",
|
|
@@ -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
|
-
}
|