@bostonuniversity/buwp-local 0.7.1 → 0.7.3
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/bin/buwp-local.js +10 -0
- package/docs/ARCHITECTURE.md +67 -5
- package/docs/CHANGELOG.md +23 -0
- package/docs/COMMANDS.md +108 -12
- package/docs/ROADMAP.md +105 -11
- package/docs/temp-breaking-0-7-2.md +37 -0
- package/lib/commands/update.js +58 -7
- package/lib/commands/watch-jobs.js +192 -0
- package/lib/compose-generator.js +3 -4
- package/package.json +1 -1
package/bin/buwp-local.js
CHANGED
|
@@ -27,6 +27,7 @@ import destroyCommand from '../lib/commands/destroy.js';
|
|
|
27
27
|
import updateCommand from '../lib/commands/update.js';
|
|
28
28
|
import logsCommand from '../lib/commands/logs.js';
|
|
29
29
|
import wpCommand from '../lib/commands/wp.js';
|
|
30
|
+
import watchJobsCommand from '../lib/commands/watch-jobs.js';
|
|
30
31
|
import shellCommand from '../lib/commands/shell.js';
|
|
31
32
|
import configCommand from '../lib/commands/config.js';
|
|
32
33
|
import initCommand from '../lib/commands/init.js';
|
|
@@ -66,6 +67,7 @@ program
|
|
|
66
67
|
.command('update')
|
|
67
68
|
.description('Update Docker images and recreate containers')
|
|
68
69
|
.option('--all', 'Update all service images (default: WordPress only)')
|
|
70
|
+
.option('--preserve-wpbuild', 'Preserve existing WordPress volume (prevents core file updates)')
|
|
69
71
|
.action(updateCommand);
|
|
70
72
|
|
|
71
73
|
// Logs command
|
|
@@ -107,6 +109,14 @@ program
|
|
|
107
109
|
.allowUnknownOption()
|
|
108
110
|
.action(wpCommand);
|
|
109
111
|
|
|
112
|
+
// Watch Jobs command
|
|
113
|
+
program
|
|
114
|
+
.command('watch-jobs')
|
|
115
|
+
.description('Watch and automatically process site-manager jobs')
|
|
116
|
+
.option('--interval <seconds>', `Polling interval in seconds (default: 300, min: 30)`)
|
|
117
|
+
.option('--quiet', 'Suppress output unless jobs are found')
|
|
118
|
+
.action(watchJobsCommand);
|
|
119
|
+
|
|
110
120
|
// Shell command
|
|
111
121
|
program
|
|
112
122
|
.command('shell')
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -465,16 +465,78 @@ This architecture provides clean boundaries:
|
|
|
465
465
|
|
|
466
466
|
| Concern | Location | Update Mechanism |
|
|
467
467
|
|---------|----------|------------------|
|
|
468
|
-
| WordPress core | Docker image | `
|
|
469
|
-
| BU plugins | Docker image | `
|
|
470
|
-
| BU theme | Docker image | `
|
|
468
|
+
| WordPress core | Docker image | `buwp-local update` |
|
|
469
|
+
| BU plugins | Docker image | `buwp-local update` |
|
|
470
|
+
| BU theme | Docker image | `buwp-local update` |
|
|
471
471
|
| **Your plugin** | **Local filesystem** | **Your editor** |
|
|
472
472
|
| **Your theme** | **Local filesystem** | **Your editor** |
|
|
473
|
-
| Database | Docker volume | Persists across updates |
|
|
474
|
-
| Uploads |
|
|
473
|
+
| Database | Docker volume (db_data) | Persists across updates |
|
|
474
|
+
| **Uploads** | **S3 bucket** | **Via s3proxy** |
|
|
475
475
|
|
|
476
476
|
Updates to WordPress never touch your development code. Updates to your code never require rebuilding WordPress.
|
|
477
477
|
|
|
478
|
+
### S3 Upload Architecture
|
|
479
|
+
|
|
480
|
+
A critical design decision enables safe WordPress core updates: **all media uploads are stored in S3, not in the container filesystem**.
|
|
481
|
+
|
|
482
|
+
```
|
|
483
|
+
WordPress Upload Flow:
|
|
484
|
+
┌──────────────┐
|
|
485
|
+
│ User │ Uploads file via WP admin
|
|
486
|
+
└──────┬───────┘
|
|
487
|
+
│
|
|
488
|
+
↓
|
|
489
|
+
┌──────────────────────────────────┐
|
|
490
|
+
│ WordPress Container │
|
|
491
|
+
│ │
|
|
492
|
+
│ BU S3 Uploads Plugin │
|
|
493
|
+
│ (intercepts upload) │
|
|
494
|
+
└──────┬───────────────────────────┘
|
|
495
|
+
│
|
|
496
|
+
↓
|
|
497
|
+
┌──────────────────────────────────┐
|
|
498
|
+
│ s3proxy Container │
|
|
499
|
+
│ (AWS SigV4 authentication) │
|
|
500
|
+
└──────┬───────────────────────────┘
|
|
501
|
+
│
|
|
502
|
+
↓
|
|
503
|
+
┌──────────────────────────────────┐
|
|
504
|
+
│ S3 Bucket │
|
|
505
|
+
│ (permanent storage) │
|
|
506
|
+
└──────────────────────────────────┘
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
**Key Configuration** (from `compose-generator.js`):
|
|
510
|
+
|
|
511
|
+
```javascript
|
|
512
|
+
if (config.services.s3proxy) {
|
|
513
|
+
wpConfigExtra += "define('S3_UPLOADS_AUTOENABLE', true);\n";
|
|
514
|
+
wpConfigExtra += "define('S3_UPLOADS_DISABLE_REPLACE_UPLOAD_URL', true);\n";
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
**What this means for updates:**
|
|
519
|
+
|
|
520
|
+
The `wp_build` volume contains **zero user data**:
|
|
521
|
+
- ✅ WordPress core files (disposable - from image)
|
|
522
|
+
- ✅ BU plugins/themes (disposable - from image)
|
|
523
|
+
- ❌ **No media uploads** (in S3 bucket)
|
|
524
|
+
- ❌ **No database** (separate db_data volume)
|
|
525
|
+
- ❌ **No custom code** (mapped from host filesystem)
|
|
526
|
+
|
|
527
|
+
**Result:** The `wp_build` volume is purely infrastructure and can be safely wiped during updates to refresh WordPress core files.
|
|
528
|
+
|
|
529
|
+
```bash
|
|
530
|
+
# This is safe because:
|
|
531
|
+
# - Database preserved (db_data volume)
|
|
532
|
+
# - Uploads preserved (S3 bucket)
|
|
533
|
+
# - Your code preserved (local filesystem)
|
|
534
|
+
npx buwp-local update # Wipes wp_build, recreates from image
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
**Contrast with traditional WordPress:**
|
|
538
|
+
In a standard WordPress installation, `wp-content/uploads/` contains irreplaceable user media files. In buwp-local, that directory is empty or contains only regenerable thumbnails/cache.
|
|
539
|
+
|
|
478
540
|
For detailed migration guidance, see [MIGRATION_FROM_VM.md](MIGRATION_FROM_VM.md).
|
|
479
541
|
|
|
480
542
|
## Security Model
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ All notable changes to buwp-local will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.7.3]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `watch-jobs` command for automatic processing of site-manager jobs at regular intervals
|
|
12
|
+
- Configurable polling interval with `--interval` flag (default: 60 seconds, minimum: 10 seconds), and through `jobWatchInterval` in configuration file
|
|
13
|
+
- `--quiet` flag to suppress routine output for long-running background use
|
|
14
|
+
|
|
15
|
+
## [0.7.2]
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
- **Volume Naming Fix:** Corrected double-prefixing bug in Docker volume names
|
|
19
|
+
- Old: `projectname_projectname_wp_build`
|
|
20
|
+
- New: `projectname_wp_build`
|
|
21
|
+
- **Migration required:** See [Migration Guide](docs/temp-breaking-0-7-2.md)
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- `buwp-local update` now properly refreshes WordPress core files from new images
|
|
25
|
+
- `--preserve-wpbuild` flag to opt-out of WordPress volume refresh during updates
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- Volume deletion during update now works correctly (containers released first)
|
|
29
|
+
- Docker Compose auto-prefixing no longer causes duplicated volume names
|
|
30
|
+
|
|
8
31
|
## [0.7.1]
|
|
9
32
|
|
|
10
33
|
Documentation only release.
|
package/docs/COMMANDS.md
CHANGED
|
@@ -117,7 +117,7 @@ npx buwp-local destroy --force
|
|
|
117
117
|
|
|
118
118
|
### `update`
|
|
119
119
|
|
|
120
|
-
Update Docker images and
|
|
120
|
+
Update Docker images and refresh WordPress core files.
|
|
121
121
|
|
|
122
122
|
```bash
|
|
123
123
|
npx buwp-local update [options]
|
|
@@ -125,33 +125,53 @@ npx buwp-local update [options]
|
|
|
125
125
|
|
|
126
126
|
**Options:**
|
|
127
127
|
- `--all` - Update all service images (default: WordPress image only)
|
|
128
|
+
- `--preserve-wpbuild` - Keep existing WordPress volume (prevents core file updates)
|
|
128
129
|
|
|
129
130
|
**Examples:**
|
|
130
131
|
```bash
|
|
131
|
-
# Update WordPress image
|
|
132
|
+
# Update WordPress core from new image (recommended)
|
|
132
133
|
npx buwp-local update
|
|
133
134
|
|
|
134
135
|
# Update all service images (Redis, S3 proxy, etc.)
|
|
135
136
|
npx buwp-local update --all
|
|
137
|
+
|
|
138
|
+
# Preserve WordPress volume (skip core file refresh)
|
|
139
|
+
npx buwp-local update --preserve-wpbuild
|
|
136
140
|
```
|
|
137
141
|
|
|
138
142
|
**What it does:**
|
|
139
143
|
- Checks if environment exists and Docker is running
|
|
140
144
|
- Pulls latest Docker images from registry
|
|
141
|
-
-
|
|
145
|
+
- **Removes wp_build volume** to get fresh WordPress core files (unless `--preserve-wpbuild`)
|
|
146
|
+
- Recreates containers with new images
|
|
142
147
|
- Loads credentials from Keychain and/or `.env.local`
|
|
143
|
-
- **
|
|
144
|
-
-
|
|
148
|
+
- **Always preserves database** (separate volume)
|
|
149
|
+
- **Always preserves custom mapped code** (your local files)
|
|
150
|
+
- **Uploads safe** (stored in S3, not in container)
|
|
151
|
+
|
|
152
|
+
**What gets updated:**
|
|
153
|
+
- ✅ WordPress core files (wp-admin, wp-includes, core PHP files)
|
|
154
|
+
- ✅ BU plugins and themes bundled in image
|
|
155
|
+
- ✅ PHP/Apache configuration from image
|
|
156
|
+
|
|
157
|
+
**What's preserved:**
|
|
158
|
+
- ✅ Database content (posts, users, settings) - separate volume
|
|
159
|
+
- ✅ Your custom mapped code - lives on your Mac
|
|
160
|
+
- ✅ Media uploads - stored in S3 bucket
|
|
145
161
|
|
|
146
162
|
**Use cases:**
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
163
|
+
- Get latest WordPress security patches
|
|
164
|
+
- Pull updated BU plugins/themes from new image
|
|
165
|
+
- Test code against newer WordPress version
|
|
166
|
+
- Refresh environment without losing development work
|
|
150
167
|
|
|
151
|
-
**Key difference from
|
|
152
|
-
- `stop` → `start`: Reuses
|
|
153
|
-
- `update`:
|
|
154
|
-
- `destroy`: Removes everything including
|
|
168
|
+
**Key difference from other commands:**
|
|
169
|
+
- `stop` → `start`: Reuses containers and volumes (no updates)
|
|
170
|
+
- `update`: Refreshes WordPress from image, preserves database
|
|
171
|
+
- `destroy`: Removes everything including database (nuclear option)
|
|
172
|
+
|
|
173
|
+
**Why it's safe:**
|
|
174
|
+
Because buwp-local uses S3 for media uploads (via s3proxy), the WordPress volume contains no user data - only WordPress core and BU infrastructure code. Your custom development code is mapped from your local filesystem and never touched.
|
|
155
175
|
|
|
156
176
|
---
|
|
157
177
|
|
|
@@ -401,6 +421,82 @@ npx buwp-local keychain clear [--force]
|
|
|
401
421
|
|
|
402
422
|
---
|
|
403
423
|
|
|
424
|
+
### `watch-jobs`
|
|
425
|
+
|
|
426
|
+
Watch for and automatically process site-manager jobs at regular intervals.
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
npx buwp-local watch-jobs [options]
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**Options:**
|
|
433
|
+
- `--interval <seconds>` - Polling interval in seconds (default: 60, min: 10)
|
|
434
|
+
- `--quiet` - Suppress all routine output (for long-running background use)
|
|
435
|
+
|
|
436
|
+
**Examples:**
|
|
437
|
+
```bash
|
|
438
|
+
# Watch with default 1 minute interval
|
|
439
|
+
npx buwp-local watch-jobs
|
|
440
|
+
|
|
441
|
+
# Watch with custom 2 minute interval
|
|
442
|
+
npx buwp-local watch-jobs --interval 120
|
|
443
|
+
|
|
444
|
+
# Quiet mode (silent operation, check web UI for job status)
|
|
445
|
+
npx buwp-local watch-jobs --quiet
|
|
446
|
+
|
|
447
|
+
# Quiet mode with short interval for active monitoring
|
|
448
|
+
npx buwp-local watch-jobs --interval 20 --quiet
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**What it does:**
|
|
452
|
+
- Runs `wp site-manager process-jobs` inside the WordPress container at configured intervals
|
|
453
|
+
- **Default mode**: Shows timestamped output for all checks and job results
|
|
454
|
+
- **Quiet mode**: Silently processes jobs (check site-manager web UI for status)
|
|
455
|
+
- Continues running until stopped (Ctrl+C)
|
|
456
|
+
- Mirrors production cron/EventBridge behavior locally
|
|
457
|
+
|
|
458
|
+
**Use cases:**
|
|
459
|
+
- Automatic processing of jobs created via web UI
|
|
460
|
+
|
|
461
|
+
**Configuration:**
|
|
462
|
+
|
|
463
|
+
Optionally configure default interval in `.buwp-local.json`:
|
|
464
|
+
```json
|
|
465
|
+
{
|
|
466
|
+
"projectName": "my-project",
|
|
467
|
+
"jobWatchInterval": 200
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
Command-line `--interval` flag overrides config file setting.
|
|
472
|
+
|
|
473
|
+
**Requirements:**
|
|
474
|
+
- WordPress container must be running (`start` first)
|
|
475
|
+
- Site-manager plugin must be installed and activated
|
|
476
|
+
|
|
477
|
+
**Quiet mode behavior:**
|
|
478
|
+
- **Startup**: Shows configuration banner
|
|
479
|
+
- **Routine operation**: Completely silent - no output for checks or job processing
|
|
480
|
+
- **Critical errors**: Shows only failures requiring intervention (Docker stopped, environment missing)
|
|
481
|
+
- **Job status**: Check site-manager plugin web UI to see job results and history
|
|
482
|
+
- **Best for**: Long-running background monitoring (hours/days) without terminal noise
|
|
483
|
+
|
|
484
|
+
**Error handling:**
|
|
485
|
+
- **Default mode**: Shows all errors and warnings with timestamps
|
|
486
|
+
- **Quiet mode**: Silently retries transient errors (container restarts), only shows critical failures
|
|
487
|
+
- Graceful shutdown on SIGINT/SIGTERM
|
|
488
|
+
|
|
489
|
+
**Tips:**
|
|
490
|
+
- Run in separate terminal window for visibility
|
|
491
|
+
- Use **default mode** during active development to see what's happening
|
|
492
|
+
- Use **quiet mode** for set-it-and-forget-it background monitoring
|
|
493
|
+
- Reduce interval during active testing (e.g., `--interval 60`)
|
|
494
|
+
- Increase interval for background monitoring (e.g., `--interval 600`)
|
|
495
|
+
|
|
496
|
+
**⚠️ Note:** This is a development tool. Production environments should continue using cron/AWS EventBridge for job processing.
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
404
500
|
## Global Options
|
|
405
501
|
|
|
406
502
|
These options work with all commands:
|
package/docs/ROADMAP.md
CHANGED
|
@@ -154,20 +154,69 @@ hostile.remove('127.0.0.1', config.hostname);
|
|
|
154
154
|
**Focus:** Ease of use and visibility
|
|
155
155
|
|
|
156
156
|
### Shipped in v0.7.0
|
|
157
|
-
- **Docker Image Update Command**
|
|
158
|
-
-
|
|
159
|
-
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
- **Benefit:** Safe, explicit way to apply WordPress/service updates without `destroy`
|
|
164
|
-
- **Implementation:** Wrapper around `docker-compose pull && docker-compose up -d --force-recreate`
|
|
157
|
+
- **Docker Image Update Command** ✅
|
|
158
|
+
- Pull latest Docker images from registry
|
|
159
|
+
- Recreate containers with new images
|
|
160
|
+
- Preserve volumes (database, WordPress data)
|
|
161
|
+
- Safely apply WordPress and service updates without `destroy`
|
|
162
|
+
- Implementation: Stop containers, conditionally remove wp_build volume, start with new images
|
|
165
163
|
|
|
166
164
|
### Shipped in v0.7.1
|
|
167
165
|
- **Documentation Improvements**
|
|
168
166
|
|
|
167
|
+
### Shipped in v0.7.2
|
|
168
|
+
- **Volume Naming Fix** ✅
|
|
169
|
+
- Fixed double-prefixing bug in Docker volume names
|
|
170
|
+
- Old: `projectname_projectname_wp_build` → New: `projectname_wp_build`
|
|
171
|
+
- Corrected `compose-generator.js` to use simple names (Docker Compose handles prefixing)
|
|
172
|
+
- Update command now properly releases volume locks before deletion
|
|
173
|
+
- Added `--preserve-wpbuild` flag for opt-out of WordPress volume refresh
|
|
174
|
+
|
|
175
|
+
### Shipped in v0.7.3
|
|
176
|
+
|
|
177
|
+
- **Job Watcher Command** 🚧
|
|
178
|
+
- New `watch-jobs` command to periodically run `wp site-manager process-jobs`
|
|
179
|
+
- Configurable polling interval (default: 5 minutes)
|
|
180
|
+
- Runs as standalone process in terminal window
|
|
181
|
+
- Timestamped output for job processing visibility
|
|
182
|
+
- Graceful shutdown (Ctrl+C)
|
|
183
|
+
|
|
184
|
+
**Problem:** Production environments use cron/AWS EventBridge to automatically process site-manager jobs (content migration, deployments). Local developers currently must manually run `npx buwp-local wp site-manager process-jobs` to see queued jobs complete.
|
|
185
|
+
|
|
186
|
+
**Solution:** Standalone `watch-jobs` command that runs indefinitely, polling for jobs at configurable intervals. Mirrors production behavior without requiring cron setup. Enables developers to use the site-manager web UI for content operations and see jobs complete automatically.
|
|
187
|
+
|
|
188
|
+
**Implementation location:** `lib/commands/watch-jobs.js`
|
|
189
|
+
|
|
190
|
+
**Configuration support:**
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"jobWatchInterval": 60 // seconds, default 60 seconds
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Command syntax:**
|
|
198
|
+
```bash
|
|
199
|
+
buwp-local watch-jobs [--interval 200] [--quiet]
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Technical considerations:**
|
|
203
|
+
- Requires WordPress container to be running
|
|
204
|
+
- Uses `docker compose exec` to run WP-CLI command
|
|
205
|
+
- Handles container stop/restart gracefully
|
|
206
|
+
- Minimal resource usage (sleeps between checks)
|
|
207
|
+
- Output includes timestamps for audit trail
|
|
208
|
+
|
|
209
|
+
**Future enhancement (v0.8.0+):** If widely adopted, consider adding `--watch-jobs` flag to `start` command for automatic background execution.
|
|
210
|
+
|
|
169
211
|
### Potential Features
|
|
170
212
|
|
|
213
|
+
- **Ability to add custom WORDPRESS_CONFIG_EXTRA environment variables**
|
|
214
|
+
- Support for adding custom WP config snippets via env vars
|
|
215
|
+
|
|
216
|
+
- **Credential Export**
|
|
217
|
+
- Commands to export credentials to JSON file
|
|
218
|
+
- Useful for migrating between machines or sharing setup
|
|
219
|
+
|
|
171
220
|
- **Database Security**
|
|
172
221
|
- Check database access on db port (e.g. `localhost:3306`)
|
|
173
222
|
- Consider more stringent default database passwords
|
|
@@ -201,9 +250,6 @@ hostile.remove('127.0.0.1', config.hostname);
|
|
|
201
250
|
- Credential issues → clear next steps
|
|
202
251
|
- Port conflicts → suggest alternatives
|
|
203
252
|
|
|
204
|
-
- **Multi project experience**
|
|
205
|
-
- There is a problem when starting a new project when an existing project exists in docker but is stopped. When starting the new project, docker first starts the container for the stopped project for unknown reasons. If the new project uses the same ports, this causes conflicts. Need to investigate and resolve, projects should be isolated and not interfere with each other.
|
|
206
|
-
|
|
207
253
|
- **Docker Volume management assistant**
|
|
208
254
|
- listing and cleanup of unused volumes
|
|
209
255
|
|
|
@@ -269,6 +315,54 @@ Will be informed by feedback from initial small group of users and actual pain p
|
|
|
269
315
|
### Quality
|
|
270
316
|
- Standardized help for all CLI commands
|
|
271
317
|
|
|
318
|
+
### Code Structure
|
|
319
|
+
|
|
320
|
+
#### Refactor credential handling to avoid duplication
|
|
321
|
+
Suggested refactor:
|
|
322
|
+
```javascript
|
|
323
|
+
// lib/docker-helpers.js (new file)
|
|
324
|
+
export function prepareDockerComposeEnv(projectPath, projectName) {
|
|
325
|
+
const credentials = loadKeychainCredentials();
|
|
326
|
+
let tempEnvPath = null;
|
|
327
|
+
|
|
328
|
+
if (Object.keys(credentials).length > 0) {
|
|
329
|
+
try {
|
|
330
|
+
tempEnvPath = createSecureTempEnvFile(credentials, projectName);
|
|
331
|
+
} catch (err) {
|
|
332
|
+
console.warn(chalk.yellow(`⚠️ Could not load keychain credentials: ${err.message}`));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const envFilePath = path.join(projectPath, ENV_FILE_NAME);
|
|
337
|
+
const envFileFlag = fs.existsSync(envFilePath) ? `--env-file ${envFilePath}` : '';
|
|
338
|
+
const tempEnvFileFlag = tempEnvPath ? `--env-file ${tempEnvPath}` : '';
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
flags: `${tempEnvFileFlag} ${envFileFlag}`.trim(),
|
|
342
|
+
tempEnvPath,
|
|
343
|
+
cleanup: () => tempEnvPath && secureDeleteTempEnvFile(tempEnvPath)
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
#### Centralize volume naming logic
|
|
349
|
+
|
|
350
|
+
Suggested refactor:
|
|
351
|
+
```javascript
|
|
352
|
+
// lib/volume-naming.js (new file)
|
|
353
|
+
export function getVolumeNames(projectName) {
|
|
354
|
+
return {
|
|
355
|
+
db: `${projectName}_db_data`,
|
|
356
|
+
wp: `${projectName}_wp_build`
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Or simpler - just add to config.js
|
|
361
|
+
export function getWpVolumeName(projectName) {
|
|
362
|
+
return `${projectName}_wp_build`;
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
272
366
|
---
|
|
273
367
|
|
|
274
368
|
## Decision Framework
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Temporary Breaking Changes in v0.7.2
|
|
2
|
+
|
|
3
|
+
Version 0.7.2 unwinds a problem where the project name was being duplicated in the volume names, e.g. `projectname_projectname_wp_build`. This requires a one-time manual migration of your Docker volumes from the old names to the new names.
|
|
4
|
+
|
|
5
|
+
## Option 1: Simple Destruction and Recreation
|
|
6
|
+
|
|
7
|
+
If you don't need to preserve your database or any data in your WordPress volume, you can simply destroy and recreate your environment:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx buwp-local destroy
|
|
11
|
+
npx buwp-local start
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Manual Migration Steps
|
|
15
|
+
|
|
16
|
+
If you want to preserve your database and WordPress volume, follow these steps to rename your Docker volumes:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Stop environment
|
|
20
|
+
npx buwp-local stop
|
|
21
|
+
|
|
22
|
+
# Rename volumes manually
|
|
23
|
+
docker volume create projectname_db_data
|
|
24
|
+
docker volume create projectname_wp_build
|
|
25
|
+
|
|
26
|
+
# Copy data from old to new
|
|
27
|
+
docker run --rm -v projectname_projectname_db_data:/from -v projectname_db_data:/to alpine ash -c "cd /from && cp -av . /to"
|
|
28
|
+
docker run --rm -v projectname_projectname_wp_build:/from -v projectname_wp_build:/to alpine ash -c "cd /from && cp -av . /to"
|
|
29
|
+
|
|
30
|
+
# Update buwp-local and start
|
|
31
|
+
npm update @bostonuniversity/buwp-local
|
|
32
|
+
npx buwp-local start
|
|
33
|
+
|
|
34
|
+
# Verify, then remove old volumes
|
|
35
|
+
docker volume rm projectname_projectname_db_data projectname_projectname_wp_build
|
|
36
|
+
|
|
37
|
+
```
|
package/lib/commands/update.js
CHANGED
|
@@ -57,9 +57,52 @@ async function updateCommand(options = {}) {
|
|
|
57
57
|
process.exit(1);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
// Step 2:
|
|
60
|
+
// Step 2: Stop and remove containers to ensure new images are used
|
|
61
|
+
// Note: We use 'down' without -v flag to preserve all volumes
|
|
62
|
+
console.log(chalk.cyan('\n🛑 Stopping containers...'));
|
|
63
|
+
try {
|
|
64
|
+
// Remove containers but preserve volumes (no -v flag)
|
|
65
|
+
execSync(
|
|
66
|
+
`docker compose -p ${projectName} -f "${composePath}" down`,
|
|
67
|
+
{
|
|
68
|
+
cwd: composeDir,
|
|
69
|
+
stdio: 'inherit'
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error(chalk.red('\n❌ Failed to stop containers'));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Step 3: Conditionally remove wp_build volume to get fresh WordPress core
|
|
78
|
+
const preserveWpBuild = options.preserveWpbuild || false;
|
|
79
|
+
|
|
80
|
+
if (!preserveWpBuild) {
|
|
81
|
+
// Docker Compose prefixes volume names with project name
|
|
82
|
+
const wpVolumeName = `${projectName}_wp_build`;
|
|
83
|
+
|
|
84
|
+
console.log(chalk.cyan('\n🗑️ Removing WordPress volume to get fresh core files...'));
|
|
85
|
+
try {
|
|
86
|
+
execSync(
|
|
87
|
+
`docker volume rm ${wpVolumeName}`,
|
|
88
|
+
{
|
|
89
|
+
cwd: composeDir,
|
|
90
|
+
stdio: 'ignore'
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
console.log(chalk.green('✓ WordPress volume removed\n'));
|
|
94
|
+
} catch (err) {
|
|
95
|
+
// Volume might not exist - that's okay
|
|
96
|
+
console.log(chalk.yellow('⚠️ WordPress volume not found (will be created fresh)\n'));
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
console.log(chalk.yellow('\n⚠️ Preserving existing WordPress volume'));
|
|
100
|
+
console.log(chalk.gray('WordPress core files will NOT be updated from the image.\n'));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Step 4: Start containers with new images
|
|
61
104
|
// Need to pass environment variables just like start command does
|
|
62
|
-
console.log(chalk.cyan('
|
|
105
|
+
console.log(chalk.cyan('🔨 Starting containers with new images...'));
|
|
63
106
|
|
|
64
107
|
// Load keychain credentials and create secure temp env file if available
|
|
65
108
|
let tempEnvPath = null;
|
|
@@ -80,14 +123,14 @@ async function updateCommand(options = {}) {
|
|
|
80
123
|
|
|
81
124
|
try {
|
|
82
125
|
execSync(
|
|
83
|
-
`docker compose -p ${projectName} ${tempEnvFileFlag} ${envFileFlag} -f "${composePath}" up -d
|
|
126
|
+
`docker compose -p ${projectName} ${tempEnvFileFlag} ${envFileFlag} -f "${composePath}" up -d`,
|
|
84
127
|
{
|
|
85
128
|
cwd: composeDir,
|
|
86
129
|
stdio: 'inherit'
|
|
87
130
|
}
|
|
88
131
|
);
|
|
89
132
|
} catch (err) {
|
|
90
|
-
console.error(chalk.red('\n❌ Failed to
|
|
133
|
+
console.error(chalk.red('\n❌ Failed to start containers'));
|
|
91
134
|
process.exit(1);
|
|
92
135
|
} finally {
|
|
93
136
|
// Always clean up temp env file
|
|
@@ -98,9 +141,17 @@ async function updateCommand(options = {}) {
|
|
|
98
141
|
|
|
99
142
|
// Success message
|
|
100
143
|
console.log(chalk.green('\n✅ Update complete!\n'));
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
144
|
+
|
|
145
|
+
if (!preserveWpBuild) {
|
|
146
|
+
console.log(chalk.cyan('Updated:'));
|
|
147
|
+
console.log(chalk.gray(' ✓ WordPress core files (from new image)'));
|
|
148
|
+
console.log(chalk.gray(' ✓ BU plugins and themes (from new image)\n'));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log(chalk.cyan('Preserved:'));
|
|
152
|
+
console.log(chalk.gray(' ✓ Database (all content and settings)'));
|
|
153
|
+
console.log(chalk.gray(' ✓ Custom mapped code (your local files)'));
|
|
154
|
+
console.log(chalk.gray(' ✓ Uploads (stored in S3)\n'));
|
|
104
155
|
console.log(chalk.cyan('Access your site at:'));
|
|
105
156
|
console.log(chalk.white(` https://${config.hostname}\n`));
|
|
106
157
|
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watch Jobs command - Periodically process site-manager jobs
|
|
3
|
+
* Mirrors production cron/EventBridge behavior for local development
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { execSync, exec } from 'child_process';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import { loadConfig } from '../config.js';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_INTERVAL = 60; // 1 minute in seconds
|
|
13
|
+
const MIN_INTERVAL = 10; // Minimum 10 seconds
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Format timestamp for log output
|
|
17
|
+
*/
|
|
18
|
+
function timestamp() {
|
|
19
|
+
const now = new Date();
|
|
20
|
+
return now.toLocaleString('en-US', {
|
|
21
|
+
year: 'numeric',
|
|
22
|
+
month: '2-digit',
|
|
23
|
+
day: '2-digit',
|
|
24
|
+
hour: '2-digit',
|
|
25
|
+
minute: '2-digit',
|
|
26
|
+
second: '2-digit',
|
|
27
|
+
hour12: false
|
|
28
|
+
}).replace(',', '');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if container is running
|
|
33
|
+
*/
|
|
34
|
+
function isContainerRunning(projectName, composePath) {
|
|
35
|
+
try {
|
|
36
|
+
const result = execSync(
|
|
37
|
+
`docker compose -p ${projectName} -f "${composePath}" ps --status running --services`,
|
|
38
|
+
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }
|
|
39
|
+
);
|
|
40
|
+
return result.includes('wordpress');
|
|
41
|
+
} catch (err) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Process site-manager jobs
|
|
48
|
+
*/
|
|
49
|
+
async function processJobs(projectName, composePath, quiet) {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
const composeDir = path.dirname(composePath);
|
|
52
|
+
const command = `docker compose -p ${projectName} -f "${composePath}" exec -T wordpress wp site-manager process-jobs`;
|
|
53
|
+
|
|
54
|
+
if (!quiet) {
|
|
55
|
+
console.log(chalk.gray(`[${timestamp()}] Checking for jobs...`));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
exec(command, { cwd: composeDir }, (error, stdout, stderr) => {
|
|
59
|
+
if (error) {
|
|
60
|
+
// In quiet mode, silently retry on transient errors (container might restart)
|
|
61
|
+
// Only verbose mode shows these errors
|
|
62
|
+
if (!quiet) {
|
|
63
|
+
if (stderr.includes('container') || stderr.includes('not running')) {
|
|
64
|
+
console.log(chalk.yellow(`[${timestamp()}] ⚠️ Container not running. Waiting...`));
|
|
65
|
+
} else {
|
|
66
|
+
console.log(chalk.red(`[${timestamp()}] ❌ Error executing command:`));
|
|
67
|
+
console.log(chalk.red(stderr || error.message));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
resolve(false);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const output = stdout.trim();
|
|
75
|
+
|
|
76
|
+
// Check if jobs were found and processed
|
|
77
|
+
if (output && output.length > 0) {
|
|
78
|
+
// In quiet mode, suppress all output (user checks web UI for job status)
|
|
79
|
+
// In verbose mode, show full job output
|
|
80
|
+
if (!quiet) {
|
|
81
|
+
console.log(chalk.green(`[${timestamp()}] ✓ Processing jobs:`));
|
|
82
|
+
console.log(output);
|
|
83
|
+
}
|
|
84
|
+
resolve(true);
|
|
85
|
+
} else if (!quiet) {
|
|
86
|
+
// No jobs found - only show in verbose mode
|
|
87
|
+
console.log(chalk.gray(`[${timestamp()}] No jobs found.`));
|
|
88
|
+
resolve(false);
|
|
89
|
+
} else {
|
|
90
|
+
// Quiet mode and no jobs - silent
|
|
91
|
+
resolve(false);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Watch jobs command
|
|
99
|
+
*/
|
|
100
|
+
async function watchJobsCommand(options) {
|
|
101
|
+
try {
|
|
102
|
+
const projectPath = process.cwd();
|
|
103
|
+
const composePath = path.join(projectPath, '.buwp-local', 'docker-compose.yml');
|
|
104
|
+
|
|
105
|
+
// Check if docker-compose.yml exists
|
|
106
|
+
if (!fs.existsSync(composePath)) {
|
|
107
|
+
console.log(chalk.yellow('⚠️ No running environment found.'));
|
|
108
|
+
console.log(chalk.gray('Run "buwp-local start" to create an environment.\n'));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Load config to get project name and optional interval setting
|
|
113
|
+
const config = loadConfig(projectPath);
|
|
114
|
+
const projectName = config.projectName || 'buwp-local';
|
|
115
|
+
|
|
116
|
+
// Determine interval (priority: CLI flag > config file > default)
|
|
117
|
+
let interval = DEFAULT_INTERVAL;
|
|
118
|
+
if (options.interval) {
|
|
119
|
+
interval = parseInt(options.interval, 10);
|
|
120
|
+
if (isNaN(interval) || interval < MIN_INTERVAL) {
|
|
121
|
+
console.log(chalk.red(`❌ Invalid interval. Minimum is ${MIN_INTERVAL} seconds.`));
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
} else if (config.jobWatchInterval) {
|
|
125
|
+
interval = config.jobWatchInterval;
|
|
126
|
+
if (interval < MIN_INTERVAL) {
|
|
127
|
+
console.log(chalk.yellow(`⚠️ Config interval too low. Using minimum: ${MIN_INTERVAL}s`));
|
|
128
|
+
interval = MIN_INTERVAL;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const quiet = options.quiet || false;
|
|
133
|
+
|
|
134
|
+
// Check if Docker is running
|
|
135
|
+
try {
|
|
136
|
+
execSync('docker info', { stdio: 'ignore' });
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.error(chalk.red('❌ Docker is not running.'));
|
|
139
|
+
console.log(chalk.gray('Start Docker Desktop and try again.'));
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Check if container is running initially
|
|
144
|
+
if (!isContainerRunning(projectName, composePath)) {
|
|
145
|
+
console.log(chalk.yellow('⚠️ WordPress container is not running.'));
|
|
146
|
+
console.log(chalk.gray('Run "buwp-local start" first, then try again.\n'));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Display startup message
|
|
151
|
+
console.log(chalk.cyan('🔍 Watching for site-manager jobs...'));
|
|
152
|
+
console.log(chalk.gray(` Interval: ${interval}s`));
|
|
153
|
+
console.log(chalk.gray(` Project: ${projectName}`));
|
|
154
|
+
console.log(chalk.gray(` Mode: ${quiet ? 'quiet' : 'verbose'}`));
|
|
155
|
+
console.log(chalk.gray('\nPress Ctrl+C to stop\n'));
|
|
156
|
+
|
|
157
|
+
// Set up graceful shutdown
|
|
158
|
+
let isShuttingDown = false;
|
|
159
|
+
const shutdown = () => {
|
|
160
|
+
if (isShuttingDown) return;
|
|
161
|
+
isShuttingDown = true;
|
|
162
|
+
console.log(chalk.cyan('\n\n👋 Stopping job watcher...'));
|
|
163
|
+
process.exit(0);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
process.on('SIGINT', shutdown);
|
|
167
|
+
process.on('SIGTERM', shutdown);
|
|
168
|
+
|
|
169
|
+
// Main watch loop
|
|
170
|
+
const watch = async () => {
|
|
171
|
+
// Check if we should stop
|
|
172
|
+
if (isShuttingDown) return;
|
|
173
|
+
|
|
174
|
+
// Process jobs
|
|
175
|
+
await processJobs(projectName, composePath, quiet);
|
|
176
|
+
|
|
177
|
+
// Schedule next check
|
|
178
|
+
if (!isShuttingDown) {
|
|
179
|
+
setTimeout(watch, interval * 1000);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Start watching
|
|
184
|
+
await watch();
|
|
185
|
+
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.error(chalk.red('\n❌ Error:'), err.message);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export default watchJobsCommand;
|
package/lib/compose-generator.js
CHANGED
|
@@ -13,10 +13,9 @@ import path from 'path';
|
|
|
13
13
|
* @returns {object} Docker Compose configuration object
|
|
14
14
|
*/
|
|
15
15
|
function generateComposeConfig(config) {
|
|
16
|
-
//
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const wpVolumeName = `${projectName}_wp_build`;
|
|
16
|
+
// Volume names without project prefix - Docker Compose will add the prefix automatically
|
|
17
|
+
const dbVolumeName = 'db_data';
|
|
18
|
+
const wpVolumeName = 'wp_build';
|
|
20
19
|
|
|
21
20
|
const composeConfig = {
|
|
22
21
|
services: {},
|