@holmdigital/engine 1.2.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.
@@ -0,0 +1,501 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ PseudoAutomationEngine,
4
+ RegulatoryScanner
5
+ } from "../chunk-MFACMI2F.mjs";
6
+ import {
7
+ getCurrentLang,
8
+ setLanguage,
9
+ t
10
+ } from "../chunk-OXZPYWBW.mjs";
11
+ import "../chunk-Y6FXYEAI.mjs";
12
+
13
+ // src/cli/index.ts
14
+ import { Command } from "commander";
15
+ import chalk from "chalk";
16
+ import ora from "ora";
17
+
18
+ // src/reporting/html-template.ts
19
+ function generateReportHTML(result) {
20
+ const criticalCount = result.stats.critical;
21
+ const highCount = result.stats.high;
22
+ const scoreColor = result.score > 90 ? "#16a34a" : result.score > 70 ? "#eab308" : "#dc2626";
23
+ const formatDate = (dateString) => {
24
+ return new Date(dateString).toLocaleDateString(getCurrentLang() === "sv" ? "sv-SE" : "en-US", {
25
+ year: "numeric",
26
+ month: "long",
27
+ day: "numeric",
28
+ hour: "2-digit",
29
+ minute: "2-digit"
30
+ });
31
+ };
32
+ return `
33
+ <!DOCTYPE html>
34
+ <html lang="${getCurrentLang()}">
35
+ <head>
36
+ <meta charset="UTF-8">
37
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
38
+ <title>${t("report.title", { url: result.url })}</title>
39
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
40
+ <style>
41
+ @page {
42
+ margin: 0;
43
+ }
44
+ body {
45
+ font-family: 'Inter', sans-serif;
46
+ background-color: #ffffff;
47
+ color: #0f172a;
48
+ margin: 0;
49
+ padding: 40px;
50
+ -webkit-print-color-adjust: exact;
51
+ }
52
+ .header {
53
+ display: flex;
54
+ justify-content: space-between;
55
+ align-items: center;
56
+ border-bottom: 2px solid #f1f5f9;
57
+ padding-bottom: 2rem;
58
+ margin-bottom: 3rem;
59
+ }
60
+ .brand {
61
+ font-size: 1.5rem;
62
+ font-weight: 700;
63
+ color: #0f172a;
64
+ }
65
+ .brand span {
66
+ color: #0ea5e9;
67
+ }
68
+ .meta {
69
+ text-align: right;
70
+ color: #64748b;
71
+ font-size: 0.875rem;
72
+ }
73
+ .summary-grid {
74
+ display: grid;
75
+ grid-template-columns: repeat(4, 1fr);
76
+ gap: 1.5rem;
77
+ margin-bottom: 3rem;
78
+ }
79
+ .card {
80
+ background: #f8fafc;
81
+ border: 1px solid #e2e8f0;
82
+ border-radius: 12px;
83
+ padding: 1.5rem;
84
+ }
85
+ .score-card {
86
+ background: #f0f9ff;
87
+ border-color: #bae6fd;
88
+ }
89
+ .metric-label {
90
+ font-size: 0.875rem;
91
+ color: #64748b;
92
+ margin-bottom: 0.5rem;
93
+ font-weight: 500;
94
+ }
95
+ .metric-value {
96
+ font-size: 2rem;
97
+ font-weight: 700;
98
+ color: #0f172a;
99
+ }
100
+ .section-title {
101
+ font-size: 1.25rem;
102
+ font-weight: 700;
103
+ margin-bottom: 1.5rem;
104
+ color: #0f172a;
105
+ }
106
+ .violation-card {
107
+ background: white;
108
+ border: 1px solid #e2e8f0;
109
+ border-radius: 8px;
110
+ padding: 1.5rem;
111
+ margin-bottom: 1rem;
112
+ page-break-inside: avoid;
113
+ }
114
+ .violation-header {
115
+ display: flex;
116
+ justify-content: space-between;
117
+ align-items: flex-start;
118
+ margin-bottom: 1rem;
119
+ }
120
+ .violation-title {
121
+ font-weight: 600;
122
+ font-size: 1.125rem;
123
+ color: #0f172a;
124
+ }
125
+ .badge {
126
+ display: inline-block;
127
+ padding: 0.25rem 0.75rem;
128
+ border-radius: 9999px;
129
+ font-size: 0.75rem;
130
+ font-weight: 600;
131
+ text-transform: uppercase;
132
+ }
133
+ .badge-critical { background: #fef2f2; color: #dc2626; border: 1px solid #fecaca; }
134
+ .badge-high { background: #fffbeb; color: #d97706; border: 1px solid #fde68a; }
135
+ .badge-medium { background: #fefce8; color: #ca8a04; border: 1px solid #fef08a; }
136
+ .badge-low { background: #f8fafc; color: #475569; border: 1px solid #e2e8f0; }
137
+
138
+ .violation-meta {
139
+ font-size: 0.875rem;
140
+ color: #64748b;
141
+ margin-bottom: 1rem;
142
+ padding-bottom: 1rem;
143
+ border-bottom: 1px solid #f1f5f9;
144
+ }
145
+ .remediation-box {
146
+ background: #f0fdf4;
147
+ border: 1px solid #bbf7d0;
148
+ border-radius: 6px;
149
+ padding: 1rem;
150
+ margin-top: 1rem;
151
+ }
152
+ .remediation-title {
153
+ color: #166534;
154
+ font-weight: 600;
155
+ font-size: 0.875rem;
156
+ margin-bottom: 0.5rem;
157
+ }
158
+ .remediation-text {
159
+ color: #15803d;
160
+ font-size: 0.875rem;
161
+ }
162
+ footer {
163
+ margin-top: 4rem;
164
+ text-align: center;
165
+ color: #94a3b8;
166
+ font-size: 0.75rem;
167
+ border-top: 1px solid #f1f5f9;
168
+ padding-top: 2rem;
169
+ }
170
+ </style>
171
+ </head>
172
+ <body>
173
+ <div class="header">
174
+ <div class="brand">Holm<span>Digital</span></div>
175
+ <div class="meta">
176
+ <div>${t("report.scan_target", { url: result.url })}</div>
177
+ <div>${t("report.generated", { date: formatDate(result.timestamp) })}</div>
178
+ </div>
179
+ </div>
180
+
181
+ <div class="summary-grid">
182
+ <div class="card score-card">
183
+ <div class="metric-label" style="color: #0369a1;">${t("report.overall_score")}</div>
184
+ <div class="metric-value" style="color: ${scoreColor};">${Math.round(result.score)}</div>
185
+ </div>
186
+ <div class="card">
187
+ <div class="metric-label">${t("report.critical_issues")}</div>
188
+ <div class="metric-value" style="color: #dc2626;">${criticalCount}</div>
189
+ </div>
190
+ <div class="card">
191
+ <div class="metric-label">${t("report.high_issues")}</div>
192
+ <div class="metric-value" style="color: #d97706;">${highCount}</div>
193
+ </div>
194
+ <div class="card">
195
+ <div class="metric-label">${t("report.total_issues")}</div>
196
+ <div class="metric-value">${result.stats.total}</div>
197
+ </div>
198
+ </div>
199
+
200
+ <div class="section-title">${t("report.detailed_violations")}</div>
201
+
202
+ ${result.reports.map((report) => {
203
+ const riskClass = `badge-${report.holmdigitalInsight.diggRisk}`;
204
+ return `
205
+ <div class="violation-card">
206
+ <div class="violation-header">
207
+ <div class="violation-title">${report.ruleId}</div>
208
+ <span class="badge ${riskClass}">${report.holmdigitalInsight.diggRisk}</span>
209
+ </div>
210
+ <div class="violation-meta">
211
+ WCAG ${report.wcagCriteria} \u2022 EN 301 549 ${report.en301549Criteria}
212
+ ${report.dosLagenReference ? `\u2022 ${report.dosLagenReference}` : ""}
213
+ </div>
214
+ <div style="font-size: 0.95rem; color: #334155; line-height: 1.5;">
215
+ ${report.holmdigitalInsight.swedishInterpretation}
216
+ ${report.holmdigitalInsight.priorityRationale ? `<br/><br/><strong>Priority Rationale:</strong> ${report.holmdigitalInsight.priorityRationale}` : ""}
217
+ </div>
218
+ ${report.remediation.component ? `
219
+ <div class="remediation-box">
220
+ <div class="remediation-title">${t("report.prescriptive_fix")}</div>
221
+ <div class="remediation-text">${t("report.use")} <strong>${report.remediation.component}</strong>: ${report.remediation.description}</div>
222
+ </div>
223
+ ` : ""}
224
+ </div>
225
+ `;
226
+ }).join("")}
227
+
228
+ <footer>
229
+ ${t("report.footer")}
230
+ </footer>
231
+ </body>
232
+ </html>
233
+ `;
234
+ }
235
+
236
+ // src/reporting/pdf-generator.ts
237
+ import puppeteer from "puppeteer";
238
+ async function generatePDF(htmlContent, outputPath) {
239
+ const browser = await puppeteer.launch({
240
+ headless: true,
241
+ args: ["--no-sandbox", "--disable-setuid-sandbox"]
242
+ });
243
+ try {
244
+ const page = await browser.newPage();
245
+ await page.setContent(htmlContent, {
246
+ waitUntil: "domcontentloaded",
247
+ timeout: 6e4
248
+ });
249
+ await page.pdf({
250
+ path: outputPath,
251
+ format: "A4",
252
+ printBackground: true,
253
+ margin: {
254
+ top: "0px",
255
+ right: "0px",
256
+ bottom: "0px",
257
+ left: "0px"
258
+ }
259
+ });
260
+ } finally {
261
+ await browser.close();
262
+ }
263
+ }
264
+
265
+ // src/cli/cloud-client.ts
266
+ var ENGINE_VERSION = "1.1.0";
267
+ function transformToCloudPayload(result) {
268
+ const violations = result.reports.map((report) => {
269
+ const firstNode = report.failingNodes?.[0];
270
+ return {
271
+ rule_id: report.ruleId,
272
+ impact: report.holmdigitalInsight?.diggRisk || "medium",
273
+ wcag_criteria: report.wcagCriteria ? [report.wcagCriteria] : [],
274
+ element_selector: firstNode?.target || "",
275
+ element_html: firstNode?.html || "",
276
+ failure_summary: report.holmdigitalInsight?.reasoning || report.remediation?.description || "",
277
+ fix_suggestion: report.remediation?.description || ""
278
+ };
279
+ });
280
+ return {
281
+ url: result.url,
282
+ compliance_score: result.score,
283
+ compliance_status: result.complianceStatus,
284
+ total_violations: result.stats.total,
285
+ critical_count: result.stats.critical,
286
+ serious_count: result.stats.high,
287
+ moderate_count: result.stats.medium,
288
+ minor_count: result.stats.low,
289
+ engine_version: ENGINE_VERSION,
290
+ violations
291
+ };
292
+ }
293
+ async function sendToCloud(config, result) {
294
+ const payload = transformToCloudPayload(result);
295
+ const endpoint = `${config.cloudUrl.replace(/\/$/, "")}/api/v1/ingest`;
296
+ try {
297
+ const response = await fetch(endpoint, {
298
+ method: "POST",
299
+ headers: {
300
+ "Content-Type": "application/json",
301
+ "x-api-key": config.apiKey
302
+ },
303
+ body: JSON.stringify(payload)
304
+ });
305
+ if (!response.ok) {
306
+ const errorText = await response.text();
307
+ if (response.status === 401) {
308
+ return {
309
+ success: false,
310
+ error: "Authentication failed. Please check your API key."
311
+ };
312
+ }
313
+ if (response.status === 403) {
314
+ return {
315
+ success: false,
316
+ error: "Access denied. Your API key may not have permission for this action."
317
+ };
318
+ }
319
+ return {
320
+ success: false,
321
+ error: `Server error (${response.status}): ${errorText}`
322
+ };
323
+ }
324
+ const data = await response.json().catch(() => ({}));
325
+ return {
326
+ success: true,
327
+ message: data.message || "Results uploaded successfully"
328
+ };
329
+ } catch (error) {
330
+ const errorMessage = error instanceof Error ? error.message : String(error);
331
+ if (errorMessage.includes("ENOTFOUND") || errorMessage.includes("ECONNREFUSED")) {
332
+ return {
333
+ success: false,
334
+ error: `Could not connect to cloud server at ${config.cloudUrl}`
335
+ };
336
+ }
337
+ return {
338
+ success: false,
339
+ error: `Network error: ${errorMessage}`
340
+ };
341
+ }
342
+ }
343
+
344
+ // src/cli/index.ts
345
+ function isValidUrl(urlString) {
346
+ try {
347
+ const url = new URL(urlString);
348
+ return url.protocol === "http:" || url.protocol === "https:";
349
+ } catch {
350
+ return false;
351
+ }
352
+ }
353
+ var program = new Command();
354
+ program.name("hd-a11y-scan").description("HolmDigital Regulatory Scanner").version("0.1.0");
355
+ program.argument("<url>", "URL to scan").option("--lang <code", "Language code (en, sv)", "en").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("--pdf <path>", "Generate PDF report to path").option("--viewport <size>", 'Set viewport (e.g. "mobile", "desktop", "1024x768")').option("--api-key <key>", "API key for HolmDigital Cloud authentication").option("--cloud-url <url>", "Cloud API URL", "https://cloud.holmdigital.se").action(async (url, options) => {
356
+ setLanguage(options.lang);
357
+ if (!isValidUrl(url)) {
358
+ console.error(chalk.red(`Error: Invalid URL format '${url}'`));
359
+ console.error(chalk.gray("URL must start with http:// or https://"));
360
+ process.exit(1);
361
+ }
362
+ if (!options.json) {
363
+ console.log(chalk.blue.bold(t("cli.title")));
364
+ console.log(chalk.gray(t("cli.scanning", { url })));
365
+ }
366
+ const spinner = !options.json ? ora(t("cli.initializing")).start() : null;
367
+ let scanner;
368
+ try {
369
+ let viewport = { width: 1280, height: 720 };
370
+ if (options.viewport) {
371
+ if (options.viewport === "mobile") viewport = { width: 375, height: 667 };
372
+ else if (options.viewport === "desktop") viewport = { width: 1920, height: 1080 };
373
+ else if (options.viewport === "tablet") viewport = { width: 768, height: 1024 };
374
+ else {
375
+ const [w, h] = options.viewport.split("x").map(Number);
376
+ if (w && h) viewport = { width: w, height: h };
377
+ }
378
+ }
379
+ scanner = new RegulatoryScanner({
380
+ url,
381
+ failOnCritical: options.ci,
382
+ viewport,
383
+ silent: options.json
384
+ // Suppress debug output for JSON mode
385
+ });
386
+ if (spinner) spinner.text = t("cli.analyzing");
387
+ const result = await scanner.scan();
388
+ if (spinner) spinner.succeed(t("cli.complete"));
389
+ if (options.pdf) {
390
+ if (spinner) spinner.start(t("cli.generating_pdf"));
391
+ const html = generateReportHTML(result);
392
+ await generatePDF(html, options.pdf);
393
+ if (spinner) spinner.succeed(t("cli.pdf_saved", { path: options.pdf }));
394
+ }
395
+ if (options.json) {
396
+ console.log(JSON.stringify(result, null, 2));
397
+ } else {
398
+ console.log(chalk.bold(t("cli.score", { score: result.score })));
399
+ const statusColor = result.complianceStatus === "PASS" ? chalk.green : chalk.red;
400
+ console.log(statusColor.bold(t("cli.status", { status: result.complianceStatus })));
401
+ if (result.complianceStatus === "FAIL") {
402
+ console.log(chalk.red(t("cli.not_compliant")));
403
+ }
404
+ console.log(chalk.gray("----------------------------------------"));
405
+ if (options.viewport) {
406
+ console.log(chalk.blue(t("cli.viewport", { width: viewport.width, height: viewport.height })));
407
+ }
408
+ if (result.htmlValidation && !result.htmlValidation.valid) {
409
+ console.log(chalk.red.bold("\n\u26A0\uFE0F Structural HTML Issues Detected"));
410
+ console.log(chalk.yellow(" These issues may affect accessibility tool accuracy (e.g. contrast calculations)\n"));
411
+ result.htmlValidation.errors.forEach((error) => {
412
+ console.log(chalk.red(` [${error.rule}] ${error.message}`));
413
+ if (error.selector) console.log(chalk.gray(` ${error.selector}`));
414
+ console.log(chalk.gray(` Line: ${error.line}, Col: ${error.column}
415
+ `));
416
+ });
417
+ console.log(chalk.gray("----------------------------------------"));
418
+ }
419
+ result.reports.forEach((report) => {
420
+ const color = report.holmdigitalInsight.diggRisk === "critical" ? chalk.red : chalk.yellow;
421
+ console.log(color.bold(`
422
+ [${report.holmdigitalInsight.diggRisk.toUpperCase()}] ${report.ruleId}`));
423
+ console.log(chalk.white(`WCAG: ${report.wcagCriteria} | EN 301 549: ${report.en301549Criteria}`));
424
+ console.log(chalk.gray(`Legitimitet: ${report.dosLagenReference}`));
425
+ if (report.remediation.component) {
426
+ console.log(chalk.green(t("cli.prescriptive_fix")));
427
+ console.log(t("cli.use_component", { component: chalk.bold(report.remediation.component) }));
428
+ }
429
+ if (report.failingNodes && report.failingNodes.length > 0) {
430
+ console.log(chalk.gray("\nAffected Elements:"));
431
+ report.failingNodes.forEach((node, index) => {
432
+ if (index < 5) {
433
+ console.log(chalk.cyan(`\u279C ${node.target}`));
434
+ console.log(chalk.gray(` ${node.html}`));
435
+ }
436
+ });
437
+ if (report.failingNodes.length > 5) {
438
+ console.log(chalk.gray(` ...and ${report.failingNodes.length - 5} more`));
439
+ }
440
+ }
441
+ });
442
+ console.log(chalk.gray("\n----------------------------------------"));
443
+ console.log(`Critical: ${result.stats.critical} | High: ${result.stats.high} | Medium: ${result.stats.medium} | Total: ${result.stats.total}
444
+ `);
445
+ if (options.generateTests) {
446
+ console.log(chalk.magenta.bold(t("cli.pseudo_tests")));
447
+ const automation = new PseudoAutomationEngine();
448
+ result.reports.forEach((report) => {
449
+ if (report.testability.pseudoAutomation) {
450
+ console.log(chalk.cyan(t("cli.test_for", { ruleId: report.ruleId })));
451
+ console.log(chalk.gray(automation.generateTestScript(report, url)));
452
+ }
453
+ });
454
+ }
455
+ }
456
+ if (options.ci && result.stats.critical > 0) {
457
+ if (!options.json) console.error(chalk.red(t("cli.critical_failure")));
458
+ process.exit(1);
459
+ }
460
+ if (options.apiKey) {
461
+ const cloudSpinner = !options.json ? ora("Uploading results to HolmDigital Cloud...").start() : null;
462
+ const cloudConfig = {
463
+ apiKey: options.apiKey,
464
+ cloudUrl: options.cloudUrl
465
+ };
466
+ const cloudResponse = await sendToCloud(cloudConfig, result);
467
+ if (cloudResponse.success) {
468
+ if (cloudSpinner) cloudSpinner.succeed("Results uploaded to HolmDigital Cloud");
469
+ if (!options.json) {
470
+ console.log(chalk.green(`\u2713 ${cloudResponse.message}`));
471
+ }
472
+ } else {
473
+ if (cloudSpinner) cloudSpinner.fail("Cloud upload failed");
474
+ if (!options.json) {
475
+ console.error(chalk.red(`Cloud error: ${cloudResponse.error}`));
476
+ }
477
+ }
478
+ }
479
+ } catch (error) {
480
+ if (spinner) spinner.fail(t("cli.scan_failed"));
481
+ const errorMessage = error instanceof Error ? error.message : String(error);
482
+ if (errorMessage.includes("ERR_NAME_NOT_RESOLVED")) {
483
+ console.error(chalk.red(`Error: Could not resolve domain for '${url}'`));
484
+ console.error(chalk.gray("Please check that the URL is correct and the site is accessible."));
485
+ } else if (errorMessage.includes("ERR_CONNECTION_REFUSED")) {
486
+ console.error(chalk.red(`Error: Connection refused for '${url}'`));
487
+ console.error(chalk.gray("The server may be down or blocking automated access."));
488
+ } else if (errorMessage.includes("Timeout")) {
489
+ console.error(chalk.red(`Error: Connection timed out for '${url}'`));
490
+ console.error(chalk.gray("The page took too long to respond."));
491
+ } else {
492
+ console.error(chalk.red(`Error: ${errorMessage}`));
493
+ }
494
+ process.exit(1);
495
+ } finally {
496
+ if (typeof scanner !== "undefined") {
497
+ await scanner.close();
498
+ }
499
+ }
500
+ });
501
+ program.parse();
@@ -0,0 +1,11 @@
1
+ import {
2
+ getCurrentLang,
3
+ setLanguage,
4
+ t
5
+ } from "./chunk-OXZPYWBW.mjs";
6
+ import "./chunk-Y6FXYEAI.mjs";
7
+ export {
8
+ getCurrentLang,
9
+ setLanguage,
10
+ t
11
+ };
@@ -0,0 +1,88 @@
1
+ import { RegulatoryReport } from '@holmdigital/standards';
2
+ import { Page } from 'puppeteer';
3
+
4
+ interface ValidationResult {
5
+ valid: boolean;
6
+ errors: Array<{
7
+ rule: string;
8
+ message: string;
9
+ line: number;
10
+ column: number;
11
+ selector?: string;
12
+ }>;
13
+ }
14
+
15
+ interface ScannerOptions {
16
+ url: string;
17
+ headless?: boolean;
18
+ standard?: 'wcag' | 'en301549' | 'dos-lagen';
19
+ failOnCritical?: boolean;
20
+ viewport?: {
21
+ width: number;
22
+ height: number;
23
+ };
24
+ silent?: boolean;
25
+ }
26
+ interface ScanResult {
27
+ url: string;
28
+ timestamp: string;
29
+ reports: RegulatoryReport[];
30
+ stats: {
31
+ critical: number;
32
+ high: number;
33
+ medium: number;
34
+ low: number;
35
+ total: number;
36
+ };
37
+ score: number;
38
+ complianceStatus: 'PASS' | 'FAIL';
39
+ htmlValidation?: ValidationResult;
40
+ }
41
+ declare class RegulatoryScanner {
42
+ private browser;
43
+ private options;
44
+ private htmlValidator;
45
+ constructor(options: ScannerOptions);
46
+ private log;
47
+ scan(): Promise<ScanResult>;
48
+ private initBrowser;
49
+ private getPage;
50
+ private injectAxe;
51
+ private enrichResults;
52
+ private generateResultPackage;
53
+ close(): Promise<void>;
54
+ }
55
+
56
+ interface VirtualNode {
57
+ nodeId: string;
58
+ tagName: string;
59
+ attributes: Record<string, string>;
60
+ children: VirtualNode[];
61
+ parentId?: string;
62
+ isShadowRoot?: boolean;
63
+ shadowMode?: 'open' | 'closed';
64
+ rect: {
65
+ x: number;
66
+ y: number;
67
+ width: number;
68
+ height: number;
69
+ };
70
+ computedStyle?: Record<string, string>;
71
+ textContent?: string;
72
+ }
73
+ interface VirtualDOMConfig {
74
+ includeComputedStyle?: string[];
75
+ maxDepth?: number;
76
+ }
77
+ declare class VirtualDOMBuilder {
78
+ private page;
79
+ constructor(page: Page);
80
+ build(config?: VirtualDOMConfig): Promise<VirtualNode>;
81
+ }
82
+
83
+ declare class PseudoAutomationEngine {
84
+ generateTestScript(report: RegulatoryReport, url: string): string;
85
+ generateManualChecklist(report: RegulatoryReport): string;
86
+ }
87
+
88
+ export { PseudoAutomationEngine, RegulatoryScanner, type ScanResult, type ScannerOptions, VirtualDOMBuilder, type VirtualDOMConfig, type VirtualNode };
@@ -0,0 +1,88 @@
1
+ import { RegulatoryReport } from '@holmdigital/standards';
2
+ import { Page } from 'puppeteer';
3
+
4
+ interface ValidationResult {
5
+ valid: boolean;
6
+ errors: Array<{
7
+ rule: string;
8
+ message: string;
9
+ line: number;
10
+ column: number;
11
+ selector?: string;
12
+ }>;
13
+ }
14
+
15
+ interface ScannerOptions {
16
+ url: string;
17
+ headless?: boolean;
18
+ standard?: 'wcag' | 'en301549' | 'dos-lagen';
19
+ failOnCritical?: boolean;
20
+ viewport?: {
21
+ width: number;
22
+ height: number;
23
+ };
24
+ silent?: boolean;
25
+ }
26
+ interface ScanResult {
27
+ url: string;
28
+ timestamp: string;
29
+ reports: RegulatoryReport[];
30
+ stats: {
31
+ critical: number;
32
+ high: number;
33
+ medium: number;
34
+ low: number;
35
+ total: number;
36
+ };
37
+ score: number;
38
+ complianceStatus: 'PASS' | 'FAIL';
39
+ htmlValidation?: ValidationResult;
40
+ }
41
+ declare class RegulatoryScanner {
42
+ private browser;
43
+ private options;
44
+ private htmlValidator;
45
+ constructor(options: ScannerOptions);
46
+ private log;
47
+ scan(): Promise<ScanResult>;
48
+ private initBrowser;
49
+ private getPage;
50
+ private injectAxe;
51
+ private enrichResults;
52
+ private generateResultPackage;
53
+ close(): Promise<void>;
54
+ }
55
+
56
+ interface VirtualNode {
57
+ nodeId: string;
58
+ tagName: string;
59
+ attributes: Record<string, string>;
60
+ children: VirtualNode[];
61
+ parentId?: string;
62
+ isShadowRoot?: boolean;
63
+ shadowMode?: 'open' | 'closed';
64
+ rect: {
65
+ x: number;
66
+ y: number;
67
+ width: number;
68
+ height: number;
69
+ };
70
+ computedStyle?: Record<string, string>;
71
+ textContent?: string;
72
+ }
73
+ interface VirtualDOMConfig {
74
+ includeComputedStyle?: string[];
75
+ maxDepth?: number;
76
+ }
77
+ declare class VirtualDOMBuilder {
78
+ private page;
79
+ constructor(page: Page);
80
+ build(config?: VirtualDOMConfig): Promise<VirtualNode>;
81
+ }
82
+
83
+ declare class PseudoAutomationEngine {
84
+ generateTestScript(report: RegulatoryReport, url: string): string;
85
+ generateManualChecklist(report: RegulatoryReport): string;
86
+ }
87
+
88
+ export { PseudoAutomationEngine, RegulatoryScanner, type ScanResult, type ScannerOptions, VirtualDOMBuilder, type VirtualDOMConfig, type VirtualNode };