@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.
- package/README.md +361 -0
- package/dist/adapters/ddev.d.ts +16 -0
- package/dist/adapters/ddev.js +75 -0
- package/dist/adapters/ddev.js.map +1 -0
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.js +2 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +167 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/doctor.d.ts +4 -0
- package/dist/commands/doctor.js +263 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.d.ts +12 -0
- package/dist/commands/init.js +319 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/pr.d.ts +20 -0
- package/dist/commands/pr.js +282 -0
- package/dist/commands/pr.js.map +1 -0
- package/dist/commands/refresh.d.ts +22 -0
- package/dist/commands/refresh.js +375 -0
- package/dist/commands/refresh.js.map +1 -0
- package/dist/commands/suppress.d.ts +10 -0
- package/dist/commands/suppress.js +86 -0
- package/dist/commands/suppress.js.map +1 -0
- package/dist/commands/test.d.ts +5 -0
- package/dist/commands/test.js +106 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/update.d.ts +26 -0
- package/dist/commands/update.js +573 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.js +187 -0
- package/dist/config.js.map +1 -0
- package/dist/formatters/index.d.ts +2 -0
- package/dist/formatters/index.js +3 -0
- package/dist/formatters/index.js.map +1 -0
- package/dist/formatters/json.d.ts +5 -0
- package/dist/formatters/json.js +7 -0
- package/dist/formatters/json.js.map +1 -0
- package/dist/formatters/markdown.d.ts +5 -0
- package/dist/formatters/markdown.js +144 -0
- package/dist/formatters/markdown.js.map +1 -0
- package/dist/formatters/text.d.ts +5 -0
- package/dist/formatters/text.js +123 -0
- package/dist/formatters/text.js.map +1 -0
- package/dist/plugins/accessibility.d.ts +5 -0
- package/dist/plugins/accessibility.js +116 -0
- package/dist/plugins/accessibility.js.map +1 -0
- package/dist/plugins/browser-smoke.d.ts +6 -0
- package/dist/plugins/browser-smoke.js +331 -0
- package/dist/plugins/browser-smoke.js.map +1 -0
- package/dist/plugins/field-interaction.d.ts +6 -0
- package/dist/plugins/field-interaction.js +570 -0
- package/dist/plugins/field-interaction.js.map +1 -0
- package/dist/plugins/index.d.ts +6 -0
- package/dist/plugins/index.js +28 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/linkit.d.ts +8 -0
- package/dist/plugins/linkit.js +170 -0
- package/dist/plugins/linkit.js.map +1 -0
- package/dist/plugins/media-browser.d.ts +6 -0
- package/dist/plugins/media-browser.js +257 -0
- package/dist/plugins/media-browser.js.map +1 -0
- package/dist/plugins/structural-smoke.d.ts +6 -0
- package/dist/plugins/structural-smoke.js +90 -0
- package/dist/plugins/structural-smoke.js.map +1 -0
- package/dist/plugins/visual-regression.d.ts +8 -0
- package/dist/plugins/visual-regression.js +214 -0
- package/dist/plugins/visual-regression.js.map +1 -0
- package/dist/plugins/wysiwyg.d.ts +8 -0
- package/dist/plugins/wysiwyg.js +221 -0
- package/dist/plugins/wysiwyg.js.map +1 -0
- package/dist/runner.d.ts +21 -0
- package/dist/runner.js +293 -0
- package/dist/runner.js.map +1 -0
- package/dist/suppression.d.ts +55 -0
- package/dist/suppression.js +223 -0
- package/dist/suppression.js.map +1 -0
- package/dist/types.d.ts +178 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/modules/bolt_inspect/bolt_inspect.info.yml +6 -0
- package/modules/bolt_inspect/bolt_inspect.services.yml +22 -0
- package/modules/bolt_inspect/composer.json +16 -0
- package/modules/bolt_inspect/drush.services.yml +10 -0
- package/modules/bolt_inspect/src/Drush/Commands/BoltInspectCommands.php +203 -0
- package/modules/bolt_inspect/src/Service/ContentGenerator.php +586 -0
- package/modules/bolt_inspect/src/Service/SiteProfiler.php +362 -0
- package/modules/bolt_inspect/src/Service/TestEntityTracker.php +98 -0
- package/package.json +46 -0
- 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 @@
|
|
|
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
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
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|