@holmdigital/engine 1.2.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/LICENSE +59 -0
- package/README.md +60 -0
- package/dist/chunk-MFACMI2F.mjs +411 -0
- package/dist/chunk-OXZPYWBW.mjs +233 -0
- package/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1197 -0
- package/dist/cli/index.mjs +501 -0
- package/dist/i18n-FLJMYIKJ.mjs +11 -0
- package/dist/index.d.mts +88 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.js +718 -0
- package/dist/index.mjs +11 -0
- package/package.json +69 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,718 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// src/locales/en.json
|
|
34
|
+
var en_default;
|
|
35
|
+
var init_en = __esm({
|
|
36
|
+
"src/locales/en.json"() {
|
|
37
|
+
en_default = {
|
|
38
|
+
cli: {
|
|
39
|
+
title: "\n\u{1F310} HolmDigital Regulatory Scanner\n",
|
|
40
|
+
scanning: "Scanning {url}...\n",
|
|
41
|
+
initializing: "Initializing scanner...",
|
|
42
|
+
analyzing: "Analyzing DOM & Shadow DOM...",
|
|
43
|
+
complete: "Scan complete!",
|
|
44
|
+
generating_pdf: "Generating PDF Report...",
|
|
45
|
+
pdf_saved: "PDF Report saved to {path}",
|
|
46
|
+
scan_failed: "Scan failed",
|
|
47
|
+
critical_failure: "\nCI/CD Failure: Critical regulatory violations found.",
|
|
48
|
+
score: "\nCompliance Score: {score}/100",
|
|
49
|
+
status: "Compliance Status: {status}",
|
|
50
|
+
not_compliant: " (Not compliant with legal requirements)",
|
|
51
|
+
viewport: "Viewport: {width}x{height}",
|
|
52
|
+
prescriptive_fix: "\n\u{1F4A1} Prescriptive Fix:",
|
|
53
|
+
use_component: "Use component: {component}",
|
|
54
|
+
pseudo_tests: "\n\u{1F9EC} Generating Pseudo-Automation Tests...\n",
|
|
55
|
+
test_for: "Test for {ruleId}:"
|
|
56
|
+
},
|
|
57
|
+
report: {
|
|
58
|
+
title: "Accessibility Report - {url}",
|
|
59
|
+
generated: "Generated: {date}",
|
|
60
|
+
scan_target: "Scan Target: {url}",
|
|
61
|
+
overall_score: "Overall Score",
|
|
62
|
+
critical_issues: "Critical Issues",
|
|
63
|
+
high_issues: "High Issues",
|
|
64
|
+
total_issues: "Total Issues",
|
|
65
|
+
detailed_violations: "Detailed Violations",
|
|
66
|
+
prescriptive_fix: "\u{1F4A1} Prescriptive Fix",
|
|
67
|
+
use: "Use",
|
|
68
|
+
footer: "Generated by @holmdigital/engine v0.1.0 \u2022 Standards: WCAG 2.1 AA, EN 301 549, DOS-lagen"
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// src/locales/sv.json
|
|
75
|
+
var sv_default;
|
|
76
|
+
var init_sv = __esm({
|
|
77
|
+
"src/locales/sv.json"() {
|
|
78
|
+
sv_default = {
|
|
79
|
+
cli: {
|
|
80
|
+
title: "\n\u{1F310} HolmDigital Regulatorisk Scanner\n",
|
|
81
|
+
scanning: "Skannar {url}...\n",
|
|
82
|
+
initializing: "Initierar skanner...",
|
|
83
|
+
analyzing: "Analyserar DOM & Shadow DOM...",
|
|
84
|
+
complete: "Skanning klar!",
|
|
85
|
+
generating_pdf: "Genererar PDF-rapport...",
|
|
86
|
+
pdf_saved: "PDF-rapport sparad till {path}",
|
|
87
|
+
scan_failed: "Skanning misslyckades",
|
|
88
|
+
critical_failure: "\nCI/CD Fel: Kritiska regulatoriska brister hittades.",
|
|
89
|
+
score: "\nEfterlevnadspo\xE4ng: {score}/100",
|
|
90
|
+
status: "Efterlevnadsstatus: {status}",
|
|
91
|
+
not_compliant: " (Uppfyller ej legala krav)",
|
|
92
|
+
viewport: "Sk\xE4rmstorlek: {width}x{height}",
|
|
93
|
+
prescriptive_fix: "\n\u{1F4A1} Preskriptiv L\xF6sning:",
|
|
94
|
+
use_component: "Anv\xE4nd komponent: {component}",
|
|
95
|
+
pseudo_tests: "\n\u{1F9EC} Genererar Pseudo-automationstester...\n",
|
|
96
|
+
test_for: "Test f\xF6r {ruleId}:"
|
|
97
|
+
},
|
|
98
|
+
report: {
|
|
99
|
+
title: "Tillg\xE4nglighetsrapport - {url}",
|
|
100
|
+
generated: "Genererad: {date}",
|
|
101
|
+
scan_target: "Skannat m\xE5l: {url}",
|
|
102
|
+
overall_score: "Totalpo\xE4ng",
|
|
103
|
+
critical_issues: "Kritiska fel",
|
|
104
|
+
high_issues: "Allvarliga fel",
|
|
105
|
+
total_issues: "Antal brister",
|
|
106
|
+
detailed_violations: "Detaljerade brister",
|
|
107
|
+
prescriptive_fix: "\u{1F4A1} Preskriptiv L\xF6sning",
|
|
108
|
+
use: "Anv\xE4nd",
|
|
109
|
+
footer: "Genererad av @holmdigital/engine v0.1.0 \u2022 Standarder: WCAG 2.1 AA, EN 301 549, DOS-lagen"
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// src/locales/de.json
|
|
116
|
+
var de_default;
|
|
117
|
+
var init_de = __esm({
|
|
118
|
+
"src/locales/de.json"() {
|
|
119
|
+
de_default = {
|
|
120
|
+
cli: {
|
|
121
|
+
title: "\n\u{1F310} HolmDigital Regulatorischer Scanner\n",
|
|
122
|
+
scanning: "Scanne {url}...\n",
|
|
123
|
+
initializing: "Initialisiere Scanner...",
|
|
124
|
+
analyzing: "Analysiere DOM & Shadow DOM...",
|
|
125
|
+
complete: "Scan abgeschlossen!",
|
|
126
|
+
generating_pdf: "Generiere PDF-Bericht...",
|
|
127
|
+
pdf_saved: "PDF-Bericht gespeichert unter {path}",
|
|
128
|
+
scan_failed: "Scan fehlgeschlagen",
|
|
129
|
+
critical_failure: "\nCI/CD Fehler: Kritische regulatorische Verst\xF6\xDFe gefunden.",
|
|
130
|
+
score: "\nKonformit\xE4tsbewertung: {score}/100",
|
|
131
|
+
status: "Konformit\xE4tsstatus: {status}",
|
|
132
|
+
not_compliant: " (Nicht konform mit gesetzlichen Anforderungen - BITV 2.0)",
|
|
133
|
+
viewport: "Ansichtsfenster: {width}x{height}",
|
|
134
|
+
prescriptive_fix: "\n\u{1F4A1} Vorschriftsm\xE4\xDFige L\xF6sung:",
|
|
135
|
+
use_component: "Verwenden Sie die Komponente: {component}",
|
|
136
|
+
pseudo_tests: "\n\u{1F9EC} Generiere Pseudo-Automatisierungstests...\n",
|
|
137
|
+
test_for: "Test f\xFCr {ruleId}:"
|
|
138
|
+
},
|
|
139
|
+
report: {
|
|
140
|
+
title: "Barrierefreiheitsbericht - {url}",
|
|
141
|
+
generated: "Generiert: {date}",
|
|
142
|
+
scan_target: "Scan-Ziel: {url}",
|
|
143
|
+
overall_score: "Gesamtbewertung",
|
|
144
|
+
critical_issues: "Kritische Probleme",
|
|
145
|
+
high_issues: "Hohe Probleme",
|
|
146
|
+
total_issues: "Gesamtprobleme",
|
|
147
|
+
detailed_violations: "Detaillierte Verst\xF6\xDFe",
|
|
148
|
+
prescriptive_fix: "\u{1F4A1} Vorschriftsm\xE4\xDFige L\xF6sung",
|
|
149
|
+
use: "Verwenden",
|
|
150
|
+
footer: "Generiert von @holmdigital/engine v0.1.0 \u2022 Standards: WCAG 2.1 AA, EN 301 549, BITV 2.0"
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// src/locales/fr.json
|
|
157
|
+
var fr_default;
|
|
158
|
+
var init_fr = __esm({
|
|
159
|
+
"src/locales/fr.json"() {
|
|
160
|
+
fr_default = {
|
|
161
|
+
cli: {
|
|
162
|
+
title: "\n\u{1F310} HolmDigital Scanner R\xE9glementaire\n",
|
|
163
|
+
scanning: "Scan de {url} en cours...\n",
|
|
164
|
+
initializing: "Initialisation du scanner...",
|
|
165
|
+
analyzing: "Analyse du DOM & Shadow DOM...",
|
|
166
|
+
complete: "Scan termin\xE9 !",
|
|
167
|
+
generating_pdf: "G\xE9n\xE9ration du rapport PDF...",
|
|
168
|
+
pdf_saved: "Rapport PDF enregistr\xE9 sous {path}",
|
|
169
|
+
scan_failed: "\xC9chec du scan",
|
|
170
|
+
critical_failure: "\n\xC9chec CI/CD : Violations r\xE9glementaires critiques d\xE9tect\xE9es.",
|
|
171
|
+
score: "\nScore de Conformit\xE9 : {score}/100",
|
|
172
|
+
status: "Statut de Conformit\xE9 : {status}",
|
|
173
|
+
not_compliant: " (Non conforme aux exigences l\xE9gales - RGAA)",
|
|
174
|
+
viewport: "Fen\xEAtre d'affichage : {width}x{height}",
|
|
175
|
+
prescriptive_fix: "\n\u{1F4A1} Solution Prescriptive :",
|
|
176
|
+
use_component: "Utilisez le composant : {component}",
|
|
177
|
+
pseudo_tests: "\n\u{1F9EC} G\xE9n\xE9ration de tests de pseudo-automatisation...\n",
|
|
178
|
+
test_for: "Test pour {ruleId} :"
|
|
179
|
+
},
|
|
180
|
+
report: {
|
|
181
|
+
title: "Rapport d'Accessibilit\xE9 - {url}",
|
|
182
|
+
generated: "G\xE9n\xE9r\xE9 le : {date}",
|
|
183
|
+
scan_target: "Cible du scan : {url}",
|
|
184
|
+
overall_score: "Score Global",
|
|
185
|
+
critical_issues: "Probl\xE8mes Critiques",
|
|
186
|
+
high_issues: "Probl\xE8mes Importants",
|
|
187
|
+
total_issues: "Total des Probl\xE8mes",
|
|
188
|
+
detailed_violations: "Violations D\xE9taill\xE9es",
|
|
189
|
+
prescriptive_fix: "\u{1F4A1} Solution Prescriptive",
|
|
190
|
+
use: "Utiliser",
|
|
191
|
+
footer: "G\xE9n\xE9r\xE9 par @holmdigital/engine v0.1.0 \u2022 Standards : WCAG 2.1 AA, EN 301 549, RGAA"
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// src/locales/es.json
|
|
198
|
+
var es_default;
|
|
199
|
+
var init_es = __esm({
|
|
200
|
+
"src/locales/es.json"() {
|
|
201
|
+
es_default = {
|
|
202
|
+
cli: {
|
|
203
|
+
title: "\n\u{1F310} HolmDigital Esc\xE1ner Regulatorio\n",
|
|
204
|
+
scanning: "Escaneando {url}...\n",
|
|
205
|
+
initializing: "Inicializando esc\xE1ner...",
|
|
206
|
+
analyzing: "Analizando DOM y Shadow DOM...",
|
|
207
|
+
complete: "\xA1Escaneo completo!",
|
|
208
|
+
generating_pdf: "Generando Informe PDF...",
|
|
209
|
+
pdf_saved: "Informe PDF guardado en {path}",
|
|
210
|
+
scan_failed: "Escaneo fallido",
|
|
211
|
+
critical_failure: "\nFallo de CI/CD: Se encontraron violaciones regulatorias cr\xEDticas.",
|
|
212
|
+
score: "\nPuntuaci\xF3n de Cumplimiento: {score}/100",
|
|
213
|
+
status: "Estado de Cumplimiento: {status}",
|
|
214
|
+
not_compliant: " (No cumple con los requisitos legales - UNE 139803)",
|
|
215
|
+
viewport: "Ventana gr\xE1fica: {width}x{height}",
|
|
216
|
+
prescriptive_fix: "\n\u{1F4A1} Soluci\xF3n Prescriptiva:",
|
|
217
|
+
use_component: "Use el componente: {component}",
|
|
218
|
+
pseudo_tests: "\n\u{1F9EC} Generando pruebas de pseudo-automatizaci\xF3n...\n",
|
|
219
|
+
test_for: "Prueba para {ruleId}:"
|
|
220
|
+
},
|
|
221
|
+
report: {
|
|
222
|
+
title: "Informe de Accesibilidad - {url}",
|
|
223
|
+
generated: "Generado: {date}",
|
|
224
|
+
scan_target: "Objetivo de escaneo: {url}",
|
|
225
|
+
overall_score: "Puntuaci\xF3n General",
|
|
226
|
+
critical_issues: "Problemas Cr\xEDticos",
|
|
227
|
+
high_issues: "Problemas Altos",
|
|
228
|
+
total_issues: "Problemas Totales",
|
|
229
|
+
detailed_violations: "Violaciones Detalladas",
|
|
230
|
+
prescriptive_fix: "\u{1F4A1} Soluci\xF3n Prescriptiva",
|
|
231
|
+
use: "Usar",
|
|
232
|
+
footer: "Generado por @holmdigital/engine v0.1.0 \u2022 Est\xE1ndares: WCAG 2.1 AA, EN 301 549, UNE 139803"
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// src/i18n/index.ts
|
|
239
|
+
var i18n_exports = {};
|
|
240
|
+
__export(i18n_exports, {
|
|
241
|
+
getCurrentLang: () => getCurrentLang,
|
|
242
|
+
setLanguage: () => setLanguage,
|
|
243
|
+
t: () => t
|
|
244
|
+
});
|
|
245
|
+
function setLanguage(lang) {
|
|
246
|
+
if (locales[lang]) {
|
|
247
|
+
currentLang = lang;
|
|
248
|
+
} else {
|
|
249
|
+
console.warn(`Language '${lang}' not found, falling back to 'en'.`);
|
|
250
|
+
currentLang = "en";
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function t(key, params) {
|
|
254
|
+
const keys = key.split(".");
|
|
255
|
+
let value = locales[currentLang];
|
|
256
|
+
for (const k of keys) {
|
|
257
|
+
if (value && typeof value === "object" && k in value) {
|
|
258
|
+
value = value[k];
|
|
259
|
+
} else {
|
|
260
|
+
let fallbackValue = locales["en"];
|
|
261
|
+
for (const fk of keys) {
|
|
262
|
+
if (fallbackValue && typeof fallbackValue === "object" && fk in fallbackValue) {
|
|
263
|
+
fallbackValue = fallbackValue[fk];
|
|
264
|
+
} else {
|
|
265
|
+
return key;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
value = fallbackValue;
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (typeof value !== "string") return key;
|
|
273
|
+
if (params) {
|
|
274
|
+
return value.replace(/{(\w+)}/g, (_, k) => {
|
|
275
|
+
return params[k] !== void 0 ? String(params[k]) : `{${k}}`;
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
return value;
|
|
279
|
+
}
|
|
280
|
+
function getCurrentLang() {
|
|
281
|
+
return currentLang;
|
|
282
|
+
}
|
|
283
|
+
var locales, currentLang;
|
|
284
|
+
var init_i18n = __esm({
|
|
285
|
+
"src/i18n/index.ts"() {
|
|
286
|
+
"use strict";
|
|
287
|
+
init_en();
|
|
288
|
+
init_sv();
|
|
289
|
+
init_de();
|
|
290
|
+
init_fr();
|
|
291
|
+
init_es();
|
|
292
|
+
locales = {
|
|
293
|
+
en: en_default,
|
|
294
|
+
sv: sv_default,
|
|
295
|
+
de: de_default,
|
|
296
|
+
fr: fr_default,
|
|
297
|
+
es: es_default
|
|
298
|
+
};
|
|
299
|
+
currentLang = "en";
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// src/index.ts
|
|
304
|
+
var index_exports = {};
|
|
305
|
+
__export(index_exports, {
|
|
306
|
+
PseudoAutomationEngine: () => PseudoAutomationEngine,
|
|
307
|
+
RegulatoryScanner: () => RegulatoryScanner,
|
|
308
|
+
VirtualDOMBuilder: () => VirtualDOMBuilder
|
|
309
|
+
});
|
|
310
|
+
module.exports = __toCommonJS(index_exports);
|
|
311
|
+
|
|
312
|
+
// src/core/regulatory-scanner.ts
|
|
313
|
+
var import_puppeteer = __toESM(require("puppeteer"));
|
|
314
|
+
|
|
315
|
+
// src/core/virtual-dom.ts
|
|
316
|
+
var VirtualDOMBuilder = class {
|
|
317
|
+
page;
|
|
318
|
+
constructor(page) {
|
|
319
|
+
this.page = page;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Bygg ett virtuellt träd av hela sidan
|
|
323
|
+
*/
|
|
324
|
+
async build(config = {}) {
|
|
325
|
+
return await this.page.evaluate((config2) => {
|
|
326
|
+
let nodeIdCounter = 0;
|
|
327
|
+
function generateId() {
|
|
328
|
+
return `vn-${++nodeIdCounter}`;
|
|
329
|
+
}
|
|
330
|
+
function getAttributes(element) {
|
|
331
|
+
const attrs = {};
|
|
332
|
+
for (let i = 0; i < element.attributes.length; i++) {
|
|
333
|
+
const attr = element.attributes[i];
|
|
334
|
+
attrs[attr.name] = attr.value;
|
|
335
|
+
}
|
|
336
|
+
return attrs;
|
|
337
|
+
}
|
|
338
|
+
function getRect(element) {
|
|
339
|
+
const r = element.getBoundingClientRect();
|
|
340
|
+
return {
|
|
341
|
+
x: r.x,
|
|
342
|
+
y: r.y,
|
|
343
|
+
width: r.width,
|
|
344
|
+
height: r.height
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function traverse(node, parent, depth = 0) {
|
|
348
|
+
if (!node) return null;
|
|
349
|
+
if (config2.maxDepth && depth > config2.maxDepth) return null;
|
|
350
|
+
if (node.nodeType !== Node.ELEMENT_NODE && node.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) return null;
|
|
351
|
+
const element = node;
|
|
352
|
+
const isShadowRoot = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
|
|
353
|
+
const tagName = isShadowRoot ? "#shadow-root" : element.tagName.toLowerCase();
|
|
354
|
+
const attributes = !isShadowRoot ? getAttributes(element) : {};
|
|
355
|
+
let computedStyle;
|
|
356
|
+
if (!isShadowRoot && config2.includeComputedStyle) {
|
|
357
|
+
const style = window.getComputedStyle(element);
|
|
358
|
+
computedStyle = {};
|
|
359
|
+
config2.includeComputedStyle.forEach((prop) => {
|
|
360
|
+
computedStyle[prop] = style.getPropertyValue(prop);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
const vNode = {
|
|
364
|
+
nodeId: generateId(),
|
|
365
|
+
tagName,
|
|
366
|
+
attributes,
|
|
367
|
+
children: [],
|
|
368
|
+
parentId: parent?.nodeId,
|
|
369
|
+
isShadowRoot,
|
|
370
|
+
rect: !isShadowRoot ? getRect(element) : { x: 0, y: 0, width: 0, height: 0 },
|
|
371
|
+
computedStyle,
|
|
372
|
+
textContent: node.textContent || void 0
|
|
373
|
+
};
|
|
374
|
+
if (isShadowRoot && node.mode) {
|
|
375
|
+
vNode.shadowMode = node.mode;
|
|
376
|
+
}
|
|
377
|
+
if (element.childNodes) {
|
|
378
|
+
Array.from(element.childNodes).forEach((child) => {
|
|
379
|
+
if (child.nodeType === Node.ELEMENT_NODE) {
|
|
380
|
+
const childVNode = traverse(child, vNode, depth + 1);
|
|
381
|
+
if (childVNode) vNode.children.push(childVNode);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
if (!isShadowRoot && element.shadowRoot) {
|
|
386
|
+
const shadowVNode = traverse(element.shadowRoot, vNode, depth + 1);
|
|
387
|
+
if (shadowVNode) vNode.children.push(shadowVNode);
|
|
388
|
+
}
|
|
389
|
+
return vNode;
|
|
390
|
+
}
|
|
391
|
+
if (!document.body) {
|
|
392
|
+
return {
|
|
393
|
+
nodeId: "root-fallback",
|
|
394
|
+
tagName: "body",
|
|
395
|
+
attributes: {},
|
|
396
|
+
children: [],
|
|
397
|
+
rect: { x: 0, y: 0, width: 0, height: 0 }
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
return traverse(document.body);
|
|
401
|
+
}, config);
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
// src/core/html-validator.ts
|
|
406
|
+
var import_html_validate = require("html-validate");
|
|
407
|
+
var HtmlValidator = class {
|
|
408
|
+
validator;
|
|
409
|
+
constructor() {
|
|
410
|
+
const config = {
|
|
411
|
+
extends: ["html-validate:recommended"],
|
|
412
|
+
rules: {
|
|
413
|
+
"no-deprecated-attr": "off",
|
|
414
|
+
// Focus on structure, not deprecation
|
|
415
|
+
"prefer-native-element": "off",
|
|
416
|
+
"no-trailing-whitespace": "off",
|
|
417
|
+
"void-style": "off",
|
|
418
|
+
"no-inline-style": "off",
|
|
419
|
+
// Allowed for A11y overrides
|
|
420
|
+
"no-implicit-button-type": "off"
|
|
421
|
+
// Common in React
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
this.validator = new import_html_validate.HtmlValidate(config);
|
|
425
|
+
}
|
|
426
|
+
async validate(html) {
|
|
427
|
+
const report = await this.validator.validateString(html);
|
|
428
|
+
return {
|
|
429
|
+
valid: report.valid,
|
|
430
|
+
errors: report.results.flatMap(
|
|
431
|
+
(result) => result.messages.map((msg) => ({
|
|
432
|
+
rule: msg.ruleId,
|
|
433
|
+
message: msg.message,
|
|
434
|
+
line: msg.line,
|
|
435
|
+
column: msg.column,
|
|
436
|
+
selector: msg.selector ?? void 0
|
|
437
|
+
}))
|
|
438
|
+
)
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// src/core/regulatory-scanner.ts
|
|
444
|
+
var RegulatoryScanner = class {
|
|
445
|
+
browser = null;
|
|
446
|
+
options;
|
|
447
|
+
htmlValidator;
|
|
448
|
+
constructor(options) {
|
|
449
|
+
this.options = {
|
|
450
|
+
headless: true,
|
|
451
|
+
standard: "dos-lagen",
|
|
452
|
+
// Default till striktaste
|
|
453
|
+
silent: false,
|
|
454
|
+
...options
|
|
455
|
+
};
|
|
456
|
+
this.htmlValidator = new HtmlValidator();
|
|
457
|
+
}
|
|
458
|
+
/** Log only when not in silent mode */
|
|
459
|
+
log(message) {
|
|
460
|
+
if (!this.options.silent) {
|
|
461
|
+
console.log(message);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Kör en fullständig regulatorisk scan
|
|
466
|
+
*/
|
|
467
|
+
async scan() {
|
|
468
|
+
try {
|
|
469
|
+
await this.initBrowser();
|
|
470
|
+
const page = await this.getPage();
|
|
471
|
+
if (this.options.viewport) {
|
|
472
|
+
await page.setViewport(this.options.viewport);
|
|
473
|
+
}
|
|
474
|
+
let retries = 3;
|
|
475
|
+
while (retries > 0) {
|
|
476
|
+
try {
|
|
477
|
+
await page.goto(this.options.url, {
|
|
478
|
+
waitUntil: "domcontentloaded",
|
|
479
|
+
timeout: 6e4
|
|
480
|
+
});
|
|
481
|
+
break;
|
|
482
|
+
} catch (e) {
|
|
483
|
+
retries--;
|
|
484
|
+
if (retries === 0) throw e;
|
|
485
|
+
this.log(`Navigation failed, retrying... (${retries} attempts left)`);
|
|
486
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
try {
|
|
490
|
+
await page.waitForNetworkIdle({
|
|
491
|
+
idleTime: 500,
|
|
492
|
+
timeout: 1e4,
|
|
493
|
+
concurrency: 2
|
|
494
|
+
});
|
|
495
|
+
} catch (e) {
|
|
496
|
+
this.log("Network busy, proceeding with scan anyway...");
|
|
497
|
+
}
|
|
498
|
+
const pageContent = await page.content();
|
|
499
|
+
const htmlValidation = await this.htmlValidator.validate(pageContent);
|
|
500
|
+
if (!htmlValidation.valid) {
|
|
501
|
+
this.log(`HTML Validation: Found ${htmlValidation.errors.length} structural issues.`);
|
|
502
|
+
}
|
|
503
|
+
const vDomBuilder = new VirtualDOMBuilder(page);
|
|
504
|
+
await vDomBuilder.build({ includeComputedStyle: ["color", "background-color"] });
|
|
505
|
+
await this.injectAxe(page);
|
|
506
|
+
this.log("Axe injected. Running analysis...");
|
|
507
|
+
const axeResults = await page.evaluate(async () => {
|
|
508
|
+
if (!document || !document.documentElement) {
|
|
509
|
+
return { violations: [] };
|
|
510
|
+
}
|
|
511
|
+
return await window.axe.run(document, {
|
|
512
|
+
iframes: false
|
|
513
|
+
// Inaktivera iframe-scanning för att undvika kraschar på tunga annons-sajter
|
|
514
|
+
// Vi tar bort runOnly tillfälligt för att se ALLA fel
|
|
515
|
+
/*
|
|
516
|
+
runOnly: {
|
|
517
|
+
type: 'tag',
|
|
518
|
+
values: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']
|
|
519
|
+
}
|
|
520
|
+
*/
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
this.log(`Raw Axe Violations: ${axeResults.violations?.length || 0}`);
|
|
524
|
+
const regulatoryReports = await this.enrichResults(axeResults);
|
|
525
|
+
const result = this.generateResultPackage(regulatoryReports);
|
|
526
|
+
result.htmlValidation = htmlValidation;
|
|
527
|
+
return result;
|
|
528
|
+
} finally {
|
|
529
|
+
await this.close();
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async initBrowser() {
|
|
533
|
+
this.browser = await import_puppeteer.default.launch({
|
|
534
|
+
headless: this.options.headless,
|
|
535
|
+
args: [
|
|
536
|
+
"--no-sandbox",
|
|
537
|
+
"--disable-setuid-sandbox",
|
|
538
|
+
"--disable-blink-features=AutomationControlled"
|
|
539
|
+
// Gömmer att det är en robot
|
|
540
|
+
]
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
async getPage() {
|
|
544
|
+
if (!this.browser) throw new Error("Browser not initialized");
|
|
545
|
+
const page = await this.browser.newPage();
|
|
546
|
+
await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
|
547
|
+
return page;
|
|
548
|
+
}
|
|
549
|
+
async injectAxe(page) {
|
|
550
|
+
const axeSource = require("axe-core").source;
|
|
551
|
+
await page.evaluate(axeSource);
|
|
552
|
+
}
|
|
553
|
+
async enrichResults(axeResults) {
|
|
554
|
+
const reports = [];
|
|
555
|
+
const { searchRulesByTags, generateRegulatoryReport } = await import("@holmdigital/standards");
|
|
556
|
+
const { getCurrentLang: getCurrentLang2 } = await Promise.resolve().then(() => (init_i18n(), i18n_exports));
|
|
557
|
+
const lang = getCurrentLang2();
|
|
558
|
+
for (const violation of axeResults.violations) {
|
|
559
|
+
let report = generateRegulatoryReport(violation.id, lang);
|
|
560
|
+
if (!report) {
|
|
561
|
+
const matchingRules = searchRulesByTags(violation.tags, lang);
|
|
562
|
+
if (matchingRules.length > 0) {
|
|
563
|
+
report = generateRegulatoryReport(matchingRules[0].ruleId, lang);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (report) {
|
|
567
|
+
reports.push({
|
|
568
|
+
...report,
|
|
569
|
+
holmdigitalInsight: {
|
|
570
|
+
...report.holmdigitalInsight,
|
|
571
|
+
reasoning: violation.help
|
|
572
|
+
// Använd Axe's hjälptext som specifik anledning
|
|
573
|
+
},
|
|
574
|
+
// Attach extra debug info for the CLI
|
|
575
|
+
failingNodes: violation.nodes.map((node) => ({
|
|
576
|
+
html: node.html,
|
|
577
|
+
target: node.target.join(" "),
|
|
578
|
+
failureSummary: node.failureSummary
|
|
579
|
+
}))
|
|
580
|
+
});
|
|
581
|
+
} else {
|
|
582
|
+
reports.push({
|
|
583
|
+
ruleId: violation.id,
|
|
584
|
+
wcagCriteria: "Unknown",
|
|
585
|
+
en301549Criteria: "Unknown",
|
|
586
|
+
dosLagenReference: "Kr\xE4ver manuell bed\xF6mning",
|
|
587
|
+
diggRisk: "medium",
|
|
588
|
+
// Default risk
|
|
589
|
+
eaaImpact: "medium",
|
|
590
|
+
remediation: {
|
|
591
|
+
description: violation.help,
|
|
592
|
+
technicalGuidance: violation.description,
|
|
593
|
+
component: void 0
|
|
594
|
+
},
|
|
595
|
+
holmdigitalInsight: {
|
|
596
|
+
diggRisk: "medium",
|
|
597
|
+
eaaImpact: "medium",
|
|
598
|
+
swedishInterpretation: violation.help,
|
|
599
|
+
priorityRationale: "Detta fel uppt\xE4cktes av scannern men saknar specifik mappning i HolmDigital-databasen."
|
|
600
|
+
},
|
|
601
|
+
testability: {
|
|
602
|
+
automated: true,
|
|
603
|
+
requiresManualCheck: false,
|
|
604
|
+
pseudoAutomation: false,
|
|
605
|
+
complexity: "moderate"
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return reports;
|
|
611
|
+
}
|
|
612
|
+
generateResultPackage(reports) {
|
|
613
|
+
const stats = {
|
|
614
|
+
critical: reports.filter((r) => r.holmdigitalInsight.diggRisk === "critical").length,
|
|
615
|
+
high: reports.filter((r) => r.holmdigitalInsight.diggRisk === "high").length,
|
|
616
|
+
medium: reports.filter((r) => r.holmdigitalInsight.diggRisk === "medium").length,
|
|
617
|
+
low: reports.filter((r) => r.holmdigitalInsight.diggRisk === "low").length,
|
|
618
|
+
total: reports.length
|
|
619
|
+
};
|
|
620
|
+
const weightedScore = stats.critical * 25 + // Critical violations are severe
|
|
621
|
+
stats.high * 15 + // High risk affects usability significantly
|
|
622
|
+
stats.medium * 5 + // Medium annoyance
|
|
623
|
+
stats.low * 1;
|
|
624
|
+
const score = Math.max(0, 100 - weightedScore);
|
|
625
|
+
const complianceStatus = stats.total === 0 ? "PASS" : "FAIL";
|
|
626
|
+
return {
|
|
627
|
+
url: this.options.url,
|
|
628
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
629
|
+
reports,
|
|
630
|
+
stats,
|
|
631
|
+
score,
|
|
632
|
+
complianceStatus
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Stänger webbläsaren och frigör resurser
|
|
637
|
+
*/
|
|
638
|
+
async close() {
|
|
639
|
+
if (this.browser) {
|
|
640
|
+
await this.browser.close();
|
|
641
|
+
this.browser = null;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
// src/automation/pseudo-automation.ts
|
|
647
|
+
var PseudoAutomationEngine = class {
|
|
648
|
+
/**
|
|
649
|
+
* Generera ett Playwright-testskript baserat på en rapport
|
|
650
|
+
*/
|
|
651
|
+
generateTestScript(report, url) {
|
|
652
|
+
if (!report.holmdigitalInsight.diggRisk) return "";
|
|
653
|
+
const testName = `Verify ${report.wcagCriteria} - ${report.ruleId}`;
|
|
654
|
+
return `
|
|
655
|
+
import { test, expect } from '@playwright/test';
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* GENERATED PSEUDO-AUTOMATION TEST
|
|
659
|
+
* Rule: ${report.ruleId}
|
|
660
|
+
* WCAG: ${report.wcagCriteria}
|
|
661
|
+
* EN 301 549: ${report.en301549Criteria}
|
|
662
|
+
* Risk: ${report.holmdigitalInsight.diggRisk}
|
|
663
|
+
*
|
|
664
|
+
* Manual Verification Required:
|
|
665
|
+
* ${report.remediation.description}
|
|
666
|
+
*/
|
|
667
|
+
|
|
668
|
+
test('${testName}', async ({ page }) => {
|
|
669
|
+
// 1. Navigate to target
|
|
670
|
+
await page.goto('${url}');
|
|
671
|
+
|
|
672
|
+
// 2. Initial state verification
|
|
673
|
+
// TODO: Add specific selectors based on report details
|
|
674
|
+
|
|
675
|
+
// 3. Interaction steps (Example for Keyboard Navigation)
|
|
676
|
+
if ('${report.ruleId}' === 'keyboard-accessible') {
|
|
677
|
+
console.log('Verifying tab order...');
|
|
678
|
+
await page.keyboard.press('Tab');
|
|
679
|
+
|
|
680
|
+
// Assert focus state
|
|
681
|
+
const focused = await page.evaluate(() => document.activeElement?.tagName);
|
|
682
|
+
console.log('Focused element:', focused);
|
|
683
|
+
|
|
684
|
+
// Add assertions here based on expected focus order
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// 4. Verification
|
|
688
|
+
// Manually verify that...
|
|
689
|
+
});
|
|
690
|
+
`;
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Generera en checklista för manuell testning i Markdown
|
|
694
|
+
*/
|
|
695
|
+
generateManualChecklist(report) {
|
|
696
|
+
return `
|
|
697
|
+
### \u{1F575}\uFE0F Manual Verification: ${report.ruleId}
|
|
698
|
+
|
|
699
|
+
**Regulatory Context**
|
|
700
|
+
- **WCAG**: ${report.wcagCriteria}
|
|
701
|
+
- **DOS-lagen**: ${report.dosLagenReference}
|
|
702
|
+
- **Risk**: ${report.holmdigitalInsight.diggRisk.toUpperCase()}
|
|
703
|
+
|
|
704
|
+
**Instructions**
|
|
705
|
+
1. [ ] ${report.remediation.description}
|
|
706
|
+
2. [ ] Verify against technical guidance: ${report.remediation.technicalGuidance}
|
|
707
|
+
|
|
708
|
+
**HolmDigital Insight**
|
|
709
|
+
> ${report.holmdigitalInsight.swedishInterpretation}
|
|
710
|
+
`;
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
714
|
+
0 && (module.exports = {
|
|
715
|
+
PseudoAutomationEngine,
|
|
716
|
+
RegulatoryScanner,
|
|
717
|
+
VirtualDOMBuilder
|
|
718
|
+
});
|