@diegovelasquezweb/a11y-engine 0.1.8 → 0.1.9
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 +47 -3
- package/scripts/index.mjs +215 -79
package/package.json
CHANGED
package/scripts/index.d.mts
CHANGED
|
@@ -63,7 +63,28 @@ export interface EnrichedFinding extends Finding {
|
|
|
63
63
|
fixDifficultyNotes: string | string[] | null;
|
|
64
64
|
screenshotPath: string | null;
|
|
65
65
|
wcagCriterionId: string | null;
|
|
66
|
+
wcagClassification: string | null;
|
|
66
67
|
impactedUsers: string | null;
|
|
68
|
+
primarySelector: string;
|
|
69
|
+
primaryFailureMode: string | null;
|
|
70
|
+
relationshipHint: string | null;
|
|
71
|
+
failureChecks: unknown[];
|
|
72
|
+
relatedContext: unknown[];
|
|
73
|
+
recommendedFix: string;
|
|
74
|
+
totalInstances: number | null;
|
|
75
|
+
relatedRules: string[];
|
|
76
|
+
ownershipStatus: string;
|
|
77
|
+
ownershipReason: string | null;
|
|
78
|
+
primarySourceScope: string[];
|
|
79
|
+
searchStrategy: string;
|
|
80
|
+
managedByLibrary: string | null;
|
|
81
|
+
componentHint: string | null;
|
|
82
|
+
verificationCommand: string | null;
|
|
83
|
+
verificationCommandFallback: string | null;
|
|
84
|
+
checkData: Record<string, unknown> | null;
|
|
85
|
+
pagesAffected: number | null;
|
|
86
|
+
affectedUrls: string[] | null;
|
|
87
|
+
effort: string;
|
|
67
88
|
}
|
|
68
89
|
|
|
69
90
|
export interface SeverityTotals {
|
|
@@ -79,12 +100,22 @@ export interface PersonaGroup {
|
|
|
79
100
|
icon: string;
|
|
80
101
|
}
|
|
81
102
|
|
|
103
|
+
export interface DetectedStack {
|
|
104
|
+
framework: string | null;
|
|
105
|
+
cms: string | null;
|
|
106
|
+
uiLibraries: string[];
|
|
107
|
+
}
|
|
108
|
+
|
|
82
109
|
export interface AuditSummary {
|
|
83
110
|
totals: SeverityTotals;
|
|
84
111
|
score: number;
|
|
85
112
|
label: string;
|
|
86
113
|
wcagStatus: "Pass" | "Conditional Pass" | "Fail";
|
|
87
114
|
personaGroups: Record<string, PersonaGroup>;
|
|
115
|
+
quickWins: EnrichedFinding[];
|
|
116
|
+
targetUrl: string;
|
|
117
|
+
detectedStack: DetectedStack;
|
|
118
|
+
totalFindings: number;
|
|
88
119
|
}
|
|
89
120
|
|
|
90
121
|
// ---------------------------------------------------------------------------
|
|
@@ -111,14 +142,27 @@ export interface ChecklistReport {
|
|
|
111
142
|
contentType: "text/html";
|
|
112
143
|
}
|
|
113
144
|
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Enrichment options
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
export interface EnrichmentOptions {
|
|
150
|
+
screenshotUrlBuilder?: (rawPath: string) => string;
|
|
151
|
+
}
|
|
152
|
+
|
|
114
153
|
// ---------------------------------------------------------------------------
|
|
115
154
|
// Public API
|
|
116
155
|
// ---------------------------------------------------------------------------
|
|
117
156
|
|
|
118
|
-
export function getEnrichedFindings(
|
|
119
|
-
|
|
157
|
+
export function getEnrichedFindings(
|
|
158
|
+
input: ScanPayload | Finding[] | Record<string, unknown>[],
|
|
159
|
+
options?: EnrichmentOptions
|
|
160
|
+
): EnrichedFinding[];
|
|
120
161
|
|
|
121
|
-
export function getAuditSummary(
|
|
162
|
+
export function getAuditSummary(
|
|
163
|
+
findings: EnrichedFinding[],
|
|
164
|
+
payload?: ScanPayload | null
|
|
165
|
+
): AuditSummary;
|
|
122
166
|
|
|
123
167
|
export function getPDFReport(
|
|
124
168
|
payload: ScanPayload,
|
package/scripts/index.mjs
CHANGED
|
@@ -42,31 +42,9 @@ function getWcagReference() {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// ---------------------------------------------------------------------------
|
|
45
|
-
//
|
|
45
|
+
// Pa11y rule canonicalization (internal)
|
|
46
46
|
// ---------------------------------------------------------------------------
|
|
47
47
|
|
|
48
|
-
/**
|
|
49
|
-
* Returns all engine asset data. Lazy-loaded and cached.
|
|
50
|
-
* @returns {{ intelligence: object, pa11yConfig: object, complianceConfig: object, wcagReference: object }}
|
|
51
|
-
*/
|
|
52
|
-
function getAssets() {
|
|
53
|
-
return {
|
|
54
|
-
intelligence: getIntelligence(),
|
|
55
|
-
pa11yConfig: getPa11yConfig(),
|
|
56
|
-
complianceConfig: getComplianceConfig(),
|
|
57
|
-
wcagReference: getWcagReference(),
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
// Pa11y rule canonicalization
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Normalizes a pa11y code token for comparison.
|
|
67
|
-
* @param {string} value
|
|
68
|
-
* @returns {string}
|
|
69
|
-
*/
|
|
70
48
|
function normalizePa11yToken(value) {
|
|
71
49
|
return value
|
|
72
50
|
.toLowerCase()
|
|
@@ -75,13 +53,6 @@ function normalizePa11yToken(value) {
|
|
|
75
53
|
.replace(/[^a-z0-9]/g, "");
|
|
76
54
|
}
|
|
77
55
|
|
|
78
|
-
/**
|
|
79
|
-
* Maps a pa11y rule ID to its canonical axe-equivalent ID.
|
|
80
|
-
* @param {string} ruleId - The current rule ID (may already be canonical or a pa11y slug).
|
|
81
|
-
* @param {string|null} sourceRuleId - The original pa11y code if available.
|
|
82
|
-
* @param {object|null} checkData - The check_data object which may contain a `code` field.
|
|
83
|
-
* @returns {string} The canonical rule ID (e.g., "color-contrast").
|
|
84
|
-
*/
|
|
85
56
|
function mapPa11yRuleToCanonical(ruleId, sourceRuleId = null, checkData = null) {
|
|
86
57
|
const equivalenceMap = getPa11yConfig().equivalenceMap || {};
|
|
87
58
|
|
|
@@ -109,63 +80,196 @@ function mapPa11yRuleToCanonical(ruleId, sourceRuleId = null, checkData = null)
|
|
|
109
80
|
return ruleId;
|
|
110
81
|
}
|
|
111
82
|
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Raw finding normalization (internal)
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
const SEVERITY_ORDER = { Critical: 1, Serious: 2, Moderate: 3, Minor: 4 };
|
|
88
|
+
|
|
89
|
+
function str(v, fallback = "") {
|
|
90
|
+
return typeof v === "string" ? v : (v != null ? String(v) : fallback);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function strOrNull(v) {
|
|
94
|
+
return typeof v === "string" && v.length > 0 ? v : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function normalizeSingleFinding(item, index, screenshotUrlBuilder) {
|
|
98
|
+
const screenshotRaw = strOrNull(item.screenshot_path);
|
|
99
|
+
const screenshotPath = screenshotRaw && screenshotUrlBuilder
|
|
100
|
+
? screenshotUrlBuilder(screenshotRaw)
|
|
101
|
+
: screenshotRaw;
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
id: str(item.id, `A11Y-${String(index + 1).padStart(3, "0")}`),
|
|
105
|
+
rule_id: str(item.rule_id),
|
|
106
|
+
source: str(item.source, "axe"),
|
|
107
|
+
source_rule_id: strOrNull(item.source_rule_id),
|
|
108
|
+
wcag_criterion_id: strOrNull(item.wcag_criterion_id),
|
|
109
|
+
category: strOrNull(item.category),
|
|
110
|
+
title: str(item.title, "Untitled finding"),
|
|
111
|
+
severity: str(item.severity, "Unknown"),
|
|
112
|
+
wcag: str(item.wcag),
|
|
113
|
+
wcag_classification: strOrNull(item.wcag_classification),
|
|
114
|
+
area: str(item.area),
|
|
115
|
+
url: str(item.url),
|
|
116
|
+
selector: str(item.selector),
|
|
117
|
+
primary_selector: str(item.primary_selector || item.selector),
|
|
118
|
+
impacted_users: str(item.impacted_users),
|
|
119
|
+
actual: str(item.actual),
|
|
120
|
+
primary_failure_mode: strOrNull(item.primary_failure_mode),
|
|
121
|
+
relationship_hint: strOrNull(item.relationship_hint),
|
|
122
|
+
failure_checks: Array.isArray(item.failure_checks) ? item.failure_checks : [],
|
|
123
|
+
related_context: Array.isArray(item.related_context) ? item.related_context : [],
|
|
124
|
+
expected: str(item.expected),
|
|
125
|
+
mdn: strOrNull(item.mdn),
|
|
126
|
+
fix_description: strOrNull(item.fix_description),
|
|
127
|
+
fix_code: strOrNull(item.fix_code),
|
|
128
|
+
fix_code_lang: str(item.fix_code_lang, "html"),
|
|
129
|
+
recommended_fix: str(item.recommended_fix),
|
|
130
|
+
evidence: Array.isArray(item.evidence) ? item.evidence : [],
|
|
131
|
+
total_instances: typeof item.total_instances === "number" ? item.total_instances : null,
|
|
132
|
+
effort: strOrNull(item.effort), // null = will be inferred during enrichment
|
|
133
|
+
related_rules: Array.isArray(item.related_rules) ? item.related_rules : [],
|
|
134
|
+
screenshot_path: screenshotPath,
|
|
135
|
+
false_positive_risk: strOrNull(item.false_positive_risk),
|
|
136
|
+
guardrails: item.guardrails && typeof item.guardrails === "object" ? item.guardrails : null,
|
|
137
|
+
fix_difficulty_notes: item.fix_difficulty_notes ?? null,
|
|
138
|
+
framework_notes: strOrNull(item.framework_notes),
|
|
139
|
+
cms_notes: strOrNull(item.cms_notes),
|
|
140
|
+
file_search_pattern: strOrNull(item.file_search_pattern),
|
|
141
|
+
ownership_status: str(item.ownership_status, "unknown"),
|
|
142
|
+
ownership_reason: strOrNull(item.ownership_reason),
|
|
143
|
+
primary_source_scope: Array.isArray(item.primary_source_scope) ? item.primary_source_scope : [],
|
|
144
|
+
search_strategy: str(item.search_strategy, "verify_ownership_before_search"),
|
|
145
|
+
managed_by_library: strOrNull(item.managed_by_library),
|
|
146
|
+
component_hint: strOrNull(item.component_hint),
|
|
147
|
+
verification_command: strOrNull(item.verification_command),
|
|
148
|
+
verification_command_fallback: strOrNull(item.verification_command_fallback),
|
|
149
|
+
check_data: item.check_data && typeof item.check_data === "object" ? item.check_data : null,
|
|
150
|
+
pages_affected: typeof item.pages_affected === "number" ? item.pages_affected : null,
|
|
151
|
+
affected_urls: Array.isArray(item.affected_urls) ? item.affected_urls : null,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
112
155
|
// ---------------------------------------------------------------------------
|
|
113
156
|
// Finding enrichment
|
|
114
157
|
// ---------------------------------------------------------------------------
|
|
115
158
|
|
|
116
159
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
160
|
+
* Normalizes and enriches raw findings with intelligence data.
|
|
161
|
+
*
|
|
162
|
+
* Accepts either:
|
|
163
|
+
* - A full scan payload: { findings: object[], metadata?: object }
|
|
164
|
+
* - An array of findings directly: object[]
|
|
165
|
+
*
|
|
166
|
+
* Options:
|
|
167
|
+
* - screenshotUrlBuilder: (rawPath: string) => string — transforms screenshot
|
|
168
|
+
* paths into consumer-specific URLs.
|
|
169
|
+
*
|
|
170
|
+
* @param {object[]|{findings: object[]}} input
|
|
171
|
+
* @param {{ screenshotUrlBuilder?: (path: string) => string }} [options={}]
|
|
172
|
+
* @returns {object[]} Enriched, normalized, sorted findings.
|
|
121
173
|
*/
|
|
122
|
-
export function getEnrichedFindings(
|
|
174
|
+
export function getEnrichedFindings(input, options = {}) {
|
|
175
|
+
const { screenshotUrlBuilder = null } = options;
|
|
123
176
|
const rules = getIntelligence().rules || {};
|
|
124
177
|
|
|
125
|
-
|
|
178
|
+
// Accept payload object or array directly
|
|
179
|
+
const rawFindings = Array.isArray(input) ? input : (input?.findings || []);
|
|
180
|
+
|
|
181
|
+
// Normalize raw findings
|
|
182
|
+
const normalized = rawFindings.map((item, index) =>
|
|
183
|
+
normalizeSingleFinding(item, index, screenshotUrlBuilder)
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// Enrich with intelligence + camelCase aliases
|
|
187
|
+
const enriched = normalized.map((finding) => {
|
|
126
188
|
const canonical = mapPa11yRuleToCanonical(
|
|
127
|
-
finding.
|
|
128
|
-
finding.
|
|
129
|
-
finding.
|
|
189
|
+
finding.rule_id,
|
|
190
|
+
finding.source_rule_id,
|
|
191
|
+
finding.check_data,
|
|
130
192
|
);
|
|
131
193
|
|
|
132
|
-
//
|
|
133
|
-
|
|
194
|
+
// Effort will be inferred after enrichment
|
|
195
|
+
|
|
196
|
+
// Always create camelCase aliases
|
|
197
|
+
const withAliases = {
|
|
134
198
|
...finding,
|
|
135
199
|
ruleId: canonical,
|
|
136
200
|
rule_id: canonical,
|
|
137
|
-
sourceRuleId: finding.
|
|
138
|
-
fixDescription: finding.
|
|
139
|
-
fixCode: finding.
|
|
140
|
-
fixCodeLang: finding.
|
|
141
|
-
falsePositiveRisk: finding.
|
|
142
|
-
fixDifficultyNotes: finding.
|
|
143
|
-
screenshotPath: finding.
|
|
144
|
-
wcagCriterionId: finding.
|
|
145
|
-
|
|
201
|
+
sourceRuleId: finding.source_rule_id || finding.rule_id || null,
|
|
202
|
+
fixDescription: finding.fix_description,
|
|
203
|
+
fixCode: finding.fix_code,
|
|
204
|
+
fixCodeLang: finding.fix_code_lang,
|
|
205
|
+
falsePositiveRisk: finding.false_positive_risk,
|
|
206
|
+
fixDifficultyNotes: finding.fix_difficulty_notes,
|
|
207
|
+
screenshotPath: finding.screenshot_path,
|
|
208
|
+
wcagCriterionId: finding.wcag_criterion_id,
|
|
209
|
+
wcagClassification: finding.wcag_classification,
|
|
210
|
+
impactedUsers: finding.impacted_users,
|
|
211
|
+
primarySelector: finding.primary_selector,
|
|
212
|
+
primaryFailureMode: finding.primary_failure_mode,
|
|
213
|
+
relationshipHint: finding.relationship_hint,
|
|
214
|
+
failureChecks: finding.failure_checks,
|
|
215
|
+
relatedContext: finding.related_context,
|
|
216
|
+
recommendedFix: finding.recommended_fix,
|
|
217
|
+
totalInstances: finding.total_instances,
|
|
218
|
+
relatedRules: finding.related_rules,
|
|
219
|
+
ownershipStatus: finding.ownership_status,
|
|
220
|
+
ownershipReason: finding.ownership_reason,
|
|
221
|
+
primarySourceScope: finding.primary_source_scope,
|
|
222
|
+
searchStrategy: finding.search_strategy,
|
|
223
|
+
managedByLibrary: finding.managed_by_library,
|
|
224
|
+
componentHint: finding.component_hint,
|
|
225
|
+
verificationCommand: finding.verification_command,
|
|
226
|
+
verificationCommandFallback: finding.verification_command_fallback,
|
|
227
|
+
checkData: finding.check_data,
|
|
228
|
+
pagesAffected: finding.pages_affected,
|
|
229
|
+
affectedUrls: finding.affected_urls,
|
|
146
230
|
};
|
|
147
231
|
|
|
148
232
|
// If fix data already exists, no need to look up intelligence
|
|
149
|
-
|
|
150
|
-
|
|
233
|
+
let result;
|
|
234
|
+
if (withAliases.fixDescription || withAliases.fixCode) {
|
|
235
|
+
result = withAliases;
|
|
236
|
+
} else {
|
|
237
|
+
const info = rules[canonical];
|
|
238
|
+
if (!info) {
|
|
239
|
+
result = withAliases;
|
|
240
|
+
} else {
|
|
241
|
+
result = {
|
|
242
|
+
...withAliases,
|
|
243
|
+
category: withAliases.category ?? info.category ?? null,
|
|
244
|
+
fixDescription: info.fix?.description ?? null,
|
|
245
|
+
fix_description: info.fix?.description ?? null,
|
|
246
|
+
fixCode: info.fix?.code ?? null,
|
|
247
|
+
fix_code: info.fix?.code ?? withAliases.fix_code ?? null,
|
|
248
|
+
falsePositiveRisk: withAliases.falsePositiveRisk ?? info.false_positive_risk ?? null,
|
|
249
|
+
false_positive_risk: withAliases.false_positive_risk ?? info.false_positive_risk ?? null,
|
|
250
|
+
fixDifficultyNotes: withAliases.fixDifficultyNotes ?? info.fix_difficulty_notes ?? null,
|
|
251
|
+
fix_difficulty_notes: withAliases.fix_difficulty_notes ?? info.fix_difficulty_notes ?? null,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
151
254
|
}
|
|
152
255
|
|
|
153
|
-
|
|
154
|
-
if (!
|
|
256
|
+
// Infer effort AFTER enrichment so intelligence-provided fixCode is considered
|
|
257
|
+
if (!result.effort || result.effort === "null") {
|
|
258
|
+
result.effort = (result.fixCode || result.fix_code) ? "low" : "high";
|
|
259
|
+
}
|
|
155
260
|
|
|
156
|
-
return
|
|
157
|
-
...normalized,
|
|
158
|
-
category: normalized.category ?? info.category ?? null,
|
|
159
|
-
fixDescription: info.fix?.description ?? null,
|
|
160
|
-
fix_description: info.fix?.description ?? normalized.fix_description ?? null,
|
|
161
|
-
fixCode: info.fix?.code ?? null,
|
|
162
|
-
fix_code: info.fix?.code ?? normalized.fix_code ?? null,
|
|
163
|
-
falsePositiveRisk: normalized.falsePositiveRisk ?? info.false_positive_risk ?? null,
|
|
164
|
-
false_positive_risk: normalized.false_positive_risk ?? info.false_positive_risk ?? null,
|
|
165
|
-
fixDifficultyNotes: normalized.fixDifficultyNotes ?? info.fix_difficulty_notes ?? null,
|
|
166
|
-
fix_difficulty_notes: normalized.fix_difficulty_notes ?? info.fix_difficulty_notes ?? null,
|
|
167
|
-
};
|
|
261
|
+
return result;
|
|
168
262
|
});
|
|
263
|
+
|
|
264
|
+
// Sort by severity then by ID
|
|
265
|
+
enriched.sort((a, b) => {
|
|
266
|
+
const sa = SEVERITY_ORDER[a.severity] ?? 99;
|
|
267
|
+
const sb = SEVERITY_ORDER[b.severity] ?? 99;
|
|
268
|
+
if (sa !== sb) return sa - sb;
|
|
269
|
+
return a.id.localeCompare(b.id);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return enriched;
|
|
169
273
|
}
|
|
170
274
|
|
|
171
275
|
// ---------------------------------------------------------------------------
|
|
@@ -266,21 +370,58 @@ function getPersonaGroups(findings) {
|
|
|
266
370
|
|
|
267
371
|
/**
|
|
268
372
|
* Computes a complete audit summary from enriched findings: severity totals,
|
|
269
|
-
* compliance score, grade label, WCAG status,
|
|
373
|
+
* compliance score, grade label, WCAG status, persona groups, quick wins,
|
|
374
|
+
* target URL, and detected stack.
|
|
375
|
+
*
|
|
270
376
|
* @param {object[]} findings - Array of enriched findings.
|
|
271
|
-
* @
|
|
377
|
+
* @param {{ findings: object[], metadata?: object }|null} [payload=null] - Original scan payload for metadata extraction.
|
|
378
|
+
* @returns {object} Full audit summary.
|
|
272
379
|
*/
|
|
273
|
-
export function getAuditSummary(findings) {
|
|
380
|
+
export function getAuditSummary(findings, payload = null) {
|
|
274
381
|
const totals = { Critical: 0, Serious: 0, Moderate: 0, Minor: 0 };
|
|
275
382
|
for (const f of findings) {
|
|
276
|
-
const severity = f.severity ||
|
|
383
|
+
const severity = f.severity || "";
|
|
277
384
|
if (severity in totals) totals[severity] += 1;
|
|
278
385
|
}
|
|
279
386
|
|
|
280
387
|
const { score, label, wcagStatus } = getComplianceScore(totals);
|
|
281
388
|
const personaGroups = getPersonaGroups(findings);
|
|
282
389
|
|
|
283
|
-
|
|
390
|
+
const quickWins = findings
|
|
391
|
+
.filter((f) =>
|
|
392
|
+
(f.severity === "Critical" || f.severity === "Serious") &&
|
|
393
|
+
(f.fixCode || f.fix_code)
|
|
394
|
+
)
|
|
395
|
+
.slice(0, 3);
|
|
396
|
+
|
|
397
|
+
// Extract metadata from payload if provided
|
|
398
|
+
let targetUrl = "";
|
|
399
|
+
let detectedStack = { framework: null, cms: null, uiLibraries: [] };
|
|
400
|
+
|
|
401
|
+
if (payload && payload.metadata) {
|
|
402
|
+
const meta = payload.metadata;
|
|
403
|
+
const firstUrl = findings.length > 0 ? str(findings[0].url) : "";
|
|
404
|
+
targetUrl = str(meta.target_url || meta.targetUrl || meta.base_url || firstUrl);
|
|
405
|
+
|
|
406
|
+
const ctx = meta.projectContext || {};
|
|
407
|
+
detectedStack = {
|
|
408
|
+
framework: strOrNull(ctx.framework),
|
|
409
|
+
cms: strOrNull(ctx.cms),
|
|
410
|
+
uiLibraries: Array.isArray(ctx.uiLibraries) ? ctx.uiLibraries : [],
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return {
|
|
415
|
+
totals,
|
|
416
|
+
score,
|
|
417
|
+
label,
|
|
418
|
+
wcagStatus,
|
|
419
|
+
personaGroups,
|
|
420
|
+
quickWins,
|
|
421
|
+
targetUrl,
|
|
422
|
+
detectedStack,
|
|
423
|
+
totalFindings: findings.length,
|
|
424
|
+
};
|
|
284
425
|
}
|
|
285
426
|
|
|
286
427
|
// ---------------------------------------------------------------------------
|
|
@@ -298,10 +439,9 @@ import {
|
|
|
298
439
|
|
|
299
440
|
/**
|
|
300
441
|
* Generates a PDF report buffer from raw scan findings.
|
|
301
|
-
*
|
|
302
|
-
* @param {{ findings: object[], metadata?: object }} payload - Raw scan output (snake_case keys).
|
|
442
|
+
* @param {{ findings: object[], metadata?: object }} payload
|
|
303
443
|
* @param {{ baseUrl?: string, target?: string }} [options={}]
|
|
304
|
-
* @returns {Promise<
|
|
444
|
+
* @returns {Promise<{ buffer: Buffer, contentType: "application/pdf" }>}
|
|
305
445
|
*/
|
|
306
446
|
export async function getPDFReport(payload, options = {}) {
|
|
307
447
|
const { chromium } = await import("playwright");
|
|
@@ -367,9 +507,8 @@ ${buildPdfAuditLimitations()}
|
|
|
367
507
|
|
|
368
508
|
/**
|
|
369
509
|
* Generates a standalone manual accessibility checklist HTML string.
|
|
370
|
-
* Does not depend on scan results — reads from manual-checks.json asset.
|
|
371
510
|
* @param {{ baseUrl?: string }} [options={}]
|
|
372
|
-
* @returns {Promise<
|
|
511
|
+
* @returns {Promise<{ html: string, contentType: "text/html" }>}
|
|
373
512
|
*/
|
|
374
513
|
export async function getChecklist(options = {}) {
|
|
375
514
|
const { buildManualCheckCard } = await import("./reports/renderers/html.mjs");
|
|
@@ -387,9 +526,6 @@ export async function getChecklist(options = {}) {
|
|
|
387
526
|
const selectClasses =
|
|
388
527
|
"pl-4 pr-10 py-3 bg-white border border-slate-300 rounded-2xl text-sm font-bold text-slate-800 focus:outline-none focus:ring-4 focus:ring-amber-500/20 focus:border-amber-400 shadow-sm transition-all appearance-none cursor-pointer bg-[url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20stroke%3D%22%23374151%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%221.5%22%3E%3Cpath%20d%3D%22m6%208%204%204%204-4%22%2F%3E%3C%2Fsvg%3E')] bg-[length:1.25rem_1.25rem] bg-[right_0.5rem_center] bg-no-repeat";
|
|
389
528
|
|
|
390
|
-
// Import the full checklist builder to reuse its buildHtml
|
|
391
|
-
// The checklist builder module has a main() that auto-runs, so we dynamically
|
|
392
|
-
// construct the same output using the renderer functions directly.
|
|
393
529
|
const html = `<!doctype html>
|
|
394
530
|
<html lang="en">
|
|
395
531
|
<head>
|