@diegovelasquezweb/a11y-engine 0.3.0 → 0.3.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.
|
@@ -29,5 +29,207 @@
|
|
|
29
29
|
["svelte", "svelte"],
|
|
30
30
|
["vue", "vue"],
|
|
31
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
|
+
}
|
|
32
234
|
]
|
|
33
235
|
}
|
|
@@ -1 +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"]]};
|
|
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']"}]}]};
|
package/package.json
CHANGED
|
@@ -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;
|
|
@@ -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 ||
|
|
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
|
-
|
|
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
|
|
package/scripts/index.mjs
CHANGED