@holmdigital/engine 2.0.0 → 2.1.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/README.md +3 -3
- package/dist/{chunk-ZO2XNHHT.mjs → chunk-F6MWN7DX.mjs} +122 -30
- package/dist/cli/index.js +171 -46
- package/dist/cli/index.mjs +3 -3
- package/dist/index.js +122 -30
- package/dist/index.mjs +1 -1
- package/dist/junit-generator-6ITTV4W5.mjs +76 -0
- package/package.json +5 -3
- package/dist/junit-generator-FK3GE5E4.mjs +0 -43
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.
|
|
@@ -298,6 +298,7 @@ var RegulatoryScanner = class {
|
|
|
298
298
|
holmdigitalInsight: {
|
|
299
299
|
diggRisk: "medium",
|
|
300
300
|
eaaImpact: "medium",
|
|
301
|
+
reasoning: violation.help,
|
|
301
302
|
swedishInterpretation: violation.help,
|
|
302
303
|
priorityRationale: "Detta fel uppt\xE4cktes av scannern men saknar specifik mappning i HolmDigital-databasen."
|
|
303
304
|
},
|
|
@@ -473,9 +474,25 @@ function generateBadgeMarkdown(score) {
|
|
|
473
474
|
import React from "react";
|
|
474
475
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
475
476
|
import { AccessibilityStatement } from "@holmdigital/components";
|
|
477
|
+
import { ENFORCEMENT_BODIES } from "@holmdigital/standards";
|
|
476
478
|
import fs from "fs/promises";
|
|
477
479
|
import path from "path";
|
|
478
480
|
async function generateStatementContent(result, lang = "en", format = "html", metadata) {
|
|
481
|
+
let template;
|
|
482
|
+
try {
|
|
483
|
+
const templatePath = path.join(__dirname, "templates", `${lang}.json`);
|
|
484
|
+
const templateData = await fs.readFile(templatePath, "utf-8");
|
|
485
|
+
template = JSON.parse(templateData);
|
|
486
|
+
} catch (e) {
|
|
487
|
+
try {
|
|
488
|
+
const fallbackPath = path.join(__dirname, "templates", "en.json");
|
|
489
|
+
const templateData = await fs.readFile(fallbackPath, "utf-8");
|
|
490
|
+
template = JSON.parse(templateData);
|
|
491
|
+
} catch (err) {
|
|
492
|
+
console.error("Could not load accessibility statement templates", err);
|
|
493
|
+
throw new Error("Accessibility statement templates missing");
|
|
494
|
+
}
|
|
495
|
+
}
|
|
479
496
|
let complianceLevel = "full";
|
|
480
497
|
if (result.stats.critical > 0 || result.legalSummary && result.legalSummary.eaaDeadlineViolations > 0) {
|
|
481
498
|
complianceLevel = "non-compliant";
|
|
@@ -560,41 +577,116 @@ async function generateStatementContent(result, lang = "en", format = "html", me
|
|
|
560
577
|
} else {
|
|
561
578
|
const dateStr = props.lastReviewDate.toISOString().split("T")[0];
|
|
562
579
|
const statusMap = {
|
|
563
|
-
"full": "
|
|
564
|
-
"partial": "
|
|
565
|
-
"non-compliant": "Non-
|
|
580
|
+
"full": lang === "sv" ? "Fullt ut f\xF6renlig" : lang === "no" || lang === "nb" ? "Helt i samsvar" : lang === "da" ? "Fuldt ud i overensstemmelse" : "Fully compliant",
|
|
581
|
+
"partial": lang === "sv" ? "Delvis f\xF6renlig" : lang === "no" || lang === "nb" ? "Delvis i samsvar" : lang === "da" ? "Delvist i overensstemmelse" : "Partially compliant",
|
|
582
|
+
"non-compliant": lang === "sv" ? "Inte f\xF6renlig" : lang === "no" || lang === "nb" ? "Ikke i samsvar" : lang === "da" ? "Ikke i overensstemmelse" : "Non-compliant"
|
|
566
583
|
};
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
584
|
+
const substitutions = {
|
|
585
|
+
statusString: statusMap[complianceLevel],
|
|
586
|
+
"{<webbplats>}": props.organizationName,
|
|
587
|
+
"{<website>}": props.organizationName,
|
|
588
|
+
"{<nettsted>}": props.organizationName,
|
|
589
|
+
"{<organisation>}": props.organizationName,
|
|
590
|
+
"{<organisasjon>}": props.organizationName,
|
|
591
|
+
"{<e-postadress>}": props.contactEmail || "",
|
|
592
|
+
"{<e-mailosoite>}": props.contactEmail || "",
|
|
593
|
+
"{<e-mailadresse>}": props.contactEmail || "",
|
|
594
|
+
"{<e-mail address>}": props.contactEmail || "",
|
|
595
|
+
"{<email address>}": props.contactEmail || "",
|
|
596
|
+
"{<telefonnummer>}": props.phoneNumber || "",
|
|
597
|
+
"{<puhelinnumero>}": props.phoneNumber || "",
|
|
598
|
+
"{<telefoonnummer>}": props.phoneNumber || "",
|
|
599
|
+
"{<telephone number>}": props.phoneNumber || "",
|
|
600
|
+
"{<svarstid>}": props.responseTime || "",
|
|
601
|
+
"{<svartid>}": props.responseTime || "",
|
|
602
|
+
"{<response time>}": props.responseTime || "",
|
|
603
|
+
"{<bed\xF6mningsdatum>}": dateStr,
|
|
604
|
+
"{<vurderingsdato>}": dateStr,
|
|
605
|
+
"{<arviointip\xE4iv\xE4>}": dateStr,
|
|
606
|
+
"{<beoordelingsdatum>}": dateStr,
|
|
607
|
+
"{<bewertungsdatum>}": dateStr,
|
|
608
|
+
"{<date_evaluation>}": dateStr,
|
|
609
|
+
"{<fecha_evaluacion>}": dateStr,
|
|
610
|
+
"{<assessment date>}": dateStr,
|
|
611
|
+
"{<uppdateringsdatum>}": dateStr,
|
|
612
|
+
"{<oppdateringsdato>}": dateStr,
|
|
613
|
+
"{<p\xE4ivitysp\xE4iv\xE4>}": dateStr,
|
|
614
|
+
"{<updatedatum>}": dateStr,
|
|
615
|
+
"{<aktualisierungsdatum>}": dateStr,
|
|
616
|
+
"{<date_mise_a_jour>}": dateStr,
|
|
617
|
+
"{<fecha_actualizacion>}": dateStr,
|
|
618
|
+
"{<update date>}": dateStr,
|
|
619
|
+
"{<publiceringsdatum>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
620
|
+
"{<publiseringsdato>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
621
|
+
"{<julkaisup\xE4iv\xE4>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
622
|
+
"{<publicatiedatum>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
623
|
+
"{<ver\xF6ffentlichungsdatum>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
624
|
+
"{<date_publication>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
625
|
+
"{<fecha_publicacion>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
626
|
+
"{<publish date>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
627
|
+
"{<metod>}": props.evaluationMethod || "Automated Scan",
|
|
628
|
+
"{<metodi>}": props.evaluationMethod || "Automated Scan",
|
|
629
|
+
"{<methode>}": props.evaluationMethod || "Automated Scan",
|
|
630
|
+
"{<m\xE9todo>}": props.evaluationMethod || "Automated Scan",
|
|
631
|
+
"{<method>}": props.evaluationMethod || "Automated Scan",
|
|
632
|
+
"{<extern akt\xF6r>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
633
|
+
"{<ekstern aktor>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
634
|
+
"{<ulkoinen taho>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
635
|
+
"{<externe partij>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
636
|
+
"{<externer Dritter>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
637
|
+
"{<tiers externe>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
638
|
+
"{<tercero externo>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
639
|
+
"{<third party>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
640
|
+
"{<enforcement_body>}": ENFORCEMENT_BODIES[country] || ENFORCEMENT_BODIES.EU,
|
|
641
|
+
"{<brister>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "sv" ? "Inga k\xE4nda brister." : "No known issues.",
|
|
642
|
+
"{<puutteet>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "fi" ? "Ei tiedossa olevia puutteita." : "No known issues.",
|
|
643
|
+
"{<gebreken>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "nl" ? "Geen bekende gebreken." : "No known issues.",
|
|
644
|
+
"{<m\xE4ngel>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "de" ? "Keine bekannten M\xE4ngel." : "No known issues.",
|
|
645
|
+
"{<d\xE9fauts>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "fr" ? "Aucun d\xE9faut connu." : "No known issues.",
|
|
646
|
+
"{<deficiencias>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "es" ? "No hay deficiencias conocidas." : "No known issues.",
|
|
647
|
+
"{<mangler>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "no" || lang === "da" ? "Ingen kendte mangler." : "No known issues.",
|
|
648
|
+
"{<issues>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : "No known issues."
|
|
649
|
+
};
|
|
650
|
+
const processText = (text) => {
|
|
651
|
+
let processed = text;
|
|
652
|
+
processed = processed.replace(/\[([\s\S]*?)\]/g, (_match, content2) => {
|
|
653
|
+
if (content2.includes("{<svarstid>}") || content2.includes("{<svartid>}") || content2.includes("{<response time>}")) {
|
|
654
|
+
return props.responseTime ? content2 : "";
|
|
655
|
+
}
|
|
656
|
+
if (content2.includes("{<telefonnummer>}") || content2.includes("{<telephone number>}")) {
|
|
657
|
+
return props.phoneNumber ? content2 : "";
|
|
658
|
+
}
|
|
659
|
+
if (content2.includes("{<brister>}") || content2.includes("{<mangler>}") || content2.includes("{<issues>}")) {
|
|
660
|
+
return complianceLevel !== "full" ? content2 : "";
|
|
661
|
+
}
|
|
662
|
+
return content2;
|
|
663
|
+
});
|
|
664
|
+
processed = processed.replace(/\{([^{}]*?)\}/g, (_match, content2) => {
|
|
665
|
+
const parts = content2.split("/");
|
|
666
|
+
if (parts.length >= 2) {
|
|
667
|
+
let idx = 0;
|
|
668
|
+
if (complianceLevel === "partial") idx = 1;
|
|
669
|
+
if (complianceLevel === "non-compliant") idx = parts.length > 2 ? 2 : 1;
|
|
670
|
+
return parts[idx].trim();
|
|
671
|
+
}
|
|
672
|
+
const key = `{${content2}}`;
|
|
673
|
+
return substitutions[key] !== void 0 ? substitutions[key] : _match;
|
|
674
|
+
});
|
|
675
|
+
return processed;
|
|
676
|
+
};
|
|
677
|
+
const title = processText(template.title);
|
|
678
|
+
const intro = processText(template.intro);
|
|
679
|
+
const sections = template.sections.map((s) => `## ${s.title}
|
|
589
680
|
|
|
590
|
-
|
|
681
|
+
${processText(s.content)}`).join("\n\n");
|
|
682
|
+
content = `# ${title}
|
|
591
683
|
|
|
592
|
-
|
|
684
|
+
${intro}
|
|
593
685
|
|
|
594
|
-
|
|
686
|
+
${sections}
|
|
595
687
|
|
|
596
|
-
|
|
597
|
-
`;
|
|
688
|
+
---
|
|
689
|
+
Generated using [${props.generatorTool?.name || "HolmDigital Engine"}](${props.generatorTool?.url || "https://holmdigital.se"})`;
|
|
598
690
|
}
|
|
599
691
|
return content;
|
|
600
692
|
}
|
package/dist/cli/index.js
CHANGED
|
@@ -550,29 +550,62 @@ 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 "";
|
|
607
|
+
const str = String(unsafe);
|
|
608
|
+
return str.replace(/[<>&'"]/g, (c) => {
|
|
576
609
|
switch (c) {
|
|
577
610
|
case "<":
|
|
578
611
|
return "<";
|
|
@@ -900,6 +933,7 @@ var RegulatoryScanner = class {
|
|
|
900
933
|
holmdigitalInsight: {
|
|
901
934
|
diggRisk: "medium",
|
|
902
935
|
eaaImpact: "medium",
|
|
936
|
+
reasoning: violation.help,
|
|
903
937
|
swedishInterpretation: violation.help,
|
|
904
938
|
priorityRationale: "Detta fel uppt\xE4cktes av scannern men saknar specifik mappning i HolmDigital-databasen."
|
|
905
939
|
},
|
|
@@ -1391,9 +1425,25 @@ async function generatePDF(htmlContent, outputPath) {
|
|
|
1391
1425
|
var import_react = __toESM(require("react"));
|
|
1392
1426
|
var import_server = require("react-dom/server");
|
|
1393
1427
|
var import_components = require("@holmdigital/components");
|
|
1428
|
+
var import_standards = require("@holmdigital/standards");
|
|
1394
1429
|
var import_promises = __toESM(require("fs/promises"));
|
|
1395
1430
|
var import_path = __toESM(require("path"));
|
|
1396
1431
|
async function generateStatementContent(result, lang = "en", format = "html", metadata) {
|
|
1432
|
+
let template;
|
|
1433
|
+
try {
|
|
1434
|
+
const templatePath = import_path.default.join(__dirname, "templates", `${lang}.json`);
|
|
1435
|
+
const templateData = await import_promises.default.readFile(templatePath, "utf-8");
|
|
1436
|
+
template = JSON.parse(templateData);
|
|
1437
|
+
} catch (e) {
|
|
1438
|
+
try {
|
|
1439
|
+
const fallbackPath = import_path.default.join(__dirname, "templates", "en.json");
|
|
1440
|
+
const templateData = await import_promises.default.readFile(fallbackPath, "utf-8");
|
|
1441
|
+
template = JSON.parse(templateData);
|
|
1442
|
+
} catch (err) {
|
|
1443
|
+
console.error("Could not load accessibility statement templates", err);
|
|
1444
|
+
throw new Error("Accessibility statement templates missing");
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1397
1447
|
let complianceLevel = "full";
|
|
1398
1448
|
if (result.stats.critical > 0 || result.legalSummary && result.legalSummary.eaaDeadlineViolations > 0) {
|
|
1399
1449
|
complianceLevel = "non-compliant";
|
|
@@ -1478,41 +1528,116 @@ async function generateStatementContent(result, lang = "en", format = "html", me
|
|
|
1478
1528
|
} else {
|
|
1479
1529
|
const dateStr = props.lastReviewDate.toISOString().split("T")[0];
|
|
1480
1530
|
const statusMap = {
|
|
1481
|
-
"full": "
|
|
1482
|
-
"partial": "
|
|
1483
|
-
"non-compliant": "Non-
|
|
1531
|
+
"full": lang === "sv" ? "Fullt ut f\xF6renlig" : lang === "no" || lang === "nb" ? "Helt i samsvar" : lang === "da" ? "Fuldt ud i overensstemmelse" : "Fully compliant",
|
|
1532
|
+
"partial": lang === "sv" ? "Delvis f\xF6renlig" : lang === "no" || lang === "nb" ? "Delvis i samsvar" : lang === "da" ? "Delvist i overensstemmelse" : "Partially compliant",
|
|
1533
|
+
"non-compliant": lang === "sv" ? "Inte f\xF6renlig" : lang === "no" || lang === "nb" ? "Ikke i samsvar" : lang === "da" ? "Ikke i overensstemmelse" : "Non-compliant"
|
|
1484
1534
|
};
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1535
|
+
const substitutions = {
|
|
1536
|
+
statusString: statusMap[complianceLevel],
|
|
1537
|
+
"{<webbplats>}": props.organizationName,
|
|
1538
|
+
"{<website>}": props.organizationName,
|
|
1539
|
+
"{<nettsted>}": props.organizationName,
|
|
1540
|
+
"{<organisation>}": props.organizationName,
|
|
1541
|
+
"{<organisasjon>}": props.organizationName,
|
|
1542
|
+
"{<e-postadress>}": props.contactEmail || "",
|
|
1543
|
+
"{<e-mailosoite>}": props.contactEmail || "",
|
|
1544
|
+
"{<e-mailadresse>}": props.contactEmail || "",
|
|
1545
|
+
"{<e-mail address>}": props.contactEmail || "",
|
|
1546
|
+
"{<email address>}": props.contactEmail || "",
|
|
1547
|
+
"{<telefonnummer>}": props.phoneNumber || "",
|
|
1548
|
+
"{<puhelinnumero>}": props.phoneNumber || "",
|
|
1549
|
+
"{<telefoonnummer>}": props.phoneNumber || "",
|
|
1550
|
+
"{<telephone number>}": props.phoneNumber || "",
|
|
1551
|
+
"{<svarstid>}": props.responseTime || "",
|
|
1552
|
+
"{<svartid>}": props.responseTime || "",
|
|
1553
|
+
"{<response time>}": props.responseTime || "",
|
|
1554
|
+
"{<bed\xF6mningsdatum>}": dateStr,
|
|
1555
|
+
"{<vurderingsdato>}": dateStr,
|
|
1556
|
+
"{<arviointip\xE4iv\xE4>}": dateStr,
|
|
1557
|
+
"{<beoordelingsdatum>}": dateStr,
|
|
1558
|
+
"{<bewertungsdatum>}": dateStr,
|
|
1559
|
+
"{<date_evaluation>}": dateStr,
|
|
1560
|
+
"{<fecha_evaluacion>}": dateStr,
|
|
1561
|
+
"{<assessment date>}": dateStr,
|
|
1562
|
+
"{<uppdateringsdatum>}": dateStr,
|
|
1563
|
+
"{<oppdateringsdato>}": dateStr,
|
|
1564
|
+
"{<p\xE4ivitysp\xE4iv\xE4>}": dateStr,
|
|
1565
|
+
"{<updatedatum>}": dateStr,
|
|
1566
|
+
"{<aktualisierungsdatum>}": dateStr,
|
|
1567
|
+
"{<date_mise_a_jour>}": dateStr,
|
|
1568
|
+
"{<fecha_actualizacion>}": dateStr,
|
|
1569
|
+
"{<update date>}": dateStr,
|
|
1570
|
+
"{<publiceringsdatum>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1571
|
+
"{<publiseringsdato>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1572
|
+
"{<julkaisup\xE4iv\xE4>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1573
|
+
"{<publicatiedatum>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1574
|
+
"{<ver\xF6ffentlichungsdatum>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1575
|
+
"{<date_publication>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1576
|
+
"{<fecha_publicacion>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1577
|
+
"{<publish date>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1578
|
+
"{<metod>}": props.evaluationMethod || "Automated Scan",
|
|
1579
|
+
"{<metodi>}": props.evaluationMethod || "Automated Scan",
|
|
1580
|
+
"{<methode>}": props.evaluationMethod || "Automated Scan",
|
|
1581
|
+
"{<m\xE9todo>}": props.evaluationMethod || "Automated Scan",
|
|
1582
|
+
"{<method>}": props.evaluationMethod || "Automated Scan",
|
|
1583
|
+
"{<extern akt\xF6r>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1584
|
+
"{<ekstern aktor>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1585
|
+
"{<ulkoinen taho>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1586
|
+
"{<externe partij>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1587
|
+
"{<externer Dritter>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1588
|
+
"{<tiers externe>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1589
|
+
"{<tercero externo>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1590
|
+
"{<third party>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1591
|
+
"{<enforcement_body>}": import_standards.ENFORCEMENT_BODIES[country] || import_standards.ENFORCEMENT_BODIES.EU,
|
|
1592
|
+
"{<brister>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "sv" ? "Inga k\xE4nda brister." : "No known issues.",
|
|
1593
|
+
"{<puutteet>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "fi" ? "Ei tiedossa olevia puutteita." : "No known issues.",
|
|
1594
|
+
"{<gebreken>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "nl" ? "Geen bekende gebreken." : "No known issues.",
|
|
1595
|
+
"{<m\xE4ngel>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "de" ? "Keine bekannten M\xE4ngel." : "No known issues.",
|
|
1596
|
+
"{<d\xE9fauts>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "fr" ? "Aucun d\xE9faut connu." : "No known issues.",
|
|
1597
|
+
"{<deficiencias>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "es" ? "No hay deficiencias conocidas." : "No known issues.",
|
|
1598
|
+
"{<mangler>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "no" || lang === "da" ? "Ingen kendte mangler." : "No known issues.",
|
|
1599
|
+
"{<issues>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : "No known issues."
|
|
1600
|
+
};
|
|
1601
|
+
const processText = (text) => {
|
|
1602
|
+
let processed = text;
|
|
1603
|
+
processed = processed.replace(/\[([\s\S]*?)\]/g, (_match, content2) => {
|
|
1604
|
+
if (content2.includes("{<svarstid>}") || content2.includes("{<svartid>}") || content2.includes("{<response time>}")) {
|
|
1605
|
+
return props.responseTime ? content2 : "";
|
|
1606
|
+
}
|
|
1607
|
+
if (content2.includes("{<telefonnummer>}") || content2.includes("{<telephone number>}")) {
|
|
1608
|
+
return props.phoneNumber ? content2 : "";
|
|
1609
|
+
}
|
|
1610
|
+
if (content2.includes("{<brister>}") || content2.includes("{<mangler>}") || content2.includes("{<issues>}")) {
|
|
1611
|
+
return complianceLevel !== "full" ? content2 : "";
|
|
1612
|
+
}
|
|
1613
|
+
return content2;
|
|
1614
|
+
});
|
|
1615
|
+
processed = processed.replace(/\{([^{}]*?)\}/g, (_match, content2) => {
|
|
1616
|
+
const parts = content2.split("/");
|
|
1617
|
+
if (parts.length >= 2) {
|
|
1618
|
+
let idx = 0;
|
|
1619
|
+
if (complianceLevel === "partial") idx = 1;
|
|
1620
|
+
if (complianceLevel === "non-compliant") idx = parts.length > 2 ? 2 : 1;
|
|
1621
|
+
return parts[idx].trim();
|
|
1622
|
+
}
|
|
1623
|
+
const key = `{${content2}}`;
|
|
1624
|
+
return substitutions[key] !== void 0 ? substitutions[key] : _match;
|
|
1625
|
+
});
|
|
1626
|
+
return processed;
|
|
1627
|
+
};
|
|
1628
|
+
const title = processText(template.title);
|
|
1629
|
+
const intro = processText(template.intro);
|
|
1630
|
+
const sections = template.sections.map((s) => `## ${s.title}
|
|
1507
1631
|
|
|
1508
|
-
|
|
1632
|
+
${processText(s.content)}`).join("\n\n");
|
|
1633
|
+
content = `# ${title}
|
|
1509
1634
|
|
|
1510
|
-
|
|
1635
|
+
${intro}
|
|
1511
1636
|
|
|
1512
|
-
|
|
1637
|
+
${sections}
|
|
1513
1638
|
|
|
1514
|
-
|
|
1515
|
-
`;
|
|
1639
|
+
---
|
|
1640
|
+
Generated using [${props.generatorTool?.name || "HolmDigital Engine"}](${props.generatorTool?.url || "https://holmdigital.se"})`;
|
|
1516
1641
|
}
|
|
1517
1642
|
return content;
|
|
1518
1643
|
}
|
|
@@ -1816,7 +1941,7 @@ program.argument("<url>", "URL to scan").option("--lang <code>", "Language code
|
|
|
1816
1941
|
if (options.junit) {
|
|
1817
1942
|
const { generateJUnitXML: generateJUnitXML2 } = await Promise.resolve().then(() => (init_junit_generator(), junit_generator_exports));
|
|
1818
1943
|
const fs2 = await import("fs/promises");
|
|
1819
|
-
const xml = generateJUnitXML2(result
|
|
1944
|
+
const xml = generateJUnitXML2(result);
|
|
1820
1945
|
await fs2.writeFile(options.junit, xml);
|
|
1821
1946
|
if (spinner) spinner.succeed(t("cli.junit_saved", { path: options.junit }));
|
|
1822
1947
|
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-F6MWN7DX.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}`));
|
package/dist/index.js
CHANGED
|
@@ -828,6 +828,7 @@ var RegulatoryScanner = class {
|
|
|
828
828
|
holmdigitalInsight: {
|
|
829
829
|
diggRisk: "medium",
|
|
830
830
|
eaaImpact: "medium",
|
|
831
|
+
reasoning: violation.help,
|
|
831
832
|
swedishInterpretation: violation.help,
|
|
832
833
|
priorityRationale: "Detta fel uppt\xE4cktes av scannern men saknar specifik mappning i HolmDigital-databasen."
|
|
833
834
|
},
|
|
@@ -989,6 +990,7 @@ init_i18n();
|
|
|
989
990
|
var import_react = __toESM(require("react"));
|
|
990
991
|
var import_server = require("react-dom/server");
|
|
991
992
|
var import_components = require("@holmdigital/components");
|
|
993
|
+
var import_standards = require("@holmdigital/standards");
|
|
992
994
|
var import_promises = __toESM(require("fs/promises"));
|
|
993
995
|
var import_path = __toESM(require("path"));
|
|
994
996
|
|
|
@@ -1004,6 +1006,21 @@ function generateBadgeUrl(score) {
|
|
|
1004
1006
|
|
|
1005
1007
|
// src/reporting/statement-generator.ts
|
|
1006
1008
|
async function generateStatementContent(result, lang = "en", format = "html", metadata) {
|
|
1009
|
+
let template;
|
|
1010
|
+
try {
|
|
1011
|
+
const templatePath = import_path.default.join(__dirname, "templates", `${lang}.json`);
|
|
1012
|
+
const templateData = await import_promises.default.readFile(templatePath, "utf-8");
|
|
1013
|
+
template = JSON.parse(templateData);
|
|
1014
|
+
} catch (e) {
|
|
1015
|
+
try {
|
|
1016
|
+
const fallbackPath = import_path.default.join(__dirname, "templates", "en.json");
|
|
1017
|
+
const templateData = await import_promises.default.readFile(fallbackPath, "utf-8");
|
|
1018
|
+
template = JSON.parse(templateData);
|
|
1019
|
+
} catch (err) {
|
|
1020
|
+
console.error("Could not load accessibility statement templates", err);
|
|
1021
|
+
throw new Error("Accessibility statement templates missing");
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1007
1024
|
let complianceLevel = "full";
|
|
1008
1025
|
if (result.stats.critical > 0 || result.legalSummary && result.legalSummary.eaaDeadlineViolations > 0) {
|
|
1009
1026
|
complianceLevel = "non-compliant";
|
|
@@ -1088,41 +1105,116 @@ async function generateStatementContent(result, lang = "en", format = "html", me
|
|
|
1088
1105
|
} else {
|
|
1089
1106
|
const dateStr = props.lastReviewDate.toISOString().split("T")[0];
|
|
1090
1107
|
const statusMap = {
|
|
1091
|
-
"full": "
|
|
1092
|
-
"partial": "
|
|
1093
|
-
"non-compliant": "Non-
|
|
1108
|
+
"full": lang === "sv" ? "Fullt ut f\xF6renlig" : lang === "no" || lang === "nb" ? "Helt i samsvar" : lang === "da" ? "Fuldt ud i overensstemmelse" : "Fully compliant",
|
|
1109
|
+
"partial": lang === "sv" ? "Delvis f\xF6renlig" : lang === "no" || lang === "nb" ? "Delvis i samsvar" : lang === "da" ? "Delvist i overensstemmelse" : "Partially compliant",
|
|
1110
|
+
"non-compliant": lang === "sv" ? "Inte f\xF6renlig" : lang === "no" || lang === "nb" ? "Ikke i samsvar" : lang === "da" ? "Ikke i overensstemmelse" : "Non-compliant"
|
|
1094
1111
|
};
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1112
|
+
const substitutions = {
|
|
1113
|
+
statusString: statusMap[complianceLevel],
|
|
1114
|
+
"{<webbplats>}": props.organizationName,
|
|
1115
|
+
"{<website>}": props.organizationName,
|
|
1116
|
+
"{<nettsted>}": props.organizationName,
|
|
1117
|
+
"{<organisation>}": props.organizationName,
|
|
1118
|
+
"{<organisasjon>}": props.organizationName,
|
|
1119
|
+
"{<e-postadress>}": props.contactEmail || "",
|
|
1120
|
+
"{<e-mailosoite>}": props.contactEmail || "",
|
|
1121
|
+
"{<e-mailadresse>}": props.contactEmail || "",
|
|
1122
|
+
"{<e-mail address>}": props.contactEmail || "",
|
|
1123
|
+
"{<email address>}": props.contactEmail || "",
|
|
1124
|
+
"{<telefonnummer>}": props.phoneNumber || "",
|
|
1125
|
+
"{<puhelinnumero>}": props.phoneNumber || "",
|
|
1126
|
+
"{<telefoonnummer>}": props.phoneNumber || "",
|
|
1127
|
+
"{<telephone number>}": props.phoneNumber || "",
|
|
1128
|
+
"{<svarstid>}": props.responseTime || "",
|
|
1129
|
+
"{<svartid>}": props.responseTime || "",
|
|
1130
|
+
"{<response time>}": props.responseTime || "",
|
|
1131
|
+
"{<bed\xF6mningsdatum>}": dateStr,
|
|
1132
|
+
"{<vurderingsdato>}": dateStr,
|
|
1133
|
+
"{<arviointip\xE4iv\xE4>}": dateStr,
|
|
1134
|
+
"{<beoordelingsdatum>}": dateStr,
|
|
1135
|
+
"{<bewertungsdatum>}": dateStr,
|
|
1136
|
+
"{<date_evaluation>}": dateStr,
|
|
1137
|
+
"{<fecha_evaluacion>}": dateStr,
|
|
1138
|
+
"{<assessment date>}": dateStr,
|
|
1139
|
+
"{<uppdateringsdatum>}": dateStr,
|
|
1140
|
+
"{<oppdateringsdato>}": dateStr,
|
|
1141
|
+
"{<p\xE4ivitysp\xE4iv\xE4>}": dateStr,
|
|
1142
|
+
"{<updatedatum>}": dateStr,
|
|
1143
|
+
"{<aktualisierungsdatum>}": dateStr,
|
|
1144
|
+
"{<date_mise_a_jour>}": dateStr,
|
|
1145
|
+
"{<fecha_actualizacion>}": dateStr,
|
|
1146
|
+
"{<update date>}": dateStr,
|
|
1147
|
+
"{<publiceringsdatum>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1148
|
+
"{<publiseringsdato>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1149
|
+
"{<julkaisup\xE4iv\xE4>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1150
|
+
"{<publicatiedatum>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1151
|
+
"{<ver\xF6ffentlichungsdatum>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1152
|
+
"{<date_publication>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1153
|
+
"{<fecha_publicacion>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1154
|
+
"{<publish date>}": props.publishDate?.toISOString().split("T")[0] || "2024-01-01",
|
|
1155
|
+
"{<metod>}": props.evaluationMethod || "Automated Scan",
|
|
1156
|
+
"{<metodi>}": props.evaluationMethod || "Automated Scan",
|
|
1157
|
+
"{<methode>}": props.evaluationMethod || "Automated Scan",
|
|
1158
|
+
"{<m\xE9todo>}": props.evaluationMethod || "Automated Scan",
|
|
1159
|
+
"{<method>}": props.evaluationMethod || "Automated Scan",
|
|
1160
|
+
"{<extern akt\xF6r>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1161
|
+
"{<ekstern aktor>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1162
|
+
"{<ulkoinen taho>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1163
|
+
"{<externe partij>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1164
|
+
"{<externer Dritter>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1165
|
+
"{<tiers externe>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1166
|
+
"{<tercero externo>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1167
|
+
"{<third party>}": props.generatorTool?.name || "HolmDigital Engine",
|
|
1168
|
+
"{<enforcement_body>}": import_standards.ENFORCEMENT_BODIES[country] || import_standards.ENFORCEMENT_BODIES.EU,
|
|
1169
|
+
"{<brister>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "sv" ? "Inga k\xE4nda brister." : "No known issues.",
|
|
1170
|
+
"{<puutteet>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "fi" ? "Ei tiedossa olevia puutteita." : "No known issues.",
|
|
1171
|
+
"{<gebreken>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "nl" ? "Geen bekende gebreken." : "No known issues.",
|
|
1172
|
+
"{<m\xE4ngel>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "de" ? "Keine bekannten M\xE4ngel." : "No known issues.",
|
|
1173
|
+
"{<d\xE9fauts>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "fr" ? "Aucun d\xE9faut connu." : "No known issues.",
|
|
1174
|
+
"{<deficiencias>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "es" ? "No hay deficiencias conocidas." : "No known issues.",
|
|
1175
|
+
"{<mangler>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : lang === "no" || lang === "da" ? "Ingen kendte mangler." : "No known issues.",
|
|
1176
|
+
"{<issues>}": nonComplianceItems.length > 0 ? nonComplianceItems.map((item) => `* ${item}`).join("\n") : "No known issues."
|
|
1177
|
+
};
|
|
1178
|
+
const processText = (text) => {
|
|
1179
|
+
let processed = text;
|
|
1180
|
+
processed = processed.replace(/\[([\s\S]*?)\]/g, (_match, content2) => {
|
|
1181
|
+
if (content2.includes("{<svarstid>}") || content2.includes("{<svartid>}") || content2.includes("{<response time>}")) {
|
|
1182
|
+
return props.responseTime ? content2 : "";
|
|
1183
|
+
}
|
|
1184
|
+
if (content2.includes("{<telefonnummer>}") || content2.includes("{<telephone number>}")) {
|
|
1185
|
+
return props.phoneNumber ? content2 : "";
|
|
1186
|
+
}
|
|
1187
|
+
if (content2.includes("{<brister>}") || content2.includes("{<mangler>}") || content2.includes("{<issues>}")) {
|
|
1188
|
+
return complianceLevel !== "full" ? content2 : "";
|
|
1189
|
+
}
|
|
1190
|
+
return content2;
|
|
1191
|
+
});
|
|
1192
|
+
processed = processed.replace(/\{([^{}]*?)\}/g, (_match, content2) => {
|
|
1193
|
+
const parts = content2.split("/");
|
|
1194
|
+
if (parts.length >= 2) {
|
|
1195
|
+
let idx = 0;
|
|
1196
|
+
if (complianceLevel === "partial") idx = 1;
|
|
1197
|
+
if (complianceLevel === "non-compliant") idx = parts.length > 2 ? 2 : 1;
|
|
1198
|
+
return parts[idx].trim();
|
|
1199
|
+
}
|
|
1200
|
+
const key = `{${content2}}`;
|
|
1201
|
+
return substitutions[key] !== void 0 ? substitutions[key] : _match;
|
|
1202
|
+
});
|
|
1203
|
+
return processed;
|
|
1204
|
+
};
|
|
1205
|
+
const title = processText(template.title);
|
|
1206
|
+
const intro = processText(template.intro);
|
|
1207
|
+
const sections = template.sections.map((s) => `## ${s.title}
|
|
1117
1208
|
|
|
1118
|
-
|
|
1209
|
+
${processText(s.content)}`).join("\n\n");
|
|
1210
|
+
content = `# ${title}
|
|
1119
1211
|
|
|
1120
|
-
|
|
1212
|
+
${intro}
|
|
1121
1213
|
|
|
1122
|
-
|
|
1214
|
+
${sections}
|
|
1123
1215
|
|
|
1124
|
-
|
|
1125
|
-
`;
|
|
1216
|
+
---
|
|
1217
|
+
Generated using [${props.generatorTool?.name || "HolmDigital Engine"}](${props.generatorTool?.url || "https://holmdigital.se"})`;
|
|
1126
1218
|
}
|
|
1127
1219
|
return content;
|
|
1128
1220
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/reporting/junit-generator.ts
|
|
2
|
+
function generateJUnitXML(result) {
|
|
3
|
+
const { url, reports, stats, metadata } = result;
|
|
4
|
+
const duration = metadata.scanDuration;
|
|
5
|
+
const timestamp = result.timestamp;
|
|
6
|
+
const totalTests = stats.passed + stats.total;
|
|
7
|
+
const failures = stats.total;
|
|
8
|
+
const xmlLines = [
|
|
9
|
+
`<?xml version="1.0" encoding="UTF-8"?>`,
|
|
10
|
+
`<testsuites name="HolmDigital Accessibility Scan" time="${duration / 1e3}" tests="${totalTests}" failures="${failures}">`,
|
|
11
|
+
` <testsuite name="${url}" tests="${totalTests}" failures="${failures}" errors="0" time="${duration / 1e3}" timestamp="${timestamp}">`,
|
|
12
|
+
` <properties>`,
|
|
13
|
+
` <property name="engineVersion" value="${metadata.engineVersion}"/>`,
|
|
14
|
+
` <property name="axeCoreVersion" value="${metadata.axeCoreVersion}"/>`,
|
|
15
|
+
` <property name="standardsVersion" value="${metadata.standardsVersion}"/>`,
|
|
16
|
+
` <property name="pageTitle" value="${escapeXML(metadata.pageTitle || "N/A")}"/>`,
|
|
17
|
+
` <property name="pageLanguage" value="${escapeXML(metadata.pageLanguage || "N/A")}"/>`,
|
|
18
|
+
` <property name="score" value="${result.score}"/>`,
|
|
19
|
+
` <property name="complianceStatus" value="${result.complianceStatus}"/>`,
|
|
20
|
+
` </properties>`
|
|
21
|
+
];
|
|
22
|
+
for (let i = 0; i < stats.passed; i++) {
|
|
23
|
+
xmlLines.push(` <testcase name="Passed Accessibility Rule ${i + 1}" classname="Accessibility.Success" time="0" />`);
|
|
24
|
+
}
|
|
25
|
+
reports.forEach((report) => {
|
|
26
|
+
const severity = report.holmdigitalInsight.diggRisk;
|
|
27
|
+
const message = escapeXML(report.holmdigitalInsight.reasoning);
|
|
28
|
+
const criteria = `WCAG ${report.wcagCriteria} | EN 301 549 ${report.en301549Criteria}`;
|
|
29
|
+
const help = `Ref: ${report.dosLagenReference}. Remediation: ${report.remediation.component || "Manual"}`;
|
|
30
|
+
xmlLines.push(` <testcase name="[${severity.toUpperCase()}] ${report.ruleId}" classname="${report.wcagCriteria}" time="0">`);
|
|
31
|
+
xmlLines.push(` <failure message="${message}" type="${severity}">${escapeXML(criteria)}
|
|
32
|
+
${escapeXML(help)}</failure>`);
|
|
33
|
+
if (report.failingNodes && report.failingNodes.length > 0) {
|
|
34
|
+
let nodeInfo = "Failing Nodes:\n";
|
|
35
|
+
report.failingNodes.forEach((node, idx) => {
|
|
36
|
+
nodeInfo += `
|
|
37
|
+
[Node ${idx + 1}]
|
|
38
|
+
`;
|
|
39
|
+
nodeInfo += `Target: ${node.target}
|
|
40
|
+
`;
|
|
41
|
+
nodeInfo += `HTML: ${node.html}
|
|
42
|
+
`;
|
|
43
|
+
nodeInfo += `Fix: ${node.failureSummary}
|
|
44
|
+
`;
|
|
45
|
+
});
|
|
46
|
+
xmlLines.push(` <system-out>${escapeXML(nodeInfo)}</system-out>`);
|
|
47
|
+
}
|
|
48
|
+
xmlLines.push(` </testcase>`);
|
|
49
|
+
});
|
|
50
|
+
xmlLines.push(` </testsuite>`);
|
|
51
|
+
xmlLines.push(`</testsuites>`);
|
|
52
|
+
return xmlLines.join("\n");
|
|
53
|
+
}
|
|
54
|
+
function escapeXML(unsafe) {
|
|
55
|
+
if (unsafe === void 0 || unsafe === null) return "";
|
|
56
|
+
const str = String(unsafe);
|
|
57
|
+
return str.replace(/[<>&'"]/g, (c) => {
|
|
58
|
+
switch (c) {
|
|
59
|
+
case "<":
|
|
60
|
+
return "<";
|
|
61
|
+
case ">":
|
|
62
|
+
return ">";
|
|
63
|
+
case "&":
|
|
64
|
+
return "&";
|
|
65
|
+
case "'":
|
|
66
|
+
return "'";
|
|
67
|
+
case '"':
|
|
68
|
+
return """;
|
|
69
|
+
default:
|
|
70
|
+
return c;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
export {
|
|
75
|
+
generateJUnitXML
|
|
76
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holmdigital/engine",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"author": "Holm Digital AB",
|
|
61
61
|
"license": "MIT",
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@holmdigital/components": "^2.
|
|
63
|
+
"@holmdigital/components": "^2.1.0",
|
|
64
64
|
"@holmdigital/standards": "*",
|
|
65
65
|
"axe-core": "4.10.2",
|
|
66
66
|
"chalk": "^5.3.0",
|
|
@@ -76,9 +76,11 @@
|
|
|
76
76
|
"devDependencies": {
|
|
77
77
|
"@types/cosmiconfig": "^5.0.3",
|
|
78
78
|
"@types/node": "^22.10.2",
|
|
79
|
+
"@types/react": "^18.3.1",
|
|
80
|
+
"@types/react-dom": "^18.3.1",
|
|
79
81
|
"@types/ws": "^8.5.13",
|
|
80
82
|
"tsup": "^8.3.5",
|
|
81
83
|
"typescript": "^5.7.2",
|
|
82
84
|
"vitest": "^4.0.16"
|
|
83
85
|
}
|
|
84
|
-
}
|
|
86
|
+
}
|
|
@@ -1,43 +0,0 @@
|
|
|
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 "<";
|
|
28
|
-
case ">":
|
|
29
|
-
return ">";
|
|
30
|
-
case "&":
|
|
31
|
-
return "&";
|
|
32
|
-
case "'":
|
|
33
|
-
return "'";
|
|
34
|
-
case '"':
|
|
35
|
-
return """;
|
|
36
|
-
default:
|
|
37
|
-
return c;
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
export {
|
|
42
|
-
generateJUnitXML
|
|
43
|
-
};
|