@diegovelasquezweb/a11y-engine 0.1.6 → 0.1.8
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/package.json +1 -1
- package/scripts/index.d.mts +101 -33
- package/scripts/index.mjs +57 -27
package/package.json
CHANGED
package/scripts/index.d.mts
CHANGED
|
@@ -1,3 +1,71 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Core types
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
export interface Finding {
|
|
6
|
+
id: string;
|
|
7
|
+
rule_id: string;
|
|
8
|
+
title: string;
|
|
9
|
+
severity: string;
|
|
10
|
+
wcag: string;
|
|
11
|
+
wcag_classification: string | null;
|
|
12
|
+
wcag_criterion_id: string | null;
|
|
13
|
+
category: string | null;
|
|
14
|
+
area: string;
|
|
15
|
+
url: string;
|
|
16
|
+
selector: string;
|
|
17
|
+
primary_selector: string;
|
|
18
|
+
impacted_users: string;
|
|
19
|
+
actual: string;
|
|
20
|
+
expected: string;
|
|
21
|
+
primary_failure_mode: string | null;
|
|
22
|
+
relationship_hint: string | null;
|
|
23
|
+
failure_checks: unknown[];
|
|
24
|
+
related_context: unknown[];
|
|
25
|
+
mdn: string | null;
|
|
26
|
+
fix_description: string | null;
|
|
27
|
+
fix_code: string | null;
|
|
28
|
+
fix_code_lang: string | null;
|
|
29
|
+
recommended_fix: string;
|
|
30
|
+
evidence: unknown[];
|
|
31
|
+
total_instances: number | null;
|
|
32
|
+
effort: string | null;
|
|
33
|
+
related_rules: string[];
|
|
34
|
+
screenshot_path: string | null;
|
|
35
|
+
false_positive_risk: string | null;
|
|
36
|
+
guardrails: Record<string, unknown> | null;
|
|
37
|
+
fix_difficulty_notes: string | string[] | null;
|
|
38
|
+
framework_notes: string | null;
|
|
39
|
+
cms_notes: string | null;
|
|
40
|
+
file_search_pattern: string | null;
|
|
41
|
+
ownership_status: string;
|
|
42
|
+
ownership_reason: string | null;
|
|
43
|
+
primary_source_scope: string[];
|
|
44
|
+
search_strategy: string;
|
|
45
|
+
managed_by_library: string | null;
|
|
46
|
+
component_hint: string | null;
|
|
47
|
+
verification_command: string | null;
|
|
48
|
+
verification_command_fallback: string | null;
|
|
49
|
+
check_data: Record<string, unknown> | null;
|
|
50
|
+
source?: string;
|
|
51
|
+
source_rule_id?: string | null;
|
|
52
|
+
pages_affected?: number | null;
|
|
53
|
+
affected_urls?: string[] | null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface EnrichedFinding extends Finding {
|
|
57
|
+
ruleId: string;
|
|
58
|
+
sourceRuleId: string | null;
|
|
59
|
+
fixDescription: string | null;
|
|
60
|
+
fixCode: string | null;
|
|
61
|
+
fixCodeLang: string | null;
|
|
62
|
+
falsePositiveRisk: string | null;
|
|
63
|
+
fixDifficultyNotes: string | string[] | null;
|
|
64
|
+
screenshotPath: string | null;
|
|
65
|
+
wcagCriterionId: string | null;
|
|
66
|
+
impactedUsers: string | null;
|
|
67
|
+
}
|
|
68
|
+
|
|
1
69
|
export interface SeverityTotals {
|
|
2
70
|
Critical: number;
|
|
3
71
|
Serious: number;
|
|
@@ -5,45 +73,26 @@ export interface SeverityTotals {
|
|
|
5
73
|
Minor: number;
|
|
6
74
|
}
|
|
7
75
|
|
|
8
|
-
export interface ScoreResult {
|
|
9
|
-
score: number;
|
|
10
|
-
label: string;
|
|
11
|
-
wcagStatus: "Pass" | "Conditional Pass" | "Fail";
|
|
12
|
-
}
|
|
13
|
-
|
|
14
76
|
export interface PersonaGroup {
|
|
15
77
|
label: string;
|
|
16
78
|
count: number;
|
|
17
79
|
icon: string;
|
|
18
80
|
}
|
|
19
81
|
|
|
20
|
-
export interface
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
82
|
+
export interface AuditSummary {
|
|
83
|
+
totals: SeverityTotals;
|
|
84
|
+
score: number;
|
|
85
|
+
label: string;
|
|
86
|
+
wcagStatus: "Pass" | "Conditional Pass" | "Fail";
|
|
87
|
+
personaGroups: Record<string, PersonaGroup>;
|
|
25
88
|
}
|
|
26
89
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
ruleId: string,
|
|
31
|
-
sourceRuleId?: string | null,
|
|
32
|
-
checkData?: Record<string, unknown> | null
|
|
33
|
-
): string;
|
|
34
|
-
|
|
35
|
-
export function enrichFindings<T extends Record<string, unknown>>(
|
|
36
|
-
findings: T[]
|
|
37
|
-
): T[];
|
|
38
|
-
|
|
39
|
-
export function computeScore(totals: SeverityTotals): ScoreResult;
|
|
40
|
-
|
|
41
|
-
export function computePersonaGroups(
|
|
42
|
-
findings: Record<string, unknown>[]
|
|
43
|
-
): Record<string, PersonaGroup>;
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Report types
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
44
93
|
|
|
45
94
|
export interface ScanPayload {
|
|
46
|
-
findings: Record<string, unknown>[];
|
|
95
|
+
findings: Finding[] | Record<string, unknown>[];
|
|
47
96
|
metadata?: Record<string, unknown>;
|
|
48
97
|
}
|
|
49
98
|
|
|
@@ -52,11 +101,30 @@ export interface ReportOptions {
|
|
|
52
101
|
target?: string;
|
|
53
102
|
}
|
|
54
103
|
|
|
55
|
-
export
|
|
104
|
+
export interface PDFReport {
|
|
105
|
+
buffer: Buffer;
|
|
106
|
+
contentType: "application/pdf";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface ChecklistReport {
|
|
110
|
+
html: string;
|
|
111
|
+
contentType: "text/html";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Public API
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
export function getEnrichedFindings(findings: Finding[]): EnrichedFinding[];
|
|
119
|
+
export function getEnrichedFindings(findings: Record<string, unknown>[]): EnrichedFinding[];
|
|
120
|
+
|
|
121
|
+
export function getAuditSummary(findings: EnrichedFinding[] | Record<string, unknown>[]): AuditSummary;
|
|
122
|
+
|
|
123
|
+
export function getPDFReport(
|
|
56
124
|
payload: ScanPayload,
|
|
57
125
|
options?: ReportOptions
|
|
58
|
-
): Promise<
|
|
126
|
+
): Promise<PDFReport>;
|
|
59
127
|
|
|
60
|
-
export function
|
|
128
|
+
export function getChecklist(
|
|
61
129
|
options?: Pick<ReportOptions, "baseUrl">
|
|
62
|
-
): Promise<
|
|
130
|
+
): Promise<ChecklistReport>;
|
package/scripts/index.mjs
CHANGED
|
@@ -49,7 +49,7 @@ function getWcagReference() {
|
|
|
49
49
|
* Returns all engine asset data. Lazy-loaded and cached.
|
|
50
50
|
* @returns {{ intelligence: object, pa11yConfig: object, complianceConfig: object, wcagReference: object }}
|
|
51
51
|
*/
|
|
52
|
-
|
|
52
|
+
function getAssets() {
|
|
53
53
|
return {
|
|
54
54
|
intelligence: getIntelligence(),
|
|
55
55
|
pa11yConfig: getPa11yConfig(),
|
|
@@ -82,7 +82,7 @@ function normalizePa11yToken(value) {
|
|
|
82
82
|
* @param {object|null} checkData - The check_data object which may contain a `code` field.
|
|
83
83
|
* @returns {string} The canonical rule ID (e.g., "color-contrast").
|
|
84
84
|
*/
|
|
85
|
-
|
|
85
|
+
function mapPa11yRuleToCanonical(ruleId, sourceRuleId = null, checkData = null) {
|
|
86
86
|
const equivalenceMap = getPa11yConfig().equivalenceMap || {};
|
|
87
87
|
|
|
88
88
|
const checkCode = checkData && typeof checkData === "object" && typeof checkData.code === "string"
|
|
@@ -119,7 +119,7 @@ export function mapPa11yRuleToCanonical(ruleId, sourceRuleId = null, checkData =
|
|
|
119
119
|
* @param {object[]} findings - Array of findings with camelCase keys.
|
|
120
120
|
* @returns {object[]} Enriched findings.
|
|
121
121
|
*/
|
|
122
|
-
export function
|
|
122
|
+
export function getEnrichedFindings(findings) {
|
|
123
123
|
const rules = getIntelligence().rules || {};
|
|
124
124
|
|
|
125
125
|
return findings.map((finding) => {
|
|
@@ -129,15 +129,24 @@ export function enrichFindings(findings) {
|
|
|
129
129
|
finding.checkData || finding.check_data || null,
|
|
130
130
|
);
|
|
131
131
|
|
|
132
|
+
// Always create camelCase aliases from snake_case fields
|
|
132
133
|
const normalized = {
|
|
133
134
|
...finding,
|
|
134
135
|
ruleId: canonical,
|
|
135
136
|
rule_id: canonical,
|
|
136
137
|
sourceRuleId: finding.sourceRuleId || finding.source_rule_id || finding.ruleId || finding.rule_id || null,
|
|
138
|
+
fixDescription: finding.fixDescription ?? finding.fix_description ?? null,
|
|
139
|
+
fixCode: finding.fixCode ?? finding.fix_code ?? null,
|
|
140
|
+
fixCodeLang: finding.fixCodeLang ?? finding.fix_code_lang ?? null,
|
|
141
|
+
falsePositiveRisk: finding.falsePositiveRisk ?? finding.false_positive_risk ?? null,
|
|
142
|
+
fixDifficultyNotes: finding.fixDifficultyNotes ?? finding.fix_difficulty_notes ?? null,
|
|
143
|
+
screenshotPath: finding.screenshotPath ?? finding.screenshot_path ?? null,
|
|
144
|
+
wcagCriterionId: finding.wcagCriterionId ?? finding.wcag_criterion_id ?? null,
|
|
145
|
+
impactedUsers: finding.impactedUsers ?? finding.impacted_users ?? null,
|
|
137
146
|
};
|
|
138
147
|
|
|
139
|
-
|
|
140
|
-
|
|
148
|
+
// If fix data already exists, no need to look up intelligence
|
|
149
|
+
if (normalized.fixDescription || normalized.fixCode) {
|
|
141
150
|
return normalized;
|
|
142
151
|
}
|
|
143
152
|
|
|
@@ -148,27 +157,22 @@ export function enrichFindings(findings) {
|
|
|
148
157
|
...normalized,
|
|
149
158
|
category: normalized.category ?? info.category ?? null,
|
|
150
159
|
fixDescription: info.fix?.description ?? null,
|
|
151
|
-
fix_description: info.fix?.description ?? null,
|
|
160
|
+
fix_description: info.fix?.description ?? normalized.fix_description ?? null,
|
|
152
161
|
fixCode: info.fix?.code ?? null,
|
|
153
|
-
fix_code: info.fix?.code ?? null,
|
|
154
|
-
falsePositiveRisk: normalized.falsePositiveRisk ??
|
|
162
|
+
fix_code: info.fix?.code ?? normalized.fix_code ?? null,
|
|
163
|
+
falsePositiveRisk: normalized.falsePositiveRisk ?? info.false_positive_risk ?? null,
|
|
155
164
|
false_positive_risk: normalized.false_positive_risk ?? info.false_positive_risk ?? null,
|
|
156
|
-
fixDifficultyNotes: normalized.fixDifficultyNotes ??
|
|
165
|
+
fixDifficultyNotes: normalized.fixDifficultyNotes ?? info.fix_difficulty_notes ?? null,
|
|
157
166
|
fix_difficulty_notes: normalized.fix_difficulty_notes ?? info.fix_difficulty_notes ?? null,
|
|
158
167
|
};
|
|
159
168
|
});
|
|
160
169
|
}
|
|
161
170
|
|
|
162
171
|
// ---------------------------------------------------------------------------
|
|
163
|
-
// Score computation
|
|
172
|
+
// Score computation (internal)
|
|
164
173
|
// ---------------------------------------------------------------------------
|
|
165
174
|
|
|
166
|
-
|
|
167
|
-
* Computes compliance score, grade label, and WCAG pass/fail status.
|
|
168
|
-
* @param {{ Critical: number, Serious: number, Moderate: number, Minor: number }} totals
|
|
169
|
-
* @returns {{ score: number, label: string, wcagStatus: "Pass" | "Conditional Pass" | "Fail" }}
|
|
170
|
-
*/
|
|
171
|
-
export function computeScore(totals) {
|
|
175
|
+
function getComplianceScore(totals) {
|
|
172
176
|
const config = getComplianceConfig();
|
|
173
177
|
const penalties = config.complianceScore.penalties;
|
|
174
178
|
const thresholds = config.gradeThresholds;
|
|
@@ -198,15 +202,10 @@ export function computeScore(totals) {
|
|
|
198
202
|
}
|
|
199
203
|
|
|
200
204
|
// ---------------------------------------------------------------------------
|
|
201
|
-
// Persona grouping
|
|
205
|
+
// Persona grouping (internal)
|
|
202
206
|
// ---------------------------------------------------------------------------
|
|
203
207
|
|
|
204
|
-
|
|
205
|
-
* Groups findings by accessibility persona (screen reader, keyboard, cognitive, etc.).
|
|
206
|
-
* @param {object[]} findings - Array of findings with ruleId, wcagCriterionId, impactedUsers.
|
|
207
|
-
* @returns {Record<string, { label: string, count: number, icon: string }>}
|
|
208
|
-
*/
|
|
209
|
-
export function computePersonaGroups(findings) {
|
|
208
|
+
function getPersonaGroups(findings) {
|
|
210
209
|
const ref = getWcagReference();
|
|
211
210
|
const personaConfig = ref.personaConfig || {};
|
|
212
211
|
const personaMapping = ref.personaMapping || {};
|
|
@@ -261,6 +260,29 @@ export function computePersonaGroups(findings) {
|
|
|
261
260
|
return groups;
|
|
262
261
|
}
|
|
263
262
|
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
// Audit summary
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Computes a complete audit summary from enriched findings: severity totals,
|
|
269
|
+
* compliance score, grade label, WCAG status, and persona impact groups.
|
|
270
|
+
* @param {object[]} findings - Array of enriched findings.
|
|
271
|
+
* @returns {{ totals, score, label, wcagStatus, personaGroups }}
|
|
272
|
+
*/
|
|
273
|
+
export function getAuditSummary(findings) {
|
|
274
|
+
const totals = { Critical: 0, Serious: 0, Moderate: 0, Minor: 0 };
|
|
275
|
+
for (const f of findings) {
|
|
276
|
+
const severity = f.severity || f.Severity || "";
|
|
277
|
+
if (severity in totals) totals[severity] += 1;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const { score, label, wcagStatus } = getComplianceScore(totals);
|
|
281
|
+
const personaGroups = getPersonaGroups(findings);
|
|
282
|
+
|
|
283
|
+
return { totals, score, label, wcagStatus, personaGroups };
|
|
284
|
+
}
|
|
285
|
+
|
|
264
286
|
// ---------------------------------------------------------------------------
|
|
265
287
|
// Report generation
|
|
266
288
|
// ---------------------------------------------------------------------------
|
|
@@ -281,7 +303,7 @@ import {
|
|
|
281
303
|
* @param {{ baseUrl?: string, target?: string }} [options={}]
|
|
282
304
|
* @returns {Promise<Buffer>} The PDF as a Node.js Buffer.
|
|
283
305
|
*/
|
|
284
|
-
export async function
|
|
306
|
+
export async function getPDFReport(payload, options = {}) {
|
|
285
307
|
const { chromium } = await import("playwright");
|
|
286
308
|
const {
|
|
287
309
|
buildPdfCoverPage,
|
|
@@ -334,7 +356,10 @@ ${buildPdfAuditLimitations()}
|
|
|
334
356
|
margin: { top: "1cm", right: "1cm", bottom: "1cm", left: "1cm" },
|
|
335
357
|
displayHeaderFooter: false,
|
|
336
358
|
});
|
|
337
|
-
return
|
|
359
|
+
return {
|
|
360
|
+
buffer: Buffer.from(pdfBuffer),
|
|
361
|
+
contentType: "application/pdf",
|
|
362
|
+
};
|
|
338
363
|
} finally {
|
|
339
364
|
await browser.close();
|
|
340
365
|
}
|
|
@@ -346,7 +371,7 @@ ${buildPdfAuditLimitations()}
|
|
|
346
371
|
* @param {{ baseUrl?: string }} [options={}]
|
|
347
372
|
* @returns {Promise<string>} The complete checklist HTML document.
|
|
348
373
|
*/
|
|
349
|
-
export async function
|
|
374
|
+
export async function getChecklist(options = {}) {
|
|
350
375
|
const { buildManualCheckCard } = await import("./reports/renderers/html.mjs");
|
|
351
376
|
const { escapeHtml } = await import("./reports/renderers/utils.mjs");
|
|
352
377
|
|
|
@@ -365,7 +390,7 @@ export async function generateChecklist(options = {}) {
|
|
|
365
390
|
// Import the full checklist builder to reuse its buildHtml
|
|
366
391
|
// The checklist builder module has a main() that auto-runs, so we dynamically
|
|
367
392
|
// construct the same output using the renderer functions directly.
|
|
368
|
-
|
|
393
|
+
const html = `<!doctype html>
|
|
369
394
|
<html lang="en">
|
|
370
395
|
<head>
|
|
371
396
|
<meta charset="utf-8">
|
|
@@ -441,4 +466,9 @@ export async function generateChecklist(options = {}) {
|
|
|
441
466
|
<\/script>
|
|
442
467
|
</body>
|
|
443
468
|
</html>`;
|
|
469
|
+
|
|
470
|
+
return {
|
|
471
|
+
html,
|
|
472
|
+
contentType: "text/html",
|
|
473
|
+
};
|
|
444
474
|
}
|