@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegovelasquezweb/a11y-engine",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "WCAG 2.2 AA accessibility audit engine — scanner, analyzer, and report builders",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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 EngineAssets {
21
- intelligence: Record<string, unknown>;
22
- pa11yConfig: Record<string, unknown>;
23
- complianceConfig: Record<string, unknown>;
24
- wcagReference: Record<string, unknown>;
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
- export function getAssets(): EngineAssets;
28
-
29
- export function mapPa11yRuleToCanonical(
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 function generatePDF(
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<Buffer>;
126
+ ): Promise<PDFReport>;
59
127
 
60
- export function generateChecklist(
128
+ export function getChecklist(
61
129
  options?: Pick<ReportOptions, "baseUrl">
62
- ): Promise<string>;
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
- export function getAssets() {
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
- export function mapPa11yRuleToCanonical(ruleId, sourceRuleId = null, checkData = null) {
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 enrichFindings(findings) {
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
- if (normalized.fixDescription || normalized.fix_description ||
140
- normalized.fixCode || normalized.fix_code) {
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 ?? normalized.false_positive_risk ?? info.false_positive_risk ?? null,
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 ?? normalized.fix_difficulty_notes ?? info.fix_difficulty_notes ?? null,
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 generatePDF(payload, options = {}) {
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 Buffer.from(pdfBuffer);
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 generateChecklist(options = {}) {
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
- return `<!doctype html>
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
  }