@a11yst/core 1.0.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 (106) hide show
  1. package/LICENSE +373 -0
  2. package/dist/aggregate.d.ts +7 -0
  3. package/dist/aggregate.d.ts.map +1 -0
  4. package/dist/aggregate.js +181 -0
  5. package/dist/aggregate.js.map +1 -0
  6. package/dist/apply-source-analysis.d.ts +5 -0
  7. package/dist/apply-source-analysis.d.ts.map +1 -0
  8. package/dist/apply-source-analysis.js +66 -0
  9. package/dist/apply-source-analysis.js.map +1 -0
  10. package/dist/baseline-comparison.d.ts +16 -0
  11. package/dist/baseline-comparison.d.ts.map +1 -0
  12. package/dist/baseline-comparison.js +47 -0
  13. package/dist/baseline-comparison.js.map +1 -0
  14. package/dist/create-audit-plan.d.ts +20 -0
  15. package/dist/create-audit-plan.d.ts.map +1 -0
  16. package/dist/create-audit-plan.js +117 -0
  17. package/dist/create-audit-plan.js.map +1 -0
  18. package/dist/create-github-annotations-input.d.ts +4 -0
  19. package/dist/create-github-annotations-input.d.ts.map +1 -0
  20. package/dist/create-github-annotations-input.js +8 -0
  21. package/dist/create-github-annotations-input.js.map +1 -0
  22. package/dist/create-junit-input.d.ts +4 -0
  23. package/dist/create-junit-input.d.ts.map +1 -0
  24. package/dist/create-junit-input.js +26 -0
  25. package/dist/create-junit-input.js.map +1 -0
  26. package/dist/create-markdown-input.d.ts +4 -0
  27. package/dist/create-markdown-input.d.ts.map +1 -0
  28. package/dist/create-markdown-input.js +57 -0
  29. package/dist/create-markdown-input.js.map +1 -0
  30. package/dist/create-sarif-input.d.ts +4 -0
  31. package/dist/create-sarif-input.d.ts.map +1 -0
  32. package/dist/create-sarif-input.js +23 -0
  33. package/dist/create-sarif-input.js.map +1 -0
  34. package/dist/emit-report.d.ts +59 -0
  35. package/dist/emit-report.d.ts.map +1 -0
  36. package/dist/emit-report.js +89 -0
  37. package/dist/emit-report.js.map +1 -0
  38. package/dist/execute-audit.d.ts +96 -0
  39. package/dist/execute-audit.d.ts.map +1 -0
  40. package/dist/execute-audit.js +646 -0
  41. package/dist/execute-audit.js.map +1 -0
  42. package/dist/generate-github-annotations-report.d.ts +18 -0
  43. package/dist/generate-github-annotations-report.d.ts.map +1 -0
  44. package/dist/generate-github-annotations-report.js +51 -0
  45. package/dist/generate-github-annotations-report.js.map +1 -0
  46. package/dist/generate-junit-report.d.ts +18 -0
  47. package/dist/generate-junit-report.d.ts.map +1 -0
  48. package/dist/generate-junit-report.js +51 -0
  49. package/dist/generate-junit-report.js.map +1 -0
  50. package/dist/generate-markdown-report.d.ts +24 -0
  51. package/dist/generate-markdown-report.d.ts.map +1 -0
  52. package/dist/generate-markdown-report.js +62 -0
  53. package/dist/generate-markdown-report.js.map +1 -0
  54. package/dist/generate-sarif-report.d.ts +18 -0
  55. package/dist/generate-sarif-report.d.ts.map +1 -0
  56. package/dist/generate-sarif-report.js +53 -0
  57. package/dist/generate-sarif-report.js.map +1 -0
  58. package/dist/index.d.ts +31 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +31 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/plan-flows.d.ts +7 -0
  63. package/dist/plan-flows.d.ts.map +1 -0
  64. package/dist/plan-flows.js +79 -0
  65. package/dist/plan-flows.js.map +1 -0
  66. package/dist/policy-evaluation.d.ts +4 -0
  67. package/dist/policy-evaluation.d.ts.map +1 -0
  68. package/dist/policy-evaluation.js +13 -0
  69. package/dist/policy-evaluation.js.map +1 -0
  70. package/dist/readiness-strategy.d.ts +7 -0
  71. package/dist/readiness-strategy.d.ts.map +1 -0
  72. package/dist/readiness-strategy.js +24 -0
  73. package/dist/readiness-strategy.js.map +1 -0
  74. package/dist/report-manifest.d.ts +21 -0
  75. package/dist/report-manifest.d.ts.map +1 -0
  76. package/dist/report-manifest.js +66 -0
  77. package/dist/report-manifest.js.map +1 -0
  78. package/dist/resolve-github-report-options.d.ts +30 -0
  79. package/dist/resolve-github-report-options.d.ts.map +1 -0
  80. package/dist/resolve-github-report-options.js +43 -0
  81. package/dist/resolve-github-report-options.js.map +1 -0
  82. package/dist/resolve-junit-report-options.d.ts +18 -0
  83. package/dist/resolve-junit-report-options.d.ts.map +1 -0
  84. package/dist/resolve-junit-report-options.js +29 -0
  85. package/dist/resolve-junit-report-options.js.map +1 -0
  86. package/dist/resolve-markdown-report-options.d.ts +18 -0
  87. package/dist/resolve-markdown-report-options.d.ts.map +1 -0
  88. package/dist/resolve-markdown-report-options.js +32 -0
  89. package/dist/resolve-markdown-report-options.js.map +1 -0
  90. package/dist/resolve-project-routes.d.ts +17 -0
  91. package/dist/resolve-project-routes.d.ts.map +1 -0
  92. package/dist/resolve-project-routes.js +95 -0
  93. package/dist/resolve-project-routes.js.map +1 -0
  94. package/dist/resolve-sarif-report-options.d.ts +18 -0
  95. package/dist/resolve-sarif-report-options.d.ts.map +1 -0
  96. package/dist/resolve-sarif-report-options.js +29 -0
  97. package/dist/resolve-sarif-report-options.js.map +1 -0
  98. package/dist/run-id.d.ts +13 -0
  99. package/dist/run-id.d.ts.map +1 -0
  100. package/dist/run-id.js +27 -0
  101. package/dist/run-id.js.map +1 -0
  102. package/dist/select-runs.d.ts +41 -0
  103. package/dist/select-runs.d.ts.map +1 -0
  104. package/dist/select-runs.js +128 -0
  105. package/dist/select-runs.js.map +1 -0
  106. package/package.json +51 -0
@@ -0,0 +1,646 @@
1
+ import { basename, isAbsolute, resolve } from "node:path";
2
+ import { createArtifactWriter, appendGitHubStepSummary } from "@a11yst/artifacts";
3
+ import { runWebAudit, runFlowAudit, sortFindings, } from "@a11yst/browser";
4
+ import { productMetadata } from "@a11yst/types";
5
+ import { aggregateSummary, buildProfileSummary, buildFlowSummary, emptySeverityCounts, sortRunResults } from "./aggregate.js";
6
+ import { createAuditPlan } from "./create-audit-plan.js";
7
+ import { prepareAuditConfig } from "./resolve-project-routes.js";
8
+ import { applyBaselineComparison } from "./baseline-comparison.js";
9
+ import { applySourceAnalysis } from "./apply-source-analysis.js";
10
+ import { applyPolicyEvaluation } from "./policy-evaluation.js";
11
+ import { shouldGenerateSarifForAuditResult, } from "./generate-sarif-report.js";
12
+ import { shouldGenerateJunitForAuditResult, } from "./generate-junit-report.js";
13
+ import { generateMarkdownContentFromAuditResult, shouldGenerateMarkdownForAuditResult, } from "./generate-markdown-report.js";
14
+ import { shouldGenerateGitHubAnnotationsForAuditResult, } from "./generate-github-annotations-report.js";
15
+ import { emitReportArtifact } from "./emit-report.js";
16
+ import { buildReportManifestEntry, buildJunitReportReference, buildMarkdownReportReference, buildGitHubAnnotationsReportReference, buildGitHubStepSummaryReference, buildSarifReportReference, mergeReportReferences, } from "./report-manifest.js";
17
+ import { selectRuns, skipReasonForRun, UnknownProfileError, UnknownProjectError, UnknownFlowError, isRouteRun, isFlowCheckpointRun } from "./select-runs.js";
18
+ const LIMITATIONS = [
19
+ "Automated checks do not establish accessibility conformance.",
20
+ "Automated checks cover only part of accessibility. Manual review and testing with disabled users remain necessary.",
21
+ ];
22
+ function buildEnvironment(headed) {
23
+ return {
24
+ product: productMetadata.name,
25
+ productVersion: productMetadata.version,
26
+ nodeVersion: process.version,
27
+ browser: "chromium",
28
+ headed: headed ?? false,
29
+ };
30
+ }
31
+ function diagnosticForSkippedRun(run, reason) {
32
+ return {
33
+ code: "PROFILE_NOT_ENABLED",
34
+ severity: "info",
35
+ message: reason,
36
+ path: `runs.${run.id}`,
37
+ };
38
+ }
39
+ function skippedRunResult(run) {
40
+ const reason = skipReasonForRun(run) ?? "This run is not executed in this phase.";
41
+ return {
42
+ runId: run.id,
43
+ projectName: run.projectName,
44
+ platform: run.platform,
45
+ framework: run.framework,
46
+ routeId: run.routeId,
47
+ routeName: run.routeName,
48
+ route: run.route?.path,
49
+ url: run.baseUrl,
50
+ profile: run.profile,
51
+ viewport: run.viewport,
52
+ status: "skipped",
53
+ startedAt: new Date().toISOString(),
54
+ durationMs: 0,
55
+ findings: [],
56
+ diagnostics: [diagnosticForSkippedRun(run, reason)],
57
+ skipReason: reason,
58
+ ...(run.adapter !== undefined ? { adapter: run.adapter } : {}),
59
+ };
60
+ }
61
+ function groupRunsByProject(runs) {
62
+ const groups = new Map();
63
+ for (const run of runs) {
64
+ const existing = groups.get(run.projectName);
65
+ if (existing) {
66
+ existing.push(run);
67
+ }
68
+ else {
69
+ groups.set(run.projectName, [run]);
70
+ }
71
+ }
72
+ return groups;
73
+ }
74
+ /** Resolve the artifact root without depending on the process working directory. */
75
+ export function resolveAuditOutputDirectory(config, outputDir) {
76
+ const configured = outputDir ?? config.outputDir;
77
+ return isAbsolute(configured) ? resolve(configured) : resolve(config.configDir, configured);
78
+ }
79
+ function findingLogicalId(finding) {
80
+ return finding.id || finding.fingerprint.slice(0, 16) || finding.ruleId || "unknown";
81
+ }
82
+ function sanitizeFlowSegment(value) {
83
+ return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "item";
84
+ }
85
+ export function createFlowEvidenceSink(writer, projectName) {
86
+ return {
87
+ writeFlowTrace: async ({ flowId, profile, viewportName, data }) => {
88
+ const bundlePath = writer.relativePath("evidence", sanitizeFlowSegment(projectName), "flows", sanitizeFlowSegment(flowId), sanitizeFlowSegment(profile), sanitizeFlowSegment(viewportName), "flow-trace.json");
89
+ await writer.writeJson(bundlePath, data);
90
+ return bundlePath;
91
+ },
92
+ writeStepScreenshot: async ({ flowId, profile, viewportName, stepIndex, action, data }) => {
93
+ const bundlePath = writer.relativePath("evidence", sanitizeFlowSegment(projectName), "flows", sanitizeFlowSegment(flowId), sanitizeFlowSegment(profile), sanitizeFlowSegment(viewportName), "steps", `${String(stepIndex + 1).padStart(3, "0")}-${sanitizeFlowSegment(action)}`, "page.png");
94
+ await writer.writeBuffer(bundlePath, data);
95
+ return bundlePath;
96
+ },
97
+ writeCheckpointScreenshot: async ({ flowId, checkpointId, profile, viewportName, data }) => {
98
+ const bundlePath = writer.relativePath("evidence", sanitizeFlowSegment(projectName), "flows", sanitizeFlowSegment(flowId), sanitizeFlowSegment(profile), sanitizeFlowSegment(viewportName), "checkpoints", sanitizeFlowSegment(checkpointId), "page.png");
99
+ await writer.writeBuffer(bundlePath, data);
100
+ return bundlePath;
101
+ },
102
+ };
103
+ }
104
+ /**
105
+ * Adapt an artifact writer to the browser's immediate evidence persistence API.
106
+ * Filenames use finding identities only and never include target selectors.
107
+ */
108
+ export function createArtifactEvidenceSink(writer) {
109
+ return {
110
+ writeRunScreenshot: ({ run, data }) => writer.writeEvidence({
111
+ projectName: run.projectName,
112
+ routeId: run.routeId ?? "route",
113
+ profile: run.profile,
114
+ viewportName: run.viewport?.name ?? "default",
115
+ filename: "page.png",
116
+ data,
117
+ }),
118
+ writeFindingScreenshot: ({ run, finding, targetIndex, data }) => writer.writeEvidence({
119
+ projectName: run.projectName,
120
+ routeId: run.routeId ?? "route",
121
+ profile: run.profile,
122
+ viewportName: run.viewport?.name ?? "default",
123
+ filename: `finding-${findingLogicalId(finding)}-${targetIndex}.png`,
124
+ data,
125
+ }),
126
+ };
127
+ }
128
+ function buildManifest(config, result, writer, reportGenerated, sarifEntry, junitEntry, markdownEntry, githubAnnotationsEntry, githubStepSummaryWritten) {
129
+ const manifest = {
130
+ schemaVersion: "1",
131
+ auditId: writer.auditId,
132
+ createdAt: writer.createdAt,
133
+ status: result.status,
134
+ productVersion: productMetadata.version,
135
+ ...(config.configPath ? { configPath: basename(config.configPath) } : {}),
136
+ projectRoot: ".",
137
+ resultsPath: "results.json",
138
+ ...(reportGenerated ? { reportPath: "report/index.html" } : {}),
139
+ ...(writer.screenshotCount > 0 ? { evidenceDirectory: "evidence" } : {}),
140
+ projects: result.plan.projects.map((project) => ({
141
+ name: project.name,
142
+ platform: project.platform,
143
+ framework: project.framework,
144
+ })),
145
+ artifactCounts: {
146
+ screenshots: writer.screenshotCount,
147
+ findings: result.findings.length,
148
+ runs: result.runs.length,
149
+ },
150
+ };
151
+ const reports = buildReportManifestEntry(reportGenerated, reportGenerated ? "report/index.html" : undefined, sarifEntry, junitEntry, markdownEntry, githubAnnotationsEntry, githubStepSummaryWritten);
152
+ if (reports) {
153
+ manifest.reports = reports;
154
+ }
155
+ if (result.policyEvaluation) {
156
+ manifest.policy = {
157
+ status: result.policyEvaluation.status,
158
+ policyEnabled: result.policyEvaluation.policyEnabled,
159
+ totalBreaches: result.policyEvaluation.summary.totalBreaches,
160
+ };
161
+ }
162
+ return manifest;
163
+ }
164
+ function relativeArtifactReferences(writer, reportGenerated, sarifBundlePath, junitBundlePath, markdownBundlePath, githubAnnotationsBundlePath) {
165
+ return {
166
+ outputDirectory: ".",
167
+ manifestPath: "manifest.json",
168
+ resultsPath: "results.json",
169
+ latestPath: "../../latest.json",
170
+ ...(writer.screenshotCount > 0 ? { evidenceDirectory: "evidence" } : {}),
171
+ ...(reportGenerated ? { reportPath: "report/index.html" } : {}),
172
+ ...(sarifBundlePath ? { sarifPath: sarifBundlePath } : {}),
173
+ ...(junitBundlePath ? { junitPath: junitBundlePath } : {}),
174
+ ...(markdownBundlePath ? { markdownPath: markdownBundlePath } : {}),
175
+ ...(githubAnnotationsBundlePath ? { githubAnnotationsPath: githubAnnotationsBundlePath } : {}),
176
+ };
177
+ }
178
+ function removeUndefinedProperties(value) {
179
+ if (Array.isArray(value)) {
180
+ for (const entry of value)
181
+ removeUndefinedProperties(entry);
182
+ return;
183
+ }
184
+ if (!value || typeof value !== "object")
185
+ return;
186
+ for (const [key, entry] of Object.entries(value)) {
187
+ if (entry === undefined) {
188
+ delete value[key];
189
+ }
190
+ else {
191
+ removeUndefinedProperties(entry);
192
+ }
193
+ }
194
+ }
195
+ /**
196
+ * Build a `"failed"` result for global operational failures (unknown
197
+ * `--project` selection, ...) that happen before any run could be attempted.
198
+ */
199
+ function buildGlobalFailure(plan, startedAt, environment, diagnostic) {
200
+ return {
201
+ schemaVersion: "1",
202
+ status: "failed",
203
+ summary: {
204
+ status: "failed",
205
+ startedAt,
206
+ durationMs: Math.max(0, Date.now() - new Date(startedAt).getTime()),
207
+ plannedRuns: 0,
208
+ completedRuns: 0,
209
+ skippedRuns: 0,
210
+ failedRuns: 0,
211
+ findingCount: 0,
212
+ findingsBySeverity: emptySeverityCounts(),
213
+ },
214
+ plan,
215
+ runs: [],
216
+ findings: [],
217
+ diagnostics: [...plan.diagnostics, diagnostic],
218
+ limitations: [...LIMITATIONS],
219
+ environment,
220
+ };
221
+ }
222
+ /**
223
+ * Execute a full audit for a resolved configuration: build the plan, select
224
+ * which planned runs are actually executable in this phase, run web audits
225
+ * through `@a11yst/browser`, and aggregate everything into a single
226
+ * serialisable `AuditExecutionResult`.
227
+ *
228
+ * Never throws for per-run failures (navigation errors, dev server issues,
229
+ * a missing browser, ...) — those are captured as `"failed"` runs and
230
+ * reflected in `summary.status`. It only returns early with a `"failed"`
231
+ * result (no runs attempted) for a global configuration/selection error,
232
+ * e.g. an unknown `--project` name.
233
+ */
234
+ export async function executeAudit(config, options = {}) {
235
+ const startedAt = new Date().toISOString();
236
+ const progress = options.progress;
237
+ const preparedConfig = await prepareAuditConfig(config, progress);
238
+ const plan = createAuditPlan(preparedConfig);
239
+ const environment = buildEnvironment(options.headed);
240
+ const writeArtifacts = options.writeArtifacts ?? true;
241
+ let writer;
242
+ if (writeArtifacts) {
243
+ try {
244
+ writer = createArtifactWriter({
245
+ outputDir: resolveAuditOutputDirectory(config, options.outputDir),
246
+ auditId: options.artifactAuditId,
247
+ now: options.artifactNow,
248
+ });
249
+ }
250
+ catch (error) {
251
+ const message = error instanceof Error ? error.message : String(error);
252
+ throw new Error(`Failed to create audit artifact bundle: ${message}`, { cause: error });
253
+ }
254
+ }
255
+ let result;
256
+ let selection;
257
+ try {
258
+ selection = selectRuns(plan, {
259
+ projectNames: options.projectNames,
260
+ profileNames: options.profileNames,
261
+ flowNames: options.flowNames,
262
+ routesOnly: options.routesOnly,
263
+ flowsOnly: options.flowsOnly,
264
+ });
265
+ }
266
+ catch (error) {
267
+ const message = error instanceof Error ? error.message : String(error);
268
+ const code = error instanceof UnknownProjectError
269
+ ? "UNKNOWN_PROJECT"
270
+ : error instanceof UnknownProfileError
271
+ ? "UNKNOWN_PROFILE"
272
+ : error instanceof UnknownFlowError
273
+ ? "UNKNOWN_FLOW"
274
+ : "AUDIT_SELECTION_FAILED";
275
+ result = buildGlobalFailure(plan, startedAt, environment, {
276
+ code,
277
+ severity: "error",
278
+ message,
279
+ });
280
+ }
281
+ if (selection) {
282
+ const { executable, skipped } = selection;
283
+ const skippedResults = skipped.map(skippedRunResult);
284
+ const completedResults = [];
285
+ const runDiagnostics = [];
286
+ const flowExecutions = [];
287
+ const totalExecutableRuns = executable.length;
288
+ let completedExecutableRuns = 0;
289
+ const screenshots = writeArtifacts
290
+ ? (options.screenshots ?? config.evidence?.screenshots ?? true)
291
+ : false;
292
+ const fullPage = options.fullPageScreenshots ?? config.evidence?.fullPage ?? false;
293
+ for (const [projectName, runsForProject] of groupRunsByProject(executable)) {
294
+ const project = plan.projects.find((candidate) => candidate.name === projectName);
295
+ if (!project || project.platform !== "web") {
296
+ continue;
297
+ }
298
+ const routeRuns = runsForProject.filter((run) => isRouteRun(run));
299
+ const flowRuns = runsForProject.filter((run) => isFlowCheckpointRun(run));
300
+ const sharedEvidence = {
301
+ screenshots,
302
+ fullPage,
303
+ ...(screenshots && writer
304
+ ? { sink: createArtifactEvidenceSink(writer) }
305
+ : {}),
306
+ ...(writer
307
+ ? {
308
+ writeStructuredEvidence: async ({ run, filename, data, }) => writer.writeEvidence({
309
+ projectName: run.projectName,
310
+ routeId: run.routeId ?? run.flowId ?? "route",
311
+ profile: run.profile,
312
+ viewportName: run.viewport?.name ?? "default",
313
+ filename,
314
+ data: Buffer.from(`${JSON.stringify(data, null, 2)}\n`, "utf8"),
315
+ }),
316
+ ...(flowRuns.length > 0
317
+ ? { writeFlowEvidence: createFlowEvidenceSink(writer, project.name) }
318
+ : {}),
319
+ }
320
+ : {}),
321
+ };
322
+ if (routeRuns.length > 0) {
323
+ const batch = await runWebAudit({
324
+ project,
325
+ runs: routeRuns,
326
+ configDir: config.configDir,
327
+ evidence: sharedEvidence,
328
+ options: {
329
+ headed: options.headed,
330
+ navigationTimeoutMs: options.navigationTimeoutMs,
331
+ noStartServer: options.noStartServer,
332
+ signal: options.signal,
333
+ progress,
334
+ runProgressOffset: completedExecutableRuns,
335
+ runProgressTotal: totalExecutableRuns,
336
+ },
337
+ });
338
+ completedResults.push(...batch.runs);
339
+ runDiagnostics.push(...batch.diagnostics);
340
+ completedExecutableRuns += routeRuns.length;
341
+ }
342
+ if (flowRuns.length > 0) {
343
+ const batch = await runFlowAudit({
344
+ project,
345
+ runs: flowRuns,
346
+ configDir: config.configDir,
347
+ evidence: sharedEvidence,
348
+ options: {
349
+ headed: options.headed,
350
+ navigationTimeoutMs: options.navigationTimeoutMs,
351
+ stepTimeoutMs: options.stepTimeoutMs,
352
+ noStartServer: options.noStartServer,
353
+ signal: options.signal,
354
+ progress,
355
+ runProgressOffset: completedExecutableRuns,
356
+ runProgressTotal: totalExecutableRuns,
357
+ },
358
+ });
359
+ completedResults.push(...batch.runs);
360
+ runDiagnostics.push(...batch.diagnostics);
361
+ flowExecutions.push(...batch.flowExecutions);
362
+ completedExecutableRuns += flowRuns.length;
363
+ }
364
+ }
365
+ const runs = sortRunResults([...completedResults, ...skippedResults]);
366
+ const findings = sortFindings(runs.flatMap((run) => run.findings));
367
+ const summary = aggregateSummary(runs, startedAt);
368
+ const diagnostics = [...plan.diagnostics, ...runDiagnostics];
369
+ result = {
370
+ schemaVersion: "1",
371
+ status: summary.status,
372
+ summary,
373
+ plan,
374
+ runs,
375
+ findings,
376
+ diagnostics,
377
+ limitations: [...LIMITATIONS],
378
+ profileSummary: buildProfileSummary(runs),
379
+ flowSummary: buildFlowSummary(plan, runs),
380
+ ...(flowExecutions.length > 0 ? { flowExecutions } : {}),
381
+ environment,
382
+ };
383
+ }
384
+ if (!result) {
385
+ throw new Error("Audit execution did not produce a result.");
386
+ }
387
+ result = await applySourceAnalysis(config, result, progress);
388
+ const baselineApplied = await applyBaselineComparison(config, result, {
389
+ noBaseline: options.noBaseline,
390
+ baselinePath: options.baselinePath,
391
+ explicitBaselineRequired: options.explicitBaselineRequired,
392
+ });
393
+ result = baselineApplied.result;
394
+ const resolvedPolicy = options.ciPolicy ?? config.ci;
395
+ result = {
396
+ ...result,
397
+ policyEvaluation: applyPolicyEvaluation(result, resolvedPolicy, baselineApplied),
398
+ };
399
+ if (!writer) {
400
+ return result;
401
+ }
402
+ result.auditId = writer.auditId;
403
+ let reportGenerated = false;
404
+ let sarifBundlePath;
405
+ let sarifManifestEntry;
406
+ let sarifExternalPath;
407
+ let junitBundlePath;
408
+ let junitManifestEntry;
409
+ let junitExternalPath;
410
+ let markdownBundlePath;
411
+ let markdownManifestEntry;
412
+ let markdownExternalPath;
413
+ let githubAnnotationsBundlePath;
414
+ let githubAnnotationsManifestEntry;
415
+ let githubAnnotationsExternalPath;
416
+ let githubStepSummaryWritten = false;
417
+ let markdownContentForSummary;
418
+ const sarifEnabled = options.sarif?.enabled ?? false;
419
+ const shouldGenerateSarif = sarifEnabled && shouldGenerateSarifForAuditResult(result);
420
+ const junitEnabled = options.junit?.enabled ?? false;
421
+ const shouldGenerateJunit = junitEnabled && shouldGenerateJunitForAuditResult(result);
422
+ const markdownEnabled = options.markdown?.enabled ?? true;
423
+ const shouldGenerateMarkdown = markdownEnabled && shouldGenerateMarkdownForAuditResult(result);
424
+ const githubAnnotationsEnabled = options.githubAnnotations?.enabled ?? false;
425
+ const shouldGenerateGitHubAnnotations = githubAnnotationsEnabled && shouldGenerateGitHubAnnotationsForAuditResult(result);
426
+ const githubStepSummaryEnabled = options.githubStepSummary ?? false;
427
+ const shouldWriteStepSummary = githubStepSummaryEnabled && shouldGenerateMarkdownForAuditResult(result);
428
+ const willGenerateReports = (options.html ?? true) ||
429
+ shouldGenerateSarif ||
430
+ shouldGenerateJunit ||
431
+ shouldGenerateMarkdown ||
432
+ shouldGenerateGitHubAnnotations ||
433
+ shouldWriteStepSummary;
434
+ if (willGenerateReports) {
435
+ progress?.start("Generating reports…");
436
+ }
437
+ if (options.html ?? true) {
438
+ result.artifacts = relativeArtifactReferences(writer, true);
439
+ try {
440
+ await emitReportArtifact({
441
+ format: "html",
442
+ result,
443
+ bundleDirectory: writer.runDirectory,
444
+ auditId: writer.auditId,
445
+ });
446
+ reportGenerated = true;
447
+ }
448
+ catch (error) {
449
+ const message = error instanceof Error ? error.message : String(error);
450
+ result.diagnostics.push({
451
+ code: "REPORT_GENERATION_FAILED",
452
+ severity: "error",
453
+ message: `Could not generate the HTML report: ${message}`,
454
+ });
455
+ if (result.status !== "failed") {
456
+ result.status = "completed-with-errors";
457
+ result.summary.status = "completed-with-errors";
458
+ }
459
+ }
460
+ }
461
+ if (shouldGenerateSarif) {
462
+ try {
463
+ const externalPath = options.sarifExternalOutputPath
464
+ ?? (options.sarif?.outputPath
465
+ ? resolve(config.configDir, options.sarif.outputPath)
466
+ : undefined);
467
+ const sarifReport = await emitReportArtifact({
468
+ format: "sarif",
469
+ result,
470
+ bundleDirectory: writer.runDirectory,
471
+ ...(externalPath ? { externalOutputPath: externalPath } : {}),
472
+ });
473
+ sarifBundlePath = sarifReport.bundlePath;
474
+ sarifManifestEntry = sarifReport.manifestEntry;
475
+ sarifExternalPath = sarifReport.externalPath;
476
+ result.reports = mergeReportReferences(result.reports, buildSarifReportReference(sarifReport.resultReference));
477
+ }
478
+ catch (error) {
479
+ await writer.cleanupPartial().catch(() => {
480
+ // Keep the original SARIF failure as the operational error.
481
+ });
482
+ const message = error instanceof Error ? error.message : String(error);
483
+ throw new Error(`Failed to generate SARIF report: ${message}`, { cause: error });
484
+ }
485
+ }
486
+ if (shouldGenerateJunit) {
487
+ try {
488
+ const externalPath = options.junitExternalOutputPath
489
+ ?? (options.junit?.outputPath
490
+ ? resolve(config.configDir, options.junit.outputPath)
491
+ : undefined);
492
+ const junitReport = await emitReportArtifact({
493
+ format: "junit",
494
+ result,
495
+ bundleDirectory: writer.runDirectory,
496
+ ...(externalPath ? { externalOutputPath: externalPath } : {}),
497
+ policy: resolvedPolicy,
498
+ });
499
+ junitBundlePath = junitReport.bundlePath;
500
+ junitManifestEntry = junitReport.manifestEntry;
501
+ junitExternalPath = junitReport.externalPath;
502
+ result.reports = mergeReportReferences(result.reports, buildJunitReportReference(junitReport.resultReference));
503
+ }
504
+ catch (error) {
505
+ await writer.cleanupPartial().catch(() => {
506
+ // Keep the original JUnit failure as the operational error.
507
+ });
508
+ const message = error instanceof Error ? error.message : String(error);
509
+ throw new Error(`Failed to generate JUnit report: ${message}`, { cause: error });
510
+ }
511
+ }
512
+ if (shouldGenerateMarkdown) {
513
+ try {
514
+ const externalPath = options.markdownExternalOutputPath
515
+ ?? (options.markdown?.outputPath
516
+ ? resolve(config.configDir, options.markdown.outputPath)
517
+ : undefined);
518
+ const markdownReport = await emitReportArtifact({
519
+ format: "markdown",
520
+ result,
521
+ bundleDirectory: writer.runDirectory,
522
+ ...(externalPath ? { externalOutputPath: externalPath } : {}),
523
+ policy: resolvedPolicy,
524
+ artifactReportPaths: {
525
+ ...(reportGenerated ? { html: "report/index.html" } : {}),
526
+ ...(sarifBundlePath ? { sarif: sarifBundlePath } : {}),
527
+ ...(junitBundlePath ? { junit: junitBundlePath } : {}),
528
+ },
529
+ });
530
+ markdownBundlePath = markdownReport.bundlePath;
531
+ markdownManifestEntry = markdownReport.manifestEntry;
532
+ markdownExternalPath = markdownReport.externalPath;
533
+ markdownContentForSummary = markdownReport.serialized;
534
+ result.reports = mergeReportReferences(result.reports, buildMarkdownReportReference(markdownReport.resultReference));
535
+ }
536
+ catch (error) {
537
+ await writer.cleanupPartial().catch(() => { });
538
+ const message = error instanceof Error ? error.message : String(error);
539
+ throw new Error(`Failed to generate Markdown report: ${message}`, { cause: error });
540
+ }
541
+ }
542
+ if (shouldGenerateGitHubAnnotations) {
543
+ try {
544
+ const externalPath = options.githubAnnotationsExternalOutputPath
545
+ ?? (options.githubAnnotations?.outputPath
546
+ ? resolve(config.configDir, options.githubAnnotations.outputPath)
547
+ : undefined);
548
+ const githubReport = await emitReportArtifact({
549
+ format: "github-annotations",
550
+ result,
551
+ bundleDirectory: writer.runDirectory,
552
+ ...(externalPath ? { externalOutputPath: externalPath } : {}),
553
+ policy: resolvedPolicy,
554
+ });
555
+ githubAnnotationsBundlePath = githubReport.bundlePath;
556
+ githubAnnotationsManifestEntry = githubReport.manifestEntry;
557
+ githubAnnotationsExternalPath = githubReport.externalPath;
558
+ result.reports = mergeReportReferences(result.reports, buildGitHubAnnotationsReportReference(githubReport.resultReference));
559
+ }
560
+ catch (error) {
561
+ await writer.cleanupPartial().catch(() => { });
562
+ const message = error instanceof Error ? error.message : String(error);
563
+ throw new Error(`Failed to generate GitHub annotations: ${message}`, { cause: error });
564
+ }
565
+ }
566
+ if (shouldWriteStepSummary) {
567
+ const stepSummaryPath = options.githubStepSummaryPath?.trim();
568
+ if (!stepSummaryPath) {
569
+ await writer.cleanupPartial().catch(() => { });
570
+ throw new Error("GITHUB_STEP_SUMMARY is not set. Export GITHUB_STEP_SUMMARY in the GitHub Actions runner or omit --github-step-summary.");
571
+ }
572
+ try {
573
+ const markdown = markdownContentForSummary ??
574
+ generateMarkdownContentFromAuditResult(result, resolvedPolicy);
575
+ await appendGitHubStepSummary(stepSummaryPath, markdown);
576
+ githubStepSummaryWritten = true;
577
+ result.reports = mergeReportReferences(result.reports, buildGitHubStepSummaryReference());
578
+ }
579
+ catch (error) {
580
+ await writer.cleanupPartial().catch(() => { });
581
+ const message = error instanceof Error ? error.message : String(error);
582
+ throw new Error(`Failed to write GitHub step summary: ${message}`, { cause: error });
583
+ }
584
+ }
585
+ if (willGenerateReports) {
586
+ const reportLabels = [];
587
+ if (reportGenerated) {
588
+ reportLabels.push("HTML");
589
+ }
590
+ if (markdownBundlePath) {
591
+ reportLabels.push("Markdown");
592
+ }
593
+ if (sarifBundlePath) {
594
+ reportLabels.push("SARIF");
595
+ }
596
+ if (junitBundlePath) {
597
+ reportLabels.push("JUnit");
598
+ }
599
+ reportLabels.push("JSON");
600
+ progress?.succeed(reportLabels.join(" · "));
601
+ }
602
+ result.artifacts = relativeArtifactReferences(writer, reportGenerated, sarifBundlePath, junitBundlePath, markdownBundlePath, githubAnnotationsBundlePath);
603
+ const sarifExternalPathForOutput = sarifExternalPath;
604
+ const junitExternalPathForOutput = junitExternalPath;
605
+ const markdownExternalPathForOutput = markdownExternalPath;
606
+ const githubAnnotationsExternalPathForOutput = githubAnnotationsExternalPath;
607
+ const persistedResult = structuredClone(result);
608
+ removeUndefinedProperties(persistedResult);
609
+ const manifest = buildManifest(config, persistedResult, writer, reportGenerated, sarifManifestEntry, junitManifestEntry, markdownManifestEntry, githubAnnotationsManifestEntry, githubStepSummaryWritten);
610
+ try {
611
+ const artifactReferences = await writer.finalize({
612
+ result: persistedResult,
613
+ manifest,
614
+ baselineComparison: baselineApplied.artifact,
615
+ });
616
+ result.artifacts = artifactReferences;
617
+ if (sarifExternalPathForOutput) {
618
+ result.sarifExternalPath =
619
+ sarifExternalPathForOutput;
620
+ }
621
+ if (junitExternalPathForOutput) {
622
+ result.junitExternalPath =
623
+ junitExternalPathForOutput;
624
+ }
625
+ if (markdownExternalPathForOutput) {
626
+ result.markdownExternalPath =
627
+ markdownExternalPathForOutput;
628
+ }
629
+ if (githubAnnotationsExternalPathForOutput) {
630
+ result.githubAnnotationsExternalPath = githubAnnotationsExternalPathForOutput;
631
+ }
632
+ if (githubStepSummaryWritten) {
633
+ result.githubStepSummaryWritten =
634
+ true;
635
+ }
636
+ return result;
637
+ }
638
+ catch (error) {
639
+ await writer.cleanupPartial().catch(() => {
640
+ // Keep the original persistence failure as the operational error.
641
+ });
642
+ const message = error instanceof Error ? error.message : String(error);
643
+ throw new Error(`Failed to finalize audit artifact bundle: ${message}`, { cause: error });
644
+ }
645
+ }
646
+ //# sourceMappingURL=execute-audit.js.map