@holmdigital/engine 1.4.12 → 2.0.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 -59
- package/README.md +198 -158
- package/dist/{chunk-YWRTSIUX.mjs → chunk-32WU5BD6.mjs} +151 -7
- package/dist/{chunk-FFTRXRZU.mjs → chunk-ZO2XNHHT.mjs} +177 -4
- package/dist/cli/index.js +591 -45
- package/dist/cli/index.mjs +194 -56
- package/dist/github-actions-CGWJSLMB.mjs +24 -0
- package/dist/{i18n-WJLEZT5A.mjs → i18n-VBP5BVTT.mjs} +1 -1
- package/dist/index.d.mts +21 -2
- package/dist/index.d.ts +21 -2
- package/dist/index.js +339 -9
- package/dist/index.mjs +7 -3
- package/dist/junit-generator-FK3GE5E4.mjs +43 -0
- package/package.json +83 -79
package/dist/cli/index.js
CHANGED
|
@@ -45,6 +45,8 @@ var init_en = __esm({
|
|
|
45
45
|
pdf_saved: "PDF Report saved to {path}",
|
|
46
46
|
scan_failed: "Scan failed",
|
|
47
47
|
critical_failure: "\nCI/CD Failure: Critical regulatory violations found.",
|
|
48
|
+
generating_statement: "Generating Accessibility Statement...",
|
|
49
|
+
statement_saved: "Accessibility Statement saved to {path}",
|
|
48
50
|
score: "\nCompliance Score: {score}/100",
|
|
49
51
|
status: "Compliance Status: {status}",
|
|
50
52
|
not_compliant: " (Not compliant with legal requirements)",
|
|
@@ -52,7 +54,8 @@ var init_en = __esm({
|
|
|
52
54
|
prescriptive_fix: "\n\u{1F4A1} Prescriptive Fix:",
|
|
53
55
|
use_component: "Use component: {component}",
|
|
54
56
|
pseudo_tests: "\n\u{1F9EC} Generating Pseudo-Automation Tests...\n",
|
|
55
|
-
test_for: "Test for {ruleId}:"
|
|
57
|
+
test_for: "Test for {ruleId}:",
|
|
58
|
+
junit_saved: "JUnit XML report saved to {path}"
|
|
56
59
|
},
|
|
57
60
|
report: {
|
|
58
61
|
title: "Accessibility Report - {url}",
|
|
@@ -87,6 +90,8 @@ var init_sv = __esm({
|
|
|
87
90
|
pdf_saved: "PDF-rapport sparad till {path}",
|
|
88
91
|
scan_failed: "Skanning misslyckades",
|
|
89
92
|
critical_failure: "\nCI/CD Fel: Kritiska regulatoriska brister hittades.",
|
|
93
|
+
generating_statement: "Genererar tillg\xE4nglighetsredog\xF6relse...",
|
|
94
|
+
statement_saved: "Tillg\xE4nglighetsredog\xF6relse sparad till {path}",
|
|
90
95
|
score: "\nEfterlevnadspo\xE4ng: {score}/100",
|
|
91
96
|
status: "Efterlevnadsstatus: {status}",
|
|
92
97
|
not_compliant: " (Uppfyller ej legala krav)",
|
|
@@ -94,7 +99,8 @@ var init_sv = __esm({
|
|
|
94
99
|
prescriptive_fix: "\n\u{1F4A1} Preskriptiv L\xF6sning:",
|
|
95
100
|
use_component: "Anv\xE4nd komponent: {component}",
|
|
96
101
|
pseudo_tests: "\n\u{1F9EC} Genererar Pseudo-automationstester...\n",
|
|
97
|
-
test_for: "Test f\xF6r {ruleId}:"
|
|
102
|
+
test_for: "Test f\xF6r {ruleId}:",
|
|
103
|
+
junit_saved: "JUnit XML-rapport sparad till {path}"
|
|
98
104
|
},
|
|
99
105
|
report: {
|
|
100
106
|
title: "Tillg\xE4nglighetsrapport - {url}",
|
|
@@ -129,6 +135,8 @@ var init_de = __esm({
|
|
|
129
135
|
pdf_saved: "PDF-Bericht gespeichert unter {path}",
|
|
130
136
|
scan_failed: "Scan fehlgeschlagen",
|
|
131
137
|
critical_failure: "\nCI/CD Fehler: Kritische regulatorische Verst\xF6\xDFe gefunden.",
|
|
138
|
+
generating_statement: "Generiere Barrierefreiheitserkl\xE4rung...",
|
|
139
|
+
statement_saved: "Barrierefreiheitserkl\xE4rung gespeichert unter {path}",
|
|
132
140
|
score: "\nKonformit\xE4tsbewertung: {score}/100",
|
|
133
141
|
status: "Konformit\xE4tsstatus: {status}",
|
|
134
142
|
not_compliant: " (Nicht konform mit gesetzlichen Anforderungen - BITV 2.0)",
|
|
@@ -136,7 +144,8 @@ var init_de = __esm({
|
|
|
136
144
|
prescriptive_fix: "\n\u{1F4A1} Vorschriftsm\xE4\xDFige L\xF6sung:",
|
|
137
145
|
use_component: "Verwenden Sie die Komponente: {component}",
|
|
138
146
|
pseudo_tests: "\n\u{1F9EC} Generiere Pseudo-Automatisierungstests...\n",
|
|
139
|
-
test_for: "Test f\xFCr {ruleId}:"
|
|
147
|
+
test_for: "Test f\xFCr {ruleId}:",
|
|
148
|
+
junit_saved: "JUnit XML-Bericht gespeichert unter {path}"
|
|
140
149
|
},
|
|
141
150
|
report: {
|
|
142
151
|
title: "Barrierefreiheitsbericht - {url}",
|
|
@@ -171,6 +180,8 @@ var init_fr = __esm({
|
|
|
171
180
|
pdf_saved: "Rapport PDF enregistr\xE9 sous {path}",
|
|
172
181
|
scan_failed: "\xC9chec du scan",
|
|
173
182
|
critical_failure: "\n\xC9chec CI/CD : Violations r\xE9glementaires critiques d\xE9tect\xE9es.",
|
|
183
|
+
generating_statement: "G\xE9n\xE9ration de la d\xE9claration d'accessibilit\xE9...",
|
|
184
|
+
statement_saved: "D\xE9claration d'accessibilit\xE9 enregistr\xE9e sous {path}",
|
|
174
185
|
score: "\nScore de Conformit\xE9 : {score}/100",
|
|
175
186
|
status: "Statut de Conformit\xE9 : {status}",
|
|
176
187
|
not_compliant: " (Non conforme aux exigences l\xE9gales - RGAA)",
|
|
@@ -178,7 +189,8 @@ var init_fr = __esm({
|
|
|
178
189
|
prescriptive_fix: "\n\u{1F4A1} Solution Prescriptive :",
|
|
179
190
|
use_component: "Utilisez le composant : {component}",
|
|
180
191
|
pseudo_tests: "\n\u{1F9EC} G\xE9n\xE9ration de tests de pseudo-automatisation...\n",
|
|
181
|
-
test_for: "Test pour {ruleId} :"
|
|
192
|
+
test_for: "Test pour {ruleId} :",
|
|
193
|
+
junit_saved: "Rapport XML JUnit enregistr\xE9 sous {path}"
|
|
182
194
|
},
|
|
183
195
|
report: {
|
|
184
196
|
title: "Rapport d'Accessibilit\xE9 - {url}",
|
|
@@ -213,6 +225,8 @@ var init_es = __esm({
|
|
|
213
225
|
pdf_saved: "Informe PDF guardado en {path}",
|
|
214
226
|
scan_failed: "Escaneo fallido",
|
|
215
227
|
critical_failure: "\nFallo de CI/CD: Se encontraron violaciones regulatorias cr\xEDticas.",
|
|
228
|
+
generating_statement: "Generando Declaraci\xF3n de Accesibilidad...",
|
|
229
|
+
statement_saved: "Declaraci\xF3n de Accesibilidad guardada en {path}",
|
|
216
230
|
score: "\nPuntuaci\xF3n de Cumplimiento: {score}/100",
|
|
217
231
|
status: "Estado de Cumplimiento: {status}",
|
|
218
232
|
not_compliant: " (No cumple con los requisitos legales - UNE 139803)",
|
|
@@ -220,7 +234,8 @@ var init_es = __esm({
|
|
|
220
234
|
prescriptive_fix: "\n\u{1F4A1} Soluci\xF3n Prescriptiva:",
|
|
221
235
|
use_component: "Use el componente: {component}",
|
|
222
236
|
pseudo_tests: "\n\u{1F9EC} Generando pruebas de pseudo-automatizaci\xF3n...\n",
|
|
223
|
-
test_for: "Prueba para {ruleId}:"
|
|
237
|
+
test_for: "Prueba para {ruleId}:",
|
|
238
|
+
junit_saved: "Informe JUnit XML guardado en {path}"
|
|
224
239
|
},
|
|
225
240
|
report: {
|
|
226
241
|
title: "Informe de Accesibilidad - {url}",
|
|
@@ -240,6 +255,141 @@ var init_es = __esm({
|
|
|
240
255
|
}
|
|
241
256
|
});
|
|
242
257
|
|
|
258
|
+
// src/locales/fi.json
|
|
259
|
+
var fi_default;
|
|
260
|
+
var init_fi = __esm({
|
|
261
|
+
"src/locales/fi.json"() {
|
|
262
|
+
fi_default = {
|
|
263
|
+
cli: {
|
|
264
|
+
title: "\n\u{1F310} HolmDigital S\xE4\xE4ntelyskanneri\n",
|
|
265
|
+
scanning: "Skannataan {url}...\n",
|
|
266
|
+
initializing: "Alustetaan skanneria...",
|
|
267
|
+
analyzing: "Analysoidaan DOM & Shadow DOM...",
|
|
268
|
+
complete: "Skannaus valmis!",
|
|
269
|
+
generating_pdf: "Luodaan PDF-raporttia...",
|
|
270
|
+
pdf_saved: "PDF-raportti tallennettu kohteeseen {path}",
|
|
271
|
+
scan_failed: "Skannaus ep\xE4onnistui",
|
|
272
|
+
critical_failure: "\nCI/CD Virhe: Kriittisi\xE4 s\xE4\xE4ntelyrikkomuksia havaittu.",
|
|
273
|
+
generating_statement: "Luodaan saavutettavuusselostetta...",
|
|
274
|
+
statement_saved: "Saavutettavuusseloste tallennettu kohteeseen {path}",
|
|
275
|
+
score: "\nVaatimustenmukaisuus: {score}/100",
|
|
276
|
+
status: "Tila: {status}",
|
|
277
|
+
not_compliant: " (Ei t\xE4yt\xE4 lakis\xE4\xE4teisi\xE4 vaatimuksia - Laki digitaalisten palvelujen tarjoamisesta)",
|
|
278
|
+
viewport: "N\xE4kym\xE4: {width}x{height}",
|
|
279
|
+
prescriptive_fix: "\n\u{1F4A1} Ehdotettu korjaus:",
|
|
280
|
+
use_component: "K\xE4yt\xE4 komponenttia: {component}",
|
|
281
|
+
pseudo_tests: "\n\u{1F9EC} Luodaan Pseudo-automaatiotestej\xE4...\n",
|
|
282
|
+
test_for: "Testi {ruleId}:lle:",
|
|
283
|
+
junit_saved: "JUnit XML -raportti tallennettu kohteeseen {path}"
|
|
284
|
+
},
|
|
285
|
+
report: {
|
|
286
|
+
title: "Saavutettavuusraportti - {url}",
|
|
287
|
+
generated: "Luotu: {date}",
|
|
288
|
+
scan_target: "Skannauksen kohde: {url}",
|
|
289
|
+
overall_score: "Kokonaispisteet",
|
|
290
|
+
critical_issues: "Kriittiset ongelmat",
|
|
291
|
+
high_issues: "Vakavat ongelmat",
|
|
292
|
+
total_issues: "Ongelmat yhteens\xE4",
|
|
293
|
+
html_errors: "HTML-virheet",
|
|
294
|
+
detailed_violations: "Yksityiskohtaiset rikkomukset",
|
|
295
|
+
prescriptive_fix: "\u{1F4A1} Ehdotettu korjaus",
|
|
296
|
+
use: "K\xE4yt\xE4",
|
|
297
|
+
footer: "Luonut @holmdigital/engine v0.1.0 \u2022 Standardit: WCAG 2.1 AA, EN 301 549, Digipalvelulaki"
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// src/locales/dk.json
|
|
304
|
+
var dk_default;
|
|
305
|
+
var init_dk = __esm({
|
|
306
|
+
"src/locales/dk.json"() {
|
|
307
|
+
dk_default = {
|
|
308
|
+
cli: {
|
|
309
|
+
title: "\n\u{1F310} HolmDigital Regulatorisk Scanner\n",
|
|
310
|
+
scanning: "Scanner {url}...\n",
|
|
311
|
+
initializing: "Initialiserer scanner...",
|
|
312
|
+
analyzing: "Analyserer DOM & Shadow DOM...",
|
|
313
|
+
complete: "Scanning f\xE6rdig!",
|
|
314
|
+
generating_pdf: "Genererer PDF-rapport...",
|
|
315
|
+
pdf_saved: "PDF-rapport gemt i {path}",
|
|
316
|
+
scan_failed: "Scanning mislykkedes",
|
|
317
|
+
critical_failure: "\nCI/CD Fejl: Kritiske regulatoriske overtr\xE6delser fundet.",
|
|
318
|
+
generating_statement: "Genererer tilg\xE6ngelighedserkl\xE6ring...",
|
|
319
|
+
statement_saved: "Tilg\xE6ngelighedserkl\xE6ring gemt i {path}",
|
|
320
|
+
score: "\nOverholdelsesscore: {score}/100",
|
|
321
|
+
status: "Overholdelsesstatus: {status}",
|
|
322
|
+
not_compliant: " (Opfylder ikke lovkrav - Lov om tilg\xE6ngelighed)",
|
|
323
|
+
viewport: "Viewport: {width}x{height}",
|
|
324
|
+
prescriptive_fix: "\n\u{1F4A1} Foresl\xE5et l\xF8sning:",
|
|
325
|
+
use_component: "Brug komponent: {component}",
|
|
326
|
+
pseudo_tests: "\n\u{1F9EC} Genererer Pseudo-automatiseringstests...\n",
|
|
327
|
+
test_for: "Test for {ruleId}:",
|
|
328
|
+
junit_saved: "JUnit XML-rapport gemt i {path}"
|
|
329
|
+
},
|
|
330
|
+
report: {
|
|
331
|
+
title: "Tilg\xE6ngelighedsrapport - {url}",
|
|
332
|
+
generated: "Genereret: {date}",
|
|
333
|
+
scan_target: "Scanningsm\xE5l: {url}",
|
|
334
|
+
overall_score: "Samlet Score",
|
|
335
|
+
critical_issues: "Kritiske Fejl",
|
|
336
|
+
high_issues: "Alvorlige Fejl",
|
|
337
|
+
total_issues: "Totale Fejl",
|
|
338
|
+
html_errors: "HTML Fejl",
|
|
339
|
+
detailed_violations: "Detaljerede Overtr\xE6delser",
|
|
340
|
+
prescriptive_fix: "\u{1F4A1} Foresl\xE5et L\xF8sning",
|
|
341
|
+
use: "Brug",
|
|
342
|
+
footer: "Genereret af @holmdigital/engine v0.1.0 \u2022 Standarder: WCAG 2.1 AA, EN 301 549, Lov om tilg\xE6ngelighed"
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// src/locales/no.json
|
|
349
|
+
var no_default;
|
|
350
|
+
var init_no = __esm({
|
|
351
|
+
"src/locales/no.json"() {
|
|
352
|
+
no_default = {
|
|
353
|
+
cli: {
|
|
354
|
+
title: "\n\u{1F310} HolmDigital Regulatorisk Skanner\n",
|
|
355
|
+
scanning: "Skanner {url}...\n",
|
|
356
|
+
initializing: "Initialiserer skanner...",
|
|
357
|
+
analyzing: "Analyserer DOM & Shadow DOM...",
|
|
358
|
+
complete: "Skanning ferdig!",
|
|
359
|
+
generating_pdf: "Genererer PDF-rapport...",
|
|
360
|
+
pdf_saved: "PDF-rapport lagret i {path}",
|
|
361
|
+
scan_failed: "Skanning mislyktes",
|
|
362
|
+
critical_failure: "\nCI/CD Feil: Kritiske regulatoriske brudd funnet.",
|
|
363
|
+
generating_statement: "Genererer tilgjengelighetserkl\xE6ring...",
|
|
364
|
+
statement_saved: "Tilgjengelighetserkl\xE6ring lagret i {path}",
|
|
365
|
+
score: "\nEtterlevelsespoeng: {score}/100",
|
|
366
|
+
status: "Etterlevelsesstatus: {status}",
|
|
367
|
+
not_compliant: " (Oppfyller ikke lovkrav - Diskriminerings- og tilgjengelighetsloven)",
|
|
368
|
+
viewport: "Visningsomr\xE5de: {width}x{height}",
|
|
369
|
+
prescriptive_fix: "\n\u{1F4A1} Foresl\xE5tt l\xF8sning:",
|
|
370
|
+
use_component: "Bruk komponent: {component}",
|
|
371
|
+
pseudo_tests: "\n\u{1F9EC} Genererer Pseudo-automatiseringstester...\n",
|
|
372
|
+
test_for: "Test for {ruleId}:",
|
|
373
|
+
junit_saved: "JUnit XML-rapport lagret i {path}"
|
|
374
|
+
},
|
|
375
|
+
report: {
|
|
376
|
+
title: "Tilgjengelighetsrapport - {url}",
|
|
377
|
+
generated: "Generert: {date}",
|
|
378
|
+
scan_target: "Skannet m\xE5l: {url}",
|
|
379
|
+
overall_score: "Total Score",
|
|
380
|
+
critical_issues: "Kritiske Feil",
|
|
381
|
+
high_issues: "Alvorlige Feil",
|
|
382
|
+
total_issues: "Totale Feil",
|
|
383
|
+
html_errors: "HTML Feil",
|
|
384
|
+
detailed_violations: "Detaljerte Brudd",
|
|
385
|
+
prescriptive_fix: "\u{1F4A1} Foresl\xE5tt L\xF8sning",
|
|
386
|
+
use: "Bruk",
|
|
387
|
+
footer: "Generert av @holmdigital/engine v0.1.0 \u2022 Standarder: WCAG 2.1 AA, EN 301 549, Uu-tilsynet"
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
243
393
|
// src/locales/nl.json
|
|
244
394
|
var nl_default;
|
|
245
395
|
var init_nl = __esm({
|
|
@@ -255,6 +405,8 @@ var init_nl = __esm({
|
|
|
255
405
|
pdf_saved: "PDF-rapport opgeslagen op {path}",
|
|
256
406
|
scan_failed: "Scan mislukt",
|
|
257
407
|
critical_failure: "\nCI/CD Fout: Kritieke wettelijke overtredingen gevonden.",
|
|
408
|
+
generating_statement: "Toegankelijkheidsverklaring genereren...",
|
|
409
|
+
statement_saved: "Toegankelijkheidsverklaring opgeslagen op {path}",
|
|
258
410
|
score: "\nNalevingsscore: {score}/100",
|
|
259
411
|
status: "Nalevingsstatus: {status}",
|
|
260
412
|
not_compliant: " (Niet in overeenstemming met wettelijke vereisten)",
|
|
@@ -262,7 +414,8 @@ var init_nl = __esm({
|
|
|
262
414
|
prescriptive_fix: "\n\u{1F4A1} Voorgeschreven oplossing:",
|
|
263
415
|
use_component: "Gebruik component: {component}",
|
|
264
416
|
pseudo_tests: "\n\u{1F9EC} Pseudo-automatiseringstests genereren...\n",
|
|
265
|
-
test_for: "Test voor {ruleId}:"
|
|
417
|
+
test_for: "Test voor {ruleId}:",
|
|
418
|
+
junit_saved: "JUnit XML-rapport opgeslagen op {path}"
|
|
266
419
|
},
|
|
267
420
|
report: {
|
|
268
421
|
title: "Toegankelijkheidsrapport - {url}",
|
|
@@ -336,6 +489,9 @@ var init_i18n = __esm({
|
|
|
336
489
|
init_de();
|
|
337
490
|
init_fr();
|
|
338
491
|
init_es();
|
|
492
|
+
init_fi();
|
|
493
|
+
init_dk();
|
|
494
|
+
init_no();
|
|
339
495
|
init_nl();
|
|
340
496
|
locales = {
|
|
341
497
|
en: en_default,
|
|
@@ -344,14 +500,101 @@ var init_i18n = __esm({
|
|
|
344
500
|
fr: fr_default,
|
|
345
501
|
es: es_default,
|
|
346
502
|
nl: nl_default,
|
|
503
|
+
fi: fi_default,
|
|
504
|
+
dk: dk_default,
|
|
505
|
+
no: no_default,
|
|
347
506
|
"en-gb": en_default,
|
|
348
507
|
"en-us": en_default,
|
|
349
|
-
"en-ca": en_default
|
|
508
|
+
"en-ca": en_default,
|
|
509
|
+
"da": dk_default,
|
|
510
|
+
"nb": no_default
|
|
511
|
+
// Norwegian Bokmål alias
|
|
350
512
|
};
|
|
351
513
|
currentLang = "en";
|
|
352
514
|
}
|
|
353
515
|
});
|
|
354
516
|
|
|
517
|
+
// src/reporting/github-actions.ts
|
|
518
|
+
var github_actions_exports = {};
|
|
519
|
+
__export(github_actions_exports, {
|
|
520
|
+
generateGitHubActionsAnnotations: () => generateGitHubActionsAnnotations
|
|
521
|
+
});
|
|
522
|
+
function generateGitHubActionsAnnotations(reports) {
|
|
523
|
+
reports.forEach((report) => {
|
|
524
|
+
let level = "warning";
|
|
525
|
+
if (report.holmdigitalInsight.diggRisk === "critical") {
|
|
526
|
+
level = "error";
|
|
527
|
+
}
|
|
528
|
+
const title = `[${report.ruleId}] ${report.wcagCriteria}`;
|
|
529
|
+
const message = `${report.holmdigitalInsight.reasoning}
|
|
530
|
+
|
|
531
|
+
Legitimacy: ${report.dosLagenReference}
|
|
532
|
+
Fix: ${report.remediation.component ? `Use <${report.remediation.component} />` : "Manual remediation required"}`;
|
|
533
|
+
if (report.failingNodes && report.failingNodes.length > 0) {
|
|
534
|
+
report.failingNodes.forEach((node) => {
|
|
535
|
+
console.log(`::${level} title=${title}::${message} (Target: ${node.target})`);
|
|
536
|
+
});
|
|
537
|
+
} else {
|
|
538
|
+
console.log(`::${level} title=${title}::${message}`);
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
var init_github_actions = __esm({
|
|
543
|
+
"src/reporting/github-actions.ts"() {
|
|
544
|
+
"use strict";
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
// src/reporting/junit-generator.ts
|
|
549
|
+
var junit_generator_exports = {};
|
|
550
|
+
__export(junit_generator_exports, {
|
|
551
|
+
generateJUnitXML: () => generateJUnitXML
|
|
552
|
+
});
|
|
553
|
+
function generateJUnitXML(reports, url, duration) {
|
|
554
|
+
const failures = reports.length;
|
|
555
|
+
const testSuites = [
|
|
556
|
+
`<?xml version="1.0" encoding="UTF-8"?>`,
|
|
557
|
+
`<testsuites name="HolmDigital Accessibility Scan" time="${duration / 1e3}">`,
|
|
558
|
+
` <testsuite name="${url}" tests="${failures}" failures="${failures}" errors="0" time="${duration / 1e3}">`
|
|
559
|
+
];
|
|
560
|
+
reports.forEach((report) => {
|
|
561
|
+
const severity = report.holmdigitalInsight.diggRisk;
|
|
562
|
+
const message = escapeXML(report.holmdigitalInsight.reasoning);
|
|
563
|
+
const criteria = escapeXML(`WCAG ${report.wcagCriteria} | EN 301 549 ${report.en301549Criteria}`);
|
|
564
|
+
const help = escapeXML(`Ref: ${report.dosLagenReference}. Remediation: ${report.remediation.component || "Manual"}`);
|
|
565
|
+
testSuites.push(` <testcase name="[${severity.toUpperCase()}] ${report.ruleId}" classname="${report.wcagCriteria}" time="0">`);
|
|
566
|
+
testSuites.push(` <failure message="${message}" type="${severity}">${criteria}
|
|
567
|
+
${help}</failure>`);
|
|
568
|
+
testSuites.push(` </testcase>`);
|
|
569
|
+
});
|
|
570
|
+
testSuites.push(` </testsuite>`);
|
|
571
|
+
testSuites.push(`</testsuites>`);
|
|
572
|
+
return testSuites.join("\n");
|
|
573
|
+
}
|
|
574
|
+
function escapeXML(unsafe) {
|
|
575
|
+
return unsafe.replace(/[<>&'"]/g, (c) => {
|
|
576
|
+
switch (c) {
|
|
577
|
+
case "<":
|
|
578
|
+
return "<";
|
|
579
|
+
case ">":
|
|
580
|
+
return ">";
|
|
581
|
+
case "&":
|
|
582
|
+
return "&";
|
|
583
|
+
case "'":
|
|
584
|
+
return "'";
|
|
585
|
+
case '"':
|
|
586
|
+
return """;
|
|
587
|
+
default:
|
|
588
|
+
return c;
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
var init_junit_generator = __esm({
|
|
593
|
+
"src/reporting/junit-generator.ts"() {
|
|
594
|
+
"use strict";
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
|
|
355
598
|
// src/cli/index.ts
|
|
356
599
|
var import_commander = require("commander");
|
|
357
600
|
var import_chalk = __toESM(require("chalk"));
|
|
@@ -611,7 +854,7 @@ var RegulatoryScanner = class {
|
|
|
611
854
|
}
|
|
612
855
|
async enrichResults(axeResults) {
|
|
613
856
|
const reports = [];
|
|
614
|
-
const { searchRulesByTags, generateRegulatoryReport } = await import("@holmdigital/standards");
|
|
857
|
+
const { searchRulesByTags, generateRegulatoryReport, getConvergenceRule } = await import("@holmdigital/standards");
|
|
615
858
|
const { getCurrentLang: getCurrentLang2 } = await Promise.resolve().then(() => (init_i18n(), i18n_exports));
|
|
616
859
|
const lang = getCurrentLang2();
|
|
617
860
|
for (const violation of axeResults.violations) {
|
|
@@ -623,6 +866,7 @@ var RegulatoryScanner = class {
|
|
|
623
866
|
}
|
|
624
867
|
}
|
|
625
868
|
if (report) {
|
|
869
|
+
const fullRule = getConvergenceRule(report.ruleId, lang);
|
|
626
870
|
reports.push({
|
|
627
871
|
...report,
|
|
628
872
|
holmdigitalInsight: {
|
|
@@ -630,6 +874,8 @@ var RegulatoryScanner = class {
|
|
|
630
874
|
reasoning: violation.help
|
|
631
875
|
// Använd Axe's hjälptext som specifik anledning
|
|
632
876
|
},
|
|
877
|
+
// Include legal context from the full rule
|
|
878
|
+
legalContext: fullRule?.legalContext,
|
|
633
879
|
// Attach extra debug info for the CLI
|
|
634
880
|
failingNodes: violation.nodes.map((node) => ({
|
|
635
881
|
html: node.html,
|
|
@@ -706,6 +952,18 @@ var RegulatoryScanner = class {
|
|
|
706
952
|
pageTitle,
|
|
707
953
|
pageLanguage
|
|
708
954
|
};
|
|
955
|
+
const reportsWithContext = reports.filter((r) => r.legalContext);
|
|
956
|
+
const legalSummary = {
|
|
957
|
+
wadApplicable: reportsWithContext.filter(
|
|
958
|
+
(r) => r.legalContext?.appliesTo?.includes("WAD")
|
|
959
|
+
).length,
|
|
960
|
+
eaaApplicable: reportsWithContext.filter(
|
|
961
|
+
(r) => r.legalContext?.appliesTo?.includes("EAA")
|
|
962
|
+
).length,
|
|
963
|
+
eaaDeadlineViolations: reportsWithContext.filter(
|
|
964
|
+
(r) => r.legalContext?.eaaDeadline
|
|
965
|
+
).length
|
|
966
|
+
};
|
|
709
967
|
return {
|
|
710
968
|
url: this.options.url,
|
|
711
969
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -713,7 +971,8 @@ var RegulatoryScanner = class {
|
|
|
713
971
|
reports,
|
|
714
972
|
stats,
|
|
715
973
|
score,
|
|
716
|
-
complianceStatus
|
|
974
|
+
complianceStatus,
|
|
975
|
+
legalSummary
|
|
717
976
|
};
|
|
718
977
|
}
|
|
719
978
|
/**
|
|
@@ -934,6 +1193,37 @@ function generateReportHTML(result) {
|
|
|
934
1193
|
.badge-high { background: #fffbeb; color: #d97706; border: 1px solid #fde68a; }
|
|
935
1194
|
.badge-medium { background: #fefce8; color: #ca8a04; border: 1px solid #fef08a; }
|
|
936
1195
|
.badge-low { background: #f8fafc; color: #475569; border: 1px solid #e2e8f0; }
|
|
1196
|
+
.badge-wad { background: #eff6ff; color: #1d4ed8; border: 1px solid #bfdbfe; margin-left: 0.5rem; }
|
|
1197
|
+
.badge-eaa { background: #faf5ff; color: #7c3aed; border: 1px solid #ddd6fe; margin-left: 0.5rem; }
|
|
1198
|
+
.legal-summary {
|
|
1199
|
+
background: linear-gradient(135deg, #eff6ff 0%, #faf5ff 100%);
|
|
1200
|
+
border: 1px solid #c7d2fe;
|
|
1201
|
+
border-radius: 12px;
|
|
1202
|
+
padding: 1.5rem;
|
|
1203
|
+
margin-bottom: 2rem;
|
|
1204
|
+
}
|
|
1205
|
+
.legal-summary-title {
|
|
1206
|
+
font-size: 1rem;
|
|
1207
|
+
font-weight: 600;
|
|
1208
|
+
color: #3730a3;
|
|
1209
|
+
margin-bottom: 1rem;
|
|
1210
|
+
}
|
|
1211
|
+
.legal-grid {
|
|
1212
|
+
display: grid;
|
|
1213
|
+
grid-template-columns: repeat(3, 1fr);
|
|
1214
|
+
gap: 1rem;
|
|
1215
|
+
}
|
|
1216
|
+
.legal-stat {
|
|
1217
|
+
text-align: center;
|
|
1218
|
+
}
|
|
1219
|
+
.legal-stat-value {
|
|
1220
|
+
font-size: 1.5rem;
|
|
1221
|
+
font-weight: 700;
|
|
1222
|
+
}
|
|
1223
|
+
.legal-stat-label {
|
|
1224
|
+
font-size: 0.75rem;
|
|
1225
|
+
color: #64748b;
|
|
1226
|
+
}
|
|
937
1227
|
|
|
938
1228
|
.violation-meta {
|
|
939
1229
|
font-size: 0.875rem;
|
|
@@ -1002,6 +1292,26 @@ function generateReportHTML(result) {
|
|
|
1002
1292
|
</div>
|
|
1003
1293
|
</div>
|
|
1004
1294
|
|
|
1295
|
+
${result.legalSummary ? `
|
|
1296
|
+
<div class="legal-summary">
|
|
1297
|
+
<div class="legal-summary-title">\u{1F1EA}\u{1F1FA} EU Legal Framework Impact</div>
|
|
1298
|
+
<div class="legal-grid">
|
|
1299
|
+
<div class="legal-stat">
|
|
1300
|
+
<div class="legal-stat-value" style="color: #1d4ed8;">${result.legalSummary.wadApplicable}</div>
|
|
1301
|
+
<div class="legal-stat-label">WAD (Public Sector) Violations</div>
|
|
1302
|
+
</div>
|
|
1303
|
+
<div class="legal-stat">
|
|
1304
|
+
<div class="legal-stat-value" style="color: #7c3aed;">${result.legalSummary.eaaApplicable}</div>
|
|
1305
|
+
<div class="legal-stat-label">EAA (Private Sector) Violations</div>
|
|
1306
|
+
</div>
|
|
1307
|
+
<div class="legal-stat">
|
|
1308
|
+
<div class="legal-stat-value" style="color: #dc2626;">${result.legalSummary.eaaDeadlineViolations}</div>
|
|
1309
|
+
<div class="legal-stat-label">EAA 2025 Deadline Issues</div>
|
|
1310
|
+
</div>
|
|
1311
|
+
</div>
|
|
1312
|
+
</div>
|
|
1313
|
+
` : ""}
|
|
1314
|
+
|
|
1005
1315
|
<div class="section-title">${t("report.detailed_violations")}</div>
|
|
1006
1316
|
|
|
1007
1317
|
${[...result.reports].sort((a, b) => {
|
|
@@ -1015,11 +1325,16 @@ function generateReportHTML(result) {
|
|
|
1015
1325
|
<div class="violation-card">
|
|
1016
1326
|
<div class="violation-header">
|
|
1017
1327
|
<div class="violation-title">${report.ruleId}</div>
|
|
1018
|
-
<
|
|
1328
|
+
<div>
|
|
1329
|
+
<span class="badge ${riskClass}">${report.holmdigitalInsight.diggRisk}</span>
|
|
1330
|
+
${report.legalContext?.appliesTo?.includes("WAD") ? '<span class="badge badge-wad">WAD</span>' : ""}
|
|
1331
|
+
${report.legalContext?.appliesTo?.includes("EAA") ? '<span class="badge badge-eaa">EAA</span>' : ""}
|
|
1332
|
+
</div>
|
|
1019
1333
|
</div>
|
|
1020
1334
|
<div class="violation-meta">
|
|
1021
1335
|
WCAG ${report.wcagCriteria} \u2022 EN 301 549 ${report.en301549Criteria}
|
|
1022
1336
|
${report.dosLagenReference ? `\u2022 ${report.dosLagenReference}` : ""}
|
|
1337
|
+
${report.legalContext?.eaaDeadline ? `<br/><strong>\u26A0\uFE0F EAA Deadline:</strong> ${report.legalContext.eaaDeadline}` : ""}
|
|
1023
1338
|
</div>
|
|
1024
1339
|
<div style="font-size: 0.95rem; color: #334155; line-height: 1.5;">
|
|
1025
1340
|
${report.holmdigitalInsight.swedishInterpretation}
|
|
@@ -1072,6 +1387,142 @@ async function generatePDF(htmlContent, outputPath) {
|
|
|
1072
1387
|
}
|
|
1073
1388
|
}
|
|
1074
1389
|
|
|
1390
|
+
// src/reporting/statement-generator.ts
|
|
1391
|
+
var import_react = __toESM(require("react"));
|
|
1392
|
+
var import_server = require("react-dom/server");
|
|
1393
|
+
var import_components = require("@holmdigital/components");
|
|
1394
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
1395
|
+
var import_path = __toESM(require("path"));
|
|
1396
|
+
async function generateStatementContent(result, lang = "en", format = "html", metadata) {
|
|
1397
|
+
let complianceLevel = "full";
|
|
1398
|
+
if (result.stats.critical > 0 || result.legalSummary && result.legalSummary.eaaDeadlineViolations > 0) {
|
|
1399
|
+
complianceLevel = "non-compliant";
|
|
1400
|
+
} else if (result.score < 100) {
|
|
1401
|
+
complianceLevel = "partial";
|
|
1402
|
+
}
|
|
1403
|
+
const issuesMap = /* @__PURE__ */ new Map();
|
|
1404
|
+
result.reports.forEach((report) => {
|
|
1405
|
+
if (!issuesMap.has(report.ruleId)) {
|
|
1406
|
+
const issueText = `${report.ruleId} (${report.wcagCriteria})`;
|
|
1407
|
+
issuesMap.set(report.ruleId, issueText);
|
|
1408
|
+
}
|
|
1409
|
+
});
|
|
1410
|
+
const nonComplianceItems = Array.from(issuesMap.values());
|
|
1411
|
+
let country = metadata?.country || "SE";
|
|
1412
|
+
if (!metadata?.country) {
|
|
1413
|
+
if (result.url.endsWith(".no")) country = "NO";
|
|
1414
|
+
if (result.url.endsWith(".dk")) country = "DK";
|
|
1415
|
+
if (result.url.endsWith(".fi")) country = "FI";
|
|
1416
|
+
if (result.url.endsWith(".de")) country = "DE";
|
|
1417
|
+
}
|
|
1418
|
+
const sector = "public";
|
|
1419
|
+
let logoUrl;
|
|
1420
|
+
try {
|
|
1421
|
+
const possiblePaths = [
|
|
1422
|
+
import_path.default.join(process.cwd(), "src/assets/logo.jpg"),
|
|
1423
|
+
// run from package root
|
|
1424
|
+
import_path.default.join(process.cwd(), "packages/engine/src/assets/logo.jpg")
|
|
1425
|
+
// run from monorepo root
|
|
1426
|
+
];
|
|
1427
|
+
for (const p of possiblePaths) {
|
|
1428
|
+
try {
|
|
1429
|
+
const logoBuffer = await import_promises.default.readFile(p);
|
|
1430
|
+
logoUrl = `data:image/jpeg;base64,${logoBuffer.toString("base64")}`;
|
|
1431
|
+
break;
|
|
1432
|
+
} catch (e) {
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
} catch (e) {
|
|
1436
|
+
console.warn("Could not load logo.jpg", e);
|
|
1437
|
+
}
|
|
1438
|
+
const props = {
|
|
1439
|
+
country,
|
|
1440
|
+
sector,
|
|
1441
|
+
organizationName: metadata?.organizationName || new URL(result.url).hostname.replace(/^www\./, ""),
|
|
1442
|
+
websiteUrl: result.url,
|
|
1443
|
+
complianceLevel,
|
|
1444
|
+
lastReviewDate: /* @__PURE__ */ new Date(),
|
|
1445
|
+
assessmentDate: /* @__PURE__ */ new Date(),
|
|
1446
|
+
evaluationMethod: lang === "sv" ? "Automatiserad granskning via @holmdigital/engine" : "Automated Scan via @holmdigital/engine",
|
|
1447
|
+
generatorTool: {
|
|
1448
|
+
name: "HolmDigital Regulatory Engine",
|
|
1449
|
+
url: "https://holmdigital.se"
|
|
1450
|
+
},
|
|
1451
|
+
logoUrl,
|
|
1452
|
+
contactEmail: metadata?.contactEmail || "hej@holmdigital.se",
|
|
1453
|
+
phoneNumber: metadata?.phoneNumber || "070-123 45 67",
|
|
1454
|
+
responseTime: metadata?.responseTime || (lang === "sv" ? "2 dagar" : "2 days"),
|
|
1455
|
+
nonComplianceItems,
|
|
1456
|
+
locale: lang,
|
|
1457
|
+
badgeUrl: generateBadgeUrl(result.score) || void 0,
|
|
1458
|
+
publishDate: metadata?.publishDate ? new Date(metadata.publishDate) : void 0
|
|
1459
|
+
};
|
|
1460
|
+
let content = "";
|
|
1461
|
+
if (format === "html") {
|
|
1462
|
+
const element = import_react.default.createElement(import_components.AccessibilityStatement, props);
|
|
1463
|
+
const markup = (0, import_server.renderToStaticMarkup)(element);
|
|
1464
|
+
content = `<!DOCTYPE html>
|
|
1465
|
+
<html lang="${lang}">
|
|
1466
|
+
<head>
|
|
1467
|
+
<meta charset="UTF-8">
|
|
1468
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1469
|
+
<title>Accessibility Statement - ${props.organizationName}</title>
|
|
1470
|
+
<style>
|
|
1471
|
+
body { font-family: system-ui, -apple-system, sans-serif; background: #f8f9fa; padding: 2rem; margin: 0; }
|
|
1472
|
+
</style>
|
|
1473
|
+
</head>
|
|
1474
|
+
<body>
|
|
1475
|
+
${markup}
|
|
1476
|
+
</body>
|
|
1477
|
+
</html>`;
|
|
1478
|
+
} else {
|
|
1479
|
+
const dateStr = props.lastReviewDate.toISOString().split("T")[0];
|
|
1480
|
+
const statusMap = {
|
|
1481
|
+
"full": "Full",
|
|
1482
|
+
"partial": "Partial",
|
|
1483
|
+
"non-compliant": "Non-Compliant"
|
|
1484
|
+
};
|
|
1485
|
+
content = `# Accessibility Statement for ${props.organizationName}
|
|
1486
|
+
|
|
1487
|
+
This accessibility statement applies to [${props.websiteUrl}](${props.websiteUrl}).
|
|
1488
|
+
|
|
1489
|
+
## Compliance Status
|
|
1490
|
+
|
|
1491
|
+
**Status:** ${statusMap[complianceLevel] || complianceLevel}
|
|
1492
|
+
|
|
1493
|
+
This website is ${complianceLevel} compliant with ${sector === "public" ? "EN 301 549 (WAD)" : "EAA"}.
|
|
1494
|
+
|
|
1495
|
+
## Non-accessible Content
|
|
1496
|
+
|
|
1497
|
+
The following content is non-accessible for the following reasons:
|
|
1498
|
+
|
|
1499
|
+
${nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `- ${item}`).join("\n") : "_No known issues._"}
|
|
1500
|
+
|
|
1501
|
+
## Preparation of this statement
|
|
1502
|
+
|
|
1503
|
+
This statement was prepared on ${dateStr}.
|
|
1504
|
+
Method used: **Automated Scan** via @holmdigital/engine.
|
|
1505
|
+
|
|
1506
|
+
## Feedback and contact information
|
|
1507
|
+
|
|
1508
|
+
If you need information on this website in a different format like accessible PDF, large print, easy-to-read, audio recording or braille:
|
|
1509
|
+
|
|
1510
|
+
- email: [${props.contactEmail}](mailto:${props.contactEmail})
|
|
1511
|
+
|
|
1512
|
+
## Enforcement procedure
|
|
1513
|
+
|
|
1514
|
+
The enforcement body for ${country} is responsible for enforcing these regulations.
|
|
1515
|
+
`;
|
|
1516
|
+
}
|
|
1517
|
+
return content;
|
|
1518
|
+
}
|
|
1519
|
+
async function generateStatement(result, outputPath, lang = "en", format = "html", metadata) {
|
|
1520
|
+
const content = await generateStatementContent(result, lang, format, metadata);
|
|
1521
|
+
const dir = import_path.default.dirname(outputPath);
|
|
1522
|
+
await import_promises.default.mkdir(dir, { recursive: true });
|
|
1523
|
+
await import_promises.default.writeFile(outputPath, content, "utf-8");
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1075
1526
|
// src/cli/index.ts
|
|
1076
1527
|
init_i18n();
|
|
1077
1528
|
|
|
@@ -1155,6 +1606,7 @@ async function sendToCloud(config, result) {
|
|
|
1155
1606
|
}
|
|
1156
1607
|
|
|
1157
1608
|
// src/cli/index.ts
|
|
1609
|
+
var import_cosmiconfig = require("cosmiconfig");
|
|
1158
1610
|
function isValidUrl(urlString) {
|
|
1159
1611
|
try {
|
|
1160
1612
|
const url = new URL(urlString);
|
|
@@ -1165,22 +1617,46 @@ function isValidUrl(urlString) {
|
|
|
1165
1617
|
}
|
|
1166
1618
|
var program = new import_commander.Command();
|
|
1167
1619
|
program.name("hd-a11y-scan").description("HolmDigital Regulatory Scanner").version("0.1.0");
|
|
1168
|
-
program.argument("<url>", "URL to scan").option("--lang <code>", "Language code (en, sv)"
|
|
1620
|
+
program.argument("<url>", "URL to scan").option("--lang <code>", "Language code (en, sv)").option("--ci", "Run in CI/CD mode (exit code 1 on critical failures)").option("--generate-tests", "Generate Pseudo-Automation tests").option("--json", "Output as JSON").option("--junit <path>", "Generate JUnit XML report").option("--pdf <path>", "Generate PDF report to path").option("--statement <path>", "Generate accessibility statement HTML to path").option("--format <type>", "Output format for statement (html, md)", "html").option("--viewport <size>", 'Set viewport (e.g. "mobile", "desktop", "1024x768")').option("--threshold <level>", "Severity threshold (critical, high, medium, low)").option("--api-key <key>", "API key for HolmDigital Cloud").option("--cloud-url <url>", "Cloud API URL").option("--invalid-https-cert", "Allow scanning on pages with invalid https certificate").option("--email <email>", "Contact email for accessibility statement").option("--phone <number>", "Contact phone number for accessibility statement").option("--org <name>", "Organization name for accessibility statement").option("--response-time <time>", "Expected response time for accessibility statement").option("--country <code>", "Country code for accessibility statement enforcement body").option("--publish-date <date>", "Publish date for the website (YYYY-MM-DD)").action(async (url, cliOptions) => {
|
|
1621
|
+
const explorer = (0, import_cosmiconfig.cosmiconfig)("a11y");
|
|
1622
|
+
const configResult = await explorer.search();
|
|
1623
|
+
const fileConfig = configResult ? configResult.config : {};
|
|
1624
|
+
const options = {
|
|
1625
|
+
lang: cliOptions.lang || fileConfig.lang || "en",
|
|
1626
|
+
ci: cliOptions.ci ?? fileConfig.ci ?? false,
|
|
1627
|
+
generateTests: cliOptions.generateTests ?? fileConfig.generateTests ?? false,
|
|
1628
|
+
json: cliOptions.json ?? fileConfig.json ?? false,
|
|
1629
|
+
junit: cliOptions.junit || fileConfig.junit,
|
|
1630
|
+
pdf: cliOptions.pdf || fileConfig.pdf,
|
|
1631
|
+
statement: cliOptions.statement || fileConfig.statement,
|
|
1632
|
+
format: cliOptions.format || fileConfig.format || "html",
|
|
1633
|
+
viewport: cliOptions.viewport || fileConfig.viewport,
|
|
1634
|
+
// Handled specifically below
|
|
1635
|
+
threshold: cliOptions.threshold || fileConfig.threshold || "high",
|
|
1636
|
+
apiKey: cliOptions.apiKey || fileConfig.apiKey,
|
|
1637
|
+
cloudUrl: cliOptions.cloudUrl || fileConfig.cloudUrl || "https://cloud.holmdigital.se",
|
|
1638
|
+
invalidHttpsCert: cliOptions.invalidHttpsCert ?? fileConfig.invalidHttpsCert ?? false,
|
|
1639
|
+
// Metadata for accessibility statement
|
|
1640
|
+
email: cliOptions.email || fileConfig.email,
|
|
1641
|
+
phone: cliOptions.phone || fileConfig.phone,
|
|
1642
|
+
org: cliOptions.org || fileConfig.org,
|
|
1643
|
+
responseTime: cliOptions.responseTime || fileConfig.responseTime,
|
|
1644
|
+
country: cliOptions.country || fileConfig.country,
|
|
1645
|
+
publishDate: cliOptions.publishDate || fileConfig.publishDate
|
|
1646
|
+
};
|
|
1169
1647
|
setLanguage(options.lang);
|
|
1170
1648
|
if (!isValidUrl(url)) {
|
|
1171
|
-
console.error(import_chalk.default.red(`Error: Invalid URL format '${url}'`));
|
|
1172
|
-
console.error(import_chalk.default.gray("URL must start with http:// or https://"));
|
|
1173
|
-
process.exit(1);
|
|
1174
1649
|
}
|
|
1175
1650
|
if (!options.json) {
|
|
1176
1651
|
console.log(import_chalk.default.blue.bold(t("cli.title")));
|
|
1652
|
+
if (configResult) console.log(import_chalk.default.gray(`Loaded config from ${configResult.filepath}`));
|
|
1177
1653
|
console.log(import_chalk.default.gray(t("cli.scanning", { url })));
|
|
1178
1654
|
}
|
|
1179
1655
|
const spinner = !options.json ? (0, import_ora.default)(t("cli.initializing")).start() : null;
|
|
1180
1656
|
let scanner;
|
|
1181
1657
|
try {
|
|
1182
1658
|
let viewport = { width: 1280, height: 720 };
|
|
1183
|
-
if (options.viewport) {
|
|
1659
|
+
if (options.viewport && typeof options.viewport === "string") {
|
|
1184
1660
|
if (options.viewport === "mobile") viewport = { width: 375, height: 667 };
|
|
1185
1661
|
else if (options.viewport === "desktop") viewport = { width: 1920, height: 1080 };
|
|
1186
1662
|
else if (options.viewport === "tablet") viewport = { width: 768, height: 1024 };
|
|
@@ -1188,6 +1664,8 @@ program.argument("<url>", "URL to scan").option("--lang <code>", "Language code
|
|
|
1188
1664
|
const [w, h] = options.viewport.split("x").map(Number);
|
|
1189
1665
|
if (w && h) viewport = { width: w, height: h };
|
|
1190
1666
|
}
|
|
1667
|
+
} else if (options.viewport && typeof options.viewport === "object") {
|
|
1668
|
+
viewport = options.viewport;
|
|
1191
1669
|
}
|
|
1192
1670
|
scanner = new RegulatoryScanner({
|
|
1193
1671
|
url,
|
|
@@ -1207,15 +1685,30 @@ program.argument("<url>", "URL to scan").option("--lang <code>", "Language code
|
|
|
1207
1685
|
await generatePDF(html, options.pdf);
|
|
1208
1686
|
if (spinner) spinner.succeed(t("cli.pdf_saved", { path: options.pdf }));
|
|
1209
1687
|
}
|
|
1688
|
+
if (options.statement) {
|
|
1689
|
+
if (spinner) spinner.start(t("cli.generating_statement", { path: options.statement }));
|
|
1690
|
+
const metadata = {
|
|
1691
|
+
contactEmail: options.email,
|
|
1692
|
+
phoneNumber: options.phone,
|
|
1693
|
+
organizationName: options.org,
|
|
1694
|
+
responseTime: options.responseTime,
|
|
1695
|
+
country: options.country,
|
|
1696
|
+
publishDate: options.publishDate
|
|
1697
|
+
};
|
|
1698
|
+
if (options.statement.toLowerCase().endsWith(".pdf")) {
|
|
1699
|
+
const htmlContent = await generateStatementContent(result, options.lang, "html", metadata);
|
|
1700
|
+
await generatePDF(htmlContent, options.statement);
|
|
1701
|
+
} else {
|
|
1702
|
+
await generateStatement(result, options.statement, options.lang, options.format, metadata);
|
|
1703
|
+
}
|
|
1704
|
+
if (spinner) spinner.succeed(t("cli.statement_saved", { path: options.statement }));
|
|
1705
|
+
}
|
|
1210
1706
|
if (options.json) {
|
|
1211
1707
|
console.log(JSON.stringify(result, null, 2));
|
|
1212
1708
|
} else {
|
|
1213
|
-
console.log(
|
|
1214
|
-
const
|
|
1215
|
-
console.log(
|
|
1216
|
-
if (result.complianceStatus === "FAIL") {
|
|
1217
|
-
console.log(import_chalk.default.red(t("cli.not_compliant")));
|
|
1218
|
-
}
|
|
1709
|
+
console.log("\n");
|
|
1710
|
+
const scoreColor = result.score >= 90 ? import_chalk.default.green : result.score >= 70 ? import_chalk.default.yellow : import_chalk.default.red;
|
|
1711
|
+
console.log(import_chalk.default.bold(`[ Compliance Score: ${scoreColor(result.score + "/100")} ] ${result.score >= 90 ? "\u{1F7E2}" : result.score >= 70 ? "\u{1F7E1}" : "\u{1F534}"}`));
|
|
1219
1712
|
if (result.score === 100) {
|
|
1220
1713
|
const badge = generateBadgeMarkdown(result.score);
|
|
1221
1714
|
if (badge) {
|
|
@@ -1224,46 +1717,87 @@ program.argument("<url>", "URL to scan").option("--lang <code>", "Language code
|
|
|
1224
1717
|
}
|
|
1225
1718
|
}
|
|
1226
1719
|
console.log(import_chalk.default.gray("----------------------------------------"));
|
|
1227
|
-
|
|
1228
|
-
|
|
1720
|
+
const calculateCategoryScore = (filterFn) => {
|
|
1721
|
+
const failures = result.reports.filter(filterFn).length;
|
|
1722
|
+
return Math.max(10, 100 - failures * 20);
|
|
1723
|
+
};
|
|
1724
|
+
const cats = [
|
|
1725
|
+
{
|
|
1726
|
+
name: "HTML Structure",
|
|
1727
|
+
score: calculateCategoryScore((r) => ["1.3.1", "4.1.1", "4.1.2"].some((c) => r.wcagCriteria.includes(c))),
|
|
1728
|
+
critical: false
|
|
1729
|
+
},
|
|
1730
|
+
{
|
|
1731
|
+
name: "Keyboard Nav ",
|
|
1732
|
+
score: calculateCategoryScore((r) => ["2.1.1", "2.1.2", "2.4.3", "2.4.7"].some((c) => r.wcagCriteria.includes(c))),
|
|
1733
|
+
critical: result.reports.some((r) => ["2.1.1", "2.1.2"].some((c) => r.wcagCriteria.includes(c)) && r.holmdigitalInsight.diggRisk === "critical")
|
|
1734
|
+
},
|
|
1735
|
+
{
|
|
1736
|
+
name: "Contrast ",
|
|
1737
|
+
score: calculateCategoryScore((r) => r.wcagCriteria.includes("1.4.3") || r.ruleId === "color-contrast"),
|
|
1738
|
+
critical: false
|
|
1739
|
+
}
|
|
1740
|
+
];
|
|
1741
|
+
cats.forEach((cat) => {
|
|
1742
|
+
const width = 10;
|
|
1743
|
+
const filled = Math.round(cat.score / 100 * width);
|
|
1744
|
+
const empty = width - filled;
|
|
1745
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
1746
|
+
let statusText = `${cat.score}%`;
|
|
1747
|
+
if (cat.critical) statusText += import_chalk.default.red(" (CRITICAL FAIL)");
|
|
1748
|
+
else if (cat.score < 100) statusText += import_chalk.default.gray(" (Minor issues)");
|
|
1749
|
+
console.log(`${cat.name} [${cat.score >= 80 ? import_chalk.default.green(bar) : cat.score >= 50 ? import_chalk.default.yellow(bar) : import_chalk.default.red(bar)}] ${statusText}`);
|
|
1750
|
+
});
|
|
1751
|
+
console.log(import_chalk.default.gray("----------------------------------------"));
|
|
1752
|
+
let legalRisk = "LOW";
|
|
1753
|
+
let riskReason = "No critical violations found.";
|
|
1754
|
+
let riskIcon = "\u2705";
|
|
1755
|
+
if (result.legalSummary && result.legalSummary.eaaDeadlineViolations > 0) {
|
|
1756
|
+
legalRisk = "HIGH";
|
|
1757
|
+
const hasKeyboardIssues = result.reports.some((r) => ["2.1.1", "2.1.2"].some((c) => r.wcagCriteria.includes(c)));
|
|
1758
|
+
const hasContrastIssues = result.reports.some((r) => r.wcagCriteria.includes("1.4.3") || r.ruleId === "color-contrast");
|
|
1759
|
+
const hasStructureIssues = result.reports.some((r) => r.wcagCriteria.includes("1.3.1"));
|
|
1760
|
+
if (hasKeyboardIssues) riskReason = "Keyboard accessibility blocks EAA compliance";
|
|
1761
|
+
else if (hasContrastIssues) riskReason = "Contrast issues block EAA compliance";
|
|
1762
|
+
else if (hasStructureIssues) riskReason = "HTML structure issues block EAA compliance";
|
|
1763
|
+
else riskReason = `${result.legalSummary.eaaDeadlineViolations} EAA deadline violation(s) found`;
|
|
1764
|
+
riskIcon = "\u2696\uFE0F ";
|
|
1765
|
+
} else if (result.stats.critical > 0) {
|
|
1766
|
+
legalRisk = "MEDIUM";
|
|
1767
|
+
riskReason = "Critical violations found.";
|
|
1768
|
+
riskIcon = "\u26A0\uFE0F ";
|
|
1229
1769
|
}
|
|
1770
|
+
console.log(`${riskIcon} Legal Risk: ${legalRisk === "HIGH" ? import_chalk.default.red.bold(legalRisk) : legalRisk === "MEDIUM" ? import_chalk.default.yellow.bold(legalRisk) : import_chalk.default.green(legalRisk)} (${import_chalk.default.white(riskReason)})`);
|
|
1771
|
+
console.log("\n");
|
|
1230
1772
|
if (result.htmlValidation && !result.htmlValidation.valid) {
|
|
1231
|
-
console.log(import_chalk.default.red.bold("\
|
|
1773
|
+
console.log(import_chalk.default.red.bold("\u26A0\uFE0F Structural HTML Issues Detected"));
|
|
1232
1774
|
console.log(import_chalk.default.yellow(" These issues may affect accessibility tool accuracy (e.g. contrast calculations)\n"));
|
|
1233
1775
|
result.htmlValidation.errors.forEach((error) => {
|
|
1234
1776
|
console.log(import_chalk.default.red(` [${error.rule}] ${error.message}`));
|
|
1235
|
-
if (error.selector) console.log(import_chalk.default.gray(` ${error.selector}`));
|
|
1236
|
-
console.log(import_chalk.default.gray(` Line: ${error.line}, Col: ${error.column}
|
|
1237
|
-
`));
|
|
1238
1777
|
});
|
|
1778
|
+
console.log(import_chalk.default.gray(" ... and more (run with --json for full details)"));
|
|
1239
1779
|
console.log(import_chalk.default.gray("----------------------------------------"));
|
|
1240
1780
|
}
|
|
1241
|
-
result.reports.
|
|
1781
|
+
if (result.reports.length > 0) {
|
|
1782
|
+
console.log(import_chalk.default.bold("Top Violations:"));
|
|
1783
|
+
}
|
|
1784
|
+
result.reports.forEach((report, i) => {
|
|
1785
|
+
if (i > 5) return;
|
|
1242
1786
|
const color = report.holmdigitalInsight.diggRisk === "critical" ? import_chalk.default.red : import_chalk.default.yellow;
|
|
1243
1787
|
console.log(color.bold(`
|
|
1244
1788
|
[${report.holmdigitalInsight.diggRisk.toUpperCase()}] ${report.ruleId}`));
|
|
1245
1789
|
console.log(import_chalk.default.white(`WCAG: ${report.wcagCriteria} | EN 301 549: ${report.en301549Criteria}`));
|
|
1246
|
-
console.log(import_chalk.default.gray(`Legitimitet: ${report.dosLagenReference}`));
|
|
1247
1790
|
if (report.remediation.component) {
|
|
1248
|
-
console.log(import_chalk.default.green(
|
|
1249
|
-
console.log(t("cli.use_component", { component: import_chalk.default.bold(report.remediation.component) }));
|
|
1250
|
-
}
|
|
1251
|
-
if (report.failingNodes && report.failingNodes.length > 0) {
|
|
1252
|
-
console.log(import_chalk.default.gray("\nAffected Elements:"));
|
|
1253
|
-
report.failingNodes.forEach((node, index) => {
|
|
1254
|
-
if (index < 5) {
|
|
1255
|
-
console.log(import_chalk.default.cyan(`\u279C ${node.target}`));
|
|
1256
|
-
console.log(import_chalk.default.gray(` ${node.html}`));
|
|
1257
|
-
}
|
|
1258
|
-
});
|
|
1259
|
-
if (report.failingNodes.length > 5) {
|
|
1260
|
-
console.log(import_chalk.default.gray(` ...and ${report.failingNodes.length - 5} more`));
|
|
1261
|
-
}
|
|
1791
|
+
console.log(import_chalk.default.green(`Fix: Use component <${report.remediation.component} />`));
|
|
1262
1792
|
}
|
|
1263
1793
|
});
|
|
1794
|
+
if (result.reports.length > 5) {
|
|
1795
|
+
console.log(import_chalk.default.gray(`
|
|
1796
|
+
... and ${result.reports.length - 5} more issues.`));
|
|
1797
|
+
}
|
|
1264
1798
|
console.log(import_chalk.default.gray("\n----------------------------------------"));
|
|
1265
|
-
console.log(`
|
|
1266
|
-
|
|
1799
|
+
console.log(`Scan Date: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`);
|
|
1800
|
+
console.log("\n");
|
|
1267
1801
|
if (options.generateTests) {
|
|
1268
1802
|
console.log(import_chalk.default.magenta.bold(t("cli.pseudo_tests")));
|
|
1269
1803
|
const automation = new PseudoAutomationEngine();
|
|
@@ -1275,6 +1809,18 @@ program.argument("<url>", "URL to scan").option("--lang <code>", "Language code
|
|
|
1275
1809
|
});
|
|
1276
1810
|
}
|
|
1277
1811
|
}
|
|
1812
|
+
if (options.ci && result.reports.length > 0) {
|
|
1813
|
+
const { generateGitHubActionsAnnotations: generateGitHubActionsAnnotations2 } = await Promise.resolve().then(() => (init_github_actions(), github_actions_exports));
|
|
1814
|
+
generateGitHubActionsAnnotations2(result.reports);
|
|
1815
|
+
}
|
|
1816
|
+
if (options.junit) {
|
|
1817
|
+
const { generateJUnitXML: generateJUnitXML2 } = await Promise.resolve().then(() => (init_junit_generator(), junit_generator_exports));
|
|
1818
|
+
const fs2 = await import("fs/promises");
|
|
1819
|
+
const xml = generateJUnitXML2(result.reports, url, result.metadata.scanDuration);
|
|
1820
|
+
await fs2.writeFile(options.junit, xml);
|
|
1821
|
+
if (spinner) spinner.succeed(t("cli.junit_saved", { path: options.junit }));
|
|
1822
|
+
else if (!options.json) console.log(import_chalk.default.green(`\u2713 JUnit report saved to ${options.junit}`));
|
|
1823
|
+
}
|
|
1278
1824
|
if (options.ci && result.stats.critical > 0) {
|
|
1279
1825
|
if (!options.json) console.error(import_chalk.default.red(t("cli.critical_failure")));
|
|
1280
1826
|
process.exit(1);
|