@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/README.md ADDED
@@ -0,0 +1,361 @@
1
+ # Bolt
2
+
3
+ CLI tool for automated Drupal 11+ site testing, updates, and PR creation. Built by [Electric Citizen](https://electriccitizen.com).
4
+
5
+ Install once per site ("Bolt Certified"), pull updates via npm without re-initializing.
6
+
7
+ ## Principles
8
+
9
+ These guide every design decision in Bolt:
10
+
11
+ - **Non-invasive.** You can remove Bolt from any project with no consequence. Bolt doesn't modify your site's code, config, or database beyond its own tracked test content (which it cleans up). Uninstalling the module and removing the Composer entry is a clean exit.
12
+ - **Transparent.** Bolt tells you what it's doing at every step. Progress output, clear error messages, and no hidden side effects. If Bolt changes something, it says so.
13
+ - **Careful.** When a destructive, ambiguous, or unexpected situation arises, Bolt warns you and asks before proceeding. It won't overwrite your database, force-push your code, or modify files outside its scope without explicit confirmation.
14
+ - **Idempotent.** Every command is safe to re-run. `bolt init` skips steps already done. `bolt suppress` regenerates cleanly. `bolt test` leaves the site in the state it found it.
15
+ - **Automation-friendly.** Predictable exit codes (0/1/2), structured output formats (JSON, markdown), no interactive prompts in test/CI paths. Humans and scripts get the same reliability.
16
+ - **Site-agnostic.** Bolt adapts to what it finds. It reads your site's structure and tests accordingly — no hardcoded content types, no assumptions about your setup. Works on any Drupal 11+ site.
17
+
18
+ ## Requirements
19
+
20
+ - Node.js 20+
21
+ - DDEV
22
+ - A Drupal 11+ site running in DDEV
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ # npm global install (recommended)
28
+ npm install -g @electriccitizen/bolt
29
+ npx playwright install chromium
30
+
31
+ # Verify
32
+ bolt --version
33
+ bolt doctor
34
+ ```
35
+
36
+ **Development install** (for contributors):
37
+
38
+ ```bash
39
+ git clone git@github.com:electriccitizen/bolt.git ~/projects/bolt
40
+ cd ~/projects/bolt
41
+ ./scripts/setup.sh
42
+ ```
43
+
44
+ ## Quick Start
45
+
46
+ ```bash
47
+ # From your Drupal project directory:
48
+ git checkout -b bolt # don't run bolt init on main!
49
+ bolt init # installs module, creates config
50
+ bolt doctor # verify environment
51
+ bolt test # full test suite
52
+
53
+ # Capture known issues as baseline
54
+ bolt suppress # creates .boltrc.yml
55
+ bolt test # now only NEW regressions cause failures
56
+
57
+ # Before running updates, sync local environment
58
+ bolt refresh --db # pull latest code + database
59
+ ```
60
+
61
+ ## Commands
62
+
63
+ ### `bolt test`
64
+
65
+ Run tests against a Drupal site. Auto-discovers what to test based on site structure.
66
+
67
+ ```bash
68
+ bolt test [options]
69
+ --url <url> Site URL (auto-detected from DDEV if omitted)
70
+ --mode <mode> full | read-only | admin-only (default: full)
71
+ --plugins <list> Comma-separated plugin names to run
72
+ --output <format> text | json | markdown (default: text, auto-json when piped)
73
+ --report <path> Write report to file
74
+ --exit-code Non-zero exit on failure (for CI)
75
+ --fail-on <severity> Threshold: critical | major | minor (default: major)
76
+ --headed Show browser window
77
+ --screenshots <dir> Save failure screenshots
78
+ --vr-baseline <dir> Visual regression baseline directory
79
+ --vr-threshold <n> VR diff threshold percentage (default: 0.1)
80
+ --content-types <list> Limit to specific content types
81
+ ```
82
+
83
+ **Exit codes:** 0 = pass, 1 = failure at or above threshold, 2 = error.
84
+
85
+ **Execution modes:**
86
+ - `full` — generate test content, authenticate, run all plugins, cleanup
87
+ - `read-only` — no auth, no content changes, only anonymous-safe plugins
88
+ - `admin-only` — authenticate but don't generate content
89
+
90
+ **Auto-JSON:** When stdout is piped (non-TTY), bolt outputs JSON automatically.
91
+
92
+ **Examples:**
93
+
94
+ ```bash
95
+ bolt test --screenshots=./screenshots # full suite with failure screenshots
96
+ bolt test --mode=read-only # anonymous-only tests
97
+ bolt test --plugins=accessibility # single plugin
98
+ bolt test --content-types=page,blog # specific content types
99
+ bolt test --exit-code --fail-on=major # CI mode
100
+ bolt test --output=markdown --report=report.md # markdown report for a PR
101
+ ```
102
+
103
+ ### `bolt suppress`
104
+
105
+ Capture current test failures as a suppression baseline. Creates `.boltrc.yml`.
106
+
107
+ ```bash
108
+ bolt suppress [options]
109
+ --url <url> Site URL (auto-detected from DDEV)
110
+ --dry-run Preview without writing file
111
+ --mode <mode> Execution mode (default: full)
112
+ ```
113
+
114
+ Suppressed results appear in output but don't affect exit codes. Only *new* regressions cause failures. Safe to re-run — always captures the full current state.
115
+
116
+ ### `bolt refresh`
117
+
118
+ Sync local environment to match production before running updates.
119
+
120
+ ```bash
121
+ bolt refresh [options]
122
+ --db Pull fresh database (prompted if omitted)
123
+ --skip-db Skip database pull entirely
124
+ --source <env> Database source environment (default: live)
125
+ --yes Skip confirmation prompts
126
+ --branch <name> Base branch to checkout (default: main)
127
+ ```
128
+
129
+ What it does:
130
+ 1. Checks for uncommitted changes (warns, prompts to continue)
131
+ 2. Checks out base branch and pulls latest
132
+ 3. Starts DDEV if not running
133
+ 4. Runs `composer install` (sync to lockfile)
134
+ 5. Optionally pulls database from hosting provider
135
+ 6. Runs `drush cr`, `drush updb`, `drush cim`, `drush cr`
136
+
137
+ Each step logs its duration. Exits early on failure.
138
+
139
+ **Examples:**
140
+
141
+ ```bash
142
+ bolt refresh --db # full refresh with database
143
+ bolt refresh --skip-db # code-only refresh
144
+ bolt refresh --db --yes # non-interactive (for scripting)
145
+ bolt refresh --branch=develop --db # refresh from a different branch
146
+ ```
147
+
148
+ ### `bolt update`
149
+
150
+ Run Drupal module/core updates with per-module testing and rollback.
151
+
152
+ ```bash
153
+ bolt update [options]
154
+ --module <name> Update a specific module (Composer package name)
155
+ --all Update all outdated packages
156
+ --security-only Only security updates
157
+ --dry-run Show what would be updated without doing it
158
+ --skip-refresh Skip the pre-update refresh step
159
+ --skip-test Skip the baseline test
160
+ --yes Skip confirmation prompts
161
+ ```
162
+
163
+ What it does:
164
+ 1. Pre-flight: refreshes local state, runs baseline test, gets outdated packages
165
+ 2. Per-module loop: creates branch, updates via Composer, runs `drush updb`, runs full test suite
166
+ 3. If tests pass: exports config, commits on update branch
167
+ 4. If tests fail: rolls back changes, deletes branch, continues to next module
168
+ 5. Produces summary report with per-module results
169
+
170
+ Each update gets its own branch (`update/<package>-<version>`) off the base branch for clean per-module PRs.
171
+
172
+ **Exit codes:** 0 = at least one update succeeded, 1 = all failed, 2 = error.
173
+
174
+ **Examples:**
175
+
176
+ ```bash
177
+ bolt update --dry-run --all # see what would be updated
178
+ bolt update --module=drupal/token # update a single module
179
+ bolt update --all --security-only # only security updates
180
+ bolt update --all --yes # non-interactive, all updates
181
+ bolt update --all --skip-refresh # skip the git pull / composer install step
182
+ ```
183
+
184
+ ### `bolt pr`
185
+
186
+ Create GitHub PRs from update branches produced by `bolt update`. Requires [GitHub CLI](https://cli.github.com).
187
+
188
+ ```bash
189
+ bolt pr [options]
190
+ --base <branch> Base branch for PR (default: main)
191
+ --reviewers <list> Comma-separated GitHub usernames
192
+ --draft Create as draft PR
193
+ --all Create PRs for all update/* branches
194
+ ```
195
+
196
+ What it does:
197
+ 1. Finds update branches (current branch or `--all` for all `update/*` branches)
198
+ 2. Parses commit messages for update details (package, versions, test results)
199
+ 3. Pushes branch to remote
200
+ 4. Creates PR with structured body via `gh pr create`
201
+ 5. Checks for existing PRs to avoid duplicates
202
+
203
+ **Examples:**
204
+
205
+ ```bash
206
+ bolt update --module=drupal/token # creates update/drupal-token-1.15.0
207
+ bolt pr --reviewers=broeker # creates PR for current branch
208
+
209
+ bolt update --all # creates multiple update branches
210
+ bolt pr --all --draft # creates draft PRs for all of them
211
+ ```
212
+
213
+ ### `bolt init`
214
+
215
+ Connect bolt to a client site. Run from the site's project root.
216
+
217
+ ```bash
218
+ bolt init [options]
219
+ --skip-composer Skip Composer setup
220
+ --skip-module Skip module enable
221
+ --upgrade Update bolt_inspect module only (no config changes)
222
+ ```
223
+
224
+ What it does:
225
+ 1. Warns if on `main`/`master` branch (bolt changes should go on a dedicated branch)
226
+ 2. Creates DDEV volume mount for `bolt_inspect` module
227
+ 3. Adds Composer path repository
228
+ 4. Installs and enables `bolt_inspect`
229
+ 5. Creates `.bolt.yml` config template
230
+ 6. Adds bolt artifacts to `.gitignore`
231
+
232
+ Idempotent — safe to re-run.
233
+
234
+ ### `bolt doctor`
235
+
236
+ Validate the environment.
237
+
238
+ ```bash
239
+ bolt doctor
240
+ ```
241
+
242
+ Checks: bolt version, Node.js, Playwright, DDEV, module status, CLI/module version compatibility, `.bolt.yml` validity.
243
+
244
+ ## Test Plugins
245
+
246
+ | Plugin | What it tests | Mode |
247
+ |--------|--------------|------|
248
+ | `structural-smoke` | Create + render nodes for every content type via Drush | full |
249
+ | `browser-smoke` | Visit URLs, check HTTP status, JS errors, broken images | all |
250
+ | `accessibility` | axe-core WCAG 2.0 AA scan on all representative URLs | all |
251
+ | `visual-regression` | Screenshot comparison across 3 viewports per URL | all |
252
+ | `field-interaction` | Fill and submit admin forms for each content type | full, admin |
253
+ | `media-browser` | Upload media via library interface | full, admin |
254
+ | `wysiwyg` | CKEditor 5 toolbar, formatting, media embed | full, admin |
255
+ | `linkit` | LinkIt autocomplete in CKEditor | full, admin |
256
+
257
+ Plugins skip gracefully when their required modules aren't installed.
258
+
259
+ ## Configuration
260
+
261
+ ### `.bolt.yml`
262
+
263
+ Per-site test configuration, created by `bolt init`.
264
+
265
+ ```yaml
266
+ adapter: ddev
267
+
268
+ plugins:
269
+ skip:
270
+ - visual-regression
271
+ config:
272
+ accessibility:
273
+ rules_to_skip: [color-contrast]
274
+ visual-regression:
275
+ threshold: 0.5
276
+ mask:
277
+ - selector: ".block-current-date"
278
+
279
+ custom_routes:
280
+ - /products
281
+ - /about
282
+
283
+ refresh:
284
+ base_branch: main
285
+ db:
286
+ source: pantheon # pantheon | file (or any ddev pull provider)
287
+ pantheon_site: mysite # Terminus site name
288
+ pantheon_env: live # Environment to pull from
289
+ # source: file
290
+ # file_path: /path/to/dump.sql.gz
291
+
292
+ update:
293
+ ignore:
294
+ - drupal/core-* # Update core manually
295
+ priority:
296
+ - drupal/core-recommended
297
+
298
+ pr:
299
+ base_branch: main
300
+ default_reviewers:
301
+ - broeker
302
+ draft: false
303
+ remote: origin
304
+ ```
305
+
306
+ ### `.boltrc.yml`
307
+
308
+ Per-site suppression rules, created by `bolt suppress`.
309
+
310
+ ```yaml
311
+ suppress:
312
+ js_errors:
313
+ - pattern: "Siteimprove"
314
+ reason: "Analytics script, fails locally"
315
+ urls:
316
+ - path: "/admin/orphan"
317
+ reason: "Known 403, will be removed"
318
+ accessibility:
319
+ - rule: "color-contrast"
320
+ reason: "Design fix scheduled Q2"
321
+ plugins:
322
+ - name: "visual-regression"
323
+ reason: "Baselines not captured yet"
324
+ ```
325
+
326
+ ## Severity Levels
327
+
328
+ | Severity | Meaning | Example |
329
+ |----------|---------|---------|
330
+ | `critical` | Site broken | CKEditor toolbar missing, node creation fails |
331
+ | `major` | Significant functionality broken | Media upload fails, form validation errors |
332
+ | `minor` | Cosmetic or edge-case issue | Broken image on one page, minor a11y violation |
333
+ | `info` | Informational | Test passed, baseline captured |
334
+
335
+ `--fail-on` controls which severities trigger non-zero exit. Default is `major`.
336
+
337
+ ## Upgrading
338
+
339
+ ```bash
340
+ # Update bolt CLI
341
+ npm update -g @electriccitizen/bolt
342
+
343
+ # Update the Drupal module on each site
344
+ cd ~/projects/client-acme
345
+ bolt init --upgrade
346
+ bolt doctor # verify versions match
347
+ ```
348
+
349
+ `bolt init --upgrade` updates the volume mount and module only — no config changes, no re-init.
350
+
351
+ ## Roadmap
352
+
353
+ See [docs/PLAN-MVP.md](docs/PLAN-MVP.md) for the post-MVP roadmap.
354
+
355
+ ## Development
356
+
357
+ See [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) for architecture, dev setup, and how to add plugins.
358
+
359
+ ## License
360
+
361
+ Proprietary - Electric Citizen
@@ -0,0 +1,16 @@
1
+ /**
2
+ * DDEV hosting adapter — executes Drush/Composer commands via `ddev`.
3
+ */
4
+ import type { CommandResult, HostingAdapter } from '../types.js';
5
+ export declare class DdevAdapter implements HostingAdapter {
6
+ name: string;
7
+ canConnect(): Promise<boolean>;
8
+ /**
9
+ * Get the primary site URL from DDEV's own config.
10
+ */
11
+ getSiteUrl(): Promise<string | null>;
12
+ drush(command: string, args?: string[]): Promise<CommandResult>;
13
+ composer(command: string, args?: string[]): Promise<CommandResult>;
14
+ getLoginUrl(siteUrl: string): Promise<string>;
15
+ private exec;
16
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * DDEV hosting adapter — executes Drush/Composer commands via `ddev`.
3
+ */
4
+ import { execFile } from 'child_process';
5
+ import { promisify } from 'util';
6
+ const execFileAsync = promisify(execFile);
7
+ export class DdevAdapter {
8
+ name = 'ddev';
9
+ async canConnect() {
10
+ try {
11
+ const result = await this.exec('ddev', ['describe', '-j']);
12
+ return result.exitCode === 0;
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ /**
19
+ * Get the primary site URL from DDEV's own config.
20
+ */
21
+ async getSiteUrl() {
22
+ try {
23
+ const result = await this.exec('ddev', ['describe', '-j']);
24
+ if (result.exitCode !== 0)
25
+ return null;
26
+ const data = JSON.parse(result.stdout);
27
+ // ddev describe -j returns { raw: { ... } } with httpurl/httpsurl
28
+ const raw = data.raw ?? data;
29
+ return raw.httpsurl ?? raw.httpurl ?? null;
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ async drush(command, args = []) {
36
+ return this.exec('ddev', ['drush', command, ...args]);
37
+ }
38
+ async composer(command, args = []) {
39
+ return this.exec('ddev', ['composer', command, ...args]);
40
+ }
41
+ async getLoginUrl(siteUrl) {
42
+ const result = await this.drush('uli', ['--uri=' + siteUrl, '--no-browser']);
43
+ const url = result.stdout.trim();
44
+ if (!url || result.exitCode !== 0) {
45
+ throw new Error(`Failed to get login URL: ${result.stderr}`);
46
+ }
47
+ return url;
48
+ }
49
+ async exec(command, args) {
50
+ try {
51
+ const { stdout, stderr } = await execFileAsync(command, args, {
52
+ maxBuffer: 10 * 1024 * 1024,
53
+ timeout: 120_000,
54
+ });
55
+ return { stdout, stderr, exitCode: 0 };
56
+ }
57
+ catch (err) {
58
+ // Node's execFile throws an augmented Error with stdout/stderr/code.
59
+ if (err instanceof Error) {
60
+ const execErr = err;
61
+ return {
62
+ stdout: execErr.stdout ?? '',
63
+ stderr: execErr.stderr ?? err.message,
64
+ exitCode: typeof execErr.code === 'number' ? execErr.code : 1,
65
+ };
66
+ }
67
+ return {
68
+ stdout: '',
69
+ stderr: String(err),
70
+ exitCode: 1,
71
+ };
72
+ }
73
+ }
74
+ }
75
+ //# sourceMappingURL=ddev.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ddev.js","sourceRoot":"","sources":["../../src/adapters/ddev.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export { DdevAdapter } from './ddev.js';
@@ -0,0 +1,2 @@
1
+ export { DdevAdapter } from './ddev.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { readFileSync } from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join } from 'path';
6
+ import { testCommand } from './commands/test.js';
7
+ import { initCommand } from './commands/init.js';
8
+ import { doctorCommand } from './commands/doctor.js';
9
+ import { suppressCommand } from './commands/suppress.js';
10
+ import { refreshCommand } from './commands/refresh.js';
11
+ import { updateCommand } from './commands/update.js';
12
+ import { prCommand } from './commands/pr.js';
13
+ import { loadConfig } from './config.js';
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
17
+ const program = new Command();
18
+ program
19
+ .name('bolt')
20
+ .description('CLI tool for Drupal 11+ site testing and operations')
21
+ .version(pkg.version);
22
+ program
23
+ .command('test')
24
+ .description('Run tests against a Drupal site')
25
+ .option('--url <url>', 'Site URL (auto-detected from DDEV if omitted)')
26
+ .option('--mode <mode>', 'Execution mode: full, read-only, admin-only', 'full')
27
+ .option('--plugins <list>', 'Comma-separated plugin names')
28
+ .option('--output <format>', 'Output format: text, json, markdown')
29
+ .option('--report <path>', 'Write report to file')
30
+ .option('--exit-code', 'Non-zero exit on failure', false)
31
+ .option('--fail-on <severity>', 'Minimum severity to trigger non-zero exit: critical, major, minor', 'major')
32
+ .option('--headed', 'Run Playwright in headed mode', false)
33
+ .option('--screenshots <dir>', 'Save failure screenshots to directory')
34
+ .option('--vr-baseline <dir>', 'Visual regression baseline directory')
35
+ .option('--vr-threshold <n>', 'Visual regression diff threshold percentage (default: 0.1)', parseFloat)
36
+ .option('--content-types <list>', 'Comma-separated content types to test (limits structural-smoke, field-interaction)')
37
+ .action(async (opts) => {
38
+ const validSeverities = ['critical', 'major', 'minor'];
39
+ if (!validSeverities.includes(opts.failOn)) {
40
+ process.stderr.write(`Error: --fail-on must be one of: ${validSeverities.join(', ')}\n`);
41
+ process.exit(2);
42
+ }
43
+ // Determine output format: explicit flag > auto-JSON for non-TTY > text.
44
+ let outputFormat;
45
+ if (opts.output) {
46
+ outputFormat = opts.output;
47
+ }
48
+ else if (!process.stdout.isTTY) {
49
+ outputFormat = 'json';
50
+ }
51
+ else {
52
+ outputFormat = 'text';
53
+ }
54
+ const options = {
55
+ url: opts.url ?? '',
56
+ mode: opts.mode,
57
+ plugins: opts.plugins ? opts.plugins.split(',').map((s) => s.trim()) : undefined,
58
+ output: outputFormat,
59
+ report: opts.report,
60
+ exitCode: opts.exitCode,
61
+ failOn: opts.failOn,
62
+ headed: opts.headed,
63
+ screenshots: opts.screenshots,
64
+ vrBaseline: opts.vrBaseline,
65
+ vrThreshold: opts.vrThreshold,
66
+ contentTypes: opts.contentTypes ? opts.contentTypes.split(',').map((s) => s.trim()) : undefined,
67
+ };
68
+ await testCommand(options);
69
+ });
70
+ program
71
+ .command('init')
72
+ .description('Connect bolt to a client site (run from site root)')
73
+ .option('--skip-composer', 'Skip Composer path repo and require', false)
74
+ .option('--skip-module', 'Skip enabling the bolt_inspect module', false)
75
+ .option('--upgrade', 'Update bolt_inspect module only (no config changes)', false)
76
+ .action(async (opts) => {
77
+ await initCommand({
78
+ skipComposer: opts.skipComposer,
79
+ skipModule: opts.skipModule,
80
+ upgrade: opts.upgrade,
81
+ });
82
+ });
83
+ program
84
+ .command('doctor')
85
+ .description('Validate the developer environment')
86
+ .action(async () => {
87
+ await doctorCommand();
88
+ });
89
+ program
90
+ .command('suppress')
91
+ .description('Capture current test failures as suppression baseline (.boltrc.yml)')
92
+ .option('--url <url>', 'Site URL (auto-detected from DDEV if omitted)')
93
+ .option('--dry-run', 'Preview suppressions without writing .boltrc.yml', false)
94
+ .option('--mode <mode>', 'Execution mode: full, read-only, admin-only', 'full')
95
+ .action(async (opts) => {
96
+ await suppressCommand({
97
+ url: opts.url,
98
+ dryRun: opts.dryRun,
99
+ mode: opts.mode,
100
+ });
101
+ });
102
+ program
103
+ .command('refresh')
104
+ .description('Sync local environment to match production (git pull, composer install, optional DB)')
105
+ .option('--db', 'Pull fresh database', false)
106
+ .option('--skip-db', 'Skip database pull entirely (no prompt)', false)
107
+ .option('--source <env>', 'Database source environment (default: live)')
108
+ .option('--yes', 'Skip all confirmation prompts', false)
109
+ .option('--branch <name>', 'Base branch to checkout (default: main)')
110
+ .action(async (opts) => {
111
+ const config = loadConfig();
112
+ await refreshCommand({
113
+ db: opts.db,
114
+ skipDb: opts.skipDb,
115
+ source: opts.source ?? '',
116
+ yes: opts.yes,
117
+ branch: opts.branch ?? '',
118
+ boltConfig: config,
119
+ });
120
+ });
121
+ program
122
+ .command('update')
123
+ .description('Run Drupal module/core updates with per-module testing and rollback')
124
+ .option('--module <name>', 'Update a specific module (Composer package name)')
125
+ .option('--all', 'Update all outdated packages', false)
126
+ .option('--security-only', 'Only security updates', false)
127
+ .option('--dry-run', 'Show what would be updated without doing it', false)
128
+ .option('--skip-refresh', 'Skip the pre-update refresh step', false)
129
+ .option('--skip-test', 'Skip the baseline test', false)
130
+ .option('--yes', 'Skip confirmation prompts', false)
131
+ .action(async (opts) => {
132
+ if (!opts.module && !opts.all) {
133
+ process.stderr.write('Error: specify --module <name> or --all\n' +
134
+ 'Run `bolt update --dry-run --all` to see available updates.\n');
135
+ process.exit(2);
136
+ }
137
+ const config = loadConfig();
138
+ await updateCommand({
139
+ module: opts.module,
140
+ all: opts.all,
141
+ securityOnly: opts.securityOnly,
142
+ dryRun: opts.dryRun,
143
+ skipRefresh: opts.skipRefresh,
144
+ skipTest: opts.skipTest,
145
+ yes: opts.yes,
146
+ boltConfig: config,
147
+ });
148
+ });
149
+ program
150
+ .command('pr')
151
+ .description('Create GitHub PRs from update branches')
152
+ .option('--base <branch>', 'Base branch for PR (default: main)')
153
+ .option('--reviewers <list>', 'Comma-separated GitHub usernames')
154
+ .option('--draft', 'Create as draft PR', false)
155
+ .option('--all', 'Create PRs for all update/* branches', false)
156
+ .action(async (opts) => {
157
+ const config = loadConfig();
158
+ await prCommand({
159
+ base: opts.base,
160
+ reviewers: opts.reviewers,
161
+ draft: opts.draft,
162
+ all: opts.all,
163
+ boltConfig: config,
164
+ });
165
+ });
166
+ program.parse();
167
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * bolt doctor — validate the developer environment.
3
+ */
4
+ export declare function doctorCommand(): Promise<void>;