@electriccitizen/bolt 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +361 -0
  2. package/dist/adapters/ddev.d.ts +16 -0
  3. package/dist/adapters/ddev.js +75 -0
  4. package/dist/adapters/ddev.js.map +1 -0
  5. package/dist/adapters/index.d.ts +1 -0
  6. package/dist/adapters/index.js +2 -0
  7. package/dist/adapters/index.js.map +1 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +167 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/commands/doctor.d.ts +4 -0
  12. package/dist/commands/doctor.js +263 -0
  13. package/dist/commands/doctor.js.map +1 -0
  14. package/dist/commands/init.d.ts +12 -0
  15. package/dist/commands/init.js +319 -0
  16. package/dist/commands/init.js.map +1 -0
  17. package/dist/commands/pr.d.ts +20 -0
  18. package/dist/commands/pr.js +282 -0
  19. package/dist/commands/pr.js.map +1 -0
  20. package/dist/commands/refresh.d.ts +22 -0
  21. package/dist/commands/refresh.js +375 -0
  22. package/dist/commands/refresh.js.map +1 -0
  23. package/dist/commands/suppress.d.ts +10 -0
  24. package/dist/commands/suppress.js +86 -0
  25. package/dist/commands/suppress.js.map +1 -0
  26. package/dist/commands/test.d.ts +5 -0
  27. package/dist/commands/test.js +106 -0
  28. package/dist/commands/test.js.map +1 -0
  29. package/dist/commands/update.d.ts +26 -0
  30. package/dist/commands/update.js +573 -0
  31. package/dist/commands/update.js.map +1 -0
  32. package/dist/config.d.ts +47 -0
  33. package/dist/config.js +187 -0
  34. package/dist/config.js.map +1 -0
  35. package/dist/formatters/index.d.ts +2 -0
  36. package/dist/formatters/index.js +3 -0
  37. package/dist/formatters/index.js.map +1 -0
  38. package/dist/formatters/json.d.ts +5 -0
  39. package/dist/formatters/json.js +7 -0
  40. package/dist/formatters/json.js.map +1 -0
  41. package/dist/formatters/markdown.d.ts +5 -0
  42. package/dist/formatters/markdown.js +144 -0
  43. package/dist/formatters/markdown.js.map +1 -0
  44. package/dist/formatters/text.d.ts +5 -0
  45. package/dist/formatters/text.js +123 -0
  46. package/dist/formatters/text.js.map +1 -0
  47. package/dist/plugins/accessibility.d.ts +5 -0
  48. package/dist/plugins/accessibility.js +116 -0
  49. package/dist/plugins/accessibility.js.map +1 -0
  50. package/dist/plugins/browser-smoke.d.ts +6 -0
  51. package/dist/plugins/browser-smoke.js +331 -0
  52. package/dist/plugins/browser-smoke.js.map +1 -0
  53. package/dist/plugins/field-interaction.d.ts +6 -0
  54. package/dist/plugins/field-interaction.js +570 -0
  55. package/dist/plugins/field-interaction.js.map +1 -0
  56. package/dist/plugins/index.d.ts +6 -0
  57. package/dist/plugins/index.js +28 -0
  58. package/dist/plugins/index.js.map +1 -0
  59. package/dist/plugins/linkit.d.ts +8 -0
  60. package/dist/plugins/linkit.js +170 -0
  61. package/dist/plugins/linkit.js.map +1 -0
  62. package/dist/plugins/media-browser.d.ts +6 -0
  63. package/dist/plugins/media-browser.js +257 -0
  64. package/dist/plugins/media-browser.js.map +1 -0
  65. package/dist/plugins/structural-smoke.d.ts +6 -0
  66. package/dist/plugins/structural-smoke.js +90 -0
  67. package/dist/plugins/structural-smoke.js.map +1 -0
  68. package/dist/plugins/visual-regression.d.ts +8 -0
  69. package/dist/plugins/visual-regression.js +214 -0
  70. package/dist/plugins/visual-regression.js.map +1 -0
  71. package/dist/plugins/wysiwyg.d.ts +8 -0
  72. package/dist/plugins/wysiwyg.js +221 -0
  73. package/dist/plugins/wysiwyg.js.map +1 -0
  74. package/dist/runner.d.ts +21 -0
  75. package/dist/runner.js +293 -0
  76. package/dist/runner.js.map +1 -0
  77. package/dist/suppression.d.ts +55 -0
  78. package/dist/suppression.js +223 -0
  79. package/dist/suppression.js.map +1 -0
  80. package/dist/types.d.ts +178 -0
  81. package/dist/types.js +5 -0
  82. package/dist/types.js.map +1 -0
  83. package/modules/bolt_inspect/bolt_inspect.info.yml +6 -0
  84. package/modules/bolt_inspect/bolt_inspect.services.yml +22 -0
  85. package/modules/bolt_inspect/composer.json +16 -0
  86. package/modules/bolt_inspect/drush.services.yml +10 -0
  87. package/modules/bolt_inspect/src/Drush/Commands/BoltInspectCommands.php +203 -0
  88. package/modules/bolt_inspect/src/Service/ContentGenerator.php +586 -0
  89. package/modules/bolt_inspect/src/Service/SiteProfiler.php +362 -0
  90. package/modules/bolt_inspect/src/Service/TestEntityTracker.php +98 -0
  91. package/package.json +46 -0
  92. package/scripts/setup.sh +34 -0
package/dist/config.js ADDED
@@ -0,0 +1,187 @@
1
+ /**
2
+ * .bolt.yml config loader and validator.
3
+ */
4
+ import { readFileSync, existsSync } from 'fs';
5
+ import { join } from 'path';
6
+ import { parse as parseYaml } from 'yaml';
7
+ const DEFAULT_REFRESH = {
8
+ base_branch: 'main',
9
+ db: {
10
+ source: 'pantheon',
11
+ },
12
+ };
13
+ const DEFAULT_PR = {
14
+ base_branch: 'main',
15
+ default_reviewers: [],
16
+ draft: false,
17
+ remote: 'origin',
18
+ };
19
+ const DEFAULT_UPDATE = {
20
+ ignore: [],
21
+ priority: [],
22
+ };
23
+ const DEFAULT_CONFIG = {
24
+ adapter: 'ddev',
25
+ plugins: {
26
+ skip: [],
27
+ config: {},
28
+ },
29
+ custom_routes: [],
30
+ refresh: { ...DEFAULT_REFRESH },
31
+ update: { ...DEFAULT_UPDATE },
32
+ pr: { ...DEFAULT_PR },
33
+ };
34
+ /**
35
+ * Find and load .bolt.yml from the current directory.
36
+ * Returns default config if no file exists.
37
+ */
38
+ export function loadConfig(dir) {
39
+ const configPath = join(dir ?? process.cwd(), '.bolt.yml');
40
+ if (!existsSync(configPath)) {
41
+ return { ...DEFAULT_CONFIG };
42
+ }
43
+ const raw = readFileSync(configPath, 'utf-8');
44
+ let parsed;
45
+ try {
46
+ parsed = parseYaml(raw);
47
+ }
48
+ catch {
49
+ // Malformed YAML — fall back to defaults.
50
+ return { ...DEFAULT_CONFIG };
51
+ }
52
+ if (!parsed || typeof parsed !== 'object') {
53
+ return { ...DEFAULT_CONFIG };
54
+ }
55
+ return {
56
+ adapter: typeof parsed.adapter === 'string' ? parsed.adapter : 'ddev',
57
+ plugins: mergePluginsConfig(parsed.plugins),
58
+ custom_routes: Array.isArray(parsed.custom_routes) ? parsed.custom_routes : [],
59
+ refresh: mergeRefreshConfig(parsed.refresh),
60
+ update: mergeUpdateConfig(parsed.update),
61
+ pr: mergePrConfig(parsed.pr),
62
+ };
63
+ }
64
+ function mergePluginsConfig(raw) {
65
+ const defaults = { skip: [], config: {} };
66
+ if (!raw || typeof raw !== 'object')
67
+ return defaults;
68
+ const obj = raw;
69
+ return {
70
+ skip: Array.isArray(obj.skip) ? obj.skip.filter((s) => typeof s === 'string') : [],
71
+ config: obj.config && typeof obj.config === 'object'
72
+ ? obj.config
73
+ : {},
74
+ };
75
+ }
76
+ function mergeRefreshConfig(raw) {
77
+ if (!raw || typeof raw !== 'object')
78
+ return { ...DEFAULT_REFRESH };
79
+ const obj = raw;
80
+ const baseBranch = typeof obj.base_branch === 'string' ? obj.base_branch : 'main';
81
+ let db = { source: 'pantheon' };
82
+ if (obj.db && typeof obj.db === 'object') {
83
+ const dbObj = obj.db;
84
+ db = {
85
+ source: typeof dbObj.source === 'string' ? dbObj.source : 'pantheon',
86
+ pantheon_site: typeof dbObj.pantheon_site === 'string' ? dbObj.pantheon_site : undefined,
87
+ pantheon_env: typeof dbObj.pantheon_env === 'string' ? dbObj.pantheon_env : undefined,
88
+ file_path: typeof dbObj.file_path === 'string' ? dbObj.file_path : undefined,
89
+ };
90
+ }
91
+ return { base_branch: baseBranch, db };
92
+ }
93
+ function mergeUpdateConfig(raw) {
94
+ if (!raw || typeof raw !== 'object')
95
+ return { ...DEFAULT_UPDATE };
96
+ const obj = raw;
97
+ return {
98
+ ignore: Array.isArray(obj.ignore)
99
+ ? obj.ignore.filter((s) => typeof s === 'string')
100
+ : [],
101
+ priority: Array.isArray(obj.priority)
102
+ ? obj.priority.filter((s) => typeof s === 'string')
103
+ : [],
104
+ };
105
+ }
106
+ function mergePrConfig(raw) {
107
+ if (!raw || typeof raw !== 'object')
108
+ return { ...DEFAULT_PR };
109
+ const obj = raw;
110
+ return {
111
+ base_branch: typeof obj.base_branch === 'string' ? obj.base_branch : 'main',
112
+ default_reviewers: Array.isArray(obj.default_reviewers)
113
+ ? obj.default_reviewers.filter((s) => typeof s === 'string')
114
+ : [],
115
+ draft: typeof obj.draft === 'boolean' ? obj.draft : false,
116
+ remote: typeof obj.remote === 'string' ? obj.remote : 'origin',
117
+ };
118
+ }
119
+ /**
120
+ * Validate config and return any issues found.
121
+ */
122
+ export function validateConfig(config) {
123
+ const issues = [];
124
+ const validAdapters = ['ddev', 'pantheon', 'acquia', 'ssh'];
125
+ if (!validAdapters.includes(config.adapter)) {
126
+ issues.push(`Unknown adapter "${config.adapter}". Valid: ${validAdapters.join(', ')}`);
127
+ }
128
+ if (!Array.isArray(config.plugins.skip)) {
129
+ issues.push('plugins.skip must be an array');
130
+ }
131
+ return issues;
132
+ }
133
+ /**
134
+ * Generate a .bolt.yml template string.
135
+ */
136
+ export function generateConfigTemplate() {
137
+ return `# Bolt configuration
138
+ # See: https://github.com/electriccitizen/bolt
139
+
140
+ # Hosting adapter (ddev is the only supported adapter for now)
141
+ adapter: ddev
142
+
143
+ # Test plugin configuration
144
+ plugins:
145
+ # Plugins to skip (by name)
146
+ skip: []
147
+ # - visual-regression
148
+
149
+ # Per-plugin configuration
150
+ config: {}
151
+ # browser-smoke:
152
+ # viewport: [1920, 1080]
153
+ # exclude_urls:
154
+ # - /hidden-test-page
155
+
156
+ # Additional URLs to include in testing (beyond auto-discovered ones)
157
+ custom_routes: []
158
+ # - /products
159
+ # - /about
160
+
161
+ # Environment refresh settings (bolt refresh)
162
+ # refresh:
163
+ # base_branch: main
164
+ # db:
165
+ # source: pantheon # pantheon | file (or any ddev pull provider)
166
+ # pantheon_site: mysite # Terminus site name
167
+ # pantheon_env: live # Environment to pull from
168
+ # # source: file
169
+ # # file_path: /path/to/dump.sql.gz
170
+
171
+ # Update pipeline settings (bolt update)
172
+ # update:
173
+ # ignore:
174
+ # - drupal/core-* # Update core manually
175
+ # priority:
176
+ # - drupal/core-recommended
177
+
178
+ # PR creation settings (bolt pr)
179
+ # pr:
180
+ # base_branch: main
181
+ # default_reviewers:
182
+ # - broeker
183
+ # draft: false
184
+ # remote: origin
185
+ `;
186
+ }
187
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { formatText } from './text.js';
2
+ export { formatJson } from './json.js';
@@ -0,0 +1,3 @@
1
+ export { formatText } from './text.js';
2
+ export { formatJson } from './json.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/formatters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * JSON formatter — structured output for CI/CD and automation.
3
+ */
4
+ import type { TestReport } from '../types.js';
5
+ export declare function formatJson(report: TestReport): string;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * JSON formatter — structured output for CI/CD and automation.
3
+ */
4
+ export function formatJson(report) {
5
+ return JSON.stringify(report, null, 2);
6
+ }
7
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/formatters/json.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,UAAU,UAAU,CAAC,MAAkB;IAC3C,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Markdown formatter — structured report for PRs, tickets, and documentation.
3
+ */
4
+ import type { Severity, TestReport } from '../types.js';
5
+ export declare function formatMarkdown(report: TestReport, failOn?: Severity): string;
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Markdown formatter — structured report for PRs, tickets, and documentation.
3
+ */
4
+ const severityRank = {
5
+ critical: 3,
6
+ major: 2,
7
+ minor: 1,
8
+ info: 0,
9
+ };
10
+ const severityEmoji = {
11
+ critical: '🔴',
12
+ major: '🟠',
13
+ minor: '🟡',
14
+ info: 'ℹ️',
15
+ };
16
+ export function formatMarkdown(report, failOn = 'major') {
17
+ const lines = [];
18
+ // Header.
19
+ lines.push(`# Bolt Test Report`);
20
+ lines.push('');
21
+ lines.push(`**Site:** ${report.site}`);
22
+ lines.push(`**Date:** ${new Date(report.timestamp).toLocaleString()}`);
23
+ lines.push(`**Mode:** ${report.mode}`);
24
+ lines.push(`**Duration:** ${formatDuration(report.duration)}`);
25
+ lines.push('');
26
+ // Summary.
27
+ lines.push('## Summary');
28
+ lines.push('');
29
+ lines.push(`| Metric | Count |`);
30
+ lines.push(`|--------|-------|`);
31
+ lines.push(`| Plugins ran | ${report.summary.pluginsRan} |`);
32
+ lines.push(`| Plugins skipped | ${report.summary.pluginsSkipped} |`);
33
+ lines.push(`| Tests passed | ${report.summary.passed} |`);
34
+ lines.push(`| Tests failed | ${report.summary.failed} |`);
35
+ lines.push(`| Tests skipped | ${report.summary.skipped} |`);
36
+ lines.push(`| Tests suppressed | ${report.summary.suppressed} |`);
37
+ lines.push(`| Warnings | ${report.summary.warned} |`);
38
+ if (report.summary.worstSeverity) {
39
+ lines.push(`| Worst severity | ${report.summary.worstSeverity} |`);
40
+ }
41
+ lines.push('');
42
+ // Plugin details.
43
+ lines.push('## Plugins');
44
+ lines.push('');
45
+ lines.push(`| Plugin | Status | Passed | Failed | Duration |`);
46
+ lines.push(`|--------|--------|--------|--------|----------|`);
47
+ for (const plugin of report.plugins) {
48
+ if (plugin.status === 'ran') {
49
+ const icon = plugin.failed > 0 ? '❌' : '✅';
50
+ lines.push(`| ${icon} ${plugin.name} | ran | ${plugin.passed} | ${plugin.failed} | ${formatDuration(plugin.duration)} |`);
51
+ }
52
+ else {
53
+ lines.push(`| ⏭️ ${plugin.name} | skipped | — | — | ${plugin.reason ?? ''} |`);
54
+ }
55
+ }
56
+ lines.push('');
57
+ // Failures.
58
+ const failures = report.results.filter((r) => r.status === 'fail');
59
+ if (failures.length > 0) {
60
+ lines.push('## Failures');
61
+ lines.push('');
62
+ for (const f of failures) {
63
+ const emoji = severityEmoji[f.severity] ?? '';
64
+ lines.push(`### ${emoji} ${f.plugin} › ${f.test}`);
65
+ lines.push('');
66
+ lines.push(`- **Severity:** ${f.severity}`);
67
+ if (f.message) {
68
+ const urlMatch = f.message.match(/^(https?:\/\/\S+)\s*—\s*(.*)/s);
69
+ if (urlMatch) {
70
+ lines.push(`- **URL:** ${urlMatch[1]}`);
71
+ lines.push(`- **Detail:** ${urlMatch[2]}`);
72
+ }
73
+ else {
74
+ lines.push(`- **Detail:** ${f.message}`);
75
+ }
76
+ }
77
+ if (f.screenshot) {
78
+ lines.push(`- **Screenshot:** \`${f.screenshot}\``);
79
+ }
80
+ lines.push('');
81
+ }
82
+ }
83
+ // Suppressed.
84
+ const suppressed = report.results.filter((r) => r.status === 'suppressed');
85
+ if (suppressed.length > 0) {
86
+ lines.push('## Suppressed');
87
+ lines.push('');
88
+ lines.push('> These known issues are suppressed via `.boltrc.yml` and do not affect the pass/fail result.');
89
+ lines.push('');
90
+ for (const s of suppressed) {
91
+ const reasonMatch = s.message?.match(/\[suppressed: (.+)\]$/);
92
+ const reason = reasonMatch ? reasonMatch[1] : 'suppressed';
93
+ lines.push(`- ⊘ **${s.plugin} › ${s.test}** — ${reason}`);
94
+ }
95
+ lines.push('');
96
+ }
97
+ // Warnings.
98
+ const warnings = report.results.filter((r) => r.status === 'warn');
99
+ if (warnings.length > 0) {
100
+ lines.push('## Warnings');
101
+ lines.push('');
102
+ for (const w of warnings) {
103
+ lines.push(`- **${w.plugin} › ${w.test}:** ${w.message ?? ''}`);
104
+ }
105
+ lines.push('');
106
+ }
107
+ // All test results table.
108
+ lines.push('## All Test Results');
109
+ lines.push('');
110
+ lines.push(`| Plugin | Test | Status | Severity | Duration |`);
111
+ lines.push(`|--------|------|--------|----------|----------|`);
112
+ for (const r of report.results) {
113
+ const statusIcon = r.status === 'pass' ? '✅' : r.status === 'fail' ? '❌' : r.status === 'warn' ? '⚠️' : r.status === 'suppressed' ? '⊘' : '⏭️';
114
+ lines.push(`| ${r.plugin} | ${r.test} | ${statusIcon} ${r.status} | ${r.severity} | ${formatDuration(r.duration)} |`);
115
+ }
116
+ lines.push('');
117
+ // Result line.
118
+ if (failures.length > 0) {
119
+ const threshold = severityRank[failOn];
120
+ const qualifying = failures.filter((f) => severityRank[f.severity] >= threshold);
121
+ if (qualifying.length > 0) {
122
+ lines.push(`> **Result:** ${qualifying.length} failure(s) at or above \`${failOn}\` severity threshold.`);
123
+ }
124
+ else {
125
+ lines.push(`> **Result:** ${failures.length} failure(s) below \`${failOn}\` severity threshold — non-blocking.`);
126
+ }
127
+ }
128
+ else if (report.summary.passed === 0 && report.summary.skipped > 0) {
129
+ lines.push(`> **Result:** ${report.summary.skipped} baseline(s) captured. Re-run to compare.`);
130
+ }
131
+ else {
132
+ lines.push(`> **Result:** All ${report.summary.passed} tests passed.`);
133
+ }
134
+ lines.push('');
135
+ lines.push('---');
136
+ lines.push('*Generated by [bolt](https://github.com/electriccitizen/bolt)*');
137
+ return lines.join('\n');
138
+ }
139
+ function formatDuration(ms) {
140
+ if (ms < 1000)
141
+ return `${ms}ms`;
142
+ return `${(ms / 1000).toFixed(1)}s`;
143
+ }
144
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/formatters/markdown.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Text formatter — terminal output for developers.
3
+ */
4
+ import type { Severity, TestReport } from '../types.js';
5
+ export declare function formatText(report: TestReport, failOn?: Severity): string;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Text formatter — terminal output for developers.
3
+ */
4
+ const severityRank = {
5
+ critical: 3,
6
+ major: 2,
7
+ minor: 1,
8
+ info: 0,
9
+ };
10
+ export function formatText(report, failOn = 'major') {
11
+ const lines = [];
12
+ lines.push(`bolt test — ${report.site}`);
13
+ lines.push('━'.repeat(50));
14
+ lines.push('');
15
+ // Plugin summary.
16
+ const suppressedNote = report.summary.suppressed > 0
17
+ ? `, ${report.summary.suppressed} suppressed`
18
+ : '';
19
+ lines.push(`Plugins: ${report.summary.pluginsRan} ran, ${report.summary.pluginsSkipped} skipped`);
20
+ lines.push(`Duration: ${formatDuration(report.duration)}`);
21
+ lines.push('');
22
+ for (const plugin of report.plugins) {
23
+ if (plugin.status === 'ran') {
24
+ const icon = plugin.failed > 0 ? '✗' : '✓';
25
+ const detail = plugin.failed > 0
26
+ ? `${plugin.failed} failed`
27
+ : plugin.skipped > 0 && plugin.passed === 0
28
+ ? `${plugin.skipped} baseline(s) captured`
29
+ : `${plugin.tests} tests passed`;
30
+ lines.push(` ${icon} ${padRight(plugin.name, 22)} ${padRight(detail, 30)} (${formatDuration(plugin.duration)})`);
31
+ }
32
+ else {
33
+ lines.push(` ⊘ ${padRight(plugin.name, 22)} skipped (${plugin.reason})`);
34
+ }
35
+ }
36
+ // Failures.
37
+ const failures = report.results.filter((r) => r.status === 'fail');
38
+ if (failures.length > 0) {
39
+ lines.push('');
40
+ lines.push('Failures:');
41
+ for (const f of failures) {
42
+ lines.push(` ${f.plugin} > ${f.test}`);
43
+ lines.push(` Severity: ${f.severity}`);
44
+ if (f.message) {
45
+ // Parse structured failure messages for readable output.
46
+ // Format: "URL — issue details"
47
+ const urlMatch = f.message.match(/^(https?:\/\/\S+)\s*—\s*(.*)/s);
48
+ if (urlMatch) {
49
+ lines.push(` URL: ${urlMatch[1]}`);
50
+ lines.push(` ${urlMatch[2]}`);
51
+ }
52
+ else {
53
+ lines.push(` ${f.message}`);
54
+ }
55
+ }
56
+ if (f.screenshot)
57
+ lines.push(` Screenshot: ${f.screenshot}`);
58
+ lines.push('');
59
+ }
60
+ }
61
+ // Suppressed.
62
+ const suppressed = report.results.filter((r) => r.status === 'suppressed');
63
+ if (suppressed.length > 0) {
64
+ lines.push('');
65
+ lines.push('Suppressed:');
66
+ for (const s of suppressed) {
67
+ lines.push(` ⊘ ${s.plugin} > ${s.test}`);
68
+ if (s.message) {
69
+ // Extract suppression reason from the bracketed suffix.
70
+ const reasonMatch = s.message.match(/\[suppressed: (.+)\]$/);
71
+ if (reasonMatch) {
72
+ lines.push(` Reason: ${reasonMatch[1]}`);
73
+ }
74
+ }
75
+ }
76
+ }
77
+ // Warnings.
78
+ const warnings = report.results.filter((r) => r.status === 'warn');
79
+ if (warnings.length > 0) {
80
+ lines.push('');
81
+ lines.push('Warnings:');
82
+ for (const w of warnings) {
83
+ lines.push(` ${w.plugin} > ${w.test}: ${w.message ?? ''}`);
84
+ }
85
+ }
86
+ // Summary line.
87
+ lines.push('');
88
+ if (failures.length > 0) {
89
+ const severities = failures.map((f) => f.severity);
90
+ const worst = severities.includes('critical')
91
+ ? 'critical'
92
+ : severities.includes('major')
93
+ ? 'major'
94
+ : 'minor';
95
+ const threshold = severityRank[failOn];
96
+ const qualifying = failures.filter((f) => severityRank[f.severity] >= threshold);
97
+ if (qualifying.length > 0) {
98
+ lines.push(`Result: ${qualifying.length} failure(s) at or above ${failOn} — ${report.summary.passed} passed, ${report.summary.skipped} skipped${suppressedNote}`);
99
+ }
100
+ else {
101
+ lines.push(`Result: ${failures.length} failure(s) (${worst}, below ${failOn} threshold) — ${report.summary.passed} passed, ${report.summary.skipped} skipped${suppressedNote}`);
102
+ }
103
+ }
104
+ else if (report.summary.totalTests === 0) {
105
+ lines.push('Result: No tests ran.');
106
+ }
107
+ else if (report.summary.passed === 0 && report.summary.skipped > 0) {
108
+ lines.push(`Result: ${report.summary.skipped} baseline(s) captured. Re-run to compare.`);
109
+ }
110
+ else {
111
+ lines.push(`Result: All ${report.summary.passed} tests passed.${suppressedNote}`);
112
+ }
113
+ return lines.join('\n');
114
+ }
115
+ function formatDuration(ms) {
116
+ if (ms < 1000)
117
+ return `${ms}ms`;
118
+ return `${(ms / 1000).toFixed(1)}s`;
119
+ }
120
+ function padRight(str, len) {
121
+ return str.length >= len ? str : str + ' '.repeat(len - str.length);
122
+ }
123
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/formatters/text.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * accessibility plugin — runs axe-core scans on representative URLs.
3
+ */
4
+ import type { TestPlugin } from '../types.js';
5
+ export declare const accessibilityPlugin: TestPlugin;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * accessibility plugin — runs axe-core scans on representative URLs.
3
+ */
4
+ import { mkdirSync } from 'fs';
5
+ import { join } from 'path';
6
+ import { AxeBuilder } from '@axe-core/playwright';
7
+ /** Map axe impact levels to bolt severity levels. */
8
+ function mapSeverity(impact) {
9
+ switch (impact) {
10
+ case 'critical': return 'critical';
11
+ case 'serious': return 'major';
12
+ case 'moderate': return 'minor';
13
+ case 'minor': return 'info';
14
+ default: return 'minor';
15
+ }
16
+ }
17
+ export const accessibilityPlugin = {
18
+ name: 'accessibility',
19
+ description: 'Run axe-core accessibility scans on representative URLs',
20
+ readOnly: true,
21
+ needsGeneratedContent: false,
22
+ canRun(profile) {
23
+ return profile.representativeUrls.length > 0;
24
+ },
25
+ async run(ctx) {
26
+ const results = [];
27
+ const { siteUrl, browserContext, profile, options } = ctx;
28
+ // Read per-plugin config from .bolt.yml.
29
+ const pluginConfig = options.boltConfig?.plugins?.config?.['accessibility'];
30
+ const rulesToSkip = pluginConfig?.rules_to_skip ?? [];
31
+ const tags = pluginConfig?.tags ?? ['wcag2a', 'wcag2aa'];
32
+ for (const rep of profile.representativeUrls) {
33
+ const start = Date.now();
34
+ const fullUrl = rep.url.startsWith('http') ? rep.url : siteUrl + rep.url;
35
+ let page;
36
+ try {
37
+ page = await browserContext.newPage();
38
+ await page.goto(fullUrl, { waitUntil: 'networkidle', timeout: 30_000 });
39
+ // Build axe scan.
40
+ let axe = new AxeBuilder({ page }).withTags(tags);
41
+ for (const rule of rulesToSkip) {
42
+ axe = axe.disableRules(rule);
43
+ }
44
+ const axeResults = await axe.analyze();
45
+ const duration = Date.now() - start;
46
+ if (axeResults.violations.length === 0) {
47
+ results.push({
48
+ plugin: 'accessibility',
49
+ test: `a11y:${rep.label}`,
50
+ status: 'pass',
51
+ severity: 'info',
52
+ message: `${rep.label} — ${axeResults.passes.length} rules passed`,
53
+ duration,
54
+ });
55
+ }
56
+ else {
57
+ // Group violations by severity and report per-page.
58
+ const violationDetails = axeResults.violations.map((v) => {
59
+ const nodes = v.nodes.length;
60
+ const target = v.nodes[0]?.target?.[0] ?? '';
61
+ return `[${v.impact ?? 'minor'}] ${v.id}: ${v.help} (${nodes} element${nodes > 1 ? 's' : ''}${target ? ', e.g. ' + String(target) : ''})`;
62
+ });
63
+ // Use worst violation severity for the overall result.
64
+ const order = ['minor', 'moderate', 'serious', 'critical'];
65
+ let worstImpact = 'minor';
66
+ for (const v of axeResults.violations) {
67
+ const impact = v.impact ?? 'minor';
68
+ if (order.indexOf(impact) > order.indexOf(worstImpact)) {
69
+ worstImpact = impact;
70
+ }
71
+ }
72
+ // Capture screenshot on failure.
73
+ let screenshot;
74
+ if (options.screenshots) {
75
+ mkdirSync(options.screenshots, { recursive: true });
76
+ const filename = `a11y-${slugify(rep.label)}-${Date.now()}.png`;
77
+ screenshot = join(options.screenshots, filename);
78
+ await page.screenshot({ path: screenshot, fullPage: true });
79
+ }
80
+ results.push({
81
+ plugin: 'accessibility',
82
+ test: `a11y:${rep.label}`,
83
+ status: 'fail',
84
+ severity: mapSeverity(worstImpact),
85
+ message: `${fullUrl} — ${axeResults.violations.length} violation(s):\n${violationDetails.join('\n')}`,
86
+ screenshot,
87
+ duration,
88
+ });
89
+ }
90
+ }
91
+ catch (err) {
92
+ const duration = Date.now() - start;
93
+ results.push({
94
+ plugin: 'accessibility',
95
+ test: `a11y:${rep.label}`,
96
+ status: 'fail',
97
+ severity: 'critical',
98
+ message: `Scan failed for ${rep.label}: ${err instanceof Error ? err.message : String(err)}`,
99
+ duration,
100
+ });
101
+ }
102
+ finally {
103
+ if (page)
104
+ await page.close();
105
+ }
106
+ }
107
+ return results;
108
+ },
109
+ };
110
+ function slugify(str) {
111
+ return str
112
+ .toLowerCase()
113
+ .replace(/[^a-z0-9]+/g, '-')
114
+ .replace(/^-|-$/g, '');
115
+ }
116
+ //# sourceMappingURL=accessibility.js.map