@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegovelasquezweb/a11y-engine",
3
- "version": "0.6.6",
3
+ "version": "0.7.1",
4
4
  "description": "WCAG 2.2 accessibility audit engine — scanner, analyzer, and report builders",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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
- console.info("[a11y-engine] Fetched package.json from GitHub repo");
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
- console.warn(`[a11y-engine] Could not fetch package.json from repo (non-fatal): ${err.message}`);
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
- writeProgress("page", "done");
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
- writeProgress("axe", "done", { found: axeViolationCount });
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
- writeProgress("cdp", "done", { found: cdpViolations.length });
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
- writeProgress("pa11y", "done", { found: pa11yViolations.length });
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
- writeProgress("merge", "done", {
1258
- axe: axeViolationCount,
1259
- cdp: cdpViolations.length,
1260
- pa11y: pa11yViolations.length,
1261
- merged: mergedViolations.length,
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
  };