@dallask/a11y-mcp-srv 1.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.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +9 -0
  3. package/README.md +1328 -0
  4. package/bin/server.js +8 -0
  5. package/dist/core/accessibility-runner.d.ts +123 -0
  6. package/dist/core/accessibility-runner.d.ts.map +1 -0
  7. package/dist/core/accessibility-runner.js +465 -0
  8. package/dist/core/accessibility-runner.js.map +1 -0
  9. package/dist/core/basic-auth.d.ts +35 -0
  10. package/dist/core/basic-auth.d.ts.map +1 -0
  11. package/dist/core/basic-auth.js +52 -0
  12. package/dist/core/basic-auth.js.map +1 -0
  13. package/dist/core/config.d.ts +44 -0
  14. package/dist/core/config.d.ts.map +1 -0
  15. package/dist/core/config.js +163 -0
  16. package/dist/core/config.js.map +1 -0
  17. package/dist/core/error-handler.d.ts +66 -0
  18. package/dist/core/error-handler.d.ts.map +1 -0
  19. package/dist/core/error-handler.js +305 -0
  20. package/dist/core/error-handler.js.map +1 -0
  21. package/dist/core/normalize-audit-result.d.ts +18 -0
  22. package/dist/core/normalize-audit-result.d.ts.map +1 -0
  23. package/dist/core/normalize-audit-result.js +118 -0
  24. package/dist/core/normalize-audit-result.js.map +1 -0
  25. package/dist/core/playwright-bootstrap.d.ts +21 -0
  26. package/dist/core/playwright-bootstrap.d.ts.map +1 -0
  27. package/dist/core/playwright-bootstrap.js +144 -0
  28. package/dist/core/playwright-bootstrap.js.map +1 -0
  29. package/dist/core/progress-streamer.d.ts +44 -0
  30. package/dist/core/progress-streamer.d.ts.map +1 -0
  31. package/dist/core/progress-streamer.js +160 -0
  32. package/dist/core/progress-streamer.js.map +1 -0
  33. package/dist/core/result-processor.d.ts +86 -0
  34. package/dist/core/result-processor.d.ts.map +1 -0
  35. package/dist/core/result-processor.js +475 -0
  36. package/dist/core/result-processor.js.map +1 -0
  37. package/dist/core/session-manager.d.ts +73 -0
  38. package/dist/core/session-manager.d.ts.map +1 -0
  39. package/dist/core/session-manager.js +243 -0
  40. package/dist/core/session-manager.js.map +1 -0
  41. package/dist/server.d.ts +10 -0
  42. package/dist/server.d.ts.map +1 -0
  43. package/dist/server.js +1439 -0
  44. package/dist/server.js.map +1 -0
  45. package/dist/tools/aggregate.d.ts +26 -0
  46. package/dist/tools/aggregate.d.ts.map +1 -0
  47. package/dist/tools/aggregate.js +340 -0
  48. package/dist/tools/aggregate.js.map +1 -0
  49. package/dist/tools/analysis.d.ts +68 -0
  50. package/dist/tools/analysis.d.ts.map +1 -0
  51. package/dist/tools/analysis.js +1199 -0
  52. package/dist/tools/analysis.js.map +1 -0
  53. package/dist/tools/audit.d.ts +38 -0
  54. package/dist/tools/audit.d.ts.map +1 -0
  55. package/dist/tools/audit.js +472 -0
  56. package/dist/tools/audit.js.map +1 -0
  57. package/dist/tools/comparison.d.ts +27 -0
  58. package/dist/tools/comparison.d.ts.map +1 -0
  59. package/dist/tools/comparison.js +499 -0
  60. package/dist/tools/comparison.js.map +1 -0
  61. package/dist/tools/export.d.ts +43 -0
  62. package/dist/tools/export.d.ts.map +1 -0
  63. package/dist/tools/export.js +746 -0
  64. package/dist/tools/export.js.map +1 -0
  65. package/dist/tools/filter.d.ts +26 -0
  66. package/dist/tools/filter.d.ts.map +1 -0
  67. package/dist/tools/filter.js +244 -0
  68. package/dist/tools/filter.js.map +1 -0
  69. package/dist/tools/session.d.ts +26 -0
  70. package/dist/tools/session.d.ts.map +1 -0
  71. package/dist/tools/session.js +228 -0
  72. package/dist/tools/session.js.map +1 -0
  73. package/dist/tools/visualize.d.ts +26 -0
  74. package/dist/tools/visualize.d.ts.map +1 -0
  75. package/dist/tools/visualize.js +942 -0
  76. package/dist/tools/visualize.js.map +1 -0
  77. package/dist/types/index.d.ts +792 -0
  78. package/dist/types/index.d.ts.map +1 -0
  79. package/dist/types/index.js +24 -0
  80. package/dist/types/index.js.map +1 -0
  81. package/package.json +69 -0
@@ -0,0 +1,942 @@
1
+ /**
2
+ * Visualization & dashboard tools
3
+ * Implements: generate_dashboard, generate_summary_report
4
+ */
5
+ import { resolveBasicAuth } from '../core/basic-auth.js';
6
+ import { normalizeAuditResult } from '../core/normalize-audit-result.js';
7
+ import { auditUrl } from './audit.js';
8
+ /**
9
+ * Generate ASCII bar chart
10
+ */
11
+ function generateBarChart(data, maxWidth = 50) {
12
+ const entries = Object.entries(data).sort((a, b) => b[1] - a[1]);
13
+ const maxValue = Math.max(...entries.map(([, value]) => value), 1);
14
+ const lines = [];
15
+ entries.forEach(([label, value]) => {
16
+ const barLength = Math.round((value / maxValue) * maxWidth);
17
+ const bar = '█'.repeat(barLength);
18
+ const padding = ' '.repeat(Math.max(0, maxWidth - barLength));
19
+ lines.push(`${label.padEnd(15)} │${bar}${padding} ${value}`);
20
+ });
21
+ return lines.join('\n');
22
+ }
23
+ /**
24
+ * Generate score gauge visualization
25
+ */
26
+ function generateScoreGauge(score) {
27
+ const filled = Math.round(score / 2); // Each block represents 2 points
28
+ const empty = 50 - filled;
29
+ const bar = '█'.repeat(filled) + '░'.repeat(empty);
30
+ let status;
31
+ let emoji;
32
+ if (score >= 90) {
33
+ status = 'Excellent';
34
+ emoji = '🟢';
35
+ }
36
+ else if (score >= 75) {
37
+ status = 'Good';
38
+ emoji = '🟡';
39
+ }
40
+ else if (score >= 60) {
41
+ status = 'Needs Improvement';
42
+ emoji = '🟠';
43
+ }
44
+ else {
45
+ status = 'Critical';
46
+ emoji = '🔴';
47
+ }
48
+ return `${emoji} ${score}/100 ${status}\n${bar}`;
49
+ }
50
+ /**
51
+ * Format dashboard as markdown
52
+ */
53
+ function formatDashboardAsMarkdown(results, includeCharts) {
54
+ const resultsArray = Array.isArray(results) ? results : [results];
55
+ const isMultiple = resultsArray.length > 1;
56
+ const parts = [];
57
+ // Title
58
+ if (isMultiple) {
59
+ parts.push('# Accessibility Dashboard');
60
+ parts.push(`\n**Summary of ${resultsArray.length} audit(s)**\n`);
61
+ }
62
+ else {
63
+ const url = resultsArray[0].metadata?.url || 'Unknown URL';
64
+ parts.push(`# Accessibility Dashboard`);
65
+ parts.push(`\n**URL:** ${url}\n`);
66
+ }
67
+ // Overall metrics
68
+ if (isMultiple) {
69
+ const totalIssues = resultsArray.reduce((sum, r) => sum + r.summary.totalIssues, 0);
70
+ const avgScore = resultsArray.reduce((sum, r) => sum + r.summary.score, 0) /
71
+ resultsArray.length;
72
+ const avgWCAG = {
73
+ A: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.A, 0) /
74
+ resultsArray.length,
75
+ AA: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.AA, 0) /
76
+ resultsArray.length,
77
+ AAA: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.AAA, 0) / resultsArray.length,
78
+ };
79
+ parts.push('## Overall Metrics\n');
80
+ parts.push(`- **Total Issues:** ${totalIssues}`);
81
+ parts.push(`- **Average Score:** ${Math.round(avgScore)}/100`);
82
+ parts.push(`- **WCAG Compliance:** A: ${Math.round(avgWCAG.A)}%, AA: ${Math.round(avgWCAG.AA)}%, AAA: ${Math.round(avgWCAG.AAA)}%`);
83
+ if (includeCharts) {
84
+ parts.push('\n### Score Distribution\n');
85
+ parts.push('```');
86
+ parts.push(generateScoreGauge(Math.round(avgScore)));
87
+ parts.push('```');
88
+ }
89
+ }
90
+ else {
91
+ const result = resultsArray[0];
92
+ parts.push('## Overall Metrics\n');
93
+ parts.push(`- **Total Issues:** ${result.summary.totalIssues}`);
94
+ parts.push(`- **Accessibility Score:** ${result.summary.score}/100`);
95
+ parts.push(`- **WCAG Compliance:** A: ${result.summary.wcagCompliance.A}%, AA: ${result.summary.wcagCompliance.AA}%, AAA: ${result.summary.wcagCompliance.AAA}%`);
96
+ if (includeCharts) {
97
+ parts.push('\n### Score Gauge\n');
98
+ parts.push('```');
99
+ parts.push(generateScoreGauge(result.summary.score));
100
+ parts.push('```');
101
+ }
102
+ }
103
+ // Impact breakdown (axe: critical/serious/moderate/minor; ACE: violation/potentialviolation/...)
104
+ if (isMultiple) {
105
+ const impactCounts = {};
106
+ resultsArray.forEach((result) => {
107
+ Object.entries(result.summary.byImpact).forEach(([impact, count]) => {
108
+ impactCounts[impact] = (impactCounts[impact] || 0) + count;
109
+ });
110
+ });
111
+ parts.push('\n## Issues by Impact Level\n');
112
+ if (includeCharts) {
113
+ parts.push('```');
114
+ parts.push(generateBarChart(impactCounts));
115
+ parts.push('```');
116
+ }
117
+ else {
118
+ Object.entries(impactCounts)
119
+ .sort((a, b) => b[1] - a[1])
120
+ .forEach(([impact, count]) => {
121
+ parts.push(`- **${impact}:** ${count}`);
122
+ });
123
+ }
124
+ }
125
+ else {
126
+ const result = resultsArray[0];
127
+ parts.push('\n## Issues by Impact Level\n');
128
+ if (includeCharts) {
129
+ parts.push('```');
130
+ parts.push(generateBarChart(result.summary.byImpact));
131
+ parts.push('```');
132
+ }
133
+ else {
134
+ Object.entries(result.summary.byImpact)
135
+ .sort((a, b) => b[1] - a[1])
136
+ .forEach(([impact, count]) => {
137
+ parts.push(`- **${impact}:** ${count}`);
138
+ });
139
+ }
140
+ }
141
+ // Category breakdown
142
+ if (isMultiple) {
143
+ const categoryCounts = {};
144
+ resultsArray.forEach((result) => {
145
+ Object.entries(result.summary.byCategory).forEach(([cat, count]) => {
146
+ categoryCounts[cat] = (categoryCounts[cat] || 0) + count;
147
+ });
148
+ });
149
+ parts.push('\n## Issues by Category\n');
150
+ if (includeCharts) {
151
+ parts.push('```');
152
+ parts.push(generateBarChart(categoryCounts));
153
+ parts.push('```');
154
+ }
155
+ else {
156
+ Object.entries(categoryCounts)
157
+ .sort((a, b) => b[1] - a[1])
158
+ .forEach(([category, count]) => {
159
+ parts.push(`- **${category}:** ${count}`);
160
+ });
161
+ }
162
+ }
163
+ else {
164
+ const result = resultsArray[0];
165
+ parts.push('\n## Issues by Category\n');
166
+ if (includeCharts) {
167
+ parts.push('```');
168
+ parts.push(generateBarChart(result.summary.byCategory));
169
+ parts.push('```');
170
+ }
171
+ else {
172
+ Object.entries(result.summary.byCategory)
173
+ .sort((a, b) => b[1] - a[1])
174
+ .forEach(([category, count]) => {
175
+ parts.push(`- **${category}:** ${count}`);
176
+ });
177
+ }
178
+ }
179
+ // Critical blockers and quick wins
180
+ if (!isMultiple) {
181
+ const result = resultsArray[0];
182
+ if (result.criticalBlockers.length > 0) {
183
+ parts.push('\n## 🚨 Critical Blockers\n');
184
+ parts.push(`Found ${result.criticalBlockers.length} critical blocker(s) that must be fixed before launch.\n`);
185
+ result.criticalBlockers.slice(0, 5).forEach((blocker, index) => {
186
+ parts.push(`${index + 1}. **${blocker.description}** (${blocker.affectedElements} element(s))`);
187
+ });
188
+ }
189
+ if (result.quickWins.length > 0) {
190
+ parts.push('\n## ✨ Quick Wins\n');
191
+ parts.push(`Found ${result.quickWins.length} quick win(s) - easy fixes with high impact.\n`);
192
+ result.quickWins.slice(0, 5).forEach((win, index) => {
193
+ parts.push(`${index + 1}. **${win.description}** (${win.affectedElements} element(s), ${win.estimatedTime})`);
194
+ });
195
+ }
196
+ }
197
+ // Per-URL breakdown for multiple results
198
+ if (isMultiple) {
199
+ parts.push('\n## Per-URL Breakdown\n');
200
+ parts.push('| URL | Issues | Score | WCAG A | WCAG AA | WCAG AAA |');
201
+ parts.push('|-----|--------|-------|--------|---------|----------|');
202
+ resultsArray.forEach((result) => {
203
+ const url = result.metadata?.url || 'Unknown';
204
+ const shortUrl = url.length > 50 ? url.substring(0, 47) + '...' : url;
205
+ parts.push(`| ${shortUrl} | ${result.summary.totalIssues} | ${result.summary.score}/100 | ${result.summary.wcagCompliance.A}% | ${result.summary.wcagCompliance.AA}% | ${result.summary.wcagCompliance.AAA}% |`);
206
+ });
207
+ }
208
+ return parts.join('\n');
209
+ }
210
+ /**
211
+ * Format dashboard as text
212
+ */
213
+ function formatDashboardAsText(results, includeCharts) {
214
+ const resultsArray = Array.isArray(results) ? results : [results];
215
+ const isMultiple = resultsArray.length > 1;
216
+ const parts = [];
217
+ // Title
218
+ if (isMultiple) {
219
+ parts.push('ACCESSIBILITY DASHBOARD');
220
+ parts.push(`Summary of ${resultsArray.length} audit(s)\n`);
221
+ }
222
+ else {
223
+ const url = resultsArray[0].metadata?.url || 'Unknown URL';
224
+ parts.push('ACCESSIBILITY DASHBOARD');
225
+ parts.push(`URL: ${url}\n`);
226
+ }
227
+ // Overall metrics
228
+ if (isMultiple) {
229
+ const totalIssues = resultsArray.reduce((sum, r) => sum + r.summary.totalIssues, 0);
230
+ const avgScore = resultsArray.reduce((sum, r) => sum + r.summary.score, 0) /
231
+ resultsArray.length;
232
+ const avgWCAG = {
233
+ A: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.A, 0) /
234
+ resultsArray.length,
235
+ AA: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.AA, 0) /
236
+ resultsArray.length,
237
+ AAA: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.AAA, 0) / resultsArray.length,
238
+ };
239
+ parts.push('OVERALL METRICS');
240
+ parts.push(`Total Issues: ${totalIssues}`);
241
+ parts.push(`Average Score: ${Math.round(avgScore)}/100`);
242
+ parts.push(`WCAG Compliance: A: ${Math.round(avgWCAG.A)}%, AA: ${Math.round(avgWCAG.AA)}%, AAA: ${Math.round(avgWCAG.AAA)}%`);
243
+ if (includeCharts) {
244
+ parts.push('\nSCORE DISTRIBUTION');
245
+ parts.push(generateScoreGauge(Math.round(avgScore)));
246
+ }
247
+ }
248
+ else {
249
+ const result = resultsArray[0];
250
+ parts.push('OVERALL METRICS');
251
+ parts.push(`Total Issues: ${result.summary.totalIssues}`);
252
+ parts.push(`Accessibility Score: ${result.summary.score}/100`);
253
+ parts.push(`WCAG Compliance: A: ${result.summary.wcagCompliance.A}%, AA: ${result.summary.wcagCompliance.AA}%, AAA: ${result.summary.wcagCompliance.AAA}%`);
254
+ if (includeCharts) {
255
+ parts.push('\nSCORE GAUGE');
256
+ parts.push(generateScoreGauge(result.summary.score));
257
+ }
258
+ }
259
+ // Impact breakdown (axe/ACE native levels)
260
+ if (isMultiple) {
261
+ const impactCounts = {};
262
+ resultsArray.forEach((result) => {
263
+ Object.entries(result.summary.byImpact).forEach(([impact, count]) => {
264
+ impactCounts[impact] = (impactCounts[impact] || 0) + count;
265
+ });
266
+ });
267
+ parts.push('\nISSUES BY IMPACT LEVEL');
268
+ if (includeCharts) {
269
+ parts.push(generateBarChart(impactCounts));
270
+ }
271
+ else {
272
+ Object.entries(impactCounts)
273
+ .sort((a, b) => b[1] - a[1])
274
+ .forEach(([impact, count]) => {
275
+ parts.push(`${impact}: ${count}`);
276
+ });
277
+ }
278
+ }
279
+ else {
280
+ const result = resultsArray[0];
281
+ parts.push('\nISSUES BY IMPACT LEVEL');
282
+ if (includeCharts) {
283
+ parts.push(generateBarChart(result.summary.byImpact));
284
+ }
285
+ else {
286
+ Object.entries(result.summary.byImpact)
287
+ .sort((a, b) => b[1] - a[1])
288
+ .forEach(([impact, count]) => {
289
+ parts.push(`${impact}: ${count}`);
290
+ });
291
+ }
292
+ }
293
+ // Category breakdown
294
+ if (isMultiple) {
295
+ const categoryCounts = {};
296
+ resultsArray.forEach((result) => {
297
+ Object.entries(result.summary.byCategory).forEach(([cat, count]) => {
298
+ categoryCounts[cat] = (categoryCounts[cat] || 0) + count;
299
+ });
300
+ });
301
+ parts.push('\nISSUES BY CATEGORY');
302
+ if (includeCharts) {
303
+ parts.push(generateBarChart(categoryCounts));
304
+ }
305
+ else {
306
+ Object.entries(categoryCounts)
307
+ .sort((a, b) => b[1] - a[1])
308
+ .forEach(([category, count]) => {
309
+ parts.push(`${category}: ${count}`);
310
+ });
311
+ }
312
+ }
313
+ else {
314
+ const result = resultsArray[0];
315
+ parts.push('\nISSUES BY CATEGORY');
316
+ if (includeCharts) {
317
+ parts.push(generateBarChart(result.summary.byCategory));
318
+ }
319
+ else {
320
+ Object.entries(result.summary.byCategory)
321
+ .sort((a, b) => b[1] - a[1])
322
+ .forEach(([category, count]) => {
323
+ parts.push(`${category}: ${count}`);
324
+ });
325
+ }
326
+ }
327
+ return parts.join('\n');
328
+ }
329
+ /**
330
+ * Format dashboard as HTML
331
+ */
332
+ function formatDashboardAsHTML(results, includeCharts) {
333
+ const resultsArray = Array.isArray(results) ? results : [results];
334
+ const isMultiple = resultsArray.length > 1;
335
+ const parts = [];
336
+ parts.push(`<!DOCTYPE html>
337
+ <html lang="en">
338
+ <head>
339
+ <meta charset="UTF-8">
340
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
341
+ <title>Accessibility Dashboard</title>
342
+ <style>
343
+ body {
344
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
345
+ max-width: 1200px;
346
+ margin: 0 auto;
347
+ padding: 20px;
348
+ background: #f5f5f5;
349
+ }
350
+ .dashboard {
351
+ background: white;
352
+ border-radius: 8px;
353
+ padding: 30px;
354
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
355
+ }
356
+ h1 { color: #333; border-bottom: 3px solid #4CAF50; padding-bottom: 10px; }
357
+ h2 { color: #555; margin-top: 30px; }
358
+ .metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0; }
359
+ .metric-card {
360
+ background: #f9f9f9;
361
+ padding: 15px;
362
+ border-radius: 6px;
363
+ border-left: 4px solid #4CAF50;
364
+ }
365
+ .metric-value { font-size: 2em; font-weight: bold; color: #333; }
366
+ .metric-label { color: #666; font-size: 0.9em; }
367
+ .chart { background: #f9f9f9; padding: 15px; border-radius: 6px; margin: 10px 0; font-family: monospace; }
368
+ table { width: 100%; border-collapse: collapse; margin: 20px 0; }
369
+ th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
370
+ th { background: #4CAF50; color: white; }
371
+ tr:hover { background: #f5f5f5; }
372
+ .critical { color: #d32f2f; font-weight: bold; }
373
+ .serious { color: #f57c00; }
374
+ .moderate { color: #fbc02d; }
375
+ .minor { color: #388e3c; }
376
+ </style>
377
+ </head>
378
+ <body>
379
+ <div class="dashboard">`);
380
+ // Title
381
+ if (isMultiple) {
382
+ parts.push(`<h1>Accessibility Dashboard</h1>`);
383
+ parts.push(`<p><strong>Summary of ${resultsArray.length} audit(s)</strong></p>`);
384
+ }
385
+ else {
386
+ const url = resultsArray[0].metadata?.url || 'Unknown URL';
387
+ parts.push(`<h1>Accessibility Dashboard</h1>`);
388
+ parts.push(`<p><strong>URL:</strong> ${escapeHtml(url)}</p>`);
389
+ }
390
+ // Overall metrics
391
+ if (isMultiple) {
392
+ const totalIssues = resultsArray.reduce((sum, r) => sum + r.summary.totalIssues, 0);
393
+ const avgScore = resultsArray.reduce((sum, r) => sum + r.summary.score, 0) /
394
+ resultsArray.length;
395
+ const avgWCAG = {
396
+ A: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.A, 0) /
397
+ resultsArray.length,
398
+ AA: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.AA, 0) /
399
+ resultsArray.length,
400
+ AAA: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.AAA, 0) / resultsArray.length,
401
+ };
402
+ parts.push(`<h2>Overall Metrics</h2>`);
403
+ parts.push(`<div class="metrics">`);
404
+ parts.push(`<div class="metric-card"><div class="metric-value">${totalIssues}</div><div class="metric-label">Total Issues</div></div>`);
405
+ parts.push(`<div class="metric-card"><div class="metric-value">${Math.round(avgScore)}</div><div class="metric-label">Average Score</div></div>`);
406
+ parts.push(`<div class="metric-card"><div class="metric-value">${Math.round(avgWCAG.A)}%</div><div class="metric-label">WCAG A</div></div>`);
407
+ parts.push(`<div class="metric-card"><div class="metric-value">${Math.round(avgWCAG.AA)}%</div><div class="metric-label">WCAG AA</div></div>`);
408
+ parts.push(`</div>`);
409
+ if (includeCharts) {
410
+ parts.push(`<div class="chart"><pre>${generateScoreGauge(Math.round(avgScore))}</pre></div>`);
411
+ }
412
+ }
413
+ else {
414
+ const result = resultsArray[0];
415
+ parts.push(`<h2>Overall Metrics</h2>`);
416
+ parts.push(`<div class="metrics">`);
417
+ parts.push(`<div class="metric-card"><div class="metric-value">${result.summary.totalIssues}</div><div class="metric-label">Total Issues</div></div>`);
418
+ parts.push(`<div class="metric-card"><div class="metric-value">${result.summary.score}</div><div class="metric-label">Accessibility Score</div></div>`);
419
+ parts.push(`<div class="metric-card"><div class="metric-value">${result.summary.wcagCompliance.A}%</div><div class="metric-label">WCAG A</div></div>`);
420
+ parts.push(`<div class="metric-card"><div class="metric-value">${result.summary.wcagCompliance.AA}%</div><div class="metric-label">WCAG AA</div></div>`);
421
+ parts.push(`</div>`);
422
+ if (includeCharts) {
423
+ parts.push(`<div class="chart"><pre>${generateScoreGauge(result.summary.score)}</pre></div>`);
424
+ }
425
+ }
426
+ // Impact breakdown (axe/ACE native levels)
427
+ if (isMultiple) {
428
+ const impactCounts = {};
429
+ resultsArray.forEach((result) => {
430
+ Object.entries(result.summary.byImpact).forEach(([impact, count]) => {
431
+ impactCounts[impact] = (impactCounts[impact] || 0) + count;
432
+ });
433
+ });
434
+ parts.push(`<h2>Issues by Impact Level</h2>`);
435
+ if (includeCharts) {
436
+ parts.push(`<div class="chart"><pre>${generateBarChart(impactCounts)}</pre></div>`);
437
+ }
438
+ else {
439
+ parts.push(`<ul>`);
440
+ Object.entries(impactCounts)
441
+ .sort((a, b) => b[1] - a[1])
442
+ .forEach(([impact, count]) => {
443
+ parts.push(`<li class="${impact}"><strong>${impact}:</strong> ${count}</li>`);
444
+ });
445
+ parts.push(`</ul>`);
446
+ }
447
+ }
448
+ else {
449
+ const result = resultsArray[0];
450
+ parts.push(`<h2>Issues by Impact Level</h2>`);
451
+ if (includeCharts) {
452
+ parts.push(`<div class="chart"><pre>${generateBarChart(result.summary.byImpact)}</pre></div>`);
453
+ }
454
+ else {
455
+ parts.push(`<ul>`);
456
+ Object.entries(result.summary.byImpact)
457
+ .sort((a, b) => b[1] - a[1])
458
+ .forEach(([impact, count]) => {
459
+ parts.push(`<li class="${impact}"><strong>${impact}:</strong> ${count}</li>`);
460
+ });
461
+ parts.push(`</ul>`);
462
+ }
463
+ }
464
+ // Category breakdown
465
+ if (isMultiple) {
466
+ const categoryCounts = {};
467
+ resultsArray.forEach((result) => {
468
+ Object.entries(result.summary.byCategory).forEach(([cat, count]) => {
469
+ categoryCounts[cat] = (categoryCounts[cat] || 0) + count;
470
+ });
471
+ });
472
+ parts.push(`<h2>Issues by Category</h2>`);
473
+ if (includeCharts) {
474
+ parts.push(`<div class="chart"><pre>${generateBarChart(categoryCounts)}</pre></div>`);
475
+ }
476
+ else {
477
+ parts.push(`<ul>`);
478
+ Object.entries(categoryCounts)
479
+ .sort((a, b) => b[1] - a[1])
480
+ .forEach(([category, count]) => {
481
+ parts.push(`<li><strong>${category}:</strong> ${count}</li>`);
482
+ });
483
+ parts.push(`</ul>`);
484
+ }
485
+ }
486
+ else {
487
+ const result = resultsArray[0];
488
+ parts.push(`<h2>Issues by Category</h2>`);
489
+ if (includeCharts) {
490
+ parts.push(`<div class="chart"><pre>${generateBarChart(result.summary.byCategory)}</pre></div>`);
491
+ }
492
+ else {
493
+ parts.push(`<ul>`);
494
+ Object.entries(result.summary.byCategory)
495
+ .sort((a, b) => b[1] - a[1])
496
+ .forEach(([category, count]) => {
497
+ parts.push(`<li><strong>${category}:</strong> ${count}</li>`);
498
+ });
499
+ parts.push(`</ul>`);
500
+ }
501
+ }
502
+ // Per-URL breakdown for multiple results
503
+ if (isMultiple) {
504
+ parts.push(`<h2>Per-URL Breakdown</h2>`);
505
+ parts.push(`<table>`);
506
+ parts.push(`<tr><th>URL</th><th>Issues</th><th>Score</th><th>WCAG A</th><th>WCAG AA</th><th>WCAG AAA</th></tr>`);
507
+ resultsArray.forEach((result) => {
508
+ const url = result.metadata?.url || 'Unknown';
509
+ parts.push(`<tr><td>${escapeHtml(url)}</td><td>${result.summary.totalIssues}</td><td>${result.summary.score}/100</td><td>${result.summary.wcagCompliance.A}%</td><td>${result.summary.wcagCompliance.AA}%</td><td>${result.summary.wcagCompliance.AAA}%</td></tr>`);
510
+ });
511
+ parts.push(`</table>`);
512
+ }
513
+ parts.push(` </div>
514
+ </body>
515
+ </html>`);
516
+ return parts.join('\n');
517
+ }
518
+ /**
519
+ * Escape HTML special characters
520
+ */
521
+ function escapeHtml(text) {
522
+ return text
523
+ .replace(/&/g, '&amp;')
524
+ .replace(/</g, '&lt;')
525
+ .replace(/>/g, '&gt;')
526
+ .replace(/"/g, '&quot;')
527
+ .replace(/'/g, '&#039;');
528
+ }
529
+ /**
530
+ * generate_dashboard - Create visual dashboard summary of audit results
531
+ *
532
+ * Generates a visual dashboard with key metrics, charts, and summaries
533
+ * in various formats (text, markdown, HTML, JSON).
534
+ *
535
+ * @param input - Dashboard input (results or URL/array, format, includeCharts)
536
+ * @returns Formatted dashboard with key metrics, charts, and summaries
537
+ */
538
+ export async function generateDashboard(input) {
539
+ const { results, format = 'markdown', includeCharts = true, basicAuthUsername, basicAuthPassword, } = input;
540
+ let auditResults;
541
+ // If input is a URL string, run an audit first (with optional Basic Auth, same as audit_url)
542
+ if (typeof results === 'string') {
543
+ const { urlWithoutAuth, basicAuthUsername: u, basicAuthPassword: p } = resolveBasicAuth(results, basicAuthUsername, basicAuthPassword);
544
+ const singleResult = await auditUrl({ url: urlWithoutAuth, basicAuthUsername: u, basicAuthPassword: p });
545
+ auditResults = singleResult;
546
+ }
547
+ else if (Array.isArray(results)) {
548
+ // If array contains URLs, audit them; otherwise normalize each result object
549
+ const urlResults = await Promise.all(results.map(async (r) => {
550
+ if (typeof r === 'string') {
551
+ const { urlWithoutAuth, basicAuthUsername: u, basicAuthPassword: p } = resolveBasicAuth(r, basicAuthUsername, basicAuthPassword);
552
+ return await auditUrl({ url: urlWithoutAuth, basicAuthUsername: u, basicAuthPassword: p });
553
+ }
554
+ const normalized = normalizeAuditResult(r);
555
+ return normalized ?? r;
556
+ }));
557
+ auditResults = urlResults;
558
+ }
559
+ else {
560
+ const normalized = normalizeAuditResult(results);
561
+ auditResults = normalized ?? results;
562
+ }
563
+ // Format dashboard based on requested format
564
+ let dashboard;
565
+ switch (format) {
566
+ case 'text':
567
+ dashboard = formatDashboardAsText(auditResults, includeCharts);
568
+ break;
569
+ case 'html':
570
+ dashboard = formatDashboardAsHTML(auditResults, includeCharts);
571
+ break;
572
+ case 'json':
573
+ const resultsArray = Array.isArray(auditResults)
574
+ ? auditResults
575
+ : [auditResults];
576
+ dashboard = JSON.stringify({
577
+ summary: resultsArray.map((r) => ({
578
+ url: r.metadata?.url || 'Unknown',
579
+ totalIssues: r.summary?.totalIssues ?? 0,
580
+ score: r.summary?.score ?? 0,
581
+ wcagCompliance: r.summary?.wcagCompliance ?? { A: 0, AA: 0, AAA: 0 },
582
+ byImpact: r.summary?.byImpact ?? {},
583
+ byCategory: r.summary?.byCategory ?? {},
584
+ criticalBlockers: Array.isArray(r.criticalBlockers) ? r.criticalBlockers.length : 0,
585
+ quickWins: Array.isArray(r.quickWins) ? r.quickWins.length : 0,
586
+ })),
587
+ }, null, 2);
588
+ break;
589
+ case 'markdown':
590
+ default:
591
+ dashboard = formatDashboardAsMarkdown(auditResults, includeCharts);
592
+ break;
593
+ }
594
+ return {
595
+ dashboard,
596
+ format,
597
+ includeCharts,
598
+ totalResults: Array.isArray(auditResults) ? auditResults.length : 1,
599
+ };
600
+ }
601
+ /**
602
+ * Generate executive summary report
603
+ */
604
+ function generateExecutiveSummary(results, level) {
605
+ const resultsArray = Array.isArray(results) ? results : [results];
606
+ const isMultiple = resultsArray.length > 1;
607
+ const parts = [];
608
+ if (level === 'executive') {
609
+ // High-level summary for executives
610
+ if (isMultiple) {
611
+ const totalIssues = resultsArray.reduce((sum, r) => sum + r.summary.totalIssues, 0);
612
+ const avgScore = resultsArray.reduce((sum, r) => sum + r.summary.score, 0) /
613
+ resultsArray.length;
614
+ parts.push('## Executive Summary');
615
+ parts.push(`\nAn accessibility audit was conducted on ${resultsArray.length} page(s), identifying ${totalIssues} accessibility issue(s).`);
616
+ parts.push(`\n**Key Findings:**`);
617
+ parts.push(`- Average Accessibility Score: ${Math.round(avgScore)}/100`);
618
+ parts.push(`- Total Issues Found: ${totalIssues}`);
619
+ const criticalCount = resultsArray.reduce((sum, r) => {
620
+ const byImpact = r.summary.byImpact;
621
+ return sum + (byImpact.critical ?? 0) + (byImpact.violation ?? 0) + (byImpact.serious ?? 0) + (byImpact.potentialviolation ?? 0);
622
+ }, 0);
623
+ if (criticalCount > 0) {
624
+ parts.push(`- Critical Issues: ${criticalCount} (must be addressed before launch)`);
625
+ }
626
+ const quickWinsCount = resultsArray.reduce((sum, r) => sum + r.quickWins.length, 0);
627
+ if (quickWinsCount > 0) {
628
+ parts.push(`- Quick Wins Available: ${quickWinsCount} (easy fixes with high impact)`);
629
+ }
630
+ parts.push(`\n**Recommendation:** ${avgScore >= 80 ? 'The site demonstrates good accessibility practices. Continue monitoring and address remaining issues.' : avgScore >= 60 ? 'The site needs improvement to meet accessibility standards. Prioritize critical issues and quick wins.' : 'The site requires significant accessibility improvements before launch. Address critical blockers immediately.'}`);
631
+ }
632
+ else {
633
+ const result = resultsArray[0];
634
+ parts.push('## Executive Summary');
635
+ parts.push(`\nAn accessibility audit was conducted, identifying ${result.summary.totalIssues} accessibility issue(s).`);
636
+ parts.push(`\n**Key Findings:**`);
637
+ parts.push(`- Accessibility Score: ${result.summary.score}/100`);
638
+ parts.push(`- Total Issues Found: ${result.summary.totalIssues}`);
639
+ if (result.criticalBlockers.length > 0) {
640
+ parts.push(`- Critical Blockers: ${result.criticalBlockers.length} (must be fixed before launch)`);
641
+ }
642
+ if (result.quickWins.length > 0) {
643
+ parts.push(`- Quick Wins Available: ${result.quickWins.length} (easy fixes with high impact)`);
644
+ }
645
+ parts.push(`\n**Recommendation:** ${result.summary.score >= 80 ? 'The page demonstrates good accessibility practices. Continue monitoring and address remaining issues.' : result.summary.score >= 60 ? 'The page needs improvement to meet accessibility standards. Prioritize critical issues and quick wins.' : 'The page requires significant accessibility improvements before launch. Address critical blockers immediately.'}`);
646
+ }
647
+ }
648
+ else if (level === 'detailed') {
649
+ // Detailed summary with more information
650
+ if (isMultiple) {
651
+ const totalIssues = resultsArray.reduce((sum, r) => sum + r.summary.totalIssues, 0);
652
+ const avgScore = resultsArray.reduce((sum, r) => sum + r.summary.score, 0) /
653
+ resultsArray.length;
654
+ const avgWCAG = {
655
+ A: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.A, 0) / resultsArray.length,
656
+ AA: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.AA, 0) / resultsArray.length,
657
+ AAA: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.AAA, 0) / resultsArray.length,
658
+ };
659
+ parts.push('## Detailed Summary');
660
+ parts.push(`\nAn accessibility audit was conducted on ${resultsArray.length} page(s), identifying ${totalIssues} accessibility issue(s).`);
661
+ parts.push(`\n**Overall Metrics:**`);
662
+ parts.push(`- Average Accessibility Score: ${Math.round(avgScore)}/100`);
663
+ parts.push(`- Total Issues: ${totalIssues}`);
664
+ parts.push(`- WCAG Compliance: Level A: ${Math.round(avgWCAG.A)}%, Level AA: ${Math.round(avgWCAG.AA)}%, Level AAA: ${Math.round(avgWCAG.AAA)}%`);
665
+ // Impact breakdown (axe/ACE native levels)
666
+ const impactCounts = {};
667
+ resultsArray.forEach((result) => {
668
+ Object.entries(result.summary.byImpact).forEach(([impact, count]) => {
669
+ impactCounts[impact] = (impactCounts[impact] || 0) + count;
670
+ });
671
+ });
672
+ parts.push(`\n**Issues by Impact:**`);
673
+ Object.entries(impactCounts)
674
+ .sort((a, b) => b[1] - a[1])
675
+ .forEach(([impact, count]) => {
676
+ if (count > 0) {
677
+ parts.push(`- ${impact}: ${count}`);
678
+ }
679
+ });
680
+ // Category breakdown
681
+ const categoryCounts = {};
682
+ resultsArray.forEach((result) => {
683
+ Object.entries(result.summary.byCategory).forEach(([cat, count]) => {
684
+ categoryCounts[cat] = (categoryCounts[cat] || 0) + count;
685
+ });
686
+ });
687
+ parts.push(`\n**Issues by Category:**`);
688
+ Object.entries(categoryCounts)
689
+ .sort((a, b) => b[1] - a[1])
690
+ .forEach(([category, count]) => {
691
+ parts.push(`- ${category}: ${count}`);
692
+ });
693
+ const criticalCount = resultsArray.reduce((sum, r) => {
694
+ const byImpact = r.summary.byImpact;
695
+ return sum + (byImpact.critical ?? 0) + (byImpact.violation ?? 0) + (byImpact.serious ?? 0) + (byImpact.potentialviolation ?? 0);
696
+ }, 0);
697
+ const quickWinsCount = resultsArray.reduce((sum, r) => sum + r.quickWins.length, 0);
698
+ parts.push(`\n**Priority Actions:**`);
699
+ if (criticalCount > 0) {
700
+ parts.push(`- Address ${criticalCount} critical issue(s) that prevent users with disabilities from accessing content`);
701
+ }
702
+ if (quickWinsCount > 0) {
703
+ parts.push(`- Implement ${quickWinsCount} quick win(s) for immediate accessibility improvements`);
704
+ }
705
+ parts.push(`\n**Next Steps:** ${avgScore >= 80 ? 'Continue monitoring accessibility and address remaining issues. Consider implementing automated accessibility testing in your CI/CD pipeline.' : avgScore >= 60 ? 'Prioritize critical issues and quick wins. Develop an accessibility remediation plan with clear timelines.' : 'Immediately address critical blockers. Conduct a comprehensive accessibility review and develop a detailed remediation plan.'}`);
706
+ }
707
+ else {
708
+ const result = resultsArray[0];
709
+ parts.push('## Detailed Summary');
710
+ parts.push(`\nAn accessibility audit was conducted, identifying ${result.summary.totalIssues} accessibility issue(s).`);
711
+ parts.push(`\n**Overall Metrics:**`);
712
+ parts.push(`- Accessibility Score: ${result.summary.score}/100`);
713
+ parts.push(`- Total Issues: ${result.summary.totalIssues}`);
714
+ parts.push(`- WCAG Compliance: Level A: ${result.summary.wcagCompliance.A}%, Level AA: ${result.summary.wcagCompliance.AA}%, Level AAA: ${result.summary.wcagCompliance.AAA}%`);
715
+ parts.push(`\n**Issues by Impact:**`);
716
+ Object.entries(result.summary.byImpact)
717
+ .sort((a, b) => b[1] - a[1])
718
+ .forEach(([impact, count]) => {
719
+ if (count > 0) {
720
+ parts.push(`- ${impact}: ${count}`);
721
+ }
722
+ });
723
+ parts.push(`\n**Issues by Category:**`);
724
+ Object.entries(result.summary.byCategory)
725
+ .sort((a, b) => b[1] - a[1])
726
+ .forEach(([category, count]) => {
727
+ parts.push(`- ${category}: ${count}`);
728
+ });
729
+ if (result.criticalBlockers.length > 0) {
730
+ parts.push(`\n**Critical Blockers:**`);
731
+ result.criticalBlockers.slice(0, 5).forEach((blocker, index) => {
732
+ parts.push(`${index + 1}. ${blocker.description} (${blocker.affectedElements} element(s))`);
733
+ });
734
+ }
735
+ if (result.quickWins.length > 0) {
736
+ parts.push(`\n**Quick Wins:**`);
737
+ result.quickWins.slice(0, 5).forEach((win, index) => {
738
+ parts.push(`${index + 1}. ${win.description} (${win.affectedElements} element(s), ${win.estimatedTime})`);
739
+ });
740
+ }
741
+ parts.push(`\n**Next Steps:** ${result.summary.score >= 80 ? 'Continue monitoring accessibility and address remaining issues. Consider implementing automated accessibility testing.' : result.summary.score >= 60 ? 'Prioritize critical issues and quick wins. Develop an accessibility remediation plan.' : 'Immediately address critical blockers. Conduct a comprehensive accessibility review and develop a detailed remediation plan.'}`);
742
+ }
743
+ }
744
+ else {
745
+ // Technical summary with detailed information
746
+ if (isMultiple) {
747
+ const totalIssues = resultsArray.reduce((sum, r) => sum + r.summary.totalIssues, 0);
748
+ const avgScore = resultsArray.reduce((sum, r) => sum + r.summary.score, 0) /
749
+ resultsArray.length;
750
+ parts.push('## Technical Summary');
751
+ parts.push(`\nAccessibility audit results for ${resultsArray.length} page(s):`);
752
+ parts.push(`\n**Statistics:**`);
753
+ parts.push(`- Total Issues: ${totalIssues}`);
754
+ parts.push(`- Average Score: ${Math.round(avgScore)}/100`);
755
+ // Per-URL breakdown
756
+ parts.push(`\n**Per-URL Breakdown:**`);
757
+ resultsArray.forEach((result, index) => {
758
+ const url = result.metadata?.url || 'Unknown';
759
+ parts.push(`\n${index + 1}. ${url}`);
760
+ parts.push(` - Issues: ${result.summary.totalIssues}`);
761
+ parts.push(` - Score: ${result.summary.score}/100`);
762
+ parts.push(` - WCAG: A: ${result.summary.wcagCompliance.A}%, AA: ${result.summary.wcagCompliance.AA}%, AAA: ${result.summary.wcagCompliance.AAA}%`);
763
+ if (result.criticalBlockers.length > 0) {
764
+ parts.push(` - Critical Blockers: ${result.criticalBlockers.length}`);
765
+ }
766
+ if (result.quickWins.length > 0) {
767
+ parts.push(` - Quick Wins: ${result.quickWins.length}`);
768
+ }
769
+ });
770
+ }
771
+ else {
772
+ const result = resultsArray[0];
773
+ const url = result.metadata?.url || 'Unknown';
774
+ parts.push('## Technical Summary');
775
+ parts.push(`\nAccessibility audit results for: ${url}`);
776
+ parts.push(`\n**Statistics:**`);
777
+ parts.push(`- Total Issues: ${result.summary.totalIssues}`);
778
+ parts.push(`- Score: ${result.summary.score}/100`);
779
+ parts.push(`- WCAG Compliance: A: ${result.summary.wcagCompliance.A}%, AA: ${result.summary.wcagCompliance.AA}%, AAA: ${result.summary.wcagCompliance.AAA}%`);
780
+ parts.push(`\n**Impact Breakdown:**`);
781
+ Object.entries(result.summary.byImpact).forEach(([impact, count]) => {
782
+ parts.push(`- ${impact}: ${count}`);
783
+ });
784
+ parts.push(`\n**Category Breakdown:**`);
785
+ Object.entries(result.summary.byCategory).forEach(([category, count]) => {
786
+ parts.push(`- ${category}: ${count}`);
787
+ });
788
+ if (result.criticalBlockers.length > 0) {
789
+ parts.push(`\n**Critical Blockers (${result.criticalBlockers.length}):**`);
790
+ result.criticalBlockers.forEach((blocker) => {
791
+ parts.push(`- ${blocker.ruleId}: ${blocker.description}`);
792
+ parts.push(` - Affected Elements: ${blocker.affectedElements}`);
793
+ parts.push(` - WCAG Level: ${blocker.wcagLevel}`);
794
+ });
795
+ }
796
+ if (result.quickWins.length > 0) {
797
+ parts.push(`\n**Quick Wins (${result.quickWins.length}):**`);
798
+ result.quickWins.forEach((win) => {
799
+ parts.push(`- ${win.ruleId}: ${win.description}`);
800
+ parts.push(` - Affected Elements: ${win.affectedElements}`);
801
+ parts.push(` - Estimated Time: ${win.estimatedTime}`);
802
+ });
803
+ }
804
+ }
805
+ }
806
+ return parts.join('\n');
807
+ }
808
+ /**
809
+ * Format summary report as markdown
810
+ */
811
+ function formatSummaryReportAsMarkdown(results, level) {
812
+ const parts = [];
813
+ parts.push('# Accessibility Summary Report');
814
+ parts.push(`\n**Report Date:** ${new Date().toISOString().split('T')[0]}`);
815
+ const summary = generateExecutiveSummary(results, level);
816
+ parts.push(summary);
817
+ return parts.join('\n');
818
+ }
819
+ /**
820
+ * Format summary report as text
821
+ */
822
+ function formatSummaryReportAsText(results, level) {
823
+ const parts = [];
824
+ parts.push('ACCESSIBILITY SUMMARY REPORT');
825
+ parts.push(`Report Date: ${new Date().toISOString().split('T')[0]}\n`);
826
+ const summary = generateExecutiveSummary(results, level);
827
+ // Convert markdown-style formatting to plain text
828
+ const textSummary = summary
829
+ .replace(/## /g, '\n')
830
+ .replace(/\*\*/g, '')
831
+ .replace(/# /g, '');
832
+ parts.push(textSummary);
833
+ return parts.join('\n');
834
+ }
835
+ /**
836
+ * Format summary report as HTML
837
+ */
838
+ function formatSummaryReportAsHTML(results, level) {
839
+ const parts = [];
840
+ parts.push(`<!DOCTYPE html>
841
+ <html lang="en">
842
+ <head>
843
+ <meta charset="UTF-8">
844
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
845
+ <title>Accessibility Summary Report</title>
846
+ <style>
847
+ body {
848
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
849
+ max-width: 900px;
850
+ margin: 0 auto;
851
+ padding: 20px;
852
+ background: #f5f5f5;
853
+ }
854
+ .report {
855
+ background: white;
856
+ border-radius: 8px;
857
+ padding: 30px;
858
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
859
+ }
860
+ h1 { color: #333; border-bottom: 3px solid #4CAF50; padding-bottom: 10px; }
861
+ h2 { color: #555; margin-top: 30px; }
862
+ p { line-height: 1.6; color: #333; }
863
+ ul { line-height: 1.8; }
864
+ .date { color: #666; font-size: 0.9em; }
865
+ </style>
866
+ </head>
867
+ <body>
868
+ <div class="report">`);
869
+ parts.push(`<h1>Accessibility Summary Report</h1>`);
870
+ parts.push(`<p class="date"><strong>Report Date:</strong> ${new Date().toISOString().split('T')[0]}</p>`);
871
+ const summary = generateExecutiveSummary(results, level);
872
+ // Convert markdown to HTML
873
+ const htmlSummary = summary
874
+ .replace(/## (.+)/g, '<h2>$1</h2>')
875
+ .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
876
+ .replace(/\n- (.+)/g, '<li>$1</li>')
877
+ .replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>')
878
+ .replace(/\n\n/g, '</p><p>')
879
+ .replace(/^/, '<p>')
880
+ .replace(/$/, '</p>');
881
+ parts.push(htmlSummary);
882
+ parts.push(` </div>
883
+ </body>
884
+ </html>`);
885
+ return parts.join('\n');
886
+ }
887
+ /**
888
+ * generate_summary_report - Generate executive summary report
889
+ *
890
+ * Generates an executive summary report with key findings and recommendations
891
+ * in various formats (text, markdown, HTML) and detail levels.
892
+ *
893
+ * @param input - Summary report input (results or URL/array, format, level)
894
+ * @returns Summary report with key findings and recommendations
895
+ */
896
+ export async function generateSummaryReport(input) {
897
+ const { results, format = 'markdown', level = 'executive', basicAuthUsername, basicAuthPassword, } = input;
898
+ let auditResults;
899
+ // If input is a URL string, run an audit first (with optional Basic Auth, same as audit_url)
900
+ if (typeof results === 'string') {
901
+ const { urlWithoutAuth, basicAuthUsername: u, basicAuthPassword: p } = resolveBasicAuth(results, basicAuthUsername, basicAuthPassword);
902
+ const singleResult = await auditUrl({ url: urlWithoutAuth, basicAuthUsername: u, basicAuthPassword: p });
903
+ auditResults = singleResult;
904
+ }
905
+ else if (Array.isArray(results)) {
906
+ // If array contains URLs, audit them; otherwise normalize each result object
907
+ const urlResults = await Promise.all(results.map(async (r) => {
908
+ if (typeof r === 'string') {
909
+ const { urlWithoutAuth, basicAuthUsername: u, basicAuthPassword: p } = resolveBasicAuth(r, basicAuthUsername, basicAuthPassword);
910
+ return await auditUrl({ url: urlWithoutAuth, basicAuthUsername: u, basicAuthPassword: p });
911
+ }
912
+ const normalized = normalizeAuditResult(r);
913
+ return normalized ?? r;
914
+ }));
915
+ auditResults = urlResults;
916
+ }
917
+ else {
918
+ const normalized = normalizeAuditResult(results);
919
+ auditResults = normalized ?? results;
920
+ }
921
+ // Format report based on requested format
922
+ let report;
923
+ switch (format) {
924
+ case 'text':
925
+ report = formatSummaryReportAsText(auditResults, level);
926
+ break;
927
+ case 'html':
928
+ report = formatSummaryReportAsHTML(auditResults, level);
929
+ break;
930
+ case 'markdown':
931
+ default:
932
+ report = formatSummaryReportAsMarkdown(auditResults, level);
933
+ break;
934
+ }
935
+ return {
936
+ report,
937
+ format,
938
+ level,
939
+ totalResults: Array.isArray(auditResults) ? auditResults.length : 1,
940
+ };
941
+ }
942
+ //# sourceMappingURL=visualize.js.map