@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/runner.js ADDED
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Test runner — discovers plugins, fetches site profile, runs tests.
3
+ */
4
+ import { chromium } from 'playwright';
5
+ import { readFileSync } from 'fs';
6
+ import { fileURLToPath } from 'url';
7
+ import { dirname, resolve } from 'path';
8
+ import { getPlugins } from './plugins/index.js';
9
+ import { loadSuppressionConfig, applySuppressions } from './suppression.js';
10
+ /** Write progress messages to stderr (doesn't interfere with JSON stdout). */
11
+ function log(msg) {
12
+ process.stderr.write(` ${msg}\n`);
13
+ }
14
+ /**
15
+ * Fetch the site profile from bolt_inspect via Drush.
16
+ */
17
+ async function fetchProfile(adapter) {
18
+ const result = await adapter.drush('bolt-inspect:profile');
19
+ if (result.exitCode !== 0) {
20
+ throw new Error(`Failed to fetch site profile.\n` +
21
+ `Is the bolt_inspect module enabled? Run: ddev drush en bolt_inspect\n` +
22
+ result.stderr);
23
+ }
24
+ try {
25
+ return JSON.parse(result.stdout);
26
+ }
27
+ catch {
28
+ throw new Error(`Invalid JSON from bolt-inspect:profile.\n` +
29
+ `Output starts with: ${result.stdout.slice(0, 200)}`);
30
+ }
31
+ }
32
+ /**
33
+ * Filter and sort plugins based on mode, profile, and user selection.
34
+ */
35
+ /** @internal Exported for testing. */
36
+ export function selectPlugins(allPlugins, profile, mode, selected, configSkip) {
37
+ const run = [];
38
+ const skipped = [];
39
+ for (const plugin of allPlugins) {
40
+ // User filter.
41
+ if (selected && !selected.includes(plugin.name)) {
42
+ skipped.push(summarySkipped(plugin, 'not selected'));
43
+ continue;
44
+ }
45
+ // Config skip list (from .bolt.yml).
46
+ if (configSkip && configSkip.includes(plugin.name)) {
47
+ skipped.push(summarySkipped(plugin, 'skipped in .bolt.yml'));
48
+ continue;
49
+ }
50
+ // Site applicability.
51
+ if (!plugin.canRun(profile)) {
52
+ skipped.push(summarySkipped(plugin, 'not applicable to this site'));
53
+ continue;
54
+ }
55
+ // Mode checks.
56
+ if (mode === 'read-only' && !plugin.readOnly) {
57
+ skipped.push(summarySkipped(plugin, 'requires admin access (read-only mode)'));
58
+ continue;
59
+ }
60
+ if (mode === 'admin-only' && plugin.needsGeneratedContent) {
61
+ skipped.push(summarySkipped(plugin, 'requires generated content (admin-only mode)'));
62
+ continue;
63
+ }
64
+ // Required modules.
65
+ if (plugin.requiredModules) {
66
+ const missing = plugin.requiredModules.filter((m) => !profile.enabledModules.includes(m));
67
+ if (missing.length > 0) {
68
+ skipped.push(summarySkipped(plugin, `missing modules: ${missing.join(', ')}`));
69
+ continue;
70
+ }
71
+ }
72
+ run.push(plugin);
73
+ }
74
+ // Topological sort by dependsOn (with cycle detection).
75
+ const sorted = topoSort(run);
76
+ return { run: sorted, skipped };
77
+ }
78
+ /**
79
+ * Topological sort with cycle detection.
80
+ */
81
+ /** @internal Exported for testing. */
82
+ export function topoSort(plugins) {
83
+ const byName = new Map(plugins.map((p) => [p.name, p]));
84
+ const visited = new Set();
85
+ const visiting = new Set();
86
+ const result = [];
87
+ function visit(plugin) {
88
+ if (visited.has(plugin.name))
89
+ return;
90
+ if (visiting.has(plugin.name)) {
91
+ throw new Error(`Circular plugin dependency detected: ${plugin.name}`);
92
+ }
93
+ visiting.add(plugin.name);
94
+ for (const dep of plugin.dependsOn ?? []) {
95
+ const depPlugin = byName.get(dep);
96
+ if (depPlugin)
97
+ visit(depPlugin);
98
+ }
99
+ visiting.delete(plugin.name);
100
+ visited.add(plugin.name);
101
+ result.push(plugin);
102
+ }
103
+ for (const plugin of plugins) {
104
+ visit(plugin);
105
+ }
106
+ return result;
107
+ }
108
+ function summarySkipped(plugin, reason) {
109
+ return {
110
+ name: plugin.name,
111
+ status: 'skipped',
112
+ reason,
113
+ tests: 0,
114
+ passed: 0,
115
+ failed: 0,
116
+ skipped: 0,
117
+ duration: 0,
118
+ };
119
+ }
120
+ /**
121
+ * Run the full test pipeline.
122
+ */
123
+ export async function runTests(adapter, options) {
124
+ const startTime = Date.now();
125
+ // 1. Fetch site profile.
126
+ log('Fetching site profile...');
127
+ const profile = await fetchProfile(adapter);
128
+ log(`Found ${profile.contentTypes.length} content types, ${profile.representativeUrls.length} representative URLs`);
129
+ // Version compatibility check.
130
+ if (profile.boltInspectVersion) {
131
+ const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
132
+ try {
133
+ const cliVersion = JSON.parse(readFileSync(pkgPath, 'utf-8')).version;
134
+ if (cliVersion && profile.boltInspectVersion !== cliVersion) {
135
+ log(`Warning: version mismatch — CLI is ${cliVersion}, module is ${profile.boltInspectVersion}`);
136
+ }
137
+ }
138
+ catch {
139
+ // Non-fatal.
140
+ }
141
+ }
142
+ // 2. Filter content types if --content-types is set.
143
+ if (options.contentTypes && options.contentTypes.length > 0) {
144
+ const allowed = new Set(options.contentTypes);
145
+ profile.contentTypes = profile.contentTypes.filter((ct) => allowed.has(ct.id));
146
+ log(`Filtered to ${profile.contentTypes.length} content type(s): ${options.contentTypes.join(', ')}`);
147
+ }
148
+ // 3. Select plugins.
149
+ const allPlugins = getPlugins();
150
+ const { run: pluginsToRun, skipped: skippedPlugins } = selectPlugins(allPlugins, profile, options.mode, options.plugins, options.configSkip);
151
+ if (pluginsToRun.length === 0) {
152
+ log('No applicable plugins to run.');
153
+ }
154
+ // 4. Generate test content if needed (full mode only).
155
+ const needsGenerate = options.mode === 'full' &&
156
+ pluginsToRun.some((p) => p.needsGeneratedContent);
157
+ if (needsGenerate) {
158
+ log('Generating test content...');
159
+ const genResult = await adapter.drush('bolt-inspect:generate');
160
+ if (genResult.exitCode !== 0) {
161
+ throw new Error(`Failed to generate test content: ${genResult.stderr}`);
162
+ }
163
+ }
164
+ // 5. Launch browser + authenticate.
165
+ log('Launching browser...');
166
+ const browser = await chromium.launch({
167
+ headless: !options.headed,
168
+ });
169
+ const allResults = [];
170
+ const pluginSummaries = [...skippedPlugins];
171
+ try {
172
+ const browserContext = await browser.newContext({
173
+ ignoreHTTPSErrors: true,
174
+ });
175
+ // Authenticate if not read-only mode.
176
+ if (options.mode !== 'read-only') {
177
+ try {
178
+ log('Authenticating...');
179
+ const loginUrl = await adapter.getLoginUrl(options.url);
180
+ const page = await browserContext.newPage();
181
+ await page.goto(loginUrl, { waitUntil: 'networkidle', timeout: 30_000 });
182
+ await page.close();
183
+ }
184
+ catch (err) {
185
+ log(`Warning: authentication failed: ${err instanceof Error ? err.message : err}`);
186
+ }
187
+ }
188
+ // 6. Run plugins.
189
+ const ctx = {
190
+ siteUrl: options.url,
191
+ browser,
192
+ browserContext,
193
+ profile,
194
+ adapter,
195
+ mode: options.mode,
196
+ options,
197
+ };
198
+ for (const plugin of pluginsToRun) {
199
+ log(`Running ${plugin.name}...`);
200
+ const pluginStart = Date.now();
201
+ let results;
202
+ try {
203
+ results = await plugin.run(ctx);
204
+ }
205
+ catch (err) {
206
+ results = [
207
+ {
208
+ plugin: plugin.name,
209
+ test: 'plugin-execution',
210
+ status: 'fail',
211
+ severity: 'critical',
212
+ message: `Plugin crashed: ${err instanceof Error ? err.message : String(err)}`,
213
+ duration: Date.now() - pluginStart,
214
+ },
215
+ ];
216
+ }
217
+ allResults.push(...results);
218
+ const pluginDuration = Date.now() - pluginStart;
219
+ const passed = results.filter((r) => r.status === 'pass').length;
220
+ const failed = results.filter((r) => r.status === 'fail').length;
221
+ log(` ${plugin.name}: ${passed} passed, ${failed} failed (${formatMs(pluginDuration)})`);
222
+ pluginSummaries.push({
223
+ name: plugin.name,
224
+ status: 'ran',
225
+ tests: results.length,
226
+ passed,
227
+ failed,
228
+ skipped: results.filter((r) => r.status === 'skip').length,
229
+ duration: pluginDuration,
230
+ });
231
+ }
232
+ }
233
+ finally {
234
+ // Always close the browser, even if plugins crash.
235
+ await browser.close();
236
+ }
237
+ // 7. Cleanup test content.
238
+ if (needsGenerate) {
239
+ log('Cleaning up test content...');
240
+ const cleanResult = await adapter.drush('bolt-inspect:cleanup');
241
+ if (cleanResult.exitCode !== 0) {
242
+ log(`Warning: cleanup failed — run 'ddev drush bolt-inspect:cleanup' manually`);
243
+ }
244
+ }
245
+ // 8. Apply suppression rules from .boltrc.yml (unless skipped).
246
+ let finalResults;
247
+ let suppressedCount = 0;
248
+ if (options.skipSuppression) {
249
+ finalResults = allResults;
250
+ }
251
+ else {
252
+ const suppressionConfig = loadSuppressionConfig();
253
+ finalResults = applySuppressions(allResults, suppressionConfig);
254
+ suppressedCount = finalResults.filter((r) => r.status === 'suppressed').length;
255
+ if (suppressedCount > 0) {
256
+ log(`${suppressedCount} result(s) suppressed via .boltrc.yml`);
257
+ }
258
+ }
259
+ // 9. Build report.
260
+ const duration = Date.now() - startTime;
261
+ const failed = finalResults.filter((r) => r.status === 'fail');
262
+ const severityOrder = ['critical', 'major', 'minor', 'info'];
263
+ const worstSeverity = failed.length > 0
264
+ ? failed.reduce((worst, r) => {
265
+ const wi = severityOrder.indexOf(worst);
266
+ const ri = severityOrder.indexOf(r.severity);
267
+ return ri < wi ? r.severity : worst;
268
+ }, 'info')
269
+ : null;
270
+ return {
271
+ site: options.url,
272
+ timestamp: new Date().toISOString(),
273
+ duration,
274
+ mode: options.mode,
275
+ summary: {
276
+ pluginsRan: pluginsToRun.length,
277
+ pluginsSkipped: skippedPlugins.length,
278
+ totalTests: finalResults.length,
279
+ passed: finalResults.filter((r) => r.status === 'pass').length,
280
+ failed: failed.length,
281
+ skipped: finalResults.filter((r) => r.status === 'skip').length,
282
+ warned: finalResults.filter((r) => r.status === 'warn').length,
283
+ suppressed: suppressedCount,
284
+ worstSeverity,
285
+ },
286
+ results: finalResults,
287
+ plugins: pluginSummaries,
288
+ };
289
+ }
290
+ function formatMs(ms) {
291
+ return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
292
+ }
293
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Suppression system — load .boltrc.yml and match against test results.
3
+ *
4
+ * Suppressed results are still visible in output but don't affect exit codes.
5
+ */
6
+ import type { TestResult } from './types.js';
7
+ export interface JsErrorRule {
8
+ pattern: string;
9
+ reason?: string;
10
+ }
11
+ export interface UrlRule {
12
+ path: string;
13
+ expected_status?: number;
14
+ reason?: string;
15
+ }
16
+ export interface AccessibilityRule {
17
+ rule: string;
18
+ reason?: string;
19
+ }
20
+ export interface PluginRule {
21
+ name: string;
22
+ reason?: string;
23
+ }
24
+ export interface SuppressionConfig {
25
+ suppress: {
26
+ js_errors: JsErrorRule[];
27
+ urls: UrlRule[];
28
+ accessibility: AccessibilityRule[];
29
+ plugins: PluginRule[];
30
+ };
31
+ }
32
+ /**
33
+ * Load .boltrc.yml from the given directory (or cwd).
34
+ */
35
+ export declare function loadSuppressionConfig(dir?: string): SuppressionConfig;
36
+ /**
37
+ * Check if a single test result matches any suppression rule.
38
+ * Returns the reason string if suppressed, or null if not.
39
+ */
40
+ export declare function matchSuppression(result: TestResult, config: SuppressionConfig): string | null;
41
+ /**
42
+ * Apply suppression rules to a set of test results.
43
+ * Returns a new array with matched failures changed to 'suppressed' status.
44
+ * The original message is preserved; the suppression reason is appended.
45
+ */
46
+ export declare function applySuppressions(results: TestResult[], config: SuppressionConfig): TestResult[];
47
+ /**
48
+ * Generate a .boltrc.yml from a set of test failures.
49
+ * Used by `bolt suppress` to create the initial baseline.
50
+ */
51
+ export declare function generateSuppressionConfig(failures: TestResult[]): string;
52
+ /**
53
+ * Write .boltrc.yml to disk.
54
+ */
55
+ export declare function writeSuppressionConfig(dir: string, content: string): string;
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Suppression system — load .boltrc.yml and match against test results.
3
+ *
4
+ * Suppressed results are still visible in output but don't affect exit codes.
5
+ */
6
+ import { readFileSync, existsSync, writeFileSync } from 'fs';
7
+ import { join } from 'path';
8
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
9
+ const EMPTY_CONFIG = {
10
+ suppress: {
11
+ js_errors: [],
12
+ urls: [],
13
+ accessibility: [],
14
+ plugins: [],
15
+ },
16
+ };
17
+ /**
18
+ * Load .boltrc.yml from the given directory (or cwd).
19
+ */
20
+ export function loadSuppressionConfig(dir) {
21
+ const configPath = join(dir ?? process.cwd(), '.boltrc.yml');
22
+ if (!existsSync(configPath)) {
23
+ return structuredClone(EMPTY_CONFIG);
24
+ }
25
+ const raw = readFileSync(configPath, 'utf-8');
26
+ let parsed;
27
+ try {
28
+ parsed = parseYaml(raw);
29
+ }
30
+ catch {
31
+ return structuredClone(EMPTY_CONFIG);
32
+ }
33
+ if (!parsed || typeof parsed !== 'object') {
34
+ return structuredClone(EMPTY_CONFIG);
35
+ }
36
+ const suppress = parsed.suppress;
37
+ if (!suppress || typeof suppress !== 'object') {
38
+ return structuredClone(EMPTY_CONFIG);
39
+ }
40
+ return {
41
+ suppress: {
42
+ js_errors: parseRuleArray(suppress.js_errors, isJsErrorRule),
43
+ urls: parseRuleArray(suppress.urls, isUrlRule),
44
+ accessibility: parseRuleArray(suppress.accessibility, isAccessibilityRule),
45
+ plugins: parseRuleArray(suppress.plugins, isPluginRule),
46
+ },
47
+ };
48
+ }
49
+ function parseRuleArray(raw, guard) {
50
+ if (!Array.isArray(raw))
51
+ return [];
52
+ return raw.filter(guard);
53
+ }
54
+ function isJsErrorRule(v) {
55
+ return typeof v === 'object' && v !== null && typeof v.pattern === 'string';
56
+ }
57
+ function isUrlRule(v) {
58
+ return typeof v === 'object' && v !== null && typeof v.path === 'string';
59
+ }
60
+ function isAccessibilityRule(v) {
61
+ return typeof v === 'object' && v !== null && typeof v.rule === 'string';
62
+ }
63
+ function isPluginRule(v) {
64
+ return typeof v === 'object' && v !== null && typeof v.name === 'string';
65
+ }
66
+ /**
67
+ * Check if a single test result matches any suppression rule.
68
+ * Returns the reason string if suppressed, or null if not.
69
+ */
70
+ export function matchSuppression(result, config) {
71
+ if (result.status !== 'fail')
72
+ return null;
73
+ const { suppress } = config;
74
+ // 1. Plugin-level suppression (entire plugin suppressed).
75
+ for (const rule of suppress.plugins) {
76
+ if (result.plugin === rule.name) {
77
+ return rule.reason ?? `Plugin "${rule.name}" suppressed`;
78
+ }
79
+ }
80
+ // 2. JS error suppression (pattern match on message).
81
+ if (result.message) {
82
+ for (const rule of suppress.js_errors) {
83
+ try {
84
+ const regex = new RegExp(rule.pattern, 'i');
85
+ if (regex.test(result.message)) {
86
+ return rule.reason ?? `Matches JS error pattern: ${rule.pattern}`;
87
+ }
88
+ }
89
+ catch {
90
+ // Invalid regex — try plain string match.
91
+ if (result.message.toLowerCase().includes(rule.pattern.toLowerCase())) {
92
+ return rule.reason ?? `Matches JS error pattern: ${rule.pattern}`;
93
+ }
94
+ }
95
+ }
96
+ }
97
+ // 3. URL suppression (path match on message or test name).
98
+ if (result.message) {
99
+ for (const rule of suppress.urls) {
100
+ if (result.message.includes(rule.path)) {
101
+ return rule.reason ?? `URL "${rule.path}" suppressed`;
102
+ }
103
+ }
104
+ }
105
+ // 4. Accessibility rule suppression.
106
+ if (result.plugin === 'accessibility' && result.message) {
107
+ for (const rule of suppress.accessibility) {
108
+ if (result.test.includes(rule.rule) || result.message.includes(rule.rule)) {
109
+ return rule.reason ?? `Accessibility rule "${rule.rule}" suppressed`;
110
+ }
111
+ }
112
+ }
113
+ return null;
114
+ }
115
+ /**
116
+ * Apply suppression rules to a set of test results.
117
+ * Returns a new array with matched failures changed to 'suppressed' status.
118
+ * The original message is preserved; the suppression reason is appended.
119
+ */
120
+ export function applySuppressions(results, config) {
121
+ return results.map((result) => {
122
+ const reason = matchSuppression(result, config);
123
+ if (reason !== null) {
124
+ return {
125
+ ...result,
126
+ status: 'suppressed',
127
+ message: result.message
128
+ ? `${result.message} [suppressed: ${reason}]`
129
+ : `[suppressed: ${reason}]`,
130
+ };
131
+ }
132
+ return result;
133
+ });
134
+ }
135
+ /**
136
+ * Generate a .boltrc.yml from a set of test failures.
137
+ * Used by `bolt suppress` to create the initial baseline.
138
+ */
139
+ export function generateSuppressionConfig(failures) {
140
+ const jsErrors = [];
141
+ const urls = [];
142
+ const accessibilityRules = [];
143
+ const date = new Date().toISOString().slice(0, 10);
144
+ for (const f of failures) {
145
+ if (f.status !== 'fail')
146
+ continue;
147
+ // Accessibility failures → accessibility rules.
148
+ if (f.plugin === 'accessibility') {
149
+ // Extract rule ID from test name (e.g., "accessibility/aria-hidden-focus").
150
+ const ruleMatch = f.test.match(/accessibility\/(.+)/);
151
+ if (ruleMatch) {
152
+ const ruleId = ruleMatch[1];
153
+ if (!accessibilityRules.some((r) => r.rule === ruleId)) {
154
+ accessibilityRules.push({
155
+ rule: ruleId,
156
+ reason: `Baseline captured ${date}`,
157
+ });
158
+ }
159
+ continue;
160
+ }
161
+ }
162
+ // JS error failures → js_errors rules.
163
+ if (f.plugin === 'browser-smoke' && f.test.includes('js-errors') && f.message) {
164
+ // Extract the error text for a pattern.
165
+ const errorText = f.message.replace(/^https?:\/\/\S+\s*—\s*/, '').slice(0, 80);
166
+ if (errorText && !jsErrors.some((r) => r.pattern === errorText)) {
167
+ jsErrors.push({
168
+ pattern: escapeRegex(errorText),
169
+ reason: `Baseline captured ${date}`,
170
+ });
171
+ }
172
+ continue;
173
+ }
174
+ // URL-specific failures → urls rules.
175
+ if (f.message) {
176
+ const urlMatch = f.message.match(/^(https?:\/\/[^\s]+)/);
177
+ if (urlMatch) {
178
+ try {
179
+ const parsed = new URL(urlMatch[1]);
180
+ if (!urls.some((r) => r.path === parsed.pathname)) {
181
+ urls.push({
182
+ path: parsed.pathname,
183
+ reason: `Baseline captured ${date}: ${f.test}`,
184
+ });
185
+ }
186
+ }
187
+ catch {
188
+ // Not a valid URL — skip.
189
+ }
190
+ continue;
191
+ }
192
+ }
193
+ // Fallback: suppress by plugin + test pattern via js_errors (general pattern match).
194
+ const pattern = `${f.plugin}.*${f.test}`;
195
+ if (!jsErrors.some((r) => r.pattern === pattern)) {
196
+ jsErrors.push({
197
+ pattern,
198
+ reason: `Baseline captured ${date}`,
199
+ });
200
+ }
201
+ }
202
+ const config = {
203
+ suppress: {
204
+ ...(jsErrors.length > 0 ? { js_errors: jsErrors } : {}),
205
+ ...(urls.length > 0 ? { urls } : {}),
206
+ ...(accessibilityRules.length > 0 ? { accessibility: accessibilityRules } : {}),
207
+ },
208
+ };
209
+ const header = `# Bolt suppression rules — known issues that don't affect pass/fail.\n# Generated by: bolt suppress (${date})\n# Edit manually or re-run bolt suppress to update.\n\n`;
210
+ return header + stringifyYaml(config, { lineWidth: 120 });
211
+ }
212
+ /**
213
+ * Write .boltrc.yml to disk.
214
+ */
215
+ export function writeSuppressionConfig(dir, content) {
216
+ const path = join(dir, '.boltrc.yml');
217
+ writeFileSync(path, content, 'utf-8');
218
+ return path;
219
+ }
220
+ function escapeRegex(str) {
221
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
222
+ }
223
+ //# sourceMappingURL=suppression.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suppression.js","sourceRoot":"","sources":["../src/suppression.ts"],"names":[],"mappings":"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"}