@diegovelasquezweb/a11y-engine 0.3.0 → 0.4.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.
Files changed (46) hide show
  1. package/assets/generated/discovery/stack-detection.mjs +1 -0
  2. package/assets/source/discovery/stack-detection.json +235 -0
  3. package/package.json +12 -9
  4. package/scripts/sync-assets.mjs +67 -0
  5. package/{scripts → src/cli}/audit.mjs +10 -10
  6. package/{scripts → src}/core/asset-loader.mjs +12 -12
  7. package/{scripts/engine → src/enrichment}/analyzer.mjs +2 -0
  8. package/{scripts → src}/index.mjs +5 -4
  9. package/{scripts/engine → src/pipeline}/dom-scanner.mjs +148 -4
  10. package/assets/discovery/stack-detection.json +0 -33
  11. package/assets/discovery/stack-detection.mjs +0 -1
  12. /package/assets/{discovery → generated/discovery}/crawler-config.mjs +0 -0
  13. /package/assets/{engine → generated/engine}/cdp-checks.mjs +0 -0
  14. /package/assets/{engine → generated/engine}/pa11y-config.mjs +0 -0
  15. /package/assets/{remediation → generated/remediation}/axe-check-maps.mjs +0 -0
  16. /package/assets/{remediation → generated/remediation}/code-patterns.mjs +0 -0
  17. /package/assets/{remediation → generated/remediation}/guardrails.mjs +0 -0
  18. /package/assets/{remediation → generated/remediation}/intelligence.mjs +0 -0
  19. /package/assets/{remediation → generated/remediation}/source-boundaries.mjs +0 -0
  20. /package/assets/{reporting → generated/reporting}/compliance-config.mjs +0 -0
  21. /package/assets/{reporting → generated/reporting}/manual-checks.mjs +0 -0
  22. /package/assets/{reporting → generated/reporting}/wcag-reference.mjs +0 -0
  23. /package/assets/{discovery → source/discovery}/crawler-config.json +0 -0
  24. /package/assets/{engine → source/engine}/cdp-checks.json +0 -0
  25. /package/assets/{engine → source/engine}/pa11y-config.json +0 -0
  26. /package/assets/{remediation → source/remediation}/axe-check-maps.json +0 -0
  27. /package/assets/{remediation → source/remediation}/code-patterns.json +0 -0
  28. /package/assets/{remediation → source/remediation}/guardrails.json +0 -0
  29. /package/assets/{remediation → source/remediation}/intelligence.json +0 -0
  30. /package/assets/{remediation → source/remediation}/source-boundaries.json +0 -0
  31. /package/assets/{reporting → source/reporting}/compliance-config.json +0 -0
  32. /package/assets/{reporting → source/reporting}/manual-checks.json +0 -0
  33. /package/assets/{reporting → source/reporting}/wcag-reference.json +0 -0
  34. /package/{scripts → src}/core/toolchain.mjs +0 -0
  35. /package/{scripts → src}/core/utils.mjs +0 -0
  36. /package/{scripts → src}/index.d.mts +0 -0
  37. /package/{scripts/reports/builders → src/reports}/checklist.mjs +0 -0
  38. /package/{scripts/reports/builders → src/reports}/html.mjs +0 -0
  39. /package/{scripts/reports/builders → src/reports}/md.mjs +0 -0
  40. /package/{scripts/reports/builders → src/reports}/pdf.mjs +0 -0
  41. /package/{scripts → src}/reports/renderers/findings.mjs +0 -0
  42. /package/{scripts → src}/reports/renderers/html.mjs +0 -0
  43. /package/{scripts → src}/reports/renderers/md.mjs +0 -0
  44. /package/{scripts → src}/reports/renderers/pdf.mjs +0 -0
  45. /package/{scripts → src}/reports/renderers/utils.mjs +0 -0
  46. /package/{scripts/engine → src/source-patterns}/source-scanner.mjs +0 -0
@@ -0,0 +1 @@
1
+ export default {"platformStructureDetectors":[["wordpress",["wp-content/themes"]],["drupal",["web/themes","themes"]],["shopify",["sections","snippets","layout","templates"]]],"uiLibraryPackageDetectors":[["@radix-ui","radix"],["@headlessui","headless-ui"],["@chakra-ui","chakra"],["@mantine","mantine"],["@mui","material-ui"],["antd","ant-design"],["@shopify/polaris","polaris"],["@react-aria","react-aria"],["ariakit","ariakit"],["primevue","primevue"],["vuetify","vuetify"],["swiper","swiper"]],"frameworkPackageDetectors":[["next","nextjs"],["gatsby","gatsby"],["nuxt","nuxt"],["@nuxt/core","nuxt"],["@angular/core","angular"],["astro","astro"],["@sveltejs/kit","svelte"],["svelte","svelte"],["vue","vue"],["react","react"]],"domFrameworkDetectors":[{"id":"nextjs","type":"framework","signals":[{"kind":"global","key":"__NEXT_DATA__"},{"kind":"global","key":"__next"},{"kind":"selector","value":"script#__NEXT_DATA__"},{"kind":"selector","value":"div#__next"},{"kind":"scriptSrc","pattern":"/_next/"},{"kind":"meta","name":"next-head-count"}]},{"id":"nuxt","type":"framework","signals":[{"kind":"global","key":"__NUXT__"},{"kind":"global","key":"$nuxt"},{"kind":"selector","value":"div#__nuxt"},{"kind":"scriptSrc","pattern":"/_nuxt/"},{"kind":"meta","name":"generator","pattern":"Nuxt"}]},{"id":"gatsby","type":"framework","signals":[{"kind":"global","key":"___gatsby"},{"kind":"selector","value":"div#___gatsby"},{"kind":"meta","name":"generator","pattern":"Gatsby"}]},{"id":"angular","type":"framework","signals":[{"kind":"global","key":"ng"},{"kind":"selector","value":"[ng-version]"},{"kind":"selector","value":"app-root"}]},{"id":"svelte","type":"framework","signals":[{"kind":"global","key":"__svelte"},{"kind":"selector","value":"[data-svelte-h]"},{"kind":"meta","name":"generator","pattern":"Svelte"}]},{"id":"astro","type":"framework","signals":[{"kind":"selector","value":"[data-astro-cid]"},{"kind":"meta","name":"generator","pattern":"Astro"},{"kind":"selector","value":"astro-island"}]},{"id":"remix","type":"framework","signals":[{"kind":"global","key":"__remixContext"},{"kind":"global","key":"__remixManifest"}]},{"id":"vue","type":"framework","signals":[{"kind":"global","key":"__VUE__"},{"kind":"selector","value":"[data-v-]"},{"kind":"selector","value":"div#app[data-v-app]"}]},{"id":"react","type":"framework","signals":[{"kind":"selector","value":"[data-reactroot]"},{"kind":"selector","value":"[data-reactid]"},{"kind":"global","key":"__REACT_DEVTOOLS_GLOBAL_HOOK__"}]}],"domCmsDetectors":[{"id":"wordpress","type":"cms","signals":[{"kind":"meta","name":"generator","pattern":"WordPress"},{"kind":"scriptSrc","pattern":"/wp-content/"},{"kind":"scriptSrc","pattern":"/wp-includes/"},{"kind":"selector","value":"link[href*='wp-content']"},{"kind":"selector","value":"body.wp-site-blocks"}]},{"id":"shopify","type":"cms","signals":[{"kind":"global","key":"Shopify"},{"kind":"scriptSrc","pattern":"cdn.shopify.com"},{"kind":"meta","name":"shopify-digital-wallet"},{"kind":"selector","value":"link[href*='cdn.shopify']"}]},{"id":"drupal","type":"cms","signals":[{"kind":"meta","name":"generator","pattern":"Drupal"},{"kind":"global","key":"Drupal"},{"kind":"scriptSrc","pattern":"/sites/default/files/"}]},{"id":"wix","type":"cms","signals":[{"kind":"meta","name":"generator","pattern":"Wix"},{"kind":"scriptSrc","pattern":"static.parastorage.com"},{"kind":"scriptSrc","pattern":"static.wixstatic.com"}]},{"id":"squarespace","type":"cms","signals":[{"kind":"meta","name":"generator","pattern":"Squarespace"},{"kind":"scriptSrc","pattern":"static1.squarespace.com"}]},{"id":"webflow","type":"cms","signals":[{"kind":"meta","name":"generator","pattern":"Webflow"},{"kind":"selector","value":"html.w-mod-js"},{"kind":"scriptSrc","pattern":"assets.website-files.com"}]},{"id":"joomla","type":"cms","signals":[{"kind":"meta","name":"generator","pattern":"Joomla"},{"kind":"scriptSrc","pattern":"/media/system/js/"}]},{"id":"magento","type":"cms","signals":[{"kind":"scriptSrc","pattern":"/static/version"},{"kind":"selector","value":"script[data-requiremodule]"},{"kind":"global","key":"require"}]}],"domUiLibraryDetectors":[{"id":"bootstrap","signals":[{"kind":"selector","value":"link[href*='bootstrap']"},{"kind":"scriptSrc","pattern":"bootstrap"},{"kind":"selector","value":".container .row .col"}]},{"id":"tailwindcss","signals":[{"kind":"selector","value":"style[data-precedence]"},{"kind":"selector","value":"link[href*='tailwind']"},{"kind":"meta","name":"generator","pattern":"Tailwind"}]},{"id":"material-ui","signals":[{"kind":"selector","value":"[class*='MuiButton']"},{"kind":"selector","value":"[class*='MuiTypography']"},{"kind":"selector","value":"[class*='MuiPaper']"}]},{"id":"jquery","signals":[{"kind":"global","key":"jQuery"},{"kind":"global","key":"$"}]},{"id":"foundation","signals":[{"kind":"global","key":"Foundation"},{"kind":"selector","value":"link[href*='foundation']"}]}]};
@@ -0,0 +1,235 @@
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegovelasquezweb/a11y-engine",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "WCAG 2.2 AA accessibility audit engine — scanner, analyzer, and report builders",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -14,27 +14,30 @@
14
14
  },
15
15
  "exports": {
16
16
  ".": {
17
- "types": "./scripts/index.d.mts",
18
- "default": "./scripts/index.mjs"
17
+ "types": "./src/index.d.mts",
18
+ "default": "./src/index.mjs"
19
19
  },
20
20
  "./package.json": "./package.json"
21
21
  },
22
- "main": "scripts/index.mjs",
23
- "types": "scripts/index.d.mts",
22
+ "main": "src/index.mjs",
23
+ "types": "src/index.d.mts",
24
24
  "bin": {
25
- "a11y-audit": "scripts/audit.mjs"
25
+ "a11y-audit": "src/cli/audit.mjs"
26
26
  },
27
27
  "files": [
28
- "scripts/**",
29
- "assets/**",
28
+ "src/**",
29
+ "assets/source/**",
30
+ "assets/generated/**",
30
31
  "docs/**",
32
+ "scripts/sync-assets.mjs",
31
33
  "README.md",
32
34
  "CHANGELOG.md",
33
35
  "LICENSE"
34
36
  ],
35
37
  "scripts": {
38
+ "sync-assets": "node scripts/sync-assets.mjs",
36
39
  "test": "vitest run",
37
- "prepublishOnly": "node -e \"console.log('Publishing @diegovelasquezweb/a11y-engine...')\""
40
+ "prepublishOnly": "node scripts/sync-assets.mjs"
38
41
  },
39
42
  "dependencies": {
40
43
  "@axe-core/playwright": "^4.11.1",
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * sync-assets.mjs
4
+ * Generates assets/generated/**\/*.mjs from assets/source/**\/*.json.
5
+ * Run: node scripts/sync-assets.mjs
6
+ * Hooked automatically in prepublishOnly.
7
+ */
8
+
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+ const ROOT = path.resolve(__dirname, "..");
15
+ const SOURCE_DIR = path.join(ROOT, "assets", "source");
16
+ const GENERATED_DIR = path.join(ROOT, "assets", "generated");
17
+
18
+ let synced = 0;
19
+ let skipped = 0;
20
+
21
+ function syncDir(sourceDir, generatedDir) {
22
+ fs.mkdirSync(generatedDir, { recursive: true });
23
+
24
+ for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
25
+ const sourcePath = path.join(sourceDir, entry.name);
26
+ const generatedPath = path.join(generatedDir, entry.name);
27
+
28
+ if (entry.isDirectory()) {
29
+ syncDir(sourcePath, path.join(generatedDir, entry.name));
30
+ continue;
31
+ }
32
+
33
+ if (!entry.name.endsWith(".json")) {
34
+ skipped++;
35
+ continue;
36
+ }
37
+
38
+ const mjsName = entry.name.replace(/\.json$/, ".mjs");
39
+ const mjsPath = path.join(generatedDir, mjsName);
40
+
41
+ let data;
42
+ try {
43
+ data = JSON.parse(fs.readFileSync(sourcePath, "utf-8"));
44
+ } catch (err) {
45
+ console.error(` [ERROR] Failed to parse ${sourcePath}: ${err.message}`);
46
+ process.exit(1);
47
+ }
48
+
49
+ const content = `export default ${JSON.stringify(data)};\n`;
50
+
51
+ // Only write if content changed (avoid unnecessary fs writes)
52
+ let existing = null;
53
+ try { existing = fs.readFileSync(mjsPath, "utf-8"); } catch { /* new file */ }
54
+
55
+ if (existing !== content) {
56
+ fs.writeFileSync(mjsPath, content, "utf-8");
57
+ console.log(` synced: ${path.relative(ROOT, mjsPath)}`);
58
+ synced++;
59
+ } else {
60
+ skipped++;
61
+ }
62
+ }
63
+ }
64
+
65
+ console.log("Syncing assets/source/*.json → assets/generated/*.mjs");
66
+ syncDir(SOURCE_DIR, GENERATED_DIR);
67
+ console.log(`Done. ${synced} file(s) written, ${skipped} unchanged/skipped.`);
@@ -10,7 +10,7 @@ import { spawn, execSync } from "node:child_process";
10
10
  import path from "node:path";
11
11
  import { fileURLToPath } from "node:url";
12
12
  import fs from "node:fs";
13
- import { log, DEFAULTS, SKILL_ROOT, getInternalPath } from "./core/utils.mjs";
13
+ import { log, DEFAULTS, SKILL_ROOT, getInternalPath } from "../core/utils.mjs";
14
14
 
15
15
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
16
 
@@ -205,7 +205,7 @@ async function main() {
205
205
  log.success("Dependencies ready.");
206
206
  }
207
207
 
208
- await runScript("core/toolchain.mjs");
208
+ await runScript("../core/toolchain.mjs");
209
209
 
210
210
  const screenshotsDir = getInternalPath("screenshots");
211
211
  fs.rmSync(screenshotsDir, { recursive: true, force: true });
@@ -258,12 +258,12 @@ async function main() {
258
258
  }
259
259
  if (axeTags) scanArgs.push("--axe-tags", axeTags);
260
260
 
261
- await runScript("engine/dom-scanner.mjs", scanArgs, childEnv);
261
+ await runScript("../pipeline/dom-scanner.mjs", scanArgs, childEnv);
262
262
 
263
263
  const analyzerArgs = [];
264
264
  if (ignoreFindings) analyzerArgs.push("--ignore-findings", ignoreFindings);
265
265
  if (framework) analyzerArgs.push("--framework", framework);
266
- await runScript("engine/analyzer.mjs", analyzerArgs);
266
+ await runScript("../enrichment/analyzer.mjs", analyzerArgs);
267
267
 
268
268
  if (projectDir && !skipPatterns) {
269
269
  const patternArgs = ["--project-dir", path.resolve(projectDir)];
@@ -275,7 +275,7 @@ async function main() {
275
275
  } catch { /* ignore */ }
276
276
  }
277
277
  if (resolvedFramework) patternArgs.push("--framework", resolvedFramework);
278
- await runScript("engine/source-scanner.mjs", patternArgs);
278
+ await runScript("../source-patterns/source-scanner.mjs", patternArgs);
279
279
  }
280
280
 
281
281
  const mdOutput = getInternalPath("remediation.md");
@@ -283,7 +283,7 @@ async function main() {
283
283
  if (target) mdArgs.push("--target", target);
284
284
 
285
285
  if (skipReports) {
286
- await runScript("reports/builders/md.mjs", mdArgs);
286
+ await runScript("../reports/md.mjs", mdArgs);
287
287
  } else {
288
288
  const output = getArgValue("output");
289
289
  if (!output) {
@@ -307,10 +307,10 @@ async function main() {
307
307
  const checklistArgs = ["--output", checklistOutput, "--base-url", baseUrl];
308
308
 
309
309
  await Promise.all([
310
- runScript("reports/builders/html.mjs", buildArgs),
311
- runScript("reports/builders/checklist.mjs", checklistArgs),
312
- runScript("reports/builders/md.mjs", mdArgs),
313
- runScript("reports/builders/pdf.mjs", pdfArgs),
310
+ runScript("../reports/html.mjs", buildArgs),
311
+ runScript("../reports/checklist.mjs", checklistArgs),
312
+ runScript("../reports/md.mjs", mdArgs),
313
+ runScript("../reports/pdf.mjs", pdfArgs),
314
314
  ]);
315
315
 
316
316
  console.log(`REPORT_PATH=${absoluteOutputPath}`);
@@ -5,18 +5,18 @@
5
5
  * This ensures bundlers (Turbopack, Webpack) can trace them automatically.
6
6
  */
7
7
 
8
- import crawlerConfig from "../../assets/discovery/crawler-config.mjs";
9
- import stackDetection from "../../assets/discovery/stack-detection.mjs";
10
- import cdpChecks from "../../assets/engine/cdp-checks.mjs";
11
- import pa11yConfig from "../../assets/engine/pa11y-config.mjs";
12
- import axeCheckMaps from "../../assets/remediation/axe-check-maps.mjs";
13
- import codePatterns from "../../assets/remediation/code-patterns.mjs";
14
- import guardrails from "../../assets/remediation/guardrails.mjs";
15
- import intelligence from "../../assets/remediation/intelligence.mjs";
16
- import sourceBoundaries from "../../assets/remediation/source-boundaries.mjs";
17
- import complianceConfig from "../../assets/reporting/compliance-config.mjs";
18
- import manualChecks from "../../assets/reporting/manual-checks.mjs";
19
- import wcagReference from "../../assets/reporting/wcag-reference.mjs";
8
+ import crawlerConfig from "../../assets/generated/discovery/crawler-config.mjs";
9
+ import stackDetection from "../../assets/generated/discovery/stack-detection.mjs";
10
+ import cdpChecks from "../../assets/generated/engine/cdp-checks.mjs";
11
+ import pa11yConfig from "../../assets/generated/engine/pa11y-config.mjs";
12
+ import axeCheckMaps from "../../assets/generated/remediation/axe-check-maps.mjs";
13
+ import codePatterns from "../../assets/generated/remediation/code-patterns.mjs";
14
+ import guardrails from "../../assets/generated/remediation/guardrails.mjs";
15
+ import intelligence from "../../assets/generated/remediation/intelligence.mjs";
16
+ import sourceBoundaries from "../../assets/generated/remediation/source-boundaries.mjs";
17
+ import complianceConfig from "../../assets/generated/reporting/compliance-config.mjs";
18
+ import manualChecks from "../../assets/generated/reporting/manual-checks.mjs";
19
+ import wcagReference from "../../assets/generated/reporting/wcag-reference.mjs";
20
20
 
21
21
  /**
22
22
  * Pre-loaded asset map. Each value is the parsed JSON object, ready to use.
@@ -755,6 +755,7 @@ function computeTestingMethodology(payload) {
755
755
  pages_scanned: scanned,
756
756
  pages_errored: errored,
757
757
  framework_detected: payload.projectContext?.framework || "Not detected",
758
+ cms_detected: payload.projectContext?.cms || "Not detected",
758
759
  manual_testing: "Not performed (automated scan only)",
759
760
  assistive_tech_tested: "None (automated scan only)",
760
761
  };
@@ -772,6 +773,7 @@ function buildFindings(inputPayload, cliArgs) {
772
773
  const routes = inputPayload.routes || [];
773
774
  const ctx = inputPayload.projectContext || {
774
775
  framework: null,
776
+ cms: null,
775
777
  uiLibraries: [],
776
778
  };
777
779
  if (cliArgs?.framework) ctx.framework = cliArgs.framework;
@@ -457,8 +457,8 @@ export function getAuditSummary(findings, payload = null) {
457
457
  export async function runAudit(options) {
458
458
  if (!options.baseUrl) throw new Error("runAudit requires baseUrl");
459
459
 
460
- const { runDomScanner } = await import("./engine/dom-scanner.mjs");
461
- const { runAnalyzer } = await import("./engine/analyzer.mjs");
460
+ const { runDomScanner } = await import("./pipeline/dom-scanner.mjs");
461
+ const { runAnalyzer } = await import("./enrichment/analyzer.mjs");
462
462
 
463
463
  const onProgress = options.onProgress || null;
464
464
 
@@ -481,6 +481,7 @@ export async function runAudit(options) {
481
481
  onlyRule: options.onlyRule,
482
482
  excludeSelectors: options.excludeSelectors,
483
483
  screenshotsDir: options.screenshotsDir,
484
+ projectDir: options.projectDir,
484
485
  },
485
486
  { onProgress },
486
487
  );
@@ -496,7 +497,7 @@ export async function runAudit(options) {
496
497
  // Step 3: Source patterns (optional)
497
498
  if (options.projectDir && !options.skipPatterns) {
498
499
  try {
499
- const { resolveScanDirs, scanPattern } = await import("./engine/source-scanner.mjs");
500
+ const { resolveScanDirs, scanPattern } = await import("./source-patterns/source-scanner.mjs");
500
501
  const { patterns } = loadAssetJson(ASSET_PATHS.remediation.codePatterns, "code-patterns.json");
501
502
 
502
503
  let resolvedFramework = options.framework;
@@ -892,7 +893,7 @@ export async function getRemediationGuide(payload, options = {}) {
892
893
  * @returns {Promise<{ findings: object[], summary: { total: number, confirmed: number, potential: number } }>}
893
894
  */
894
895
  export async function getSourcePatterns(projectDir, options = {}) {
895
- const { scanPattern, resolveScanDirs } = await import("./engine/source-scanner.mjs");
896
+ const { scanPattern, resolveScanDirs } = await import("./source-patterns/source-scanner.mjs");
896
897
 
897
898
  const { patterns } = loadAssetJson(ASSET_PATHS.remediation.codePatterns, "code-patterns.json");
898
899
 
@@ -337,14 +337,18 @@ export async function discoverRoutes(page, baseUrl, maxRoutes, crawlDepth = 2) {
337
337
 
338
338
  /**
339
339
  * Detects the web framework and UI libraries used by analyzing package.json and file structure.
340
+ * @param {string|null} [explicitProjectDir=null] - Explicit project directory. Falls back to env/cwd.
340
341
  * @returns {Object} An object containing detected framework and UI libraries.
341
342
  */
342
- function detectProjectContext() {
343
+ function detectProjectContext(explicitProjectDir = null) {
343
344
  const uiLibraries = [];
344
345
  let pkgFramework = null;
345
346
  let fileFramework = null;
346
347
 
347
- const projectDir = process.env.A11Y_PROJECT_DIR || process.cwd();
348
+ const projectDir = explicitProjectDir || process.env.A11Y_PROJECT_DIR || null;
349
+ if (!projectDir) {
350
+ return { framework: null, uiLibraries: [] };
351
+ }
348
352
 
349
353
  try {
350
354
  const pkgPath = path.join(projectDir, "package.json");
@@ -388,6 +392,126 @@ function detectProjectContext() {
388
392
  return { framework: resolvedFramework, uiLibraries };
389
393
  }
390
394
 
395
+ /**
396
+ * Detects the web framework, CMS, and UI libraries by inspecting the live page DOM,
397
+ * window globals, script sources, and meta tags. This works for any remote URL
398
+ * without needing access to the project source code.
399
+ * @param {import("playwright").Page} page - The Playwright page object (already navigated).
400
+ * @returns {Promise<{ framework: string|null, cms: string|null, uiLibraries: string[] }>}
401
+ */
402
+ async function detectProjectContextFromDom(page) {
403
+ const frameworkDetectors = STACK_DETECTION.domFrameworkDetectors || [];
404
+ const cmsDetectors = STACK_DETECTION.domCmsDetectors || [];
405
+ const uiDetectors = STACK_DETECTION.domUiLibraryDetectors || [];
406
+
407
+ const result = await page.evaluate(({ frameworkDetectors, cmsDetectors, uiDetectors }) => {
408
+ function checkSignals(signals) {
409
+ let matched = 0;
410
+ for (const signal of signals) {
411
+ try {
412
+ if (signal.kind === "global") {
413
+ if (typeof window[signal.key] !== "undefined" && window[signal.key] !== null) {
414
+ matched++;
415
+ }
416
+ } else if (signal.kind === "selector") {
417
+ if (document.querySelector(signal.value)) {
418
+ matched++;
419
+ }
420
+ } else if (signal.kind === "scriptSrc") {
421
+ const scripts = document.querySelectorAll("script[src]");
422
+ for (const s of scripts) {
423
+ if (s.getAttribute("src")?.includes(signal.pattern)) {
424
+ matched++;
425
+ break;
426
+ }
427
+ }
428
+ } else if (signal.kind === "meta") {
429
+ const metas = document.querySelectorAll(`meta[name="${signal.name}"],meta[property="${signal.name}"]`);
430
+ if (metas.length > 0) {
431
+ if (signal.pattern) {
432
+ for (const m of metas) {
433
+ if (m.getAttribute("content")?.toLowerCase().includes(signal.pattern.toLowerCase())) {
434
+ matched++;
435
+ break;
436
+ }
437
+ }
438
+ } else {
439
+ matched++;
440
+ }
441
+ }
442
+ }
443
+ } catch {
444
+ // ignore individual signal errors
445
+ }
446
+ }
447
+ return matched;
448
+ }
449
+
450
+ function detectBest(detectors) {
451
+ let best = null;
452
+ let bestScore = 0;
453
+ for (const detector of detectors) {
454
+ const score = checkSignals(detector.signals);
455
+ if (score > 0 && score > bestScore) {
456
+ bestScore = score;
457
+ best = detector.id;
458
+ }
459
+ }
460
+ return best;
461
+ }
462
+
463
+ function detectAllUiLibs(detectors) {
464
+ const found = [];
465
+ for (const detector of detectors) {
466
+ let total = 0;
467
+ let strongSignals = 0;
468
+ for (const signal of detector.signals) {
469
+ try {
470
+ let hit = false;
471
+ if (signal.kind === "global") {
472
+ hit = typeof window[signal.key] !== "undefined" && window[signal.key] !== null;
473
+ } else if (signal.kind === "selector") {
474
+ hit = !!document.querySelector(signal.value);
475
+ } else if (signal.kind === "scriptSrc") {
476
+ const scripts = document.querySelectorAll("script[src]");
477
+ for (const s of scripts) {
478
+ if (s.getAttribute("src")?.includes(signal.pattern)) { hit = true; break; }
479
+ }
480
+ } else if (signal.kind === "meta") {
481
+ const metas = document.querySelectorAll(`meta[name="${signal.name}"],meta[property="${signal.name}"]`);
482
+ for (const m of metas) {
483
+ if (!signal.pattern || m.getAttribute("content")?.toLowerCase().includes(signal.pattern.toLowerCase())) {
484
+ hit = true; break;
485
+ }
486
+ }
487
+ }
488
+ if (hit) {
489
+ total++;
490
+ if (signal.kind !== "selector") strongSignals++;
491
+ }
492
+ } catch {}
493
+ }
494
+ if (total >= 2 || strongSignals >= 1) {
495
+ found.push(detector.id);
496
+ }
497
+ }
498
+ return found;
499
+ }
500
+
501
+ const framework = detectBest(frameworkDetectors);
502
+ const cms = detectBest(cmsDetectors);
503
+ const uiLibraries = detectAllUiLibs(uiDetectors);
504
+
505
+ return { framework, cms, uiLibraries };
506
+ }, { frameworkDetectors, cmsDetectors, uiDetectors });
507
+
508
+ if (result.framework) log.info(`DOM detection: framework=${result.framework}`);
509
+ if (result.cms) log.info(`DOM detection: cms=${result.cms}`);
510
+ if (result.uiLibraries.length) log.info(`DOM detection: uiLibraries=${result.uiLibraries.join(", ")}`);
511
+
512
+ return result;
513
+ }
514
+
391
515
  /**
392
516
  * Navigates to a route and performs an axe-core accessibility analysis.
393
517
  * @param {import("playwright").Page} page - The Playwright page object.
@@ -820,6 +944,7 @@ export async function runDomScanner(options = {}, callbacks = {}) {
820
944
  crawlDepth: Math.min(Math.max(options.crawlDepth ?? DEFAULTS.crawlDepth, 1), 3),
821
945
  viewport: options.viewport || null,
822
946
  axeTags: options.axeTags || null,
947
+ projectDir: options.projectDir || null,
823
948
  };
824
949
 
825
950
  if (!args.baseUrl) throw new Error("Missing required option: baseUrl");
@@ -859,14 +984,33 @@ async function _runDomScannerInternal(args) {
859
984
  const page = await context.newPage();
860
985
 
861
986
  let routes = [];
862
- let projectContext = { framework: null, uiLibraries: [] };
987
+ let projectContext = { framework: null, cms: null, uiLibraries: [] };
863
988
  try {
864
989
  await page.goto(baseUrl, {
865
990
  waitUntil: args.waitUntil,
866
991
  timeout: args.timeoutMs,
867
992
  });
868
993
 
869
- projectContext = detectProjectContext();
994
+ // 1. File-system / package.json detection (works when projectDir is available)
995
+ const repoCtx = detectProjectContext(args.projectDir || null);
996
+
997
+ // 2. DOM/runtime detection (always works for any remote URL)
998
+ let domCtx = { framework: null, cms: null, uiLibraries: [] };
999
+ try {
1000
+ domCtx = await detectProjectContextFromDom(page);
1001
+ } catch (err) {
1002
+ log.warn(`DOM stack detection failed (non-fatal): ${err.message}`);
1003
+ }
1004
+
1005
+ // 3. Merge: repo detection takes priority, DOM fills gaps
1006
+ projectContext = {
1007
+ framework: repoCtx.framework || domCtx.framework || null,
1008
+ cms: domCtx.cms || null,
1009
+ uiLibraries: [...new Set([
1010
+ ...(repoCtx.uiLibraries || []),
1011
+ ...(domCtx.uiLibraries || []),
1012
+ ])],
1013
+ };
870
1014
 
871
1015
  const cliRoutes = parseRoutesArg(args.routes, origin);
872
1016
 
@@ -1,33 +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
- }
@@ -1 +0,0 @@
1
- export default {"platformStructureDetectors":[["wordpress",["wp-content/themes"]],["drupal",["web/themes","themes"]],["shopify",["sections","snippets","layout","templates"]]],"uiLibraryPackageDetectors":[["@radix-ui","radix"],["@headlessui","headless-ui"],["@chakra-ui","chakra"],["@mantine","mantine"],["@mui","material-ui"],["antd","ant-design"],["@shopify/polaris","polaris"],["@react-aria","react-aria"],["ariakit","ariakit"],["primevue","primevue"],["vuetify","vuetify"],["swiper","swiper"]],"frameworkPackageDetectors":[["next","nextjs"],["gatsby","gatsby"],["nuxt","nuxt"],["@nuxt/core","nuxt"],["@angular/core","angular"],["astro","astro"],["@sveltejs/kit","svelte"],["svelte","svelte"],["vue","vue"],["react","react"]]};
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes