@diegovelasquezweb/a11y-engine 0.4.2 → 0.5.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.
- package/CHANGELOG.md +80 -26
- package/README.md +76 -215
- package/docs/api-reference.md +107 -0
- package/docs/architecture.md +83 -209
- package/docs/cli-handbook.md +4 -4
- package/docs/engine-manifest.md +58 -0
- package/docs/outputs.md +9 -11
- package/docs/testing.md +63 -0
- package/package.json +3 -6
- package/src/core/asset-loader.mjs +22 -14
- package/src/index.d.mts +29 -12
- package/src/index.mjs +49 -52
- package/src/pipeline/dom-scanner.mjs +4 -4
- package/src/reports/renderers/findings.mjs +32 -32
- package/src/reports/renderers/md.mjs +6 -4
- package/assets/source/discovery/crawler-config.json +0 -11
- package/assets/source/discovery/stack-detection.json +0 -235
- package/assets/source/engine/cdp-checks.json +0 -30
- package/assets/source/engine/pa11y-config.json +0 -53
- package/assets/source/remediation/axe-check-maps.json +0 -31
- package/assets/source/remediation/code-patterns.json +0 -109
- package/assets/source/remediation/guardrails.json +0 -24
- package/assets/source/remediation/intelligence.json +0 -4166
- package/assets/source/remediation/source-boundaries.json +0 -46
- package/assets/source/reporting/compliance-config.json +0 -173
- package/assets/source/reporting/manual-checks.json +0 -944
- package/assets/source/reporting/wcag-reference.json +0 -588
- package/src/sync-assets.mjs +0 -66
- /package/assets/{generated/discovery → discovery}/crawler-config.mjs +0 -0
- /package/assets/{generated/discovery → discovery}/stack-detection.mjs +0 -0
- /package/assets/{generated/remediation → remediation}/axe-check-maps.mjs +0 -0
- /package/assets/{generated/remediation → remediation}/code-patterns.mjs +0 -0
- /package/assets/{generated/remediation → remediation}/guardrails.mjs +0 -0
- /package/assets/{generated/remediation → remediation}/intelligence.mjs +0 -0
- /package/assets/{generated/remediation → remediation}/source-boundaries.mjs +0 -0
- /package/assets/{generated/reporting → reporting}/compliance-config.mjs +0 -0
- /package/assets/{generated/reporting → reporting}/manual-checks.mjs +0 -0
- /package/assets/{generated/reporting → reporting}/wcag-reference.mjs +0 -0
- /package/assets/{generated/engine → scanning}/cdp-checks.mjs +0 -0
- /package/assets/{generated/engine → scanning}/pa11y-config.mjs +0 -0
package/src/index.d.mts
CHANGED
|
@@ -53,26 +53,44 @@ export interface Finding {
|
|
|
53
53
|
affected_urls?: string[] | null;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
export interface EnrichedFinding
|
|
56
|
+
export interface EnrichedFinding {
|
|
57
|
+
id: string;
|
|
57
58
|
ruleId: string;
|
|
59
|
+
source: string;
|
|
58
60
|
sourceRuleId: string | null;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
falsePositiveRisk: string | null;
|
|
63
|
-
fixDifficultyNotes: string | string[] | null;
|
|
64
|
-
screenshotPath: string | null;
|
|
61
|
+
title: string;
|
|
62
|
+
severity: string;
|
|
63
|
+
wcag: string;
|
|
65
64
|
wcagCriterionId: string | null;
|
|
66
65
|
wcagClassification: string | null;
|
|
67
|
-
|
|
66
|
+
category: string | null;
|
|
67
|
+
area: string;
|
|
68
|
+
url: string;
|
|
69
|
+
selector: string;
|
|
68
70
|
primarySelector: string;
|
|
71
|
+
impactedUsers: string | null;
|
|
72
|
+
actual: string;
|
|
73
|
+
expected: string;
|
|
69
74
|
primaryFailureMode: string | null;
|
|
70
75
|
relationshipHint: string | null;
|
|
71
76
|
failureChecks: unknown[];
|
|
72
77
|
relatedContext: unknown[];
|
|
78
|
+
mdn: string | null;
|
|
79
|
+
fixDescription: string | null;
|
|
80
|
+
fixCode: string | null;
|
|
81
|
+
fixCodeLang: string | null;
|
|
73
82
|
recommendedFix: string;
|
|
83
|
+
evidence: unknown[];
|
|
74
84
|
totalInstances: number | null;
|
|
85
|
+
effort: string;
|
|
75
86
|
relatedRules: string[];
|
|
87
|
+
screenshotPath: string | null;
|
|
88
|
+
falsePositiveRisk: string | null;
|
|
89
|
+
guardrails: Record<string, unknown> | null;
|
|
90
|
+
fixDifficultyNotes: string | string[] | null;
|
|
91
|
+
frameworkNotes: string | null;
|
|
92
|
+
cmsNotes: string | null;
|
|
93
|
+
fileSearchPattern: string | null;
|
|
76
94
|
ownershipStatus: string;
|
|
77
95
|
ownershipReason: string | null;
|
|
78
96
|
primarySourceScope: string[];
|
|
@@ -84,7 +102,6 @@ export interface EnrichedFinding extends Finding {
|
|
|
84
102
|
checkData: Record<string, unknown> | null;
|
|
85
103
|
pagesAffected: number | null;
|
|
86
104
|
affectedUrls: string[] | null;
|
|
87
|
-
effort: string;
|
|
88
105
|
}
|
|
89
106
|
|
|
90
107
|
export interface SeverityTotals {
|
|
@@ -232,12 +249,12 @@ export interface EnrichmentOptions {
|
|
|
232
249
|
|
|
233
250
|
export function runAudit(options: RunAuditOptions): Promise<ScanPayload>;
|
|
234
251
|
|
|
235
|
-
export function
|
|
236
|
-
input: ScanPayload
|
|
252
|
+
export function getFindings(
|
|
253
|
+
input: ScanPayload,
|
|
237
254
|
options?: EnrichmentOptions
|
|
238
255
|
): EnrichedFinding[];
|
|
239
256
|
|
|
240
|
-
export function
|
|
257
|
+
export function getOverview(
|
|
241
258
|
findings: EnrichedFinding[],
|
|
242
259
|
payload?: ScanPayload | null
|
|
243
260
|
): AuditSummary;
|
package/src/index.mjs
CHANGED
|
@@ -23,7 +23,7 @@ function getIntelligence() {
|
|
|
23
23
|
function getPa11yConfig() {
|
|
24
24
|
if (!_pa11yConfig) {
|
|
25
25
|
try {
|
|
26
|
-
_pa11yConfig = loadAssetJson(ASSET_PATHS.
|
|
26
|
+
_pa11yConfig = loadAssetJson(ASSET_PATHS.scanning.pa11yConfig, "pa11y-config.json");
|
|
27
27
|
} catch {
|
|
28
28
|
_pa11yConfig = { equivalenceMap: {}, ignoreByPrinciple: [], impactMap: {} };
|
|
29
29
|
}
|
|
@@ -159,31 +159,26 @@ function normalizeSingleFinding(item, index, screenshotUrlBuilder) {
|
|
|
159
159
|
/**
|
|
160
160
|
* Normalizes and enriches raw findings with intelligence data.
|
|
161
161
|
*
|
|
162
|
-
* Accepts either:
|
|
163
|
-
* - A full scan payload: { findings: object[], metadata?: object }
|
|
164
|
-
* - An array of findings directly: object[]
|
|
165
|
-
*
|
|
166
162
|
* Options:
|
|
167
163
|
* - screenshotUrlBuilder: (rawPath: string) => string — transforms screenshot
|
|
168
164
|
* paths into consumer-specific URLs.
|
|
169
165
|
*
|
|
170
|
-
* @param {
|
|
166
|
+
* @param {{findings: object[]}} input
|
|
171
167
|
* @param {{ screenshotUrlBuilder?: (path: string) => string }} [options={}]
|
|
172
168
|
* @returns {object[]} Enriched, normalized, sorted findings.
|
|
173
169
|
*/
|
|
174
|
-
export function
|
|
170
|
+
export function getFindings(input, options = {}) {
|
|
175
171
|
const { screenshotUrlBuilder = null } = options;
|
|
176
172
|
const rules = getIntelligence().rules || {};
|
|
177
173
|
|
|
178
|
-
|
|
179
|
-
const rawFindings = Array.isArray(input) ? input : (input?.findings || []);
|
|
174
|
+
const rawFindings = input?.findings || [];
|
|
180
175
|
|
|
181
176
|
// Normalize raw findings
|
|
182
177
|
const normalized = rawFindings.map((item, index) =>
|
|
183
178
|
normalizeSingleFinding(item, index, screenshotUrlBuilder)
|
|
184
179
|
);
|
|
185
180
|
|
|
186
|
-
// Enrich with intelligence
|
|
181
|
+
// Enrich with intelligence and output camelCase-only findings
|
|
187
182
|
const enriched = normalized.map((finding) => {
|
|
188
183
|
const canonical = mapPa11yRuleToCanonical(
|
|
189
184
|
finding.rule_id,
|
|
@@ -191,31 +186,45 @@ export function getEnrichedFindings(input, options = {}) {
|
|
|
191
186
|
finding.check_data,
|
|
192
187
|
);
|
|
193
188
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const withAliases = {
|
|
198
|
-
...finding,
|
|
189
|
+
// Build camelCase-only enriched finding
|
|
190
|
+
const enrichedFinding = {
|
|
191
|
+
id: finding.id,
|
|
199
192
|
ruleId: canonical,
|
|
200
|
-
|
|
193
|
+
source: finding.source,
|
|
201
194
|
sourceRuleId: finding.source_rule_id || finding.rule_id || null,
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
falsePositiveRisk: finding.false_positive_risk,
|
|
206
|
-
fixDifficultyNotes: finding.fix_difficulty_notes,
|
|
207
|
-
screenshotPath: finding.screenshot_path,
|
|
195
|
+
title: finding.title,
|
|
196
|
+
severity: finding.severity,
|
|
197
|
+
wcag: finding.wcag,
|
|
208
198
|
wcagCriterionId: finding.wcag_criterion_id,
|
|
209
199
|
wcagClassification: finding.wcag_classification,
|
|
210
|
-
|
|
200
|
+
category: finding.category,
|
|
201
|
+
area: finding.area,
|
|
202
|
+
url: finding.url,
|
|
203
|
+
selector: finding.selector,
|
|
211
204
|
primarySelector: finding.primary_selector,
|
|
205
|
+
impactedUsers: finding.impacted_users,
|
|
206
|
+
actual: finding.actual,
|
|
207
|
+
expected: finding.expected,
|
|
212
208
|
primaryFailureMode: finding.primary_failure_mode,
|
|
213
209
|
relationshipHint: finding.relationship_hint,
|
|
214
210
|
failureChecks: finding.failure_checks,
|
|
215
211
|
relatedContext: finding.related_context,
|
|
212
|
+
mdn: finding.mdn,
|
|
213
|
+
fixDescription: finding.fix_description,
|
|
214
|
+
fixCode: finding.fix_code,
|
|
215
|
+
fixCodeLang: finding.fix_code_lang,
|
|
216
216
|
recommendedFix: finding.recommended_fix,
|
|
217
|
+
evidence: finding.evidence,
|
|
217
218
|
totalInstances: finding.total_instances,
|
|
219
|
+
effort: finding.effort,
|
|
218
220
|
relatedRules: finding.related_rules,
|
|
221
|
+
screenshotPath: finding.screenshot_path,
|
|
222
|
+
falsePositiveRisk: finding.false_positive_risk,
|
|
223
|
+
guardrails: finding.guardrails,
|
|
224
|
+
fixDifficultyNotes: finding.fix_difficulty_notes,
|
|
225
|
+
frameworkNotes: finding.framework_notes,
|
|
226
|
+
cmsNotes: finding.cms_notes,
|
|
227
|
+
fileSearchPattern: finding.file_search_pattern,
|
|
219
228
|
ownershipStatus: finding.ownership_status,
|
|
220
229
|
ownershipReason: finding.ownership_reason,
|
|
221
230
|
primarySourceScope: finding.primary_source_scope,
|
|
@@ -229,36 +238,24 @@ export function getEnrichedFindings(input, options = {}) {
|
|
|
229
238
|
affectedUrls: finding.affected_urls,
|
|
230
239
|
};
|
|
231
240
|
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
if (withAliases.fixDescription || withAliases.fixCode) {
|
|
235
|
-
result = withAliases;
|
|
236
|
-
} else {
|
|
241
|
+
// Enrich from intelligence if no fix data exists yet
|
|
242
|
+
if (!enrichedFinding.fixDescription && !enrichedFinding.fixCode) {
|
|
237
243
|
const info = rules[canonical];
|
|
238
|
-
if (
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
};
|
|
244
|
+
if (info) {
|
|
245
|
+
enrichedFinding.category = enrichedFinding.category ?? info.category ?? null;
|
|
246
|
+
enrichedFinding.fixDescription = info.fix?.description ?? null;
|
|
247
|
+
enrichedFinding.fixCode = info.fix?.code ?? null;
|
|
248
|
+
enrichedFinding.falsePositiveRisk = enrichedFinding.falsePositiveRisk ?? info.false_positive_risk ?? null;
|
|
249
|
+
enrichedFinding.fixDifficultyNotes = enrichedFinding.fixDifficultyNotes ?? info.fix_difficulty_notes ?? null;
|
|
253
250
|
}
|
|
254
251
|
}
|
|
255
252
|
|
|
256
253
|
// Infer effort AFTER enrichment so intelligence-provided fixCode is considered
|
|
257
|
-
if (!
|
|
258
|
-
|
|
254
|
+
if (!enrichedFinding.effort || enrichedFinding.effort === "null") {
|
|
255
|
+
enrichedFinding.effort = enrichedFinding.fixCode ? "low" : "high";
|
|
259
256
|
}
|
|
260
257
|
|
|
261
|
-
return
|
|
258
|
+
return enrichedFinding;
|
|
262
259
|
});
|
|
263
260
|
|
|
264
261
|
// Sort by severity then by ID
|
|
@@ -332,9 +329,9 @@ function getPersonaGroups(findings) {
|
|
|
332
329
|
}
|
|
333
330
|
|
|
334
331
|
for (const f of findings) {
|
|
335
|
-
const ruleId = (f.ruleId ||
|
|
336
|
-
const wcagCriterionId = f.wcagCriterionId ||
|
|
337
|
-
const users = (f.impactedUsers ||
|
|
332
|
+
const ruleId = (f.ruleId || "").toLowerCase();
|
|
333
|
+
const wcagCriterionId = f.wcagCriterionId || "";
|
|
334
|
+
const users = (f.impactedUsers || "").toLowerCase();
|
|
338
335
|
const matchedPersonas = new Set();
|
|
339
336
|
|
|
340
337
|
for (const [personaKey, mapping] of Object.entries(personaMapping)) {
|
|
@@ -377,7 +374,7 @@ function getPersonaGroups(findings) {
|
|
|
377
374
|
* @param {{ findings: object[], metadata?: object }|null} [payload=null] - Original scan payload for metadata extraction.
|
|
378
375
|
* @returns {object} Full audit summary.
|
|
379
376
|
*/
|
|
380
|
-
export function
|
|
377
|
+
export function getOverview(findings, payload = null) {
|
|
381
378
|
const totals = { Critical: 0, Serious: 0, Moderate: 0, Minor: 0 };
|
|
382
379
|
for (const f of findings) {
|
|
383
380
|
const severity = f.severity || "";
|
|
@@ -390,7 +387,7 @@ export function getAuditSummary(findings, payload = null) {
|
|
|
390
387
|
const quickWins = findings
|
|
391
388
|
.filter((f) =>
|
|
392
389
|
(f.severity === "Critical" || f.severity === "Serious") &&
|
|
393
|
-
|
|
390
|
+
f.fixCode
|
|
394
391
|
)
|
|
395
392
|
.slice(0, 3);
|
|
396
393
|
|
|
@@ -430,7 +427,7 @@ export function getAuditSummary(findings, payload = null) {
|
|
|
430
427
|
|
|
431
428
|
/**
|
|
432
429
|
* Runs a complete accessibility audit: crawl + scan (axe + CDP + pa11y) + analyze.
|
|
433
|
-
* Returns the
|
|
430
|
+
* Returns the scan payload ready for getFindings().
|
|
434
431
|
*
|
|
435
432
|
* @param {{
|
|
436
433
|
* baseUrl: string,
|
|
@@ -26,12 +26,12 @@ const STACK_DETECTION = loadAssetJson(
|
|
|
26
26
|
"assets/discovery/stack-detection.json",
|
|
27
27
|
);
|
|
28
28
|
const CDP_CHECKS = loadAssetJson(
|
|
29
|
-
ASSET_PATHS.
|
|
30
|
-
"assets/
|
|
29
|
+
ASSET_PATHS.scanning.cdpChecks,
|
|
30
|
+
"assets/scanning/cdp-checks.json",
|
|
31
31
|
);
|
|
32
32
|
const PA11Y_CONFIG = loadAssetJson(
|
|
33
|
-
ASSET_PATHS.
|
|
34
|
-
"assets/
|
|
33
|
+
ASSET_PATHS.scanning.pa11yConfig,
|
|
34
|
+
"assets/scanning/pa11y-config.json",
|
|
35
35
|
);
|
|
36
36
|
const AXE_TAGS = [
|
|
37
37
|
"wcag2a",
|
|
@@ -43,55 +43,55 @@ export function normalizeFindings(payload) {
|
|
|
43
43
|
return payload.findings
|
|
44
44
|
.map((item, index) => ({
|
|
45
45
|
id: String(item.id ?? `A11Y-${String(index + 1).padStart(3, "0")}`),
|
|
46
|
-
ruleId: String(item.rule_id ?? ""),
|
|
46
|
+
ruleId: String(item.ruleId ?? item.rule_id ?? ""),
|
|
47
47
|
category: item.category ?? null,
|
|
48
48
|
title: String(item.title ?? "Untitled finding"),
|
|
49
49
|
severity: String(item.severity ?? "Unknown"),
|
|
50
50
|
wcag: String(item.wcag ?? ""),
|
|
51
|
-
wcagClassification: item.wcag_classification ?? null,
|
|
51
|
+
wcagClassification: item.wcagClassification ?? item.wcag_classification ?? null,
|
|
52
52
|
area: String(item.area ?? ""),
|
|
53
53
|
url: String(item.url ?? ""),
|
|
54
54
|
selector: String(item.selector ?? ""),
|
|
55
|
-
primarySelector: String(item.primary_selector ?? item.selector ?? ""),
|
|
55
|
+
primarySelector: String(item.primarySelector ?? item.primary_selector ?? item.selector ?? ""),
|
|
56
56
|
impactedUsers: String(
|
|
57
|
-
item.impacted_users ?? "Users relying on assistive technology",
|
|
57
|
+
item.impactedUsers ?? item.impacted_users ?? "Users relying on assistive technology",
|
|
58
58
|
),
|
|
59
59
|
actual: String(item.actual ?? ""),
|
|
60
|
-
primaryFailureMode: item.primary_failure_mode ?? null,
|
|
61
|
-
relationshipHint: item.relationship_hint ?? null,
|
|
62
|
-
failureChecks: Array.isArray(item.failure_checks) ? item.failure_checks : [],
|
|
63
|
-
relatedContext: Array.isArray(item.related_context) ? item.related_context : [],
|
|
60
|
+
primaryFailureMode: item.primaryFailureMode ?? item.primary_failure_mode ?? null,
|
|
61
|
+
relationshipHint: item.relationshipHint ?? item.relationship_hint ?? null,
|
|
62
|
+
failureChecks: Array.isArray(item.failureChecks ?? item.failure_checks) ? (item.failureChecks ?? item.failure_checks) : [],
|
|
63
|
+
relatedContext: Array.isArray(item.relatedContext ?? item.related_context) ? (item.relatedContext ?? item.related_context) : [],
|
|
64
64
|
expected: String(item.expected ?? ""),
|
|
65
65
|
mdn: item.mdn ?? null,
|
|
66
|
-
fixDescription: item.fix_description ?? null,
|
|
67
|
-
fixCode: item.fix_code ?? null,
|
|
68
|
-
recommendedFix: String(item.
|
|
66
|
+
fixDescription: item.fixDescription ?? item.fix_description ?? null,
|
|
67
|
+
fixCode: item.fixCode ?? item.fix_code ?? null,
|
|
68
|
+
recommendedFix: String(item.recommendedFix ?? item.recommended_fix ?? ""),
|
|
69
69
|
evidence: Array.isArray(item.evidence) ? item.evidence : [],
|
|
70
70
|
totalInstances:
|
|
71
|
-
typeof item.total_instances === "number" ? item.total_instances : null,
|
|
71
|
+
typeof (item.totalInstances ?? item.total_instances) === "number" ? (item.totalInstances ?? item.total_instances) : null,
|
|
72
72
|
effort: item.effort ?? null,
|
|
73
|
-
relatedRules: Array.isArray(item.related_rules) ? item.related_rules : [],
|
|
74
|
-
fixCodeLang: item.fix_code_lang ?? "html",
|
|
75
|
-
screenshotPath: item.screenshot_path ?? null,
|
|
76
|
-
falsePositiveRisk: item.false_positive_risk ?? null,
|
|
73
|
+
relatedRules: Array.isArray(item.relatedRules ?? item.related_rules) ? (item.relatedRules ?? item.related_rules) : [],
|
|
74
|
+
fixCodeLang: item.fixCodeLang ?? item.fix_code_lang ?? "html",
|
|
75
|
+
screenshotPath: item.screenshotPath ?? item.screenshot_path ?? null,
|
|
76
|
+
falsePositiveRisk: item.falsePositiveRisk ?? item.false_positive_risk ?? null,
|
|
77
77
|
guardrails: item.guardrails ?? null,
|
|
78
|
-
fixDifficultyNotes: item.fix_difficulty_notes ?? null,
|
|
79
|
-
frameworkNotes: item.framework_notes ?? null,
|
|
80
|
-
cmsNotes: item.cms_notes ?? null,
|
|
81
|
-
fileSearchPattern: item.file_search_pattern ?? null,
|
|
82
|
-
ownershipStatus: item.ownership_status ?? "unknown",
|
|
83
|
-
ownershipReason: item.ownership_reason ?? null,
|
|
84
|
-
primarySourceScope: Array.isArray(item.primary_source_scope)
|
|
85
|
-
? item.primary_source_scope
|
|
78
|
+
fixDifficultyNotes: item.fixDifficultyNotes ?? item.fix_difficulty_notes ?? null,
|
|
79
|
+
frameworkNotes: item.frameworkNotes ?? item.framework_notes ?? null,
|
|
80
|
+
cmsNotes: item.cmsNotes ?? item.cms_notes ?? null,
|
|
81
|
+
fileSearchPattern: item.fileSearchPattern ?? item.file_search_pattern ?? null,
|
|
82
|
+
ownershipStatus: item.ownershipStatus ?? item.ownership_status ?? "unknown",
|
|
83
|
+
ownershipReason: item.ownershipReason ?? item.ownership_reason ?? null,
|
|
84
|
+
primarySourceScope: Array.isArray(item.primarySourceScope ?? item.primary_source_scope)
|
|
85
|
+
? (item.primarySourceScope ?? item.primary_source_scope)
|
|
86
86
|
: [],
|
|
87
|
-
searchStrategy: item.search_strategy ?? "verify_ownership_before_search",
|
|
88
|
-
managedByLibrary: item.managed_by_library ?? null,
|
|
89
|
-
componentHint: item.component_hint ?? null,
|
|
90
|
-
verificationCommand: item.verification_command ?? null,
|
|
91
|
-
verificationCommandFallback: item.verification_command_fallback ?? null,
|
|
92
|
-
pagesAffected: typeof item.pages_affected === "number" ? item.pages_affected : null,
|
|
93
|
-
affectedUrls: Array.isArray(item.affected_urls) ? item.affected_urls : null,
|
|
94
|
-
checkData: item.check_data ?? null,
|
|
87
|
+
searchStrategy: item.searchStrategy ?? item.search_strategy ?? "verify_ownership_before_search",
|
|
88
|
+
managedByLibrary: item.managedByLibrary ?? item.managed_by_library ?? null,
|
|
89
|
+
componentHint: item.componentHint ?? item.component_hint ?? null,
|
|
90
|
+
verificationCommand: item.verificationCommand ?? item.verification_command ?? null,
|
|
91
|
+
verificationCommandFallback: item.verificationCommandFallback ?? item.verification_command_fallback ?? null,
|
|
92
|
+
pagesAffected: typeof (item.pagesAffected ?? item.pages_affected) === "number" ? (item.pagesAffected ?? item.pages_affected) : null,
|
|
93
|
+
affectedUrls: Array.isArray(item.affectedUrls ?? item.affected_urls) ? (item.affectedUrls ?? item.affected_urls) : null,
|
|
94
|
+
checkData: item.checkData ?? item.check_data ?? null,
|
|
95
95
|
}))
|
|
96
96
|
.sort((a, b) => {
|
|
97
97
|
const sa = SEVERITY_ORDER[a.severity] ?? 99;
|
|
@@ -144,15 +144,17 @@ function buildIncompleteSection(incompleteFindings) {
|
|
|
144
144
|
if (!Array.isArray(incompleteFindings) || incompleteFindings.length === 0) return "";
|
|
145
145
|
const rows = incompleteFindings.map((f) => {
|
|
146
146
|
const msg = (f.message || f.description || "Needs manual review").replace(/\|/g, "\\|");
|
|
147
|
-
const
|
|
148
|
-
|
|
147
|
+
const pagesAffected = f.pagesAffected ?? f.pages_affected;
|
|
148
|
+
const ruleId = f.ruleId ?? f.rule_id;
|
|
149
|
+
const areaCell = pagesAffected > 1
|
|
150
|
+
? `${pagesAffected} pages`
|
|
149
151
|
: `\`${f.areas?.[0] ?? "?"}\``;
|
|
150
152
|
let actionableHint = "";
|
|
151
|
-
if (
|
|
153
|
+
if (ruleId === "duplicate-id-aria" && f.message) {
|
|
152
154
|
const idMatch = f.message.match(/same id attribute[:\s]+(\S+?)\.?\s*$/i);
|
|
153
155
|
if (idMatch) actionableHint = ` — grep: \`id="${idMatch[1]}"\``;
|
|
154
156
|
}
|
|
155
|
-
return `| \`${
|
|
157
|
+
return `| \`${ruleId}\` | ${f.impact ?? "?"} | ${areaCell} | ${msg}${actionableHint} |`;
|
|
156
158
|
});
|
|
157
159
|
return `## Potential Issues — Manual Review Required
|
|
158
160
|
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"blockedExtensions": [
|
|
3
|
-
"pdf", "jpg", "jpeg", "png", "gif", "svg", "webp", "ico",
|
|
4
|
-
"css", "js", "mjs", "json", "xml", "zip", "tar", "gz",
|
|
5
|
-
"mp4", "mp3", "webm", "wav", "woff", "woff2", "ttf", "otf", "eot",
|
|
6
|
-
"avif", "csv", "txt", "map", "wasm"
|
|
7
|
-
],
|
|
8
|
-
"paginationParams": [
|
|
9
|
-
"page", "paged", "p", "pg", "offset", "cursor", "start", "from", "skip", "limit", "per_page"
|
|
10
|
-
]
|
|
11
|
-
}
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"platformStructureDetectors": [
|
|
3
|
-
["wordpress", ["wp-content/themes"]],
|
|
4
|
-
["drupal", ["web/themes", "themes"]],
|
|
5
|
-
["shopify", ["sections", "snippets", "layout", "templates"]]
|
|
6
|
-
],
|
|
7
|
-
"uiLibraryPackageDetectors": [
|
|
8
|
-
["@radix-ui", "radix"],
|
|
9
|
-
["@headlessui", "headless-ui"],
|
|
10
|
-
["@chakra-ui", "chakra"],
|
|
11
|
-
["@mantine", "mantine"],
|
|
12
|
-
["@mui", "material-ui"],
|
|
13
|
-
["antd", "ant-design"],
|
|
14
|
-
["@shopify/polaris", "polaris"],
|
|
15
|
-
["@react-aria", "react-aria"],
|
|
16
|
-
["ariakit", "ariakit"],
|
|
17
|
-
["primevue", "primevue"],
|
|
18
|
-
["vuetify", "vuetify"],
|
|
19
|
-
["swiper", "swiper"]
|
|
20
|
-
],
|
|
21
|
-
"frameworkPackageDetectors": [
|
|
22
|
-
["next", "nextjs"],
|
|
23
|
-
["gatsby", "gatsby"],
|
|
24
|
-
["nuxt", "nuxt"],
|
|
25
|
-
["@nuxt/core", "nuxt"],
|
|
26
|
-
["@angular/core", "angular"],
|
|
27
|
-
["astro", "astro"],
|
|
28
|
-
["@sveltejs/kit", "svelte"],
|
|
29
|
-
["svelte", "svelte"],
|
|
30
|
-
["vue", "vue"],
|
|
31
|
-
["react", "react"]
|
|
32
|
-
],
|
|
33
|
-
"domFrameworkDetectors": [
|
|
34
|
-
{
|
|
35
|
-
"id": "nextjs",
|
|
36
|
-
"type": "framework",
|
|
37
|
-
"signals": [
|
|
38
|
-
{ "kind": "global", "key": "__NEXT_DATA__" },
|
|
39
|
-
{ "kind": "global", "key": "__next" },
|
|
40
|
-
{ "kind": "selector", "value": "script#__NEXT_DATA__" },
|
|
41
|
-
{ "kind": "selector", "value": "div#__next" },
|
|
42
|
-
{ "kind": "scriptSrc", "pattern": "/_next/" },
|
|
43
|
-
{ "kind": "meta", "name": "next-head-count" }
|
|
44
|
-
]
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
"id": "nuxt",
|
|
48
|
-
"type": "framework",
|
|
49
|
-
"signals": [
|
|
50
|
-
{ "kind": "global", "key": "__NUXT__" },
|
|
51
|
-
{ "kind": "global", "key": "$nuxt" },
|
|
52
|
-
{ "kind": "selector", "value": "div#__nuxt" },
|
|
53
|
-
{ "kind": "scriptSrc", "pattern": "/_nuxt/" },
|
|
54
|
-
{ "kind": "meta", "name": "generator", "pattern": "Nuxt" }
|
|
55
|
-
]
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
"id": "gatsby",
|
|
59
|
-
"type": "framework",
|
|
60
|
-
"signals": [
|
|
61
|
-
{ "kind": "global", "key": "___gatsby" },
|
|
62
|
-
{ "kind": "selector", "value": "div#___gatsby" },
|
|
63
|
-
{ "kind": "meta", "name": "generator", "pattern": "Gatsby" }
|
|
64
|
-
]
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
"id": "angular",
|
|
68
|
-
"type": "framework",
|
|
69
|
-
"signals": [
|
|
70
|
-
{ "kind": "global", "key": "ng" },
|
|
71
|
-
{ "kind": "selector", "value": "[ng-version]" },
|
|
72
|
-
{ "kind": "selector", "value": "app-root" }
|
|
73
|
-
]
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
"id": "svelte",
|
|
77
|
-
"type": "framework",
|
|
78
|
-
"signals": [
|
|
79
|
-
{ "kind": "global", "key": "__svelte" },
|
|
80
|
-
{ "kind": "selector", "value": "[data-svelte-h]" },
|
|
81
|
-
{ "kind": "meta", "name": "generator", "pattern": "Svelte" }
|
|
82
|
-
]
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
"id": "astro",
|
|
86
|
-
"type": "framework",
|
|
87
|
-
"signals": [
|
|
88
|
-
{ "kind": "selector", "value": "[data-astro-cid]" },
|
|
89
|
-
{ "kind": "meta", "name": "generator", "pattern": "Astro" },
|
|
90
|
-
{ "kind": "selector", "value": "astro-island" }
|
|
91
|
-
]
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
"id": "remix",
|
|
95
|
-
"type": "framework",
|
|
96
|
-
"signals": [
|
|
97
|
-
{ "kind": "global", "key": "__remixContext" },
|
|
98
|
-
{ "kind": "global", "key": "__remixManifest" }
|
|
99
|
-
]
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
"id": "vue",
|
|
103
|
-
"type": "framework",
|
|
104
|
-
"signals": [
|
|
105
|
-
{ "kind": "global", "key": "__VUE__" },
|
|
106
|
-
{ "kind": "selector", "value": "[data-v-]" },
|
|
107
|
-
{ "kind": "selector", "value": "div#app[data-v-app]" }
|
|
108
|
-
]
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
"id": "react",
|
|
112
|
-
"type": "framework",
|
|
113
|
-
"signals": [
|
|
114
|
-
{ "kind": "selector", "value": "[data-reactroot]" },
|
|
115
|
-
{ "kind": "selector", "value": "[data-reactid]" },
|
|
116
|
-
{ "kind": "global", "key": "__REACT_DEVTOOLS_GLOBAL_HOOK__" }
|
|
117
|
-
]
|
|
118
|
-
}
|
|
119
|
-
],
|
|
120
|
-
"domCmsDetectors": [
|
|
121
|
-
{
|
|
122
|
-
"id": "wordpress",
|
|
123
|
-
"type": "cms",
|
|
124
|
-
"signals": [
|
|
125
|
-
{ "kind": "meta", "name": "generator", "pattern": "WordPress" },
|
|
126
|
-
{ "kind": "scriptSrc", "pattern": "/wp-content/" },
|
|
127
|
-
{ "kind": "scriptSrc", "pattern": "/wp-includes/" },
|
|
128
|
-
{ "kind": "selector", "value": "link[href*='wp-content']" },
|
|
129
|
-
{ "kind": "selector", "value": "body.wp-site-blocks" }
|
|
130
|
-
]
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
"id": "shopify",
|
|
134
|
-
"type": "cms",
|
|
135
|
-
"signals": [
|
|
136
|
-
{ "kind": "global", "key": "Shopify" },
|
|
137
|
-
{ "kind": "scriptSrc", "pattern": "cdn.shopify.com" },
|
|
138
|
-
{ "kind": "meta", "name": "shopify-digital-wallet" },
|
|
139
|
-
{ "kind": "selector", "value": "link[href*='cdn.shopify']" }
|
|
140
|
-
]
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
"id": "drupal",
|
|
144
|
-
"type": "cms",
|
|
145
|
-
"signals": [
|
|
146
|
-
{ "kind": "meta", "name": "generator", "pattern": "Drupal" },
|
|
147
|
-
{ "kind": "global", "key": "Drupal" },
|
|
148
|
-
{ "kind": "scriptSrc", "pattern": "/sites/default/files/" }
|
|
149
|
-
]
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
"id": "wix",
|
|
153
|
-
"type": "cms",
|
|
154
|
-
"signals": [
|
|
155
|
-
{ "kind": "meta", "name": "generator", "pattern": "Wix" },
|
|
156
|
-
{ "kind": "scriptSrc", "pattern": "static.parastorage.com" },
|
|
157
|
-
{ "kind": "scriptSrc", "pattern": "static.wixstatic.com" }
|
|
158
|
-
]
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
"id": "squarespace",
|
|
162
|
-
"type": "cms",
|
|
163
|
-
"signals": [
|
|
164
|
-
{ "kind": "meta", "name": "generator", "pattern": "Squarespace" },
|
|
165
|
-
{ "kind": "scriptSrc", "pattern": "static1.squarespace.com" }
|
|
166
|
-
]
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
"id": "webflow",
|
|
170
|
-
"type": "cms",
|
|
171
|
-
"signals": [
|
|
172
|
-
{ "kind": "meta", "name": "generator", "pattern": "Webflow" },
|
|
173
|
-
{ "kind": "selector", "value": "html.w-mod-js" },
|
|
174
|
-
{ "kind": "scriptSrc", "pattern": "assets.website-files.com" }
|
|
175
|
-
]
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
"id": "joomla",
|
|
179
|
-
"type": "cms",
|
|
180
|
-
"signals": [
|
|
181
|
-
{ "kind": "meta", "name": "generator", "pattern": "Joomla" },
|
|
182
|
-
{ "kind": "scriptSrc", "pattern": "/media/system/js/" }
|
|
183
|
-
]
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
"id": "magento",
|
|
187
|
-
"type": "cms",
|
|
188
|
-
"signals": [
|
|
189
|
-
{ "kind": "scriptSrc", "pattern": "/static/version" },
|
|
190
|
-
{ "kind": "selector", "value": "script[data-requiremodule]" },
|
|
191
|
-
{ "kind": "global", "key": "require" }
|
|
192
|
-
]
|
|
193
|
-
}
|
|
194
|
-
],
|
|
195
|
-
"domUiLibraryDetectors": [
|
|
196
|
-
{
|
|
197
|
-
"id": "bootstrap",
|
|
198
|
-
"signals": [
|
|
199
|
-
{ "kind": "selector", "value": "link[href*='bootstrap']" },
|
|
200
|
-
{ "kind": "scriptSrc", "pattern": "bootstrap" },
|
|
201
|
-
{ "kind": "selector", "value": ".container .row .col" }
|
|
202
|
-
]
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
"id": "tailwindcss",
|
|
206
|
-
"signals": [
|
|
207
|
-
{ "kind": "selector", "value": "style[data-precedence]" },
|
|
208
|
-
{ "kind": "selector", "value": "link[href*='tailwind']" },
|
|
209
|
-
{ "kind": "meta", "name": "generator", "pattern": "Tailwind" }
|
|
210
|
-
]
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
"id": "material-ui",
|
|
214
|
-
"signals": [
|
|
215
|
-
{ "kind": "selector", "value": "[class*='MuiButton']" },
|
|
216
|
-
{ "kind": "selector", "value": "[class*='MuiTypography']" },
|
|
217
|
-
{ "kind": "selector", "value": "[class*='MuiPaper']" }
|
|
218
|
-
]
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
"id": "jquery",
|
|
222
|
-
"signals": [
|
|
223
|
-
{ "kind": "global", "key": "jQuery" },
|
|
224
|
-
{ "kind": "global", "key": "$" }
|
|
225
|
-
]
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
"id": "foundation",
|
|
229
|
-
"signals": [
|
|
230
|
-
{ "kind": "global", "key": "Foundation" },
|
|
231
|
-
{ "kind": "selector", "value": "link[href*='foundation']" }
|
|
232
|
-
]
|
|
233
|
-
}
|
|
234
|
-
]
|
|
235
|
-
}
|