@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.
@@ -10,6 +10,8 @@ var en_default = {
10
10
  pdf_saved: "PDF Report saved to {path}",
11
11
  scan_failed: "Scan failed",
12
12
  critical_failure: "\nCI/CD Failure: Critical regulatory violations found.",
13
+ generating_statement: "Generating Accessibility Statement...",
14
+ statement_saved: "Accessibility Statement saved to {path}",
13
15
  score: "\nCompliance Score: {score}/100",
14
16
  status: "Compliance Status: {status}",
15
17
  not_compliant: " (Not compliant with legal requirements)",
@@ -17,7 +19,8 @@ var en_default = {
17
19
  prescriptive_fix: "\n\u{1F4A1} Prescriptive Fix:",
18
20
  use_component: "Use component: {component}",
19
21
  pseudo_tests: "\n\u{1F9EC} Generating Pseudo-Automation Tests...\n",
20
- test_for: "Test for {ruleId}:"
22
+ test_for: "Test for {ruleId}:",
23
+ junit_saved: "JUnit XML report saved to {path}"
21
24
  },
22
25
  report: {
23
26
  title: "Accessibility Report - {url}",
@@ -47,6 +50,8 @@ var sv_default = {
47
50
  pdf_saved: "PDF-rapport sparad till {path}",
48
51
  scan_failed: "Skanning misslyckades",
49
52
  critical_failure: "\nCI/CD Fel: Kritiska regulatoriska brister hittades.",
53
+ generating_statement: "Genererar tillg\xE4nglighetsredog\xF6relse...",
54
+ statement_saved: "Tillg\xE4nglighetsredog\xF6relse sparad till {path}",
50
55
  score: "\nEfterlevnadspo\xE4ng: {score}/100",
51
56
  status: "Efterlevnadsstatus: {status}",
52
57
  not_compliant: " (Uppfyller ej legala krav)",
@@ -54,7 +59,8 @@ var sv_default = {
54
59
  prescriptive_fix: "\n\u{1F4A1} Preskriptiv L\xF6sning:",
55
60
  use_component: "Anv\xE4nd komponent: {component}",
56
61
  pseudo_tests: "\n\u{1F9EC} Genererar Pseudo-automationstester...\n",
57
- test_for: "Test f\xF6r {ruleId}:"
62
+ test_for: "Test f\xF6r {ruleId}:",
63
+ junit_saved: "JUnit XML-rapport sparad till {path}"
58
64
  },
59
65
  report: {
60
66
  title: "Tillg\xE4nglighetsrapport - {url}",
@@ -84,6 +90,8 @@ var de_default = {
84
90
  pdf_saved: "PDF-Bericht gespeichert unter {path}",
85
91
  scan_failed: "Scan fehlgeschlagen",
86
92
  critical_failure: "\nCI/CD Fehler: Kritische regulatorische Verst\xF6\xDFe gefunden.",
93
+ generating_statement: "Generiere Barrierefreiheitserkl\xE4rung...",
94
+ statement_saved: "Barrierefreiheitserkl\xE4rung gespeichert unter {path}",
87
95
  score: "\nKonformit\xE4tsbewertung: {score}/100",
88
96
  status: "Konformit\xE4tsstatus: {status}",
89
97
  not_compliant: " (Nicht konform mit gesetzlichen Anforderungen - BITV 2.0)",
@@ -91,7 +99,8 @@ var de_default = {
91
99
  prescriptive_fix: "\n\u{1F4A1} Vorschriftsm\xE4\xDFige L\xF6sung:",
92
100
  use_component: "Verwenden Sie die Komponente: {component}",
93
101
  pseudo_tests: "\n\u{1F9EC} Generiere Pseudo-Automatisierungstests...\n",
94
- test_for: "Test f\xFCr {ruleId}:"
102
+ test_for: "Test f\xFCr {ruleId}:",
103
+ junit_saved: "JUnit XML-Bericht gespeichert unter {path}"
95
104
  },
96
105
  report: {
97
106
  title: "Barrierefreiheitsbericht - {url}",
@@ -121,6 +130,8 @@ var fr_default = {
121
130
  pdf_saved: "Rapport PDF enregistr\xE9 sous {path}",
122
131
  scan_failed: "\xC9chec du scan",
123
132
  critical_failure: "\n\xC9chec CI/CD : Violations r\xE9glementaires critiques d\xE9tect\xE9es.",
133
+ generating_statement: "G\xE9n\xE9ration de la d\xE9claration d'accessibilit\xE9...",
134
+ statement_saved: "D\xE9claration d'accessibilit\xE9 enregistr\xE9e sous {path}",
124
135
  score: "\nScore de Conformit\xE9 : {score}/100",
125
136
  status: "Statut de Conformit\xE9 : {status}",
126
137
  not_compliant: " (Non conforme aux exigences l\xE9gales - RGAA)",
@@ -128,7 +139,8 @@ var fr_default = {
128
139
  prescriptive_fix: "\n\u{1F4A1} Solution Prescriptive :",
129
140
  use_component: "Utilisez le composant : {component}",
130
141
  pseudo_tests: "\n\u{1F9EC} G\xE9n\xE9ration de tests de pseudo-automatisation...\n",
131
- test_for: "Test pour {ruleId} :"
142
+ test_for: "Test pour {ruleId} :",
143
+ junit_saved: "Rapport XML JUnit enregistr\xE9 sous {path}"
132
144
  },
133
145
  report: {
134
146
  title: "Rapport d'Accessibilit\xE9 - {url}",
@@ -158,6 +170,8 @@ var es_default = {
158
170
  pdf_saved: "Informe PDF guardado en {path}",
159
171
  scan_failed: "Escaneo fallido",
160
172
  critical_failure: "\nFallo de CI/CD: Se encontraron violaciones regulatorias cr\xEDticas.",
173
+ generating_statement: "Generando Declaraci\xF3n de Accesibilidad...",
174
+ statement_saved: "Declaraci\xF3n de Accesibilidad guardada en {path}",
161
175
  score: "\nPuntuaci\xF3n de Cumplimiento: {score}/100",
162
176
  status: "Estado de Cumplimiento: {status}",
163
177
  not_compliant: " (No cumple con los requisitos legales - UNE 139803)",
@@ -165,7 +179,8 @@ var es_default = {
165
179
  prescriptive_fix: "\n\u{1F4A1} Soluci\xF3n Prescriptiva:",
166
180
  use_component: "Use el componente: {component}",
167
181
  pseudo_tests: "\n\u{1F9EC} Generando pruebas de pseudo-automatizaci\xF3n...\n",
168
- test_for: "Prueba para {ruleId}:"
182
+ test_for: "Prueba para {ruleId}:",
183
+ junit_saved: "Informe JUnit XML guardado en {path}"
169
184
  },
170
185
  report: {
171
186
  title: "Informe de Accesibilidad - {url}",
@@ -183,6 +198,126 @@ var es_default = {
183
198
  }
184
199
  };
185
200
 
201
+ // src/locales/fi.json
202
+ var fi_default = {
203
+ cli: {
204
+ title: "\n\u{1F310} HolmDigital S\xE4\xE4ntelyskanneri\n",
205
+ scanning: "Skannataan {url}...\n",
206
+ initializing: "Alustetaan skanneria...",
207
+ analyzing: "Analysoidaan DOM & Shadow DOM...",
208
+ complete: "Skannaus valmis!",
209
+ generating_pdf: "Luodaan PDF-raporttia...",
210
+ pdf_saved: "PDF-raportti tallennettu kohteeseen {path}",
211
+ scan_failed: "Skannaus ep\xE4onnistui",
212
+ critical_failure: "\nCI/CD Virhe: Kriittisi\xE4 s\xE4\xE4ntelyrikkomuksia havaittu.",
213
+ generating_statement: "Luodaan saavutettavuusselostetta...",
214
+ statement_saved: "Saavutettavuusseloste tallennettu kohteeseen {path}",
215
+ score: "\nVaatimustenmukaisuus: {score}/100",
216
+ status: "Tila: {status}",
217
+ not_compliant: " (Ei t\xE4yt\xE4 lakis\xE4\xE4teisi\xE4 vaatimuksia - Laki digitaalisten palvelujen tarjoamisesta)",
218
+ viewport: "N\xE4kym\xE4: {width}x{height}",
219
+ prescriptive_fix: "\n\u{1F4A1} Ehdotettu korjaus:",
220
+ use_component: "K\xE4yt\xE4 komponenttia: {component}",
221
+ pseudo_tests: "\n\u{1F9EC} Luodaan Pseudo-automaatiotestej\xE4...\n",
222
+ test_for: "Testi {ruleId}:lle:",
223
+ junit_saved: "JUnit XML -raportti tallennettu kohteeseen {path}"
224
+ },
225
+ report: {
226
+ title: "Saavutettavuusraportti - {url}",
227
+ generated: "Luotu: {date}",
228
+ scan_target: "Skannauksen kohde: {url}",
229
+ overall_score: "Kokonaispisteet",
230
+ critical_issues: "Kriittiset ongelmat",
231
+ high_issues: "Vakavat ongelmat",
232
+ total_issues: "Ongelmat yhteens\xE4",
233
+ html_errors: "HTML-virheet",
234
+ detailed_violations: "Yksityiskohtaiset rikkomukset",
235
+ prescriptive_fix: "\u{1F4A1} Ehdotettu korjaus",
236
+ use: "K\xE4yt\xE4",
237
+ footer: "Luonut @holmdigital/engine v0.1.0 \u2022 Standardit: WCAG 2.1 AA, EN 301 549, Digipalvelulaki"
238
+ }
239
+ };
240
+
241
+ // src/locales/dk.json
242
+ var dk_default = {
243
+ cli: {
244
+ title: "\n\u{1F310} HolmDigital Regulatorisk Scanner\n",
245
+ scanning: "Scanner {url}...\n",
246
+ initializing: "Initialiserer scanner...",
247
+ analyzing: "Analyserer DOM & Shadow DOM...",
248
+ complete: "Scanning f\xE6rdig!",
249
+ generating_pdf: "Genererer PDF-rapport...",
250
+ pdf_saved: "PDF-rapport gemt i {path}",
251
+ scan_failed: "Scanning mislykkedes",
252
+ critical_failure: "\nCI/CD Fejl: Kritiske regulatoriske overtr\xE6delser fundet.",
253
+ generating_statement: "Genererer tilg\xE6ngelighedserkl\xE6ring...",
254
+ statement_saved: "Tilg\xE6ngelighedserkl\xE6ring gemt i {path}",
255
+ score: "\nOverholdelsesscore: {score}/100",
256
+ status: "Overholdelsesstatus: {status}",
257
+ not_compliant: " (Opfylder ikke lovkrav - Lov om tilg\xE6ngelighed)",
258
+ viewport: "Viewport: {width}x{height}",
259
+ prescriptive_fix: "\n\u{1F4A1} Foresl\xE5et l\xF8sning:",
260
+ use_component: "Brug komponent: {component}",
261
+ pseudo_tests: "\n\u{1F9EC} Genererer Pseudo-automatiseringstests...\n",
262
+ test_for: "Test for {ruleId}:",
263
+ junit_saved: "JUnit XML-rapport gemt i {path}"
264
+ },
265
+ report: {
266
+ title: "Tilg\xE6ngelighedsrapport - {url}",
267
+ generated: "Genereret: {date}",
268
+ scan_target: "Scanningsm\xE5l: {url}",
269
+ overall_score: "Samlet Score",
270
+ critical_issues: "Kritiske Fejl",
271
+ high_issues: "Alvorlige Fejl",
272
+ total_issues: "Totale Fejl",
273
+ html_errors: "HTML Fejl",
274
+ detailed_violations: "Detaljerede Overtr\xE6delser",
275
+ prescriptive_fix: "\u{1F4A1} Foresl\xE5et L\xF8sning",
276
+ use: "Brug",
277
+ footer: "Genereret af @holmdigital/engine v0.1.0 \u2022 Standarder: WCAG 2.1 AA, EN 301 549, Lov om tilg\xE6ngelighed"
278
+ }
279
+ };
280
+
281
+ // src/locales/no.json
282
+ var no_default = {
283
+ cli: {
284
+ title: "\n\u{1F310} HolmDigital Regulatorisk Skanner\n",
285
+ scanning: "Skanner {url}...\n",
286
+ initializing: "Initialiserer skanner...",
287
+ analyzing: "Analyserer DOM & Shadow DOM...",
288
+ complete: "Skanning ferdig!",
289
+ generating_pdf: "Genererer PDF-rapport...",
290
+ pdf_saved: "PDF-rapport lagret i {path}",
291
+ scan_failed: "Skanning mislyktes",
292
+ critical_failure: "\nCI/CD Feil: Kritiske regulatoriske brudd funnet.",
293
+ generating_statement: "Genererer tilgjengelighetserkl\xE6ring...",
294
+ statement_saved: "Tilgjengelighetserkl\xE6ring lagret i {path}",
295
+ score: "\nEtterlevelsespoeng: {score}/100",
296
+ status: "Etterlevelsesstatus: {status}",
297
+ not_compliant: " (Oppfyller ikke lovkrav - Diskriminerings- og tilgjengelighetsloven)",
298
+ viewport: "Visningsomr\xE5de: {width}x{height}",
299
+ prescriptive_fix: "\n\u{1F4A1} Foresl\xE5tt l\xF8sning:",
300
+ use_component: "Bruk komponent: {component}",
301
+ pseudo_tests: "\n\u{1F9EC} Genererer Pseudo-automatiseringstester...\n",
302
+ test_for: "Test for {ruleId}:",
303
+ junit_saved: "JUnit XML-rapport lagret i {path}"
304
+ },
305
+ report: {
306
+ title: "Tilgjengelighetsrapport - {url}",
307
+ generated: "Generert: {date}",
308
+ scan_target: "Skannet m\xE5l: {url}",
309
+ overall_score: "Total Score",
310
+ critical_issues: "Kritiske Feil",
311
+ high_issues: "Alvorlige Feil",
312
+ total_issues: "Totale Feil",
313
+ html_errors: "HTML Feil",
314
+ detailed_violations: "Detaljerte Brudd",
315
+ prescriptive_fix: "\u{1F4A1} Foresl\xE5tt L\xF8sning",
316
+ use: "Bruk",
317
+ footer: "Generert av @holmdigital/engine v0.1.0 \u2022 Standarder: WCAG 2.1 AA, EN 301 549, Uu-tilsynet"
318
+ }
319
+ };
320
+
186
321
  // src/locales/nl.json
187
322
  var nl_default = {
188
323
  cli: {
@@ -195,6 +330,8 @@ var nl_default = {
195
330
  pdf_saved: "PDF-rapport opgeslagen op {path}",
196
331
  scan_failed: "Scan mislukt",
197
332
  critical_failure: "\nCI/CD Fout: Kritieke wettelijke overtredingen gevonden.",
333
+ generating_statement: "Toegankelijkheidsverklaring genereren...",
334
+ statement_saved: "Toegankelijkheidsverklaring opgeslagen op {path}",
198
335
  score: "\nNalevingsscore: {score}/100",
199
336
  status: "Nalevingsstatus: {status}",
200
337
  not_compliant: " (Niet in overeenstemming met wettelijke vereisten)",
@@ -202,7 +339,8 @@ var nl_default = {
202
339
  prescriptive_fix: "\n\u{1F4A1} Voorgeschreven oplossing:",
203
340
  use_component: "Gebruik component: {component}",
204
341
  pseudo_tests: "\n\u{1F9EC} Pseudo-automatiseringstests genereren...\n",
205
- test_for: "Test voor {ruleId}:"
342
+ test_for: "Test voor {ruleId}:",
343
+ junit_saved: "JUnit XML-rapport opgeslagen op {path}"
206
344
  },
207
345
  report: {
208
346
  title: "Toegankelijkheidsrapport - {url}",
@@ -228,9 +366,15 @@ var locales = {
228
366
  fr: fr_default,
229
367
  es: es_default,
230
368
  nl: nl_default,
369
+ fi: fi_default,
370
+ dk: dk_default,
371
+ no: no_default,
231
372
  "en-gb": en_default,
232
373
  "en-us": en_default,
233
- "en-ca": en_default
374
+ "en-ca": en_default,
375
+ "da": dk_default,
376
+ "nb": no_default
377
+ // Norwegian Bokmål alias
234
378
  };
235
379
  var currentLang = "en";
236
380
  function setLanguage(lang) {
@@ -252,8 +252,8 @@ var RegulatoryScanner = class {
252
252
  }
253
253
  async enrichResults(axeResults) {
254
254
  const reports = [];
255
- const { searchRulesByTags, generateRegulatoryReport } = await import("@holmdigital/standards");
256
- const { getCurrentLang } = await import("./i18n-WJLEZT5A.mjs");
255
+ const { searchRulesByTags, generateRegulatoryReport, getConvergenceRule } = await import("@holmdigital/standards");
256
+ const { getCurrentLang } = await import("./i18n-VBP5BVTT.mjs");
257
257
  const lang = getCurrentLang();
258
258
  for (const violation of axeResults.violations) {
259
259
  let report = generateRegulatoryReport(violation.id, lang);
@@ -264,6 +264,7 @@ var RegulatoryScanner = class {
264
264
  }
265
265
  }
266
266
  if (report) {
267
+ const fullRule = getConvergenceRule(report.ruleId, lang);
267
268
  reports.push({
268
269
  ...report,
269
270
  holmdigitalInsight: {
@@ -271,6 +272,8 @@ var RegulatoryScanner = class {
271
272
  reasoning: violation.help
272
273
  // Använd Axe's hjälptext som specifik anledning
273
274
  },
275
+ // Include legal context from the full rule
276
+ legalContext: fullRule?.legalContext,
274
277
  // Attach extra debug info for the CLI
275
278
  failingNodes: violation.nodes.map((node) => ({
276
279
  html: node.html,
@@ -347,6 +350,18 @@ var RegulatoryScanner = class {
347
350
  pageTitle,
348
351
  pageLanguage
349
352
  };
353
+ const reportsWithContext = reports.filter((r) => r.legalContext);
354
+ const legalSummary = {
355
+ wadApplicable: reportsWithContext.filter(
356
+ (r) => r.legalContext?.appliesTo?.includes("WAD")
357
+ ).length,
358
+ eaaApplicable: reportsWithContext.filter(
359
+ (r) => r.legalContext?.appliesTo?.includes("EAA")
360
+ ).length,
361
+ eaaDeadlineViolations: reportsWithContext.filter(
362
+ (r) => r.legalContext?.eaaDeadline
363
+ ).length
364
+ };
350
365
  return {
351
366
  url: this.options.url,
352
367
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -354,7 +369,8 @@ var RegulatoryScanner = class {
354
369
  reports,
355
370
  stats,
356
371
  score,
357
- complianceStatus
372
+ complianceStatus,
373
+ legalSummary
358
374
  };
359
375
  }
360
376
  /**
@@ -436,8 +452,165 @@ test('${testName}', async ({ page }) => {
436
452
  }
437
453
  };
438
454
 
455
+ // src/reporting/badge-generator.ts
456
+ var BADGE_COLOR = "00703C";
457
+ var BADGE_BASE_URL = "https://img.shields.io/badge/HolmDigital_Engine";
458
+ function generateBadgeUrl(score) {
459
+ if (score !== 100) {
460
+ return null;
461
+ }
462
+ return `${BADGE_BASE_URL}-100%25-${BADGE_COLOR}?style=flat-square`;
463
+ }
464
+ function generateBadgeMarkdown(score) {
465
+ const url = generateBadgeUrl(score);
466
+ if (!url) {
467
+ return null;
468
+ }
469
+ return `![Accessibility Status: 100% Compliant](${url})`;
470
+ }
471
+
472
+ // src/reporting/statement-generator.ts
473
+ import React from "react";
474
+ import { renderToStaticMarkup } from "react-dom/server";
475
+ import { AccessibilityStatement } from "@holmdigital/components";
476
+ import fs from "fs/promises";
477
+ import path from "path";
478
+ async function generateStatementContent(result, lang = "en", format = "html", metadata) {
479
+ let complianceLevel = "full";
480
+ if (result.stats.critical > 0 || result.legalSummary && result.legalSummary.eaaDeadlineViolations > 0) {
481
+ complianceLevel = "non-compliant";
482
+ } else if (result.score < 100) {
483
+ complianceLevel = "partial";
484
+ }
485
+ const issuesMap = /* @__PURE__ */ new Map();
486
+ result.reports.forEach((report) => {
487
+ if (!issuesMap.has(report.ruleId)) {
488
+ const issueText = `${report.ruleId} (${report.wcagCriteria})`;
489
+ issuesMap.set(report.ruleId, issueText);
490
+ }
491
+ });
492
+ const nonComplianceItems = Array.from(issuesMap.values());
493
+ let country = metadata?.country || "SE";
494
+ if (!metadata?.country) {
495
+ if (result.url.endsWith(".no")) country = "NO";
496
+ if (result.url.endsWith(".dk")) country = "DK";
497
+ if (result.url.endsWith(".fi")) country = "FI";
498
+ if (result.url.endsWith(".de")) country = "DE";
499
+ }
500
+ const sector = "public";
501
+ let logoUrl;
502
+ try {
503
+ const possiblePaths = [
504
+ path.join(process.cwd(), "src/assets/logo.jpg"),
505
+ // run from package root
506
+ path.join(process.cwd(), "packages/engine/src/assets/logo.jpg")
507
+ // run from monorepo root
508
+ ];
509
+ for (const p of possiblePaths) {
510
+ try {
511
+ const logoBuffer = await fs.readFile(p);
512
+ logoUrl = `data:image/jpeg;base64,${logoBuffer.toString("base64")}`;
513
+ break;
514
+ } catch (e) {
515
+ }
516
+ }
517
+ } catch (e) {
518
+ console.warn("Could not load logo.jpg", e);
519
+ }
520
+ const props = {
521
+ country,
522
+ sector,
523
+ organizationName: metadata?.organizationName || new URL(result.url).hostname.replace(/^www\./, ""),
524
+ websiteUrl: result.url,
525
+ complianceLevel,
526
+ lastReviewDate: /* @__PURE__ */ new Date(),
527
+ assessmentDate: /* @__PURE__ */ new Date(),
528
+ evaluationMethod: lang === "sv" ? "Automatiserad granskning via @holmdigital/engine" : "Automated Scan via @holmdigital/engine",
529
+ generatorTool: {
530
+ name: "HolmDigital Regulatory Engine",
531
+ url: "https://holmdigital.se"
532
+ },
533
+ logoUrl,
534
+ contactEmail: metadata?.contactEmail || "hej@holmdigital.se",
535
+ phoneNumber: metadata?.phoneNumber || "070-123 45 67",
536
+ responseTime: metadata?.responseTime || (lang === "sv" ? "2 dagar" : "2 days"),
537
+ nonComplianceItems,
538
+ locale: lang,
539
+ badgeUrl: generateBadgeUrl(result.score) || void 0,
540
+ publishDate: metadata?.publishDate ? new Date(metadata.publishDate) : void 0
541
+ };
542
+ let content = "";
543
+ if (format === "html") {
544
+ const element = React.createElement(AccessibilityStatement, props);
545
+ const markup = renderToStaticMarkup(element);
546
+ content = `<!DOCTYPE html>
547
+ <html lang="${lang}">
548
+ <head>
549
+ <meta charset="UTF-8">
550
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
551
+ <title>Accessibility Statement - ${props.organizationName}</title>
552
+ <style>
553
+ body { font-family: system-ui, -apple-system, sans-serif; background: #f8f9fa; padding: 2rem; margin: 0; }
554
+ </style>
555
+ </head>
556
+ <body>
557
+ ${markup}
558
+ </body>
559
+ </html>`;
560
+ } else {
561
+ const dateStr = props.lastReviewDate.toISOString().split("T")[0];
562
+ const statusMap = {
563
+ "full": "Full",
564
+ "partial": "Partial",
565
+ "non-compliant": "Non-Compliant"
566
+ };
567
+ content = `# Accessibility Statement for ${props.organizationName}
568
+
569
+ This accessibility statement applies to [${props.websiteUrl}](${props.websiteUrl}).
570
+
571
+ ## Compliance Status
572
+
573
+ **Status:** ${statusMap[complianceLevel] || complianceLevel}
574
+
575
+ This website is ${complianceLevel} compliant with ${sector === "public" ? "EN 301 549 (WAD)" : "EAA"}.
576
+
577
+ ## Non-accessible Content
578
+
579
+ The following content is non-accessible for the following reasons:
580
+
581
+ ${nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `- ${item}`).join("\n") : "_No known issues._"}
582
+
583
+ ## Preparation of this statement
584
+
585
+ This statement was prepared on ${dateStr}.
586
+ Method used: **Automated Scan** via @holmdigital/engine.
587
+
588
+ ## Feedback and contact information
589
+
590
+ If you need information on this website in a different format like accessible PDF, large print, easy-to-read, audio recording or braille:
591
+
592
+ - email: [${props.contactEmail}](mailto:${props.contactEmail})
593
+
594
+ ## Enforcement procedure
595
+
596
+ The enforcement body for ${country} is responsible for enforcing these regulations.
597
+ `;
598
+ }
599
+ return content;
600
+ }
601
+ async function generateStatement(result, outputPath, lang = "en", format = "html", metadata) {
602
+ const content = await generateStatementContent(result, lang, format, metadata);
603
+ const dir = path.dirname(outputPath);
604
+ await fs.mkdir(dir, { recursive: true });
605
+ await fs.writeFile(outputPath, content, "utf-8");
606
+ }
607
+
439
608
  export {
440
609
  VirtualDOMBuilder,
441
610
  RegulatoryScanner,
442
- PseudoAutomationEngine
611
+ PseudoAutomationEngine,
612
+ generateBadgeUrl,
613
+ generateBadgeMarkdown,
614
+ generateStatementContent,
615
+ generateStatement
443
616
  };