@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/dist/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,9 +500,15 @@ 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
  }
@@ -358,6 +520,8 @@ __export(index_exports, {
358
520
  PseudoAutomationEngine: () => PseudoAutomationEngine,
359
521
  RegulatoryScanner: () => RegulatoryScanner,
360
522
  VirtualDOMBuilder: () => VirtualDOMBuilder,
523
+ generateStatement: () => generateStatement,
524
+ generateStatementContent: () => generateStatementContent,
361
525
  getCurrentLang: () => getCurrentLang,
362
526
  setLanguage: () => setLanguage,
363
527
  t: () => t
@@ -618,7 +782,7 @@ var RegulatoryScanner = class {
618
782
  }
619
783
  async enrichResults(axeResults) {
620
784
  const reports = [];
621
- const { searchRulesByTags, generateRegulatoryReport } = await import("@holmdigital/standards");
785
+ const { searchRulesByTags, generateRegulatoryReport, getConvergenceRule } = await import("@holmdigital/standards");
622
786
  const { getCurrentLang: getCurrentLang2 } = await Promise.resolve().then(() => (init_i18n(), i18n_exports));
623
787
  const lang = getCurrentLang2();
624
788
  for (const violation of axeResults.violations) {
@@ -630,6 +794,7 @@ var RegulatoryScanner = class {
630
794
  }
631
795
  }
632
796
  if (report) {
797
+ const fullRule = getConvergenceRule(report.ruleId, lang);
633
798
  reports.push({
634
799
  ...report,
635
800
  holmdigitalInsight: {
@@ -637,6 +802,8 @@ var RegulatoryScanner = class {
637
802
  reasoning: violation.help
638
803
  // Använd Axe's hjälptext som specifik anledning
639
804
  },
805
+ // Include legal context from the full rule
806
+ legalContext: fullRule?.legalContext,
640
807
  // Attach extra debug info for the CLI
641
808
  failingNodes: violation.nodes.map((node) => ({
642
809
  html: node.html,
@@ -713,6 +880,18 @@ var RegulatoryScanner = class {
713
880
  pageTitle,
714
881
  pageLanguage
715
882
  };
883
+ const reportsWithContext = reports.filter((r) => r.legalContext);
884
+ const legalSummary = {
885
+ wadApplicable: reportsWithContext.filter(
886
+ (r) => r.legalContext?.appliesTo?.includes("WAD")
887
+ ).length,
888
+ eaaApplicable: reportsWithContext.filter(
889
+ (r) => r.legalContext?.appliesTo?.includes("EAA")
890
+ ).length,
891
+ eaaDeadlineViolations: reportsWithContext.filter(
892
+ (r) => r.legalContext?.eaaDeadline
893
+ ).length
894
+ };
716
895
  return {
717
896
  url: this.options.url,
718
897
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -720,7 +899,8 @@ var RegulatoryScanner = class {
720
899
  reports,
721
900
  stats,
722
901
  score,
723
- complianceStatus
902
+ complianceStatus,
903
+ legalSummary
724
904
  };
725
905
  }
726
906
  /**
@@ -804,11 +984,161 @@ test('${testName}', async ({ page }) => {
804
984
 
805
985
  // src/index.ts
806
986
  init_i18n();
987
+
988
+ // src/reporting/statement-generator.ts
989
+ var import_react = __toESM(require("react"));
990
+ var import_server = require("react-dom/server");
991
+ var import_components = require("@holmdigital/components");
992
+ var import_promises = __toESM(require("fs/promises"));
993
+ var import_path = __toESM(require("path"));
994
+
995
+ // src/reporting/badge-generator.ts
996
+ var BADGE_COLOR = "00703C";
997
+ var BADGE_BASE_URL = "https://img.shields.io/badge/HolmDigital_Engine";
998
+ function generateBadgeUrl(score) {
999
+ if (score !== 100) {
1000
+ return null;
1001
+ }
1002
+ return `${BADGE_BASE_URL}-100%25-${BADGE_COLOR}?style=flat-square`;
1003
+ }
1004
+
1005
+ // src/reporting/statement-generator.ts
1006
+ async function generateStatementContent(result, lang = "en", format = "html", metadata) {
1007
+ let complianceLevel = "full";
1008
+ if (result.stats.critical > 0 || result.legalSummary && result.legalSummary.eaaDeadlineViolations > 0) {
1009
+ complianceLevel = "non-compliant";
1010
+ } else if (result.score < 100) {
1011
+ complianceLevel = "partial";
1012
+ }
1013
+ const issuesMap = /* @__PURE__ */ new Map();
1014
+ result.reports.forEach((report) => {
1015
+ if (!issuesMap.has(report.ruleId)) {
1016
+ const issueText = `${report.ruleId} (${report.wcagCriteria})`;
1017
+ issuesMap.set(report.ruleId, issueText);
1018
+ }
1019
+ });
1020
+ const nonComplianceItems = Array.from(issuesMap.values());
1021
+ let country = metadata?.country || "SE";
1022
+ if (!metadata?.country) {
1023
+ if (result.url.endsWith(".no")) country = "NO";
1024
+ if (result.url.endsWith(".dk")) country = "DK";
1025
+ if (result.url.endsWith(".fi")) country = "FI";
1026
+ if (result.url.endsWith(".de")) country = "DE";
1027
+ }
1028
+ const sector = "public";
1029
+ let logoUrl;
1030
+ try {
1031
+ const possiblePaths = [
1032
+ import_path.default.join(process.cwd(), "src/assets/logo.jpg"),
1033
+ // run from package root
1034
+ import_path.default.join(process.cwd(), "packages/engine/src/assets/logo.jpg")
1035
+ // run from monorepo root
1036
+ ];
1037
+ for (const p of possiblePaths) {
1038
+ try {
1039
+ const logoBuffer = await import_promises.default.readFile(p);
1040
+ logoUrl = `data:image/jpeg;base64,${logoBuffer.toString("base64")}`;
1041
+ break;
1042
+ } catch (e) {
1043
+ }
1044
+ }
1045
+ } catch (e) {
1046
+ console.warn("Could not load logo.jpg", e);
1047
+ }
1048
+ const props = {
1049
+ country,
1050
+ sector,
1051
+ organizationName: metadata?.organizationName || new URL(result.url).hostname.replace(/^www\./, ""),
1052
+ websiteUrl: result.url,
1053
+ complianceLevel,
1054
+ lastReviewDate: /* @__PURE__ */ new Date(),
1055
+ assessmentDate: /* @__PURE__ */ new Date(),
1056
+ evaluationMethod: lang === "sv" ? "Automatiserad granskning via @holmdigital/engine" : "Automated Scan via @holmdigital/engine",
1057
+ generatorTool: {
1058
+ name: "HolmDigital Regulatory Engine",
1059
+ url: "https://holmdigital.se"
1060
+ },
1061
+ logoUrl,
1062
+ contactEmail: metadata?.contactEmail || "hej@holmdigital.se",
1063
+ phoneNumber: metadata?.phoneNumber || "070-123 45 67",
1064
+ responseTime: metadata?.responseTime || (lang === "sv" ? "2 dagar" : "2 days"),
1065
+ nonComplianceItems,
1066
+ locale: lang,
1067
+ badgeUrl: generateBadgeUrl(result.score) || void 0,
1068
+ publishDate: metadata?.publishDate ? new Date(metadata.publishDate) : void 0
1069
+ };
1070
+ let content = "";
1071
+ if (format === "html") {
1072
+ const element = import_react.default.createElement(import_components.AccessibilityStatement, props);
1073
+ const markup = (0, import_server.renderToStaticMarkup)(element);
1074
+ content = `<!DOCTYPE html>
1075
+ <html lang="${lang}">
1076
+ <head>
1077
+ <meta charset="UTF-8">
1078
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1079
+ <title>Accessibility Statement - ${props.organizationName}</title>
1080
+ <style>
1081
+ body { font-family: system-ui, -apple-system, sans-serif; background: #f8f9fa; padding: 2rem; margin: 0; }
1082
+ </style>
1083
+ </head>
1084
+ <body>
1085
+ ${markup}
1086
+ </body>
1087
+ </html>`;
1088
+ } else {
1089
+ const dateStr = props.lastReviewDate.toISOString().split("T")[0];
1090
+ const statusMap = {
1091
+ "full": "Full",
1092
+ "partial": "Partial",
1093
+ "non-compliant": "Non-Compliant"
1094
+ };
1095
+ content = `# Accessibility Statement for ${props.organizationName}
1096
+
1097
+ This accessibility statement applies to [${props.websiteUrl}](${props.websiteUrl}).
1098
+
1099
+ ## Compliance Status
1100
+
1101
+ **Status:** ${statusMap[complianceLevel] || complianceLevel}
1102
+
1103
+ This website is ${complianceLevel} compliant with ${sector === "public" ? "EN 301 549 (WAD)" : "EAA"}.
1104
+
1105
+ ## Non-accessible Content
1106
+
1107
+ The following content is non-accessible for the following reasons:
1108
+
1109
+ ${nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `- ${item}`).join("\n") : "_No known issues._"}
1110
+
1111
+ ## Preparation of this statement
1112
+
1113
+ This statement was prepared on ${dateStr}.
1114
+ Method used: **Automated Scan** via @holmdigital/engine.
1115
+
1116
+ ## Feedback and contact information
1117
+
1118
+ If you need information on this website in a different format like accessible PDF, large print, easy-to-read, audio recording or braille:
1119
+
1120
+ - email: [${props.contactEmail}](mailto:${props.contactEmail})
1121
+
1122
+ ## Enforcement procedure
1123
+
1124
+ The enforcement body for ${country} is responsible for enforcing these regulations.
1125
+ `;
1126
+ }
1127
+ return content;
1128
+ }
1129
+ async function generateStatement(result, outputPath, lang = "en", format = "html", metadata) {
1130
+ const content = await generateStatementContent(result, lang, format, metadata);
1131
+ const dir = import_path.default.dirname(outputPath);
1132
+ await import_promises.default.mkdir(dir, { recursive: true });
1133
+ await import_promises.default.writeFile(outputPath, content, "utf-8");
1134
+ }
807
1135
  // Annotate the CommonJS export names for ESM import in node:
808
1136
  0 && (module.exports = {
809
1137
  PseudoAutomationEngine,
810
1138
  RegulatoryScanner,
811
1139
  VirtualDOMBuilder,
1140
+ generateStatement,
1141
+ generateStatementContent,
812
1142
  getCurrentLang,
813
1143
  setLanguage,
814
1144
  t
package/dist/index.mjs CHANGED
@@ -1,17 +1,21 @@
1
1
  import {
2
2
  PseudoAutomationEngine,
3
3
  RegulatoryScanner,
4
- VirtualDOMBuilder
5
- } from "./chunk-FFTRXRZU.mjs";
4
+ VirtualDOMBuilder,
5
+ generateStatement,
6
+ generateStatementContent
7
+ } from "./chunk-ZO2XNHHT.mjs";
6
8
  import {
7
9
  getCurrentLang,
8
10
  setLanguage,
9
11
  t
10
- } from "./chunk-YWRTSIUX.mjs";
12
+ } from "./chunk-32WU5BD6.mjs";
11
13
  export {
12
14
  PseudoAutomationEngine,
13
15
  RegulatoryScanner,
14
16
  VirtualDOMBuilder,
17
+ generateStatement,
18
+ generateStatementContent,
15
19
  getCurrentLang,
16
20
  setLanguage,
17
21
  t
@@ -0,0 +1,43 @@
1
+ // src/reporting/junit-generator.ts
2
+ function generateJUnitXML(reports, url, duration) {
3
+ const failures = reports.length;
4
+ const testSuites = [
5
+ `<?xml version="1.0" encoding="UTF-8"?>`,
6
+ `<testsuites name="HolmDigital Accessibility Scan" time="${duration / 1e3}">`,
7
+ ` <testsuite name="${url}" tests="${failures}" failures="${failures}" errors="0" time="${duration / 1e3}">`
8
+ ];
9
+ reports.forEach((report) => {
10
+ const severity = report.holmdigitalInsight.diggRisk;
11
+ const message = escapeXML(report.holmdigitalInsight.reasoning);
12
+ const criteria = escapeXML(`WCAG ${report.wcagCriteria} | EN 301 549 ${report.en301549Criteria}`);
13
+ const help = escapeXML(`Ref: ${report.dosLagenReference}. Remediation: ${report.remediation.component || "Manual"}`);
14
+ testSuites.push(` <testcase name="[${severity.toUpperCase()}] ${report.ruleId}" classname="${report.wcagCriteria}" time="0">`);
15
+ testSuites.push(` <failure message="${message}" type="${severity}">${criteria}
16
+ ${help}</failure>`);
17
+ testSuites.push(` </testcase>`);
18
+ });
19
+ testSuites.push(` </testsuite>`);
20
+ testSuites.push(`</testsuites>`);
21
+ return testSuites.join("\n");
22
+ }
23
+ function escapeXML(unsafe) {
24
+ return unsafe.replace(/[<>&'"]/g, (c) => {
25
+ switch (c) {
26
+ case "<":
27
+ return "&lt;";
28
+ case ">":
29
+ return "&gt;";
30
+ case "&":
31
+ return "&amp;";
32
+ case "'":
33
+ return "&apos;";
34
+ case '"':
35
+ return "&quot;";
36
+ default:
37
+ return c;
38
+ }
39
+ });
40
+ }
41
+ export {
42
+ generateJUnitXML
43
+ };