@holmdigital/engine 2.0.1 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/assets/logo.jpg +0 -0
- package/dist/{chunk-IM7FTWQF.mjs → chunk-JEA4LJ7Q.mjs} +143 -30
- package/dist/cli/assets/logo.jpg +0 -0
- package/dist/cli/index.js +189 -45
- package/dist/cli/index.mjs +3 -3
- package/dist/cli/templates/da.json +41 -0
- package/dist/cli/templates/de.json +36 -0
- package/dist/cli/templates/en.json +41 -0
- package/dist/cli/templates/es.json +36 -0
- package/dist/cli/templates/fi.json +41 -0
- package/dist/cli/templates/fr.json +36 -0
- package/dist/cli/templates/nl.json +41 -0
- package/dist/cli/templates/no.json +41 -0
- package/dist/cli/templates/sv.json +41 -0
- package/dist/index.js +143 -30
- package/dist/index.mjs +1 -1
- package/dist/junit-generator-6ITTV4W5.mjs +76 -0
- package/dist/templates/da.json +41 -0
- package/dist/templates/de.json +36 -0
- package/dist/templates/en.json +41 -0
- package/dist/templates/es.json +36 -0
- package/dist/templates/fi.json +41 -0
- package/dist/templates/fr.json +36 -0
- package/dist/templates/nl.json +41 -0
- package/dist/templates/no.json +41 -0
- package/dist/templates/sv.json +41 -0
- package/package.json +4 -4
- package/dist/junit-generator-WHQYVX5X.mjs +0 -45
package/README.md
CHANGED
|
@@ -23,11 +23,11 @@ For a comprehensive guide on CLI flags, CI/CD integration, and configuration fil
|
|
|
23
23
|
|
|
24
24
|
- **Regulatory Mapping**: Maps technical failures to EU laws (EN 301 549, EAA).
|
|
25
25
|
- **HTML Structure Validation**: Built-in `html-validate` checks to prevent false positives/negatives.
|
|
26
|
-
- **Internationalization (i18n)**:
|
|
27
|
-
- **
|
|
26
|
+
- **Internationalization (i18n)**: Comprehensive support for 9 languages: English (`en`), Swedish (`sv`), Norwegian (`no`), Finnish (`fi`), Danish (`da`), German (`de`), French (`fr`), Spanish (`es`), and Dutch (`nl`).
|
|
27
|
+
- **Template-Driven Accessibility Statements**: Generates modern, glassmorphism-styled statements using externalized JSON templates for each language, allowing for professional legal phrasing and deep customization.
|
|
28
28
|
- **Multi-Company Metadata**: Easily customize statements via CLI flags or `.a11yrc` for scalable client generation.
|
|
29
|
+
- **Enriched JUnit XML**: Professional CI/CD reports including scan duration, page title, engine metadata, and **detailed failing node** snippets (Target + HTML).
|
|
29
30
|
- **Configurable Severity Threshold**: Fail CI only on critical/high issues (configurable).
|
|
30
|
-
- **Rich Metadata**: Includes scan duration, page title, language, and version info.
|
|
31
31
|
- **Pseudo-Automation**: Automatically generates Playwright/Puppeteer test scripts for manual verification steps.
|
|
32
32
|
- **PDF Reporting**: Generates beautiful, compliant PDF reports with severity-sorted violations, HTML error counts, and `@HolmDigital/engine` branding.
|
|
33
33
|
- **TypeScript**: Written in TypeScript with full type definitions included.
|
|
Binary file
|
|
@@ -474,9 +474,47 @@ function generateBadgeMarkdown(score) {
|
|
|
474
474
|
import React from "react";
|
|
475
475
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
476
476
|
import { AccessibilityStatement } from "@holmdigital/components";
|
|
477
|
+
import { ENFORCEMENT_BODIES } from "@holmdigital/standards";
|
|
477
478
|
import fs from "fs/promises";
|
|
478
479
|
import path from "path";
|
|
479
480
|
async function generateStatementContent(result, lang = "en", format = "html", metadata) {
|
|
481
|
+
let template;
|
|
482
|
+
const possibleTemplatePaths = [
|
|
483
|
+
path.join(__dirname, "templates", `${lang}.json`),
|
|
484
|
+
path.join(__dirname, "../src/reporting/templates", `${lang}.json`),
|
|
485
|
+
path.join(process.cwd(), "packages/engine/src/reporting/templates", `${lang}.json`)
|
|
486
|
+
];
|
|
487
|
+
let loaded = false;
|
|
488
|
+
for (const p of possibleTemplatePaths) {
|
|
489
|
+
try {
|
|
490
|
+
const data = await fs.readFile(p, "utf-8");
|
|
491
|
+
template = JSON.parse(data);
|
|
492
|
+
loaded = true;
|
|
493
|
+
break;
|
|
494
|
+
} catch (e) {
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (!loaded) {
|
|
499
|
+
const fallbackPaths = [
|
|
500
|
+
path.join(__dirname, "templates", "en.json"),
|
|
501
|
+
path.join(__dirname, "../src/reporting/templates", "en.json"),
|
|
502
|
+
path.join(process.cwd(), "packages/engine/src/reporting/templates", "en.json")
|
|
503
|
+
];
|
|
504
|
+
for (const p of fallbackPaths) {
|
|
505
|
+
try {
|
|
506
|
+
const data = await fs.readFile(p, "utf-8");
|
|
507
|
+
template = JSON.parse(data);
|
|
508
|
+
loaded = true;
|
|
509
|
+
break;
|
|
510
|
+
} catch (e) {
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (!loaded) {
|
|
516
|
+
throw new Error(`Accessibility statement templates missing. Looked in: ${possibleTemplatePaths.join(", ")}`);
|
|
517
|
+
}
|
|
480
518
|
let complianceLevel = "full";
|
|
481
519
|
if (result.stats.critical > 0 || result.legalSummary && result.legalSummary.eaaDeadlineViolations > 0) {
|
|
482
520
|
complianceLevel = "non-compliant";
|
|
@@ -561,41 +599,116 @@ async function generateStatementContent(result, lang = "en", format = "html", me
|
|
|
561
599
|
} else {
|
|
562
600
|
const dateStr = props.lastReviewDate.toISOString().split("T")[0];
|
|
563
601
|
const statusMap = {
|
|
564
|
-
"full": "
|
|
565
|
-
"partial": "
|
|
566
|
-
"non-compliant": "Non-
|
|
602
|
+
"full": lang === "sv" ? "Fullt ut f\xF6renlig" : lang === "no" || lang === "nb" ? "Helt i samsvar" : lang === "da" ? "Fuldt ud i overensstemmelse" : "Fully compliant",
|
|
603
|
+
"partial": lang === "sv" ? "Delvis f\xF6renlig" : lang === "no" || lang === "nb" ? "Delvis i samsvar" : lang === "da" ? "Delvist i overensstemmelse" : "Partially compliant",
|
|
604
|
+
"non-compliant": lang === "sv" ? "Inte f\xF6renlig" : lang === "no" || lang === "nb" ? "Ikke i samsvar" : lang === "da" ? "Ikke i overensstemmelse" : "Non-compliant"
|
|
567
605
|
};
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
606
|
+
const substitutions = {
|
|
607
|
+
statusString: statusMap[complianceLevel],
|
|
608
|
+
"{<webbplats>}": props.organizationName,
|
|
609
|
+
"{<website>}": props.organizationName,
|
|
610
|
+
"{<nettsted>}": props.organizationName,
|
|
611
|
+
"{<organisation>}": props.organizationName,
|
|
612
|
+
"{<organisasjon>}": props.organizationName,
|
|
613
|
+
"{<e-postadress>}": props.contactEmail || "",
|
|
614
|
+
"{<e-mailosoite>}": props.contactEmail || "",
|
|
615
|
+
"{<e-mailadresse>}": props.contactEmail || "",
|
|
616
|
+
"{<e-mail address>}": props.contactEmail || "",
|
|
617
|
+
"{<email address>}": props.contactEmail || "",
|
|
618
|
+
"{<telefonnummer>}": props.phoneNumber || "",
|
|
619
|
+
"{<puhelinnumero>}": props.phoneNumber || "",
|
|
620
|
+
"{<telefoonnummer>}": props.phoneNumber || "",
|
|
621
|
+
"{<telephone number>}": props.phoneNumber || "",
|
|
622
|
+
"{<svarstid>}": props.responseTime || "",
|
|
623
|
+
"{<svartid>}": props.responseTime || "",
|
|
624
|
+
"{<response time>}": props.responseTime || "",
|
|
625
|
+
"{<bed\xF6mningsdatum>}": dateStr,
|
|
626
|
+
"{<vurderingsdato>}": dateStr,
|
|
627
|
+
"{<arviointip\xE4iv\xE4>}": dateStr,
|
|
628
|
+
"{<beoordelingsdatum>}": dateStr,
|
|
629
|
+
"{<bewertungsdatum>}": dateStr,
|
|
630
|
+
"{<date_evaluation>}": dateStr,
|
|
631
|
+
"{<fecha_evaluacion>}": dateStr,
|
|
632
|
+
"{<assessment date>}": dateStr,
|
|
633
|
+
"{<uppdateringsdatum>}": dateStr,
|
|
634
|
+
"{<oppdateringsdato>}": dateStr,
|
|
635
|
+
"{<p\xE4ivitysp\xE4iv\xE4>}": dateStr,
|
|
636
|
+
"{<updatedatum>}": dateStr,
|
|
637
|
+
"{<aktualisierungsdatum>}": dateStr,
|
|
638
|
+
"{<date_mise_a_jour>}": dateStr,
|
|
639
|
+
"{<fecha_actualizacion>}": dateStr,
|
|
640
|
+
"{<update date>}": dateStr,
|
|
641
|
+
"{<publiceringsdatum>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
642
|
+
"{<publiseringsdato>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
643
|
+
"{<julkaisup\xE4iv\xE4>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
644
|
+
"{<publicatiedatum>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
645
|
+
"{<ver\xF6ffentlichungsdatum>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
646
|
+
"{<date_publication>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
647
|
+
"{<fecha_publicacion>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
648
|
+
"{<publish date>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
649
|
+
"{<metod>}": props.evaluationMethod || "Automated Scan",
|
|
650
|
+
"{<metodi>}": props.evaluationMethod || "Automated Scan",
|
|
651
|
+
"{<methode>}": props.evaluationMethod || "Automated Scan",
|
|
652
|
+
"{<m\xE9todo>}": props.evaluationMethod || "Automated Scan",
|
|
653
|
+
"{<method>}": props.evaluationMethod || "Automated Scan",
|
|
654
|
+
"{<extern akt\xF6r>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
655
|
+
"{<ekstern aktor>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
656
|
+
"{<ulkoinen taho>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
657
|
+
"{<externe partij>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
658
|
+
"{<externer Dritter>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
659
|
+
"{<tiers externe>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
660
|
+
"{<tercero externo>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
661
|
+
"{<third party>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
662
|
+
"{<enforcement_body>}": ENFORCEMENT_BODIES[country] || ENFORCEMENT_BODIES.EU,
|
|
663
|
+
"{<brister>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "sv" ? "Inga k\xE4nda brister." : "No known issues.",
|
|
664
|
+
"{<puutteet>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "fi" ? "Ei tiedossa olevia puutteita." : "No known issues.",
|
|
665
|
+
"{<gebreken>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "nl" ? "Geen bekende gebreken." : "No known issues.",
|
|
666
|
+
"{<m\xE4ngel>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "de" ? "Keine bekannten M\xE4ngel." : "No known issues.",
|
|
667
|
+
"{<d\xE9fauts>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "fr" ? "Aucun d\xE9faut connu." : "No known issues.",
|
|
668
|
+
"{<deficiencias>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "es" ? "No hay deficiencias conocidas." : "No known issues.",
|
|
669
|
+
"{<mangler>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "no" || lang === "da" ? "Ingen kendte mangler." : "No known issues.",
|
|
670
|
+
"{<issues>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : "No known issues."
|
|
671
|
+
};
|
|
672
|
+
const processText = (text) => {
|
|
673
|
+
let processed = text;
|
|
674
|
+
processed = processed.replace(/\[([\s\S]*?)\]/g, (_match, content2) => {
|
|
675
|
+
if (content2.includes("{<svarstid>}") || content2.includes("{<svartid>}") || content2.includes("{<response time>}")) {
|
|
676
|
+
return props.responseTime ? content2 : "";
|
|
677
|
+
}
|
|
678
|
+
if (content2.includes("{<telefonnummer>}") || content2.includes("{<telephone number>}")) {
|
|
679
|
+
return props.phoneNumber ? content2 : "";
|
|
680
|
+
}
|
|
681
|
+
if (content2.includes("{<brister>}") || content2.includes("{<mangler>}") || content2.includes("{<issues>}")) {
|
|
682
|
+
return complianceLevel !== "full" ? content2 : "";
|
|
683
|
+
}
|
|
684
|
+
return content2;
|
|
685
|
+
});
|
|
686
|
+
processed = processed.replace(/\{([^{}]*?)\}/g, (_match, content2) => {
|
|
687
|
+
const parts = content2.split("/");
|
|
688
|
+
if (parts.length >= 2) {
|
|
689
|
+
let idx = 0;
|
|
690
|
+
if (complianceLevel === "partial") idx = 1;
|
|
691
|
+
if (complianceLevel === "non-compliant") idx = parts.length > 2 ? 2 : 1;
|
|
692
|
+
return parts[idx].trim();
|
|
693
|
+
}
|
|
694
|
+
const key = `{${content2}}`;
|
|
695
|
+
return substitutions[key] !== void 0 ? substitutions[key] : _match;
|
|
696
|
+
});
|
|
697
|
+
return processed;
|
|
698
|
+
};
|
|
699
|
+
const title = processText(template.title);
|
|
700
|
+
const intro = processText(template.intro);
|
|
701
|
+
const sections = template.sections.map((s) => `## ${s.title}
|
|
590
702
|
|
|
591
|
-
|
|
703
|
+
${processText(s.content)}`).join("\n\n");
|
|
704
|
+
content = `# ${title}
|
|
592
705
|
|
|
593
|
-
|
|
706
|
+
${intro}
|
|
594
707
|
|
|
595
|
-
|
|
708
|
+
${sections}
|
|
596
709
|
|
|
597
|
-
|
|
598
|
-
`;
|
|
710
|
+
---
|
|
711
|
+
Generated using [${props.generatorTool?.name || "HolmDigital Engine"}](${props.generatorTool?.url || "https://holmdigital.se"})`;
|
|
599
712
|
}
|
|
600
713
|
return content;
|
|
601
714
|
}
|
|
Binary file
|
package/dist/cli/index.js
CHANGED
|
@@ -550,26 +550,57 @@ var junit_generator_exports = {};
|
|
|
550
550
|
__export(junit_generator_exports, {
|
|
551
551
|
generateJUnitXML: () => generateJUnitXML
|
|
552
552
|
});
|
|
553
|
-
function generateJUnitXML(
|
|
554
|
-
const
|
|
555
|
-
const
|
|
553
|
+
function generateJUnitXML(result) {
|
|
554
|
+
const { url, reports, stats, metadata } = result;
|
|
555
|
+
const duration = metadata.scanDuration;
|
|
556
|
+
const timestamp = result.timestamp;
|
|
557
|
+
const totalTests = stats.passed + stats.total;
|
|
558
|
+
const failures = stats.total;
|
|
559
|
+
const xmlLines = [
|
|
556
560
|
`<?xml version="1.0" encoding="UTF-8"?>`,
|
|
557
|
-
`<testsuites name="HolmDigital Accessibility Scan" time="${duration / 1e3}">`,
|
|
558
|
-
` <testsuite name="${url}" tests="${
|
|
561
|
+
`<testsuites name="HolmDigital Accessibility Scan" time="${duration / 1e3}" tests="${totalTests}" failures="${failures}">`,
|
|
562
|
+
` <testsuite name="${url}" tests="${totalTests}" failures="${failures}" errors="0" time="${duration / 1e3}" timestamp="${timestamp}">`,
|
|
563
|
+
` <properties>`,
|
|
564
|
+
` <property name="engineVersion" value="${metadata.engineVersion}"/>`,
|
|
565
|
+
` <property name="axeCoreVersion" value="${metadata.axeCoreVersion}"/>`,
|
|
566
|
+
` <property name="standardsVersion" value="${metadata.standardsVersion}"/>`,
|
|
567
|
+
` <property name="pageTitle" value="${escapeXML(metadata.pageTitle || "N/A")}"/>`,
|
|
568
|
+
` <property name="pageLanguage" value="${escapeXML(metadata.pageLanguage || "N/A")}"/>`,
|
|
569
|
+
` <property name="score" value="${result.score}"/>`,
|
|
570
|
+
` <property name="complianceStatus" value="${result.complianceStatus}"/>`,
|
|
571
|
+
` </properties>`
|
|
559
572
|
];
|
|
573
|
+
for (let i = 0; i < stats.passed; i++) {
|
|
574
|
+
xmlLines.push(` <testcase name="Passed Accessibility Rule ${i + 1}" classname="Accessibility.Success" time="0" />`);
|
|
575
|
+
}
|
|
560
576
|
reports.forEach((report) => {
|
|
561
577
|
const severity = report.holmdigitalInsight.diggRisk;
|
|
562
578
|
const message = escapeXML(report.holmdigitalInsight.reasoning);
|
|
563
|
-
const criteria =
|
|
564
|
-
const help =
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
${help}</failure>`);
|
|
568
|
-
|
|
579
|
+
const criteria = `WCAG ${report.wcagCriteria} | EN 301 549 ${report.en301549Criteria}`;
|
|
580
|
+
const help = `Ref: ${report.dosLagenReference}. Remediation: ${report.remediation.component || "Manual"}`;
|
|
581
|
+
xmlLines.push(` <testcase name="[${severity.toUpperCase()}] ${report.ruleId}" classname="${report.wcagCriteria}" time="0">`);
|
|
582
|
+
xmlLines.push(` <failure message="${message}" type="${severity}">${escapeXML(criteria)}
|
|
583
|
+
${escapeXML(help)}</failure>`);
|
|
584
|
+
if (report.failingNodes && report.failingNodes.length > 0) {
|
|
585
|
+
let nodeInfo = "Failing Nodes:\n";
|
|
586
|
+
report.failingNodes.forEach((node, idx) => {
|
|
587
|
+
nodeInfo += `
|
|
588
|
+
[Node ${idx + 1}]
|
|
589
|
+
`;
|
|
590
|
+
nodeInfo += `Target: ${node.target}
|
|
591
|
+
`;
|
|
592
|
+
nodeInfo += `HTML: ${node.html}
|
|
593
|
+
`;
|
|
594
|
+
nodeInfo += `Fix: ${node.failureSummary}
|
|
595
|
+
`;
|
|
596
|
+
});
|
|
597
|
+
xmlLines.push(` <system-out>${escapeXML(nodeInfo)}</system-out>`);
|
|
598
|
+
}
|
|
599
|
+
xmlLines.push(` </testcase>`);
|
|
569
600
|
});
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
return
|
|
601
|
+
xmlLines.push(` </testsuite>`);
|
|
602
|
+
xmlLines.push(`</testsuites>`);
|
|
603
|
+
return xmlLines.join("\n");
|
|
573
604
|
}
|
|
574
605
|
function escapeXML(unsafe) {
|
|
575
606
|
if (unsafe === void 0 || unsafe === null) return "";
|
|
@@ -1394,9 +1425,47 @@ async function generatePDF(htmlContent, outputPath) {
|
|
|
1394
1425
|
var import_react = __toESM(require("react"));
|
|
1395
1426
|
var import_server = require("react-dom/server");
|
|
1396
1427
|
var import_components = require("@holmdigital/components");
|
|
1428
|
+
var import_standards = require("@holmdigital/standards");
|
|
1397
1429
|
var import_promises = __toESM(require("fs/promises"));
|
|
1398
1430
|
var import_path = __toESM(require("path"));
|
|
1399
1431
|
async function generateStatementContent(result, lang = "en", format = "html", metadata) {
|
|
1432
|
+
let template;
|
|
1433
|
+
const possibleTemplatePaths = [
|
|
1434
|
+
import_path.default.join(__dirname, "templates", `${lang}.json`),
|
|
1435
|
+
import_path.default.join(__dirname, "../src/reporting/templates", `${lang}.json`),
|
|
1436
|
+
import_path.default.join(process.cwd(), "packages/engine/src/reporting/templates", `${lang}.json`)
|
|
1437
|
+
];
|
|
1438
|
+
let loaded = false;
|
|
1439
|
+
for (const p of possibleTemplatePaths) {
|
|
1440
|
+
try {
|
|
1441
|
+
const data = await import_promises.default.readFile(p, "utf-8");
|
|
1442
|
+
template = JSON.parse(data);
|
|
1443
|
+
loaded = true;
|
|
1444
|
+
break;
|
|
1445
|
+
} catch (e) {
|
|
1446
|
+
continue;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
if (!loaded) {
|
|
1450
|
+
const fallbackPaths = [
|
|
1451
|
+
import_path.default.join(__dirname, "templates", "en.json"),
|
|
1452
|
+
import_path.default.join(__dirname, "../src/reporting/templates", "en.json"),
|
|
1453
|
+
import_path.default.join(process.cwd(), "packages/engine/src/reporting/templates", "en.json")
|
|
1454
|
+
];
|
|
1455
|
+
for (const p of fallbackPaths) {
|
|
1456
|
+
try {
|
|
1457
|
+
const data = await import_promises.default.readFile(p, "utf-8");
|
|
1458
|
+
template = JSON.parse(data);
|
|
1459
|
+
loaded = true;
|
|
1460
|
+
break;
|
|
1461
|
+
} catch (e) {
|
|
1462
|
+
continue;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
if (!loaded) {
|
|
1467
|
+
throw new Error(`Accessibility statement templates missing. Looked in: ${possibleTemplatePaths.join(", ")}`);
|
|
1468
|
+
}
|
|
1400
1469
|
let complianceLevel = "full";
|
|
1401
1470
|
if (result.stats.critical > 0 || result.legalSummary && result.legalSummary.eaaDeadlineViolations > 0) {
|
|
1402
1471
|
complianceLevel = "non-compliant";
|
|
@@ -1481,41 +1550,116 @@ async function generateStatementContent(result, lang = "en", format = "html", me
|
|
|
1481
1550
|
} else {
|
|
1482
1551
|
const dateStr = props.lastReviewDate.toISOString().split("T")[0];
|
|
1483
1552
|
const statusMap = {
|
|
1484
|
-
"full": "
|
|
1485
|
-
"partial": "
|
|
1486
|
-
"non-compliant": "Non-
|
|
1553
|
+
"full": lang === "sv" ? "Fullt ut f\xF6renlig" : lang === "no" || lang === "nb" ? "Helt i samsvar" : lang === "da" ? "Fuldt ud i overensstemmelse" : "Fully compliant",
|
|
1554
|
+
"partial": lang === "sv" ? "Delvis f\xF6renlig" : lang === "no" || lang === "nb" ? "Delvis i samsvar" : lang === "da" ? "Delvist i overensstemmelse" : "Partially compliant",
|
|
1555
|
+
"non-compliant": lang === "sv" ? "Inte f\xF6renlig" : lang === "no" || lang === "nb" ? "Ikke i samsvar" : lang === "da" ? "Ikke i overensstemmelse" : "Non-compliant"
|
|
1487
1556
|
};
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1557
|
+
const substitutions = {
|
|
1558
|
+
statusString: statusMap[complianceLevel],
|
|
1559
|
+
"{<webbplats>}": props.organizationName,
|
|
1560
|
+
"{<website>}": props.organizationName,
|
|
1561
|
+
"{<nettsted>}": props.organizationName,
|
|
1562
|
+
"{<organisation>}": props.organizationName,
|
|
1563
|
+
"{<organisasjon>}": props.organizationName,
|
|
1564
|
+
"{<e-postadress>}": props.contactEmail || "",
|
|
1565
|
+
"{<e-mailosoite>}": props.contactEmail || "",
|
|
1566
|
+
"{<e-mailadresse>}": props.contactEmail || "",
|
|
1567
|
+
"{<e-mail address>}": props.contactEmail || "",
|
|
1568
|
+
"{<email address>}": props.contactEmail || "",
|
|
1569
|
+
"{<telefonnummer>}": props.phoneNumber || "",
|
|
1570
|
+
"{<puhelinnumero>}": props.phoneNumber || "",
|
|
1571
|
+
"{<telefoonnummer>}": props.phoneNumber || "",
|
|
1572
|
+
"{<telephone number>}": props.phoneNumber || "",
|
|
1573
|
+
"{<svarstid>}": props.responseTime || "",
|
|
1574
|
+
"{<svartid>}": props.responseTime || "",
|
|
1575
|
+
"{<response time>}": props.responseTime || "",
|
|
1576
|
+
"{<bed\xF6mningsdatum>}": dateStr,
|
|
1577
|
+
"{<vurderingsdato>}": dateStr,
|
|
1578
|
+
"{<arviointip\xE4iv\xE4>}": dateStr,
|
|
1579
|
+
"{<beoordelingsdatum>}": dateStr,
|
|
1580
|
+
"{<bewertungsdatum>}": dateStr,
|
|
1581
|
+
"{<date_evaluation>}": dateStr,
|
|
1582
|
+
"{<fecha_evaluacion>}": dateStr,
|
|
1583
|
+
"{<assessment date>}": dateStr,
|
|
1584
|
+
"{<uppdateringsdatum>}": dateStr,
|
|
1585
|
+
"{<oppdateringsdato>}": dateStr,
|
|
1586
|
+
"{<p\xE4ivitysp\xE4iv\xE4>}": dateStr,
|
|
1587
|
+
"{<updatedatum>}": dateStr,
|
|
1588
|
+
"{<aktualisierungsdatum>}": dateStr,
|
|
1589
|
+
"{<date_mise_a_jour>}": dateStr,
|
|
1590
|
+
"{<fecha_actualizacion>}": dateStr,
|
|
1591
|
+
"{<update date>}": dateStr,
|
|
1592
|
+
"{<publiceringsdatum>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1593
|
+
"{<publiseringsdato>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1594
|
+
"{<julkaisup\xE4iv\xE4>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1595
|
+
"{<publicatiedatum>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1596
|
+
"{<ver\xF6ffentlichungsdatum>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1597
|
+
"{<date_publication>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1598
|
+
"{<fecha_publicacion>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1599
|
+
"{<publish date>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1600
|
+
"{<metod>}": props.evaluationMethod || "Automated Scan",
|
|
1601
|
+
"{<metodi>}": props.evaluationMethod || "Automated Scan",
|
|
1602
|
+
"{<methode>}": props.evaluationMethod || "Automated Scan",
|
|
1603
|
+
"{<m\xE9todo>}": props.evaluationMethod || "Automated Scan",
|
|
1604
|
+
"{<method>}": props.evaluationMethod || "Automated Scan",
|
|
1605
|
+
"{<extern akt\xF6r>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1606
|
+
"{<ekstern aktor>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1607
|
+
"{<ulkoinen taho>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1608
|
+
"{<externe partij>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1609
|
+
"{<externer Dritter>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1610
|
+
"{<tiers externe>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1611
|
+
"{<tercero externo>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1612
|
+
"{<third party>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1613
|
+
"{<enforcement_body>}": import_standards.ENFORCEMENT_BODIES[country] || import_standards.ENFORCEMENT_BODIES.EU,
|
|
1614
|
+
"{<brister>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "sv" ? "Inga k\xE4nda brister." : "No known issues.",
|
|
1615
|
+
"{<puutteet>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "fi" ? "Ei tiedossa olevia puutteita." : "No known issues.",
|
|
1616
|
+
"{<gebreken>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "nl" ? "Geen bekende gebreken." : "No known issues.",
|
|
1617
|
+
"{<m\xE4ngel>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "de" ? "Keine bekannten M\xE4ngel." : "No known issues.",
|
|
1618
|
+
"{<d\xE9fauts>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "fr" ? "Aucun d\xE9faut connu." : "No known issues.",
|
|
1619
|
+
"{<deficiencias>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "es" ? "No hay deficiencias conocidas." : "No known issues.",
|
|
1620
|
+
"{<mangler>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "no" || lang === "da" ? "Ingen kendte mangler." : "No known issues.",
|
|
1621
|
+
"{<issues>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : "No known issues."
|
|
1622
|
+
};
|
|
1623
|
+
const processText = (text) => {
|
|
1624
|
+
let processed = text;
|
|
1625
|
+
processed = processed.replace(/\[([\s\S]*?)\]/g, (_match, content2) => {
|
|
1626
|
+
if (content2.includes("{<svarstid>}") || content2.includes("{<svartid>}") || content2.includes("{<response time>}")) {
|
|
1627
|
+
return props.responseTime ? content2 : "";
|
|
1628
|
+
}
|
|
1629
|
+
if (content2.includes("{<telefonnummer>}") || content2.includes("{<telephone number>}")) {
|
|
1630
|
+
return props.phoneNumber ? content2 : "";
|
|
1631
|
+
}
|
|
1632
|
+
if (content2.includes("{<brister>}") || content2.includes("{<mangler>}") || content2.includes("{<issues>}")) {
|
|
1633
|
+
return complianceLevel !== "full" ? content2 : "";
|
|
1634
|
+
}
|
|
1635
|
+
return content2;
|
|
1636
|
+
});
|
|
1637
|
+
processed = processed.replace(/\{([^{}]*?)\}/g, (_match, content2) => {
|
|
1638
|
+
const parts = content2.split("/");
|
|
1639
|
+
if (parts.length >= 2) {
|
|
1640
|
+
let idx = 0;
|
|
1641
|
+
if (complianceLevel === "partial") idx = 1;
|
|
1642
|
+
if (complianceLevel === "non-compliant") idx = parts.length > 2 ? 2 : 1;
|
|
1643
|
+
return parts[idx].trim();
|
|
1644
|
+
}
|
|
1645
|
+
const key = `{${content2}}`;
|
|
1646
|
+
return substitutions[key] !== void 0 ? substitutions[key] : _match;
|
|
1647
|
+
});
|
|
1648
|
+
return processed;
|
|
1649
|
+
};
|
|
1650
|
+
const title = processText(template.title);
|
|
1651
|
+
const intro = processText(template.intro);
|
|
1652
|
+
const sections = template.sections.map((s) => `## ${s.title}
|
|
1510
1653
|
|
|
1511
|
-
|
|
1654
|
+
${processText(s.content)}`).join("\n\n");
|
|
1655
|
+
content = `# ${title}
|
|
1512
1656
|
|
|
1513
|
-
|
|
1657
|
+
${intro}
|
|
1514
1658
|
|
|
1515
|
-
|
|
1659
|
+
${sections}
|
|
1516
1660
|
|
|
1517
|
-
|
|
1518
|
-
`;
|
|
1661
|
+
---
|
|
1662
|
+
Generated using [${props.generatorTool?.name || "HolmDigital Engine"}](${props.generatorTool?.url || "https://holmdigital.se"})`;
|
|
1519
1663
|
}
|
|
1520
1664
|
return content;
|
|
1521
1665
|
}
|
|
@@ -1819,7 +1963,7 @@ program.argument("<url>", "URL to scan").option("--lang <code>", "Language code
|
|
|
1819
1963
|
if (options.junit) {
|
|
1820
1964
|
const { generateJUnitXML: generateJUnitXML2 } = await Promise.resolve().then(() => (init_junit_generator(), junit_generator_exports));
|
|
1821
1965
|
const fs2 = await import("fs/promises");
|
|
1822
|
-
const xml = generateJUnitXML2(result
|
|
1966
|
+
const xml = generateJUnitXML2(result);
|
|
1823
1967
|
await fs2.writeFile(options.junit, xml);
|
|
1824
1968
|
if (spinner) spinner.succeed(t("cli.junit_saved", { path: options.junit }));
|
|
1825
1969
|
else if (!options.json) console.log(import_chalk.default.green(`\u2713 JUnit report saved to ${options.junit}`));
|
package/dist/cli/index.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
generateBadgeUrl,
|
|
7
7
|
generateStatement,
|
|
8
8
|
generateStatementContent
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-JEA4LJ7Q.mjs";
|
|
10
10
|
import {
|
|
11
11
|
getCurrentLang,
|
|
12
12
|
setLanguage,
|
|
@@ -621,9 +621,9 @@ program.argument("<url>", "URL to scan").option("--lang <code>", "Language code
|
|
|
621
621
|
generateGitHubActionsAnnotations(result.reports);
|
|
622
622
|
}
|
|
623
623
|
if (options.junit) {
|
|
624
|
-
const { generateJUnitXML } = await import("../junit-generator-
|
|
624
|
+
const { generateJUnitXML } = await import("../junit-generator-6ITTV4W5.mjs");
|
|
625
625
|
const fs = await import("fs/promises");
|
|
626
|
-
const xml = generateJUnitXML(result
|
|
626
|
+
const xml = generateJUnitXML(result);
|
|
627
627
|
await fs.writeFile(options.junit, xml);
|
|
628
628
|
if (spinner) spinner.succeed(t("cli.junit_saved", { path: options.junit }));
|
|
629
629
|
else if (!options.json) console.log(chalk.green(`\u2713 JUnit report saved to ${options.junit}`));
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Tilgængelighed for {<webbplats>}",
|
|
3
|
+
"intro": "{<organisation>} står bag dette websted. Vi ønsker, at så mange som muligt skal kunne bruge det. Dette dokument beskriver, hvordan {<webbplats>} overholder loven om webtilgængelighed, eventuelle kendte tilgængelighedsproblemer, og hvordan du kan rapportere mangler til os, så vi kan udbedre dem.",
|
|
4
|
+
"sections": [
|
|
5
|
+
{
|
|
6
|
+
"id": "how-accessible",
|
|
7
|
+
"title": "Hvor tilgængeligt er webstedet?",
|
|
8
|
+
"content": "{Vi har ingen kendte mangler i tilgængeligheden for dette websted./Vi er opmærksomme på, at dele af webstedet ikke er helt tilgængelige. Se afsnittet om indhold, der ikke er tilgængeligt nedenfor for mere information./Vi er opmærksomme på, at dele af webstedet ikke er helt tilgængelige. Se afsnittet om indhold, der ikke er tilgængeligt nedenfor for mer information.}"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "what-to-do",
|
|
12
|
+
"title": "Hvad kan du gøre, hvis du ikke kan bruge dele af webstedet?",
|
|
13
|
+
"content": "Hvis du har brug for indhold fra {<webbplats>}, som ikke er tilgængeligt for dig, men som er undtaget fra lovens område som beskrevet nedenfor, kan du meddele os det.\n\n[Svartiden er normalt {<svartid>}.]\n\n[Du kan også kontakte os på følgende måder:\n\n* send e-mail til {<e-mailadresse>}\n* ring {<telefonnummer>}]"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "reporting",
|
|
17
|
+
"title": "Rapporter mangler i webstedets tilgængelighed",
|
|
18
|
+
"content": "Vi stræber hele tiden efter at forbedre webstedets tilgængelighed. Hvis du opdager problemer, der ikke er beskrevet på denne side, eller hvis du mener, at vi ikke overholder lovens krav, skal du give os besked, så vi ved, at problemet eksisterer."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "enforcement",
|
|
22
|
+
"title": "Håndhævelsesprocedure",
|
|
23
|
+
"content": "{<enforcement_body>} har ansvaret for tilsyn med loven om webtilgængelighed. Du kan klage til {<enforcement_body>}, hvis du mener, at vores digitale service har mangler i tilgængeligheden.\n\nDu kan også klage til {<enforcement_body>}, hvis du mener, at vores vurdering af, hvad der er en uforholdsmæssig stor byrde, skal gennemgås, hvis du mener, at vores tilgængelighedserklæring har mangler, eller hvis du mener, at vi ikke har håndteret din anmodning om tilgængeliggørelse korrekt."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "technical",
|
|
27
|
+
"title": "Teknisk information om webstedets tilgængelighed",
|
|
28
|
+
"content": "{Dette websted er fuldt ud i overensstemmelse med loven om webtilgængelighed./Dette websted er delvist i overensstemmelse med loven om webtilgængelighed på grund af de mangler, der er beskrevet nedenfor./Dette websted er ikke i overensstemmelse med loven om webtilgængelighed. Utilgængelige dele er beskrevet nedenfor.}"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "non-accessible",
|
|
32
|
+
"title": "Indhold, der ikke er tilgængeligt",
|
|
33
|
+
"content": "Det indhold, der er beskrevet nedenfor, er på den ene eller anden måde ikke helt tilgængeligt.\n\n[\n### Manglende overholdelse af lovkravene\n{<mangler>}\n]"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "testing",
|
|
37
|
+
"title": "Hvordan vi har testet webstedet",
|
|
38
|
+
"content": "{Vi har foretaget en selvvurdering (intern test) af {<webbplats>}./{<ekstern aktør>} har foretaget en uafhængig gennemgang af {<webbplats>}./Vi har anslået tilgængeligheden uden test.}\n\nDen seneste vurdering blev foretaget den {<vurderingsdato>}.\n\n[Vurderingsmetode: {<metode>}]\n\nWebstedet blev offentliggjort den {<offentliggørelsesdato>}.\n\nErklæringen blev senest opdateret den {<opdateringsdato>}."
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Barrierefreiheitserklärung für {<webbplats>}",
|
|
3
|
+
"intro": "{<organisation>} ist bemüht, ihre Website im Einklang mit den nationalen Rechtsvorschriften zur Umsetzung der Richtlinie (EU) 2016/2102 des Europäischen Parlaments und des Rates barrierefrei zugänglich zu machen. Diese Erklärung zur Barrierefreiheit gilt für {<webbplats>}.",
|
|
4
|
+
"sections": [
|
|
5
|
+
{
|
|
6
|
+
"id": "how-accessible",
|
|
7
|
+
"title": "Stand der Vereinbarkeit mit den Anforderungen",
|
|
8
|
+
"content": "{Diese Website ist vollständig mit den Barrierefreiheitsanforderungen vereinbar./Diese Website ist wegen der folgenden Unvereinbarkeiten teilweise mit den Barrierefreiheitsanforderungen vereinbar./Diese Website ist nicht mit den Barrierefreiheitsanforderungen vereinbar. Die nicht barrierefreien Inhalte sind nachstehend aufgeführt.}"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "what-to-do",
|
|
12
|
+
"title": "Nicht barrierefreie Inhalte",
|
|
13
|
+
"content": "Die nachstehend aufgeführten Inhalte sind aus den folgenden Gründen nicht barrierefrei:\n\n[\n### Unvereinbarkeit mit den Barrierefreiheitsanforderungen\n{<mängel>}\n]\n\n[Unsere normale Antwortzeit beträgt {<svarstid>}.]\n\n[Sie können uns auch wie folgt kontaktieren:\n\n* E-Mail an {<e-mailadresse>}\n* Telefon {<telefonnummer>}]"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "reporting",
|
|
17
|
+
"title": "Feedback und Kontaktangaben",
|
|
18
|
+
"content": "Wir bemühen uns, die Barrierefreiheit unserer Website stetig zu verbessern. Wenn Sie Mängel bei der Einhaltung der Barrierefreiheitsanforderungen feststellen oder Informationen über von der Richtlinie ausgenommene Inhalte benötigen, können Sie uns kontaktieren."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "enforcement",
|
|
22
|
+
"title": "Durchsetzungsverfahren",
|
|
23
|
+
"content": "Die {<enforcement_body>} ist für die Überwachung der Barrierefreiheitsanforderungen zuständig. Wenn Sie auf unsere Benachrichtigungen oder Anfragen keine zufriedenstellende Antwort erhalten haben, können Sie sich an {<enforcement_body>} wenden.\n\nSie können auch eine Beschwerde bei {<enforcement_body>} einreichen, wenn Sie der Meinung sind, dass unsere Einschätzung einer unverhältnismäßigen Belastung überprüft werden sollte, wenn Sie Mängel in unserer Barrierefreiheitserklärung feststellen oder wenn Sie der Meinung sind, dass wir Ihren Antrag auf barrierefreie Bereitstellung von Inhalten nicht korrekt bearbeitet haben."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "technical",
|
|
27
|
+
"title": "Technische Informationen zur Barrierefreiheit der Website",
|
|
28
|
+
"content": "{Diese Website ist vollständig mit den Barrierefreiheitsanforderungen vereinbar./Diese Website ist wegen der folgenden Unvereinbarkeiten teilweise mit den Barrierefreiheitsanforderungen vereinbar./Diese Website ist nicht mit den Barrierefreiheitsanforderungen vereinbar.}"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "testing",
|
|
32
|
+
"title": "Erstellung dieser Erklärung zur Barrierefreiheit",
|
|
33
|
+
"content": "{Wir haben eine Selbstbewertung (interner Test) der Website {<webbplats>} durchgeführt./Ein {<externer Dritter>} hat eine unabhängige Prüfung der Website {<webbplats>} durchgeführt./Wir haben die Barrierefreiheit ohne Prüfung geschätzt.}\n\nDie letzte Bewertung wurde am {<bewertungsdatum>} vorgenommen.\n\n[Bewertungsmethode: {<methode>}]\n\nDie Website wurde am {<veröffentlichungsdatum>} veröffentlicht.\n\nDie Erklärung wurde zuletzt am {<aktualisierungsdatum>} aktualisiert."
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|