@holmdigital/engine 1.4.11 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +59 -59
- package/README.md +198 -158
- package/dist/{chunk-BKI2FFUX.mjs → chunk-32WU5BD6.mjs} +151 -15
- package/dist/{chunk-WZSPSYDS.mjs → chunk-ZO2XNHHT.mjs} +179 -10
- package/dist/cli/index.js +594 -48
- package/dist/cli/index.mjs +194 -56
- package/dist/github-actions-CGWJSLMB.mjs +24 -0
- package/dist/{i18n-M6ATDHUS.mjs → i18n-VBP5BVTT.mjs} +1 -1
- package/dist/index.d.mts +21 -2
- package/dist/index.d.ts +21 -2
- package/dist/index.js +342 -12
- package/dist/index.mjs +7 -3
- package/dist/junit-generator-FK3GE5E4.mjs +43 -0
- package/package.json +84 -79
package/dist/cli/index.mjs
CHANGED
|
@@ -1,36 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
PseudoAutomationEngine,
|
|
4
|
-
RegulatoryScanner
|
|
5
|
-
|
|
4
|
+
RegulatoryScanner,
|
|
5
|
+
generateBadgeMarkdown,
|
|
6
|
+
generateBadgeUrl,
|
|
7
|
+
generateStatement,
|
|
8
|
+
generateStatementContent
|
|
9
|
+
} from "../chunk-ZO2XNHHT.mjs";
|
|
6
10
|
import {
|
|
7
11
|
getCurrentLang,
|
|
8
12
|
setLanguage,
|
|
9
13
|
t
|
|
10
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-32WU5BD6.mjs";
|
|
11
15
|
|
|
12
16
|
// src/cli/index.ts
|
|
13
17
|
import { Command } from "commander";
|
|
14
18
|
import chalk from "chalk";
|
|
15
19
|
import ora from "ora";
|
|
16
20
|
|
|
17
|
-
// src/reporting/badge-generator.ts
|
|
18
|
-
var BADGE_COLOR = "00703C";
|
|
19
|
-
var BADGE_BASE_URL = "https://img.shields.io/badge/HolmDigital_Engine";
|
|
20
|
-
function generateBadgeUrl(score) {
|
|
21
|
-
if (score !== 100) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
return `${BADGE_BASE_URL}-100%25-${BADGE_COLOR}?style=flat-square`;
|
|
25
|
-
}
|
|
26
|
-
function generateBadgeMarkdown(score) {
|
|
27
|
-
const url = generateBadgeUrl(score);
|
|
28
|
-
if (!url) {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
return ``;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
21
|
// src/reporting/html-template.ts
|
|
35
22
|
function generateReportHTML(result) {
|
|
36
23
|
const criticalCount = result.stats.critical;
|
|
@@ -152,6 +139,37 @@ function generateReportHTML(result) {
|
|
|
152
139
|
.badge-high { background: #fffbeb; color: #d97706; border: 1px solid #fde68a; }
|
|
153
140
|
.badge-medium { background: #fefce8; color: #ca8a04; border: 1px solid #fef08a; }
|
|
154
141
|
.badge-low { background: #f8fafc; color: #475569; border: 1px solid #e2e8f0; }
|
|
142
|
+
.badge-wad { background: #eff6ff; color: #1d4ed8; border: 1px solid #bfdbfe; margin-left: 0.5rem; }
|
|
143
|
+
.badge-eaa { background: #faf5ff; color: #7c3aed; border: 1px solid #ddd6fe; margin-left: 0.5rem; }
|
|
144
|
+
.legal-summary {
|
|
145
|
+
background: linear-gradient(135deg, #eff6ff 0%, #faf5ff 100%);
|
|
146
|
+
border: 1px solid #c7d2fe;
|
|
147
|
+
border-radius: 12px;
|
|
148
|
+
padding: 1.5rem;
|
|
149
|
+
margin-bottom: 2rem;
|
|
150
|
+
}
|
|
151
|
+
.legal-summary-title {
|
|
152
|
+
font-size: 1rem;
|
|
153
|
+
font-weight: 600;
|
|
154
|
+
color: #3730a3;
|
|
155
|
+
margin-bottom: 1rem;
|
|
156
|
+
}
|
|
157
|
+
.legal-grid {
|
|
158
|
+
display: grid;
|
|
159
|
+
grid-template-columns: repeat(3, 1fr);
|
|
160
|
+
gap: 1rem;
|
|
161
|
+
}
|
|
162
|
+
.legal-stat {
|
|
163
|
+
text-align: center;
|
|
164
|
+
}
|
|
165
|
+
.legal-stat-value {
|
|
166
|
+
font-size: 1.5rem;
|
|
167
|
+
font-weight: 700;
|
|
168
|
+
}
|
|
169
|
+
.legal-stat-label {
|
|
170
|
+
font-size: 0.75rem;
|
|
171
|
+
color: #64748b;
|
|
172
|
+
}
|
|
155
173
|
|
|
156
174
|
.violation-meta {
|
|
157
175
|
font-size: 0.875rem;
|
|
@@ -220,6 +238,26 @@ function generateReportHTML(result) {
|
|
|
220
238
|
</div>
|
|
221
239
|
</div>
|
|
222
240
|
|
|
241
|
+
${result.legalSummary ? `
|
|
242
|
+
<div class="legal-summary">
|
|
243
|
+
<div class="legal-summary-title">\u{1F1EA}\u{1F1FA} EU Legal Framework Impact</div>
|
|
244
|
+
<div class="legal-grid">
|
|
245
|
+
<div class="legal-stat">
|
|
246
|
+
<div class="legal-stat-value" style="color: #1d4ed8;">${result.legalSummary.wadApplicable}</div>
|
|
247
|
+
<div class="legal-stat-label">WAD (Public Sector) Violations</div>
|
|
248
|
+
</div>
|
|
249
|
+
<div class="legal-stat">
|
|
250
|
+
<div class="legal-stat-value" style="color: #7c3aed;">${result.legalSummary.eaaApplicable}</div>
|
|
251
|
+
<div class="legal-stat-label">EAA (Private Sector) Violations</div>
|
|
252
|
+
</div>
|
|
253
|
+
<div class="legal-stat">
|
|
254
|
+
<div class="legal-stat-value" style="color: #dc2626;">${result.legalSummary.eaaDeadlineViolations}</div>
|
|
255
|
+
<div class="legal-stat-label">EAA 2025 Deadline Issues</div>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
` : ""}
|
|
260
|
+
|
|
223
261
|
<div class="section-title">${t("report.detailed_violations")}</div>
|
|
224
262
|
|
|
225
263
|
${[...result.reports].sort((a, b) => {
|
|
@@ -233,11 +271,16 @@ function generateReportHTML(result) {
|
|
|
233
271
|
<div class="violation-card">
|
|
234
272
|
<div class="violation-header">
|
|
235
273
|
<div class="violation-title">${report.ruleId}</div>
|
|
236
|
-
<
|
|
274
|
+
<div>
|
|
275
|
+
<span class="badge ${riskClass}">${report.holmdigitalInsight.diggRisk}</span>
|
|
276
|
+
${report.legalContext?.appliesTo?.includes("WAD") ? '<span class="badge badge-wad">WAD</span>' : ""}
|
|
277
|
+
${report.legalContext?.appliesTo?.includes("EAA") ? '<span class="badge badge-eaa">EAA</span>' : ""}
|
|
278
|
+
</div>
|
|
237
279
|
</div>
|
|
238
280
|
<div class="violation-meta">
|
|
239
281
|
WCAG ${report.wcagCriteria} \u2022 EN 301 549 ${report.en301549Criteria}
|
|
240
282
|
${report.dosLagenReference ? `\u2022 ${report.dosLagenReference}` : ""}
|
|
283
|
+
${report.legalContext?.eaaDeadline ? `<br/><strong>\u26A0\uFE0F EAA Deadline:</strong> ${report.legalContext.eaaDeadline}` : ""}
|
|
241
284
|
</div>
|
|
242
285
|
<div style="font-size: 0.95rem; color: #334155; line-height: 1.5;">
|
|
243
286
|
${report.holmdigitalInsight.swedishInterpretation}
|
|
@@ -370,6 +413,7 @@ async function sendToCloud(config, result) {
|
|
|
370
413
|
}
|
|
371
414
|
|
|
372
415
|
// src/cli/index.ts
|
|
416
|
+
import { cosmiconfig } from "cosmiconfig";
|
|
373
417
|
function isValidUrl(urlString) {
|
|
374
418
|
try {
|
|
375
419
|
const url = new URL(urlString);
|
|
@@ -380,22 +424,46 @@ function isValidUrl(urlString) {
|
|
|
380
424
|
}
|
|
381
425
|
var program = new Command();
|
|
382
426
|
program.name("hd-a11y-scan").description("HolmDigital Regulatory Scanner").version("0.1.0");
|
|
383
|
-
program.argument("<url>", "URL to scan").option("--lang <code>", "Language code (en, sv)"
|
|
427
|
+
program.argument("<url>", "URL to scan").option("--lang <code>", "Language code (en, sv)").option("--ci", "Run in CI/CD mode (exit code 1 on critical failures)").option("--generate-tests", "Generate Pseudo-Automation tests").option("--json", "Output as JSON").option("--junit <path>", "Generate JUnit XML report").option("--pdf <path>", "Generate PDF report to path").option("--statement <path>", "Generate accessibility statement HTML to path").option("--format <type>", "Output format for statement (html, md)", "html").option("--viewport <size>", 'Set viewport (e.g. "mobile", "desktop", "1024x768")').option("--threshold <level>", "Severity threshold (critical, high, medium, low)").option("--api-key <key>", "API key for HolmDigital Cloud").option("--cloud-url <url>", "Cloud API URL").option("--invalid-https-cert", "Allow scanning on pages with invalid https certificate").option("--email <email>", "Contact email for accessibility statement").option("--phone <number>", "Contact phone number for accessibility statement").option("--org <name>", "Organization name for accessibility statement").option("--response-time <time>", "Expected response time for accessibility statement").option("--country <code>", "Country code for accessibility statement enforcement body").option("--publish-date <date>", "Publish date for the website (YYYY-MM-DD)").action(async (url, cliOptions) => {
|
|
428
|
+
const explorer = cosmiconfig("a11y");
|
|
429
|
+
const configResult = await explorer.search();
|
|
430
|
+
const fileConfig = configResult ? configResult.config : {};
|
|
431
|
+
const options = {
|
|
432
|
+
lang: cliOptions.lang || fileConfig.lang || "en",
|
|
433
|
+
ci: cliOptions.ci ?? fileConfig.ci ?? false,
|
|
434
|
+
generateTests: cliOptions.generateTests ?? fileConfig.generateTests ?? false,
|
|
435
|
+
json: cliOptions.json ?? fileConfig.json ?? false,
|
|
436
|
+
junit: cliOptions.junit || fileConfig.junit,
|
|
437
|
+
pdf: cliOptions.pdf || fileConfig.pdf,
|
|
438
|
+
statement: cliOptions.statement || fileConfig.statement,
|
|
439
|
+
format: cliOptions.format || fileConfig.format || "html",
|
|
440
|
+
viewport: cliOptions.viewport || fileConfig.viewport,
|
|
441
|
+
// Handled specifically below
|
|
442
|
+
threshold: cliOptions.threshold || fileConfig.threshold || "high",
|
|
443
|
+
apiKey: cliOptions.apiKey || fileConfig.apiKey,
|
|
444
|
+
cloudUrl: cliOptions.cloudUrl || fileConfig.cloudUrl || "https://cloud.holmdigital.se",
|
|
445
|
+
invalidHttpsCert: cliOptions.invalidHttpsCert ?? fileConfig.invalidHttpsCert ?? false,
|
|
446
|
+
// Metadata for accessibility statement
|
|
447
|
+
email: cliOptions.email || fileConfig.email,
|
|
448
|
+
phone: cliOptions.phone || fileConfig.phone,
|
|
449
|
+
org: cliOptions.org || fileConfig.org,
|
|
450
|
+
responseTime: cliOptions.responseTime || fileConfig.responseTime,
|
|
451
|
+
country: cliOptions.country || fileConfig.country,
|
|
452
|
+
publishDate: cliOptions.publishDate || fileConfig.publishDate
|
|
453
|
+
};
|
|
384
454
|
setLanguage(options.lang);
|
|
385
455
|
if (!isValidUrl(url)) {
|
|
386
|
-
console.error(chalk.red(`Error: Invalid URL format '${url}'`));
|
|
387
|
-
console.error(chalk.gray("URL must start with http:// or https://"));
|
|
388
|
-
process.exit(1);
|
|
389
456
|
}
|
|
390
457
|
if (!options.json) {
|
|
391
458
|
console.log(chalk.blue.bold(t("cli.title")));
|
|
459
|
+
if (configResult) console.log(chalk.gray(`Loaded config from ${configResult.filepath}`));
|
|
392
460
|
console.log(chalk.gray(t("cli.scanning", { url })));
|
|
393
461
|
}
|
|
394
462
|
const spinner = !options.json ? ora(t("cli.initializing")).start() : null;
|
|
395
463
|
let scanner;
|
|
396
464
|
try {
|
|
397
465
|
let viewport = { width: 1280, height: 720 };
|
|
398
|
-
if (options.viewport) {
|
|
466
|
+
if (options.viewport && typeof options.viewport === "string") {
|
|
399
467
|
if (options.viewport === "mobile") viewport = { width: 375, height: 667 };
|
|
400
468
|
else if (options.viewport === "desktop") viewport = { width: 1920, height: 1080 };
|
|
401
469
|
else if (options.viewport === "tablet") viewport = { width: 768, height: 1024 };
|
|
@@ -403,6 +471,8 @@ program.argument("<url>", "URL to scan").option("--lang <code>", "Language code
|
|
|
403
471
|
const [w, h] = options.viewport.split("x").map(Number);
|
|
404
472
|
if (w && h) viewport = { width: w, height: h };
|
|
405
473
|
}
|
|
474
|
+
} else if (options.viewport && typeof options.viewport === "object") {
|
|
475
|
+
viewport = options.viewport;
|
|
406
476
|
}
|
|
407
477
|
scanner = new RegulatoryScanner({
|
|
408
478
|
url,
|
|
@@ -422,15 +492,30 @@ program.argument("<url>", "URL to scan").option("--lang <code>", "Language code
|
|
|
422
492
|
await generatePDF(html, options.pdf);
|
|
423
493
|
if (spinner) spinner.succeed(t("cli.pdf_saved", { path: options.pdf }));
|
|
424
494
|
}
|
|
495
|
+
if (options.statement) {
|
|
496
|
+
if (spinner) spinner.start(t("cli.generating_statement", { path: options.statement }));
|
|
497
|
+
const metadata = {
|
|
498
|
+
contactEmail: options.email,
|
|
499
|
+
phoneNumber: options.phone,
|
|
500
|
+
organizationName: options.org,
|
|
501
|
+
responseTime: options.responseTime,
|
|
502
|
+
country: options.country,
|
|
503
|
+
publishDate: options.publishDate
|
|
504
|
+
};
|
|
505
|
+
if (options.statement.toLowerCase().endsWith(".pdf")) {
|
|
506
|
+
const htmlContent = await generateStatementContent(result, options.lang, "html", metadata);
|
|
507
|
+
await generatePDF(htmlContent, options.statement);
|
|
508
|
+
} else {
|
|
509
|
+
await generateStatement(result, options.statement, options.lang, options.format, metadata);
|
|
510
|
+
}
|
|
511
|
+
if (spinner) spinner.succeed(t("cli.statement_saved", { path: options.statement }));
|
|
512
|
+
}
|
|
425
513
|
if (options.json) {
|
|
426
514
|
console.log(JSON.stringify(result, null, 2));
|
|
427
515
|
} else {
|
|
428
|
-
console.log(
|
|
429
|
-
const
|
|
430
|
-
console.log(
|
|
431
|
-
if (result.complianceStatus === "FAIL") {
|
|
432
|
-
console.log(chalk.red(t("cli.not_compliant")));
|
|
433
|
-
}
|
|
516
|
+
console.log("\n");
|
|
517
|
+
const scoreColor = result.score >= 90 ? chalk.green : result.score >= 70 ? chalk.yellow : chalk.red;
|
|
518
|
+
console.log(chalk.bold(`[ Compliance Score: ${scoreColor(result.score + "/100")} ] ${result.score >= 90 ? "\u{1F7E2}" : result.score >= 70 ? "\u{1F7E1}" : "\u{1F534}"}`));
|
|
434
519
|
if (result.score === 100) {
|
|
435
520
|
const badge = generateBadgeMarkdown(result.score);
|
|
436
521
|
if (badge) {
|
|
@@ -439,46 +524,87 @@ program.argument("<url>", "URL to scan").option("--lang <code>", "Language code
|
|
|
439
524
|
}
|
|
440
525
|
}
|
|
441
526
|
console.log(chalk.gray("----------------------------------------"));
|
|
442
|
-
|
|
443
|
-
|
|
527
|
+
const calculateCategoryScore = (filterFn) => {
|
|
528
|
+
const failures = result.reports.filter(filterFn).length;
|
|
529
|
+
return Math.max(10, 100 - failures * 20);
|
|
530
|
+
};
|
|
531
|
+
const cats = [
|
|
532
|
+
{
|
|
533
|
+
name: "HTML Structure",
|
|
534
|
+
score: calculateCategoryScore((r) => ["1.3.1", "4.1.1", "4.1.2"].some((c) => r.wcagCriteria.includes(c))),
|
|
535
|
+
critical: false
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
name: "Keyboard Nav ",
|
|
539
|
+
score: calculateCategoryScore((r) => ["2.1.1", "2.1.2", "2.4.3", "2.4.7"].some((c) => r.wcagCriteria.includes(c))),
|
|
540
|
+
critical: result.reports.some((r) => ["2.1.1", "2.1.2"].some((c) => r.wcagCriteria.includes(c)) && r.holmdigitalInsight.diggRisk === "critical")
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
name: "Contrast ",
|
|
544
|
+
score: calculateCategoryScore((r) => r.wcagCriteria.includes("1.4.3") || r.ruleId === "color-contrast"),
|
|
545
|
+
critical: false
|
|
546
|
+
}
|
|
547
|
+
];
|
|
548
|
+
cats.forEach((cat) => {
|
|
549
|
+
const width = 10;
|
|
550
|
+
const filled = Math.round(cat.score / 100 * width);
|
|
551
|
+
const empty = width - filled;
|
|
552
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
553
|
+
let statusText = `${cat.score}%`;
|
|
554
|
+
if (cat.critical) statusText += chalk.red(" (CRITICAL FAIL)");
|
|
555
|
+
else if (cat.score < 100) statusText += chalk.gray(" (Minor issues)");
|
|
556
|
+
console.log(`${cat.name} [${cat.score >= 80 ? chalk.green(bar) : cat.score >= 50 ? chalk.yellow(bar) : chalk.red(bar)}] ${statusText}`);
|
|
557
|
+
});
|
|
558
|
+
console.log(chalk.gray("----------------------------------------"));
|
|
559
|
+
let legalRisk = "LOW";
|
|
560
|
+
let riskReason = "No critical violations found.";
|
|
561
|
+
let riskIcon = "\u2705";
|
|
562
|
+
if (result.legalSummary && result.legalSummary.eaaDeadlineViolations > 0) {
|
|
563
|
+
legalRisk = "HIGH";
|
|
564
|
+
const hasKeyboardIssues = result.reports.some((r) => ["2.1.1", "2.1.2"].some((c) => r.wcagCriteria.includes(c)));
|
|
565
|
+
const hasContrastIssues = result.reports.some((r) => r.wcagCriteria.includes("1.4.3") || r.ruleId === "color-contrast");
|
|
566
|
+
const hasStructureIssues = result.reports.some((r) => r.wcagCriteria.includes("1.3.1"));
|
|
567
|
+
if (hasKeyboardIssues) riskReason = "Keyboard accessibility blocks EAA compliance";
|
|
568
|
+
else if (hasContrastIssues) riskReason = "Contrast issues block EAA compliance";
|
|
569
|
+
else if (hasStructureIssues) riskReason = "HTML structure issues block EAA compliance";
|
|
570
|
+
else riskReason = `${result.legalSummary.eaaDeadlineViolations} EAA deadline violation(s) found`;
|
|
571
|
+
riskIcon = "\u2696\uFE0F ";
|
|
572
|
+
} else if (result.stats.critical > 0) {
|
|
573
|
+
legalRisk = "MEDIUM";
|
|
574
|
+
riskReason = "Critical violations found.";
|
|
575
|
+
riskIcon = "\u26A0\uFE0F ";
|
|
444
576
|
}
|
|
577
|
+
console.log(`${riskIcon} Legal Risk: ${legalRisk === "HIGH" ? chalk.red.bold(legalRisk) : legalRisk === "MEDIUM" ? chalk.yellow.bold(legalRisk) : chalk.green(legalRisk)} (${chalk.white(riskReason)})`);
|
|
578
|
+
console.log("\n");
|
|
445
579
|
if (result.htmlValidation && !result.htmlValidation.valid) {
|
|
446
|
-
console.log(chalk.red.bold("\
|
|
580
|
+
console.log(chalk.red.bold("\u26A0\uFE0F Structural HTML Issues Detected"));
|
|
447
581
|
console.log(chalk.yellow(" These issues may affect accessibility tool accuracy (e.g. contrast calculations)\n"));
|
|
448
582
|
result.htmlValidation.errors.forEach((error) => {
|
|
449
583
|
console.log(chalk.red(` [${error.rule}] ${error.message}`));
|
|
450
|
-
if (error.selector) console.log(chalk.gray(` ${error.selector}`));
|
|
451
|
-
console.log(chalk.gray(` Line: ${error.line}, Col: ${error.column}
|
|
452
|
-
`));
|
|
453
584
|
});
|
|
585
|
+
console.log(chalk.gray(" ... and more (run with --json for full details)"));
|
|
454
586
|
console.log(chalk.gray("----------------------------------------"));
|
|
455
587
|
}
|
|
456
|
-
result.reports.
|
|
588
|
+
if (result.reports.length > 0) {
|
|
589
|
+
console.log(chalk.bold("Top Violations:"));
|
|
590
|
+
}
|
|
591
|
+
result.reports.forEach((report, i) => {
|
|
592
|
+
if (i > 5) return;
|
|
457
593
|
const color = report.holmdigitalInsight.diggRisk === "critical" ? chalk.red : chalk.yellow;
|
|
458
594
|
console.log(color.bold(`
|
|
459
595
|
[${report.holmdigitalInsight.diggRisk.toUpperCase()}] ${report.ruleId}`));
|
|
460
596
|
console.log(chalk.white(`WCAG: ${report.wcagCriteria} | EN 301 549: ${report.en301549Criteria}`));
|
|
461
|
-
console.log(chalk.gray(`Legitimitet: ${report.dosLagenReference}`));
|
|
462
597
|
if (report.remediation.component) {
|
|
463
|
-
console.log(chalk.green(
|
|
464
|
-
console.log(t("cli.use_component", { component: chalk.bold(report.remediation.component) }));
|
|
465
|
-
}
|
|
466
|
-
if (report.failingNodes && report.failingNodes.length > 0) {
|
|
467
|
-
console.log(chalk.gray("\nAffected Elements:"));
|
|
468
|
-
report.failingNodes.forEach((node, index) => {
|
|
469
|
-
if (index < 5) {
|
|
470
|
-
console.log(chalk.cyan(`\u279C ${node.target}`));
|
|
471
|
-
console.log(chalk.gray(` ${node.html}`));
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
|
-
if (report.failingNodes.length > 5) {
|
|
475
|
-
console.log(chalk.gray(` ...and ${report.failingNodes.length - 5} more`));
|
|
476
|
-
}
|
|
598
|
+
console.log(chalk.green(`Fix: Use component <${report.remediation.component} />`));
|
|
477
599
|
}
|
|
478
600
|
});
|
|
601
|
+
if (result.reports.length > 5) {
|
|
602
|
+
console.log(chalk.gray(`
|
|
603
|
+
... and ${result.reports.length - 5} more issues.`));
|
|
604
|
+
}
|
|
479
605
|
console.log(chalk.gray("\n----------------------------------------"));
|
|
480
|
-
console.log(`
|
|
481
|
-
|
|
606
|
+
console.log(`Scan Date: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`);
|
|
607
|
+
console.log("\n");
|
|
482
608
|
if (options.generateTests) {
|
|
483
609
|
console.log(chalk.magenta.bold(t("cli.pseudo_tests")));
|
|
484
610
|
const automation = new PseudoAutomationEngine();
|
|
@@ -490,6 +616,18 @@ program.argument("<url>", "URL to scan").option("--lang <code>", "Language code
|
|
|
490
616
|
});
|
|
491
617
|
}
|
|
492
618
|
}
|
|
619
|
+
if (options.ci && result.reports.length > 0) {
|
|
620
|
+
const { generateGitHubActionsAnnotations } = await import("../github-actions-CGWJSLMB.mjs");
|
|
621
|
+
generateGitHubActionsAnnotations(result.reports);
|
|
622
|
+
}
|
|
623
|
+
if (options.junit) {
|
|
624
|
+
const { generateJUnitXML } = await import("../junit-generator-FK3GE5E4.mjs");
|
|
625
|
+
const fs = await import("fs/promises");
|
|
626
|
+
const xml = generateJUnitXML(result.reports, url, result.metadata.scanDuration);
|
|
627
|
+
await fs.writeFile(options.junit, xml);
|
|
628
|
+
if (spinner) spinner.succeed(t("cli.junit_saved", { path: options.junit }));
|
|
629
|
+
else if (!options.json) console.log(chalk.green(`\u2713 JUnit report saved to ${options.junit}`));
|
|
630
|
+
}
|
|
493
631
|
if (options.ci && result.stats.critical > 0) {
|
|
494
632
|
if (!options.json) console.error(chalk.red(t("cli.critical_failure")));
|
|
495
633
|
process.exit(1);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// src/reporting/github-actions.ts
|
|
2
|
+
function generateGitHubActionsAnnotations(reports) {
|
|
3
|
+
reports.forEach((report) => {
|
|
4
|
+
let level = "warning";
|
|
5
|
+
if (report.holmdigitalInsight.diggRisk === "critical") {
|
|
6
|
+
level = "error";
|
|
7
|
+
}
|
|
8
|
+
const title = `[${report.ruleId}] ${report.wcagCriteria}`;
|
|
9
|
+
const message = `${report.holmdigitalInsight.reasoning}
|
|
10
|
+
|
|
11
|
+
Legitimacy: ${report.dosLagenReference}
|
|
12
|
+
Fix: ${report.remediation.component ? `Use <${report.remediation.component} />` : "Manual remediation required"}`;
|
|
13
|
+
if (report.failingNodes && report.failingNodes.length > 0) {
|
|
14
|
+
report.failingNodes.forEach((node) => {
|
|
15
|
+
console.log(`::${level} title=${title}::${message} (Target: ${node.target})`);
|
|
16
|
+
});
|
|
17
|
+
} else {
|
|
18
|
+
console.log(`::${level} title=${title}::${message}`);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
generateGitHubActionsAnnotations
|
|
24
|
+
};
|
package/dist/index.d.mts
CHANGED
|
@@ -49,6 +49,11 @@ interface ScanResult {
|
|
|
49
49
|
score: number;
|
|
50
50
|
complianceStatus: 'PASS' | 'FAIL';
|
|
51
51
|
htmlValidation?: ValidationResult;
|
|
52
|
+
legalSummary?: {
|
|
53
|
+
wadApplicable: number;
|
|
54
|
+
eaaApplicable: number;
|
|
55
|
+
eaaDeadlineViolations: number;
|
|
56
|
+
};
|
|
52
57
|
}
|
|
53
58
|
declare class RegulatoryScanner {
|
|
54
59
|
private browser;
|
|
@@ -107,6 +112,8 @@ var cli = {
|
|
|
107
112
|
pdf_saved: "PDF Report saved to {path}",
|
|
108
113
|
scan_failed: "Scan failed",
|
|
109
114
|
critical_failure: "\nCI/CD Failure: Critical regulatory violations found.",
|
|
115
|
+
generating_statement: "Generating Accessibility Statement...",
|
|
116
|
+
statement_saved: "Accessibility Statement saved to {path}",
|
|
110
117
|
score: "\nCompliance Score: {score}/100",
|
|
111
118
|
status: "Compliance Status: {status}",
|
|
112
119
|
not_compliant: " (Not compliant with legal requirements)",
|
|
@@ -114,7 +121,8 @@ var cli = {
|
|
|
114
121
|
prescriptive_fix: "\n💡 Prescriptive Fix:",
|
|
115
122
|
use_component: "Use component: {component}",
|
|
116
123
|
pseudo_tests: "\n🧬 Generating Pseudo-Automation Tests...\n",
|
|
117
|
-
test_for: "Test for {ruleId}:"
|
|
124
|
+
test_for: "Test for {ruleId}:",
|
|
125
|
+
junit_saved: "JUnit XML report saved to {path}"
|
|
118
126
|
};
|
|
119
127
|
var report = {
|
|
120
128
|
title: "Accessibility Report - {url}",
|
|
@@ -144,4 +152,15 @@ declare function setLanguage(lang: string): void;
|
|
|
144
152
|
declare function t(key: LocaleKey, params?: Record<string, string | number>): string;
|
|
145
153
|
declare function getCurrentLang(): string;
|
|
146
154
|
|
|
147
|
-
|
|
155
|
+
interface StatementMetadata {
|
|
156
|
+
organizationName?: string;
|
|
157
|
+
contactEmail?: string;
|
|
158
|
+
phoneNumber?: string;
|
|
159
|
+
responseTime?: string;
|
|
160
|
+
country?: string;
|
|
161
|
+
publishDate?: string | Date;
|
|
162
|
+
}
|
|
163
|
+
declare function generateStatementContent(result: ScanResult, lang?: string, format?: 'html' | 'md' | 'markdown', metadata?: StatementMetadata): Promise<string>;
|
|
164
|
+
declare function generateStatement(result: ScanResult, outputPath: string, lang?: string, format?: 'html' | 'md' | 'markdown', metadata?: StatementMetadata): Promise<void>;
|
|
165
|
+
|
|
166
|
+
export { PseudoAutomationEngine, RegulatoryScanner, type ScanMetadata, type ScanResult, type ScannerOptions, type StatementMetadata, VirtualDOMBuilder, type VirtualDOMConfig, type VirtualNode, generateStatement, generateStatementContent, getCurrentLang, setLanguage, t };
|
package/dist/index.d.ts
CHANGED
|
@@ -49,6 +49,11 @@ interface ScanResult {
|
|
|
49
49
|
score: number;
|
|
50
50
|
complianceStatus: 'PASS' | 'FAIL';
|
|
51
51
|
htmlValidation?: ValidationResult;
|
|
52
|
+
legalSummary?: {
|
|
53
|
+
wadApplicable: number;
|
|
54
|
+
eaaApplicable: number;
|
|
55
|
+
eaaDeadlineViolations: number;
|
|
56
|
+
};
|
|
52
57
|
}
|
|
53
58
|
declare class RegulatoryScanner {
|
|
54
59
|
private browser;
|
|
@@ -107,6 +112,8 @@ var cli = {
|
|
|
107
112
|
pdf_saved: "PDF Report saved to {path}",
|
|
108
113
|
scan_failed: "Scan failed",
|
|
109
114
|
critical_failure: "\nCI/CD Failure: Critical regulatory violations found.",
|
|
115
|
+
generating_statement: "Generating Accessibility Statement...",
|
|
116
|
+
statement_saved: "Accessibility Statement saved to {path}",
|
|
110
117
|
score: "\nCompliance Score: {score}/100",
|
|
111
118
|
status: "Compliance Status: {status}",
|
|
112
119
|
not_compliant: " (Not compliant with legal requirements)",
|
|
@@ -114,7 +121,8 @@ var cli = {
|
|
|
114
121
|
prescriptive_fix: "\n💡 Prescriptive Fix:",
|
|
115
122
|
use_component: "Use component: {component}",
|
|
116
123
|
pseudo_tests: "\n🧬 Generating Pseudo-Automation Tests...\n",
|
|
117
|
-
test_for: "Test for {ruleId}:"
|
|
124
|
+
test_for: "Test for {ruleId}:",
|
|
125
|
+
junit_saved: "JUnit XML report saved to {path}"
|
|
118
126
|
};
|
|
119
127
|
var report = {
|
|
120
128
|
title: "Accessibility Report - {url}",
|
|
@@ -144,4 +152,15 @@ declare function setLanguage(lang: string): void;
|
|
|
144
152
|
declare function t(key: LocaleKey, params?: Record<string, string | number>): string;
|
|
145
153
|
declare function getCurrentLang(): string;
|
|
146
154
|
|
|
147
|
-
|
|
155
|
+
interface StatementMetadata {
|
|
156
|
+
organizationName?: string;
|
|
157
|
+
contactEmail?: string;
|
|
158
|
+
phoneNumber?: string;
|
|
159
|
+
responseTime?: string;
|
|
160
|
+
country?: string;
|
|
161
|
+
publishDate?: string | Date;
|
|
162
|
+
}
|
|
163
|
+
declare function generateStatementContent(result: ScanResult, lang?: string, format?: 'html' | 'md' | 'markdown', metadata?: StatementMetadata): Promise<string>;
|
|
164
|
+
declare function generateStatement(result: ScanResult, outputPath: string, lang?: string, format?: 'html' | 'md' | 'markdown', metadata?: StatementMetadata): Promise<void>;
|
|
165
|
+
|
|
166
|
+
export { PseudoAutomationEngine, RegulatoryScanner, type ScanMetadata, type ScanResult, type ScannerOptions, type StatementMetadata, VirtualDOMBuilder, type VirtualDOMConfig, type VirtualNode, generateStatement, generateStatementContent, getCurrentLang, setLanguage, t };
|