@diegovelasquezweb/a11y-engine 0.6.6 → 0.7.1
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/src/enrichment/analyzer.mjs +11 -0
- package/src/index.mjs +20 -8
- package/src/pipeline/dom-scanner.mjs +34 -14
package/package.json
CHANGED
|
@@ -744,6 +744,14 @@ function computeTestingMethodology(payload) {
|
|
|
744
744
|
const routes = payload.routes || [];
|
|
745
745
|
const scanned = routes.filter((r) => !r.error).length;
|
|
746
746
|
const errored = routes.filter((r) => r.error).length;
|
|
747
|
+
const tags = payload.axeTags || [];
|
|
748
|
+
const conformanceLevel = tags.includes("wcag2aaa")
|
|
749
|
+
? "AAA"
|
|
750
|
+
: tags.includes("wcag2aa") || tags.includes("wcag21aa") || tags.includes("wcag22aa")
|
|
751
|
+
? "AA"
|
|
752
|
+
: tags.includes("wcag2a") || tags.includes("wcag21a") || tags.includes("wcag22a")
|
|
753
|
+
? "A"
|
|
754
|
+
: null;
|
|
747
755
|
return {
|
|
748
756
|
automated_tools: [
|
|
749
757
|
"axe-core (via @axe-core/playwright)",
|
|
@@ -752,6 +760,9 @@ function computeTestingMethodology(payload) {
|
|
|
752
760
|
"Playwright + Chromium",
|
|
753
761
|
],
|
|
754
762
|
compliance_target: "WCAG 2.2",
|
|
763
|
+
conformance_level: conformanceLevel,
|
|
764
|
+
best_practices: tags.includes("best-practice"),
|
|
765
|
+
axe_tags: tags.length > 0 ? tags : null,
|
|
755
766
|
pages_scanned: scanned,
|
|
756
767
|
pages_errored: errored,
|
|
757
768
|
framework_detected: payload.projectContext?.framework || "Not detected",
|
package/src/index.mjs
CHANGED
|
@@ -660,14 +660,17 @@ export async function runAudit(options) {
|
|
|
660
660
|
// Fetch remote package.json via GitHub API if repoUrl is provided
|
|
661
661
|
let remotePackageJson = null;
|
|
662
662
|
if (options.repoUrl && !options.projectDir) {
|
|
663
|
+
if (onProgress) onProgress("repo", "running");
|
|
663
664
|
try {
|
|
664
665
|
const { fetchPackageJson } = await import("./core/github-api.mjs");
|
|
665
666
|
remotePackageJson = await fetchPackageJson(options.repoUrl, options.githubToken);
|
|
666
667
|
if (remotePackageJson) {
|
|
667
|
-
|
|
668
|
+
if (onProgress) onProgress("repo", "done", { packageJson: true });
|
|
669
|
+
} else {
|
|
670
|
+
if (onProgress) onProgress("repo", "skipped", { reason: "Could not read package.json" });
|
|
668
671
|
}
|
|
669
672
|
} catch (err) {
|
|
670
|
-
|
|
673
|
+
if (onProgress) onProgress("repo", "skipped", { reason: err.message });
|
|
671
674
|
}
|
|
672
675
|
}
|
|
673
676
|
|
|
@@ -708,6 +711,7 @@ export async function runAudit(options) {
|
|
|
708
711
|
// Step 3: Source patterns (optional) — works with local projectDir or remote repoUrl
|
|
709
712
|
const hasSourceContext = (options.projectDir || options.repoUrl) && !options.skipPatterns;
|
|
710
713
|
if (hasSourceContext) {
|
|
714
|
+
if (onProgress) onProgress("patterns", "running");
|
|
711
715
|
try {
|
|
712
716
|
const { patterns } = loadAssetJson(ASSET_PATHS.remediation.codePatterns, "code-patterns.json");
|
|
713
717
|
|
|
@@ -741,21 +745,26 @@ export async function runAudit(options) {
|
|
|
741
745
|
}
|
|
742
746
|
}
|
|
743
747
|
|
|
748
|
+
const confirmed = allPatternFindings.filter((f) => f.status === "confirmed").length;
|
|
749
|
+
const potential = allPatternFindings.filter((f) => f.status === "potential").length;
|
|
750
|
+
|
|
744
751
|
if (allPatternFindings.length > 0) {
|
|
745
752
|
findingsPayload.patternFindings = {
|
|
746
753
|
generated_at: new Date().toISOString(),
|
|
747
754
|
project_dir: options.projectDir || options.repoUrl,
|
|
748
755
|
findings: allPatternFindings,
|
|
749
|
-
summary: {
|
|
750
|
-
total: allPatternFindings.length,
|
|
751
|
-
confirmed: allPatternFindings.filter((f) => f.status === "confirmed").length,
|
|
752
|
-
potential: allPatternFindings.filter((f) => f.status === "potential").length,
|
|
753
|
-
},
|
|
756
|
+
summary: { total: allPatternFindings.length, confirmed, potential },
|
|
754
757
|
};
|
|
755
758
|
}
|
|
759
|
+
|
|
760
|
+
if (onProgress) onProgress("patterns", "done", {
|
|
761
|
+
total: allPatternFindings.length,
|
|
762
|
+
confirmed,
|
|
763
|
+
potential,
|
|
764
|
+
});
|
|
756
765
|
} catch (err) {
|
|
757
|
-
// Non-fatal: source scanning is optional
|
|
758
766
|
const msg = err instanceof Error ? err.message : String(err);
|
|
767
|
+
if (onProgress) onProgress("patterns", "skipped", { reason: msg });
|
|
759
768
|
console.warn(`Source pattern scan failed (non-fatal): ${msg}`);
|
|
760
769
|
}
|
|
761
770
|
}
|
|
@@ -765,6 +774,9 @@ export async function runAudit(options) {
|
|
|
765
774
|
// Step 4: AI enrichment (optional) — requires ANTHROPIC_API_KEY
|
|
766
775
|
const aiOptions = options.ai || {};
|
|
767
776
|
const aiEnabled = aiOptions.enabled !== false && !!aiOptions.apiKey;
|
|
777
|
+
if (!aiEnabled && onProgress && options.ai !== undefined) {
|
|
778
|
+
onProgress("ai", "skipped", { reason: "No API key configured" });
|
|
779
|
+
}
|
|
768
780
|
if (aiEnabled) {
|
|
769
781
|
try {
|
|
770
782
|
if (onProgress) onProgress("ai", "running");
|
|
@@ -1184,6 +1184,10 @@ async function _runDomScannerInternal(args) {
|
|
|
1184
1184
|
|
|
1185
1185
|
writeProgress("page", "running");
|
|
1186
1186
|
|
|
1187
|
+
// Track which steps have already emitted "done" so multi-route scans
|
|
1188
|
+
// don't reset progress back to "running" for routes after the first.
|
|
1189
|
+
const emittedDone = new Set();
|
|
1190
|
+
|
|
1187
1191
|
for (let i = 0; i < routes.length; i += tabPages.length) {
|
|
1188
1192
|
const batch = [];
|
|
1189
1193
|
for (let j = 0; j < tabPages.length && i + j < routes.length; j++) {
|
|
@@ -1195,7 +1199,10 @@ async function _runDomScannerInternal(args) {
|
|
|
1195
1199
|
log.info(`[${idx + 1}/${total}] Scanning: ${routePath}`);
|
|
1196
1200
|
const targetUrl = new URL(routePath, baseUrl).toString();
|
|
1197
1201
|
|
|
1198
|
-
|
|
1202
|
+
if (!emittedDone.has("page")) {
|
|
1203
|
+
writeProgress("page", "done");
|
|
1204
|
+
emittedDone.add("page");
|
|
1205
|
+
}
|
|
1199
1206
|
|
|
1200
1207
|
let result = { url: targetUrl, violations: [], incomplete: [], passes: [], metadata: {} };
|
|
1201
1208
|
let cdpViolations = [];
|
|
@@ -1203,7 +1210,7 @@ async function _runDomScannerInternal(args) {
|
|
|
1203
1210
|
|
|
1204
1211
|
// Step 1: axe-core (conditional)
|
|
1205
1212
|
if (args.engines.axe) {
|
|
1206
|
-
writeProgress("axe", "running");
|
|
1213
|
+
if (!emittedDone.has("axe")) writeProgress("axe", "running");
|
|
1207
1214
|
result = await analyzeRoute(
|
|
1208
1215
|
tabPage,
|
|
1209
1216
|
targetUrl,
|
|
@@ -1216,7 +1223,10 @@ async function _runDomScannerInternal(args) {
|
|
|
1216
1223
|
args.axeTags,
|
|
1217
1224
|
);
|
|
1218
1225
|
const axeViolationCount = result.violations?.length || 0;
|
|
1219
|
-
|
|
1226
|
+
if (!emittedDone.has("axe")) {
|
|
1227
|
+
writeProgress("axe", "done", { found: axeViolationCount });
|
|
1228
|
+
emittedDone.add("axe");
|
|
1229
|
+
}
|
|
1220
1230
|
log.info(`axe-core: ${axeViolationCount} violation(s) found`);
|
|
1221
1231
|
} else {
|
|
1222
1232
|
// Navigate for CDP/pa11y even if axe is off
|
|
@@ -1228,9 +1238,12 @@ async function _runDomScannerInternal(args) {
|
|
|
1228
1238
|
|
|
1229
1239
|
// Step 2: CDP checks (conditional)
|
|
1230
1240
|
if (args.engines.cdp) {
|
|
1231
|
-
writeProgress("cdp", "running");
|
|
1241
|
+
if (!emittedDone.has("cdp")) writeProgress("cdp", "running");
|
|
1232
1242
|
cdpViolations = await runCdpChecks(tabPage);
|
|
1233
|
-
|
|
1243
|
+
if (!emittedDone.has("cdp")) {
|
|
1244
|
+
writeProgress("cdp", "done", { found: cdpViolations.length });
|
|
1245
|
+
emittedDone.add("cdp");
|
|
1246
|
+
}
|
|
1234
1247
|
log.info(`CDP checks: ${cdpViolations.length} issue(s) found`);
|
|
1235
1248
|
} else {
|
|
1236
1249
|
log.info("CDP checks: skipped (disabled)");
|
|
@@ -1238,9 +1251,12 @@ async function _runDomScannerInternal(args) {
|
|
|
1238
1251
|
|
|
1239
1252
|
// Step 3: pa11y (conditional)
|
|
1240
1253
|
if (args.engines.pa11y) {
|
|
1241
|
-
writeProgress("pa11y", "running");
|
|
1254
|
+
if (!emittedDone.has("pa11y")) writeProgress("pa11y", "running");
|
|
1242
1255
|
pa11yViolations = await runPa11yChecks(targetUrl, args.axeTags);
|
|
1243
|
-
|
|
1256
|
+
if (!emittedDone.has("pa11y")) {
|
|
1257
|
+
writeProgress("pa11y", "done", { found: pa11yViolations.length });
|
|
1258
|
+
emittedDone.add("pa11y");
|
|
1259
|
+
}
|
|
1244
1260
|
log.info(`pa11y: ${pa11yViolations.length} issue(s) found`);
|
|
1245
1261
|
} else {
|
|
1246
1262
|
log.info("pa11y: skipped (disabled)");
|
|
@@ -1248,18 +1264,21 @@ async function _runDomScannerInternal(args) {
|
|
|
1248
1264
|
|
|
1249
1265
|
// Step 4: Merge results
|
|
1250
1266
|
const axeViolationCount = result.violations?.length || 0;
|
|
1251
|
-
writeProgress("merge", "running");
|
|
1267
|
+
if (!emittedDone.has("merge")) writeProgress("merge", "running");
|
|
1252
1268
|
const mergedViolations = mergeViolations(
|
|
1253
1269
|
result.violations || [],
|
|
1254
1270
|
cdpViolations,
|
|
1255
1271
|
pa11yViolations,
|
|
1256
1272
|
);
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1273
|
+
if (!emittedDone.has("merge")) {
|
|
1274
|
+
writeProgress("merge", "done", {
|
|
1275
|
+
axe: axeViolationCount,
|
|
1276
|
+
cdp: cdpViolations.length,
|
|
1277
|
+
pa11y: pa11yViolations.length,
|
|
1278
|
+
merged: mergedViolations.length,
|
|
1279
|
+
});
|
|
1280
|
+
emittedDone.add("merge");
|
|
1281
|
+
}
|
|
1263
1282
|
log.info(`Merged: ${mergedViolations.length} total unique violations (axe: ${axeViolationCount}, cdp: ${cdpViolations.length}, pa11y: ${pa11yViolations.length})`);
|
|
1264
1283
|
|
|
1265
1284
|
// Screenshots for merged violations
|
|
@@ -1288,6 +1307,7 @@ async function _runDomScannerInternal(args) {
|
|
|
1288
1307
|
base_url: baseUrl,
|
|
1289
1308
|
onlyRule: args.onlyRule || null,
|
|
1290
1309
|
engines: args.engines,
|
|
1310
|
+
axeTags: args.axeTags || null,
|
|
1291
1311
|
projectContext,
|
|
1292
1312
|
routes: results,
|
|
1293
1313
|
};
|