@bostonuniversity/buwp-local 0.6.3 → 0.7.1

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 CHANGED
@@ -24,6 +24,7 @@ const packageJson = JSON.parse(
24
24
  import startCommand from '../lib/commands/start.js';
25
25
  import stopCommand from '../lib/commands/stop.js';
26
26
  import destroyCommand from '../lib/commands/destroy.js';
27
+ import updateCommand from '../lib/commands/update.js';
27
28
  import logsCommand from '../lib/commands/logs.js';
28
29
  import wpCommand from '../lib/commands/wp.js';
29
30
  import shellCommand from '../lib/commands/shell.js';
@@ -60,6 +61,13 @@ program
60
61
  .option('-f, --force', 'Skip confirmation prompt')
61
62
  .action(destroyCommand);
62
63
 
64
+ // Update command
65
+ program
66
+ .command('update')
67
+ .description('Update Docker images and recreate containers')
68
+ .option('--all', 'Update all service images (default: WordPress only)')
69
+ .action(updateCommand);
70
+
63
71
  // Logs command
64
72
  program
65
73
  .command('logs')
@@ -88,6 +96,7 @@ program
88
96
  .option('--plugin', 'Non-interactive: initialize as plugin')
89
97
  .option('--mu-plugin', 'Non-interactive: initialize as mu-plugin')
90
98
  .option('--theme', 'Non-interactive: initialize as theme')
99
+ .option('--sandbox', 'Non-interactive: initialize as sandbox')
91
100
  .option('-f, --force', 'Overwrite existing configuration')
92
101
  .action(initCommand);
93
102
 
@@ -442,9 +442,9 @@ Docker's **volume mapping architecture** creates a clean separation:
442
442
  │ └── wp-content/ │
443
443
  │ └── plugins/ │
444
444
  │ └── my-plugin/ ──┐ │ (mapped from Mac)
445
- │ │
446
- │ SEPARATED = No conflicts! │ │
447
- └────────────────────────────────┴─┘
445
+ │ │
446
+ │ SEPARATED = No conflicts! │ │
447
+ └───────────────────────────────┴─┘
448
448
  ```
449
449
 
450
450
  **Technical Benefits:**
@@ -459,47 +459,6 @@ Docker's **volume mapping architecture** creates a clean separation:
459
459
 
460
460
  5. **Safe Testing** - Test breaking changes in WordPress without risk. If something goes wrong, recreate the container—your code was never in danger.
461
461
 
462
- #### Workflow Comparison
463
-
464
- **VM Sandbox Workflow:**
465
- ```
466
- Week 1-3: Develop in VM
467
- Week 4: Monthly rebuild
468
- 1. Backup your code manually
469
- 2. Coordinate with team
470
- 3. Wait for new VM image
471
- 4. Restore code manually
472
- 5. Re-test everything
473
- 6. Hope nothing broke
474
- Time lost: 2-4 hours + coordination overhead
475
- ```
476
-
477
- **buwp-local Workflow:**
478
- ```
479
- Anytime: New WordPress version available
480
- 1. docker pull ghcr.io/bu-ist/buwp:latest
481
- 2. npx buwp-local start
482
- 3. Test (your code unchanged)
483
- Time: 2 minutes
484
- ```
485
-
486
- #### Real-World Example
487
-
488
- **Scenario:** 6-week plugin development project. WordPress update lands in week 3.
489
-
490
- **VM Approach:**
491
- - Wait for scheduled monthly rebuild (week 4)
492
- - Backup your work
493
- - Rebuild overwrites everything
494
- - Restore and re-test
495
- - **Impact:** 2-4 hours lost, risk of data loss
496
-
497
- **buwp-local Approach:**
498
- - Pull new image when convenient (any time in week 3-6)
499
- - Containers recreate with new WordPress version
500
- - Your code untouched on local filesystem
501
- - **Impact:** 2 minutes, zero risk
502
-
503
462
  ### Separation of Concerns
504
463
 
505
464
  This architecture provides clean boundaries:
@@ -516,18 +475,6 @@ This architecture provides clean boundaries:
516
475
 
517
476
  Updates to WordPress never touch your development code. Updates to your code never require rebuilding WordPress.
518
477
 
519
- ### Additional Architectural Benefits
520
-
521
- 1. **Live Reload** - File changes sync instantly via volume mappings. Save in your editor, refresh browser, see changes.
522
-
523
- 2. **IDE Integration** - Use any editor (VS Code, PHPStorm, Sublime) with full IDE features (autocomplete, debugging, git integration).
524
-
525
- 3. **Version Control** - Your code is already on the host filesystem where git lives. No special workflows to commit from inside VMs.
526
-
527
- 4. **Multiple Projects** - Run multiple projects simultaneously, each with isolated environments. No VM resource conflicts.
528
-
529
- 5. **Portable Configuration** - Commit `.buwp-local.json` to version control. Teammates get identical environments with one command.
530
-
531
478
  For detailed migration guidance, see [MIGRATION_FROM_VM.md](MIGRATION_FROM_VM.md).
532
479
 
533
480
  ## Security Model
package/docs/CHANGELOG.md CHANGED
@@ -5,6 +5,24 @@ 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.1]
9
+
10
+ Documentation only release.
11
+
12
+ ## [0.7.0]
13
+
14
+ ### Added
15
+ - **`update` command** - Pull latest Docker images and recreate containers without losing data
16
+ - Pull WordPress image only by default (typical use case)
17
+ - `--all` flag to update all service images (Redis, S3 proxy, etc.)
18
+ - Safely applies security updates and WordPress core updates
19
+ - Data preserved: Database, WordPress files, volume mappings
20
+
21
+ - **Non-interactive sandbox initialization** - Add `--sandbox` flag to `init` command for scripted setup of sandbox-type projects
22
+
23
+ ### Changed
24
+ - **`init` command** - Now supports `--sandbox` flag for non-interactive initialization
25
+
8
26
  ## [0.6.3]
9
27
 
10
28
  ### Added
package/docs/COMMANDS.md CHANGED
@@ -115,6 +115,46 @@ npx buwp-local destroy --force
115
115
 
116
116
  ---
117
117
 
118
+ ### `update`
119
+
120
+ Update Docker images and recreate containers without losing data.
121
+
122
+ ```bash
123
+ npx buwp-local update [options]
124
+ ```
125
+
126
+ **Options:**
127
+ - `--all` - Update all service images (default: WordPress image only)
128
+
129
+ **Examples:**
130
+ ```bash
131
+ # Update WordPress image only (recommended)
132
+ npx buwp-local update
133
+
134
+ # Update all service images (Redis, S3 proxy, etc.)
135
+ npx buwp-local update --all
136
+ ```
137
+
138
+ **What it does:**
139
+ - Checks if environment exists and Docker is running
140
+ - Pulls latest Docker images from registry
141
+ - Recreates containers with new images using `--force-recreate`
142
+ - Loads credentials from Keychain and/or `.env.local`
143
+ - **Preserves volumes** - Database and WordPress files untouched
144
+ - Shows success message confirming what was preserved
145
+
146
+ **Use cases:**
147
+ - Pull latest WordPress updates without losing development work
148
+ - Update only WordPress image (typical use case) or all services
149
+ - Safe alternative to `destroy` when you only want to refresh images
150
+
151
+ **Key difference from `stop/start`:**
152
+ - `stop` → `start`: Reuses existing containers (no new images)
153
+ - `update`: Pulls new images and recreates containers (gets updates)
154
+ - `destroy`: Removes everything including volumes (fresh start)
155
+
156
+ ---
157
+
118
158
  ### `logs`
119
159
 
120
160
  View logs from Docker containers.
@@ -198,9 +198,6 @@ npx buwp-local start
198
198
  ```bash
199
199
  # Check Docker Desktop is running
200
200
  docker info
201
-
202
- # Check port conflicts
203
- npx buwp-local start --verbose
204
201
  ```
205
202
 
206
203
  ### Code changes not appearing
@@ -224,7 +221,7 @@ Check volume mappings in `.buwp-local.json`:
224
221
  # Verify Keychain entries
225
222
  npx buwp-local keychain list
226
223
 
227
- # Or use .env.local fallback
224
+ # Or use .env.local fallback --- this is a nice idea, but the actual command does not yet exist
228
225
  npx buwp-local keychain export > .env.local
229
226
  ```
230
227
 
package/docs/ROADMAP.md CHANGED
@@ -102,7 +102,7 @@ hostile.remove('127.0.0.1', config.hostname);
102
102
  ---
103
103
 
104
104
  ## Incremental Functional Improvements: v0.6.x
105
- **Status:** Currently Ongoing
105
+ **Status:** Shipped
106
106
  **Focus:** Enhancements based on initial user experience
107
107
 
108
108
  ### Shipped in v0.6.1
@@ -115,7 +115,7 @@ hostile.remove('127.0.0.1', config.hostname);
115
115
  - **Fix issues with spaces in paths**
116
116
  - Fix issues with spaces in host paths causing Docker errors
117
117
 
118
- ### Planned for v0.6.3
118
+ ### Shipped for v0.6.3
119
119
 
120
120
  - **Basic docs on existing Xdebug features**
121
121
  - Quickstart guide for enabling and using Xdebug in containers
@@ -123,10 +123,6 @@ hostile.remove('127.0.0.1', config.hostname);
123
123
  - **Volume Mapping pattern guide**
124
124
  - Documentation on different volume mapping strategies for various development workflows
125
125
 
126
- ### Shipped in v0.6.3
127
-
128
- **Focus:** Documentation refinement based on real user patterns
129
-
130
126
  **Key Deliverables:**
131
127
 
132
128
  1. **Volume Mapping Patterns Guide** ✅
@@ -152,12 +148,24 @@ hostile.remove('127.0.0.1', config.hostname);
152
148
 
153
149
  **Result:** Documentation now accurately reflects real-world development workflows, making it easier for new users to adopt appropriate patterns.
154
150
 
155
- ## Next Phase: v0.7.0 - Developer Experience
151
+ ## Next Phase: v0.7.x - Developer Experience
156
152
 
157
- **Status:** Planned
158
- **Timeline:** After team onboarding feedback
153
+ **Status:** Ongoing
159
154
  **Focus:** Ease of use and visibility
160
155
 
156
+ ### Shipped in v0.7.0
157
+ - **Docker Image Update Command** 🎯 (Proposed for v0.7.0)
158
+ - **Problem:** Stopping and restarting containers reuses existing images; newer images aren't pulled
159
+ - **Solution:** Add `buwp-local update` command that:
160
+ - Pulls latest Docker images from registry
161
+ - Recreates containers with new images
162
+ - Preserves volumes (database, WordPress data)
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`
165
+
166
+ ### Shipped in v0.7.1
167
+ - **Documentation Improvements**
168
+
161
169
  ### Potential Features
162
170
 
163
171
  - **Database Security**
@@ -169,6 +177,10 @@ hostile.remove('127.0.0.1', config.hostname);
169
177
  - Command to help generate Xdebug configuration for IDEs (VSCode, Zed)
170
178
  - Documentation on usage patterns
171
179
 
180
+ - **Interactive setup assistant for adding volume mappings**
181
+ - Guided prompts to add common volume mappings post-initialization
182
+ - Suggestions based on detected project structure
183
+
172
184
  - **Improved Windows and Linux support**
173
185
  - Multiplatform /etc/hosts hostname guide
174
186
  - Evaluate credential storage solutions for non-macOS platforms (https://www.npmjs.com/package/keytar)
@@ -195,6 +207,10 @@ hostile.remove('127.0.0.1', config.hostname);
195
207
  - **Docker Volume management assistant**
196
208
  - listing and cleanup of unused volumes
197
209
 
210
+ - **Unit tests**
211
+ - Core modules (config, keychain, docker-compose)
212
+ - Command tests (init, start, stop, destroy, wp)
213
+
198
214
  ### Prioritization
199
215
  Will be informed by feedback from initial small group of users and actual pain points observed during rollout.
200
216
 
@@ -209,7 +225,8 @@ Will be informed by feedback from initial small group of users and actual pain p
209
225
 
210
226
  - **Cross-Platform Support** - Windows WSL2 and Linux credential storage
211
227
  - **SSL Certificate Generation** - Local HTTPS with mkcert
212
- - **Performance Monitoring** -
228
+ - **Real support for running on ports other than 443**
229
+ - **Potential GUI from Electron or SwiftUI**
213
230
 
214
231
  **Note:** Automatic `/etc/hosts` management deferred pending user feedback. See "Lessons Learned" section above for details on the `hostile` library approach.
215
232
 
@@ -85,7 +85,7 @@ function confirmDestroy(projectName) {
85
85
  console.log(chalk.yellow(`This will destroy project: ${chalk.bold(projectName)}`));
86
86
  console.log(chalk.yellow(' - Stop all containers'));
87
87
  console.log(chalk.yellow(' - Remove all containers'));
88
- console.log(chalk.yellow(' - Delete all volumes (including database data) (except maybe not, please fix)\n'));
88
+ console.log(chalk.yellow(' - Delete all volumes (including database data)\n'));
89
89
 
90
90
  rl.question(chalk.red('Are you sure you want to continue? (yes/no): '), (answer) => {
91
91
  rl.close();
@@ -9,6 +9,17 @@ import fs from 'fs';
9
9
  import os from 'os';
10
10
  import { initConfig, CONFIG_FILE_NAME } from '../config.js';
11
11
 
12
+ /**
13
+ * Container path templates for project types
14
+ * Defines where each project type maps to in the WordPress container
15
+ */
16
+ const MAPPING_TEMPLATES = {
17
+ 'plugin': '/var/www/html/wp-content/plugins/{name}',
18
+ 'mu-plugin': '/var/www/html/wp-content/mu-plugins/{name}',
19
+ 'theme': '/var/www/html/wp-content/themes/{name}',
20
+ 'sandbox': null // Sandbox has no default mapping
21
+ };
22
+
12
23
  /**
13
24
  * Detect project type from package.json or directory structure
14
25
  * @param {string} projectPath - Path to project directory
@@ -84,6 +95,7 @@ async function initCommand(options) {
84
95
  if (options.plugin) initOptions.plugin = true;
85
96
  if (options.muPlugin) initOptions.muPlugin = true;
86
97
  if (options.theme) initOptions.theme = true;
98
+ if (options.sandbox) initOptions.sandbox = true;
87
99
 
88
100
  const configPath = initConfig(projectPath, initOptions);
89
101
  console.log(chalk.green(`✅ Created configuration file: ${configPath}\n`));
@@ -101,11 +113,26 @@ async function initCommand(options) {
101
113
  console.log(chalk.cyan(`ℹ️ Detected project type: ${detectedType}\n`));
102
114
  }
103
115
 
104
- // Determine default project type index
105
- let defaultTypeIndex = 0;
106
- if (detectedType === 'plugin') defaultTypeIndex = 0;
107
- else if (detectedType === 'mu-plugin') defaultTypeIndex = 1;
108
- else if (detectedType === 'theme') defaultTypeIndex = 2;
116
+ // Define project type choices
117
+ const projectTypeChoices = [
118
+ { title: 'Plugin', value: 'plugin', description: 'WordPress plugin development' },
119
+ { title: 'MU Plugin', value: 'mu-plugin', description: 'Must-use plugin development' },
120
+ { title: 'Theme', value: 'theme', description: 'WordPress theme development' },
121
+ { title: 'Sandbox', value: 'sandbox', description: 'Base WordPress environment (add code mappings later)' }
122
+ ];
123
+
124
+ // Determine default project type with priority: CLI flag > detected type > default
125
+ const typeFromFlag = options.plugin ? 'plugin'
126
+ : options.muPlugin ? 'mu-plugin'
127
+ : options.theme ? 'theme'
128
+ : options.sandbox ? 'sandbox'
129
+ : null;
130
+
131
+ const preferredType = typeFromFlag || detectedType || 'plugin';
132
+
133
+ const defaultTypeIndex = projectTypeChoices.findIndex(
134
+ choice => choice.value === preferredType
135
+ );
109
136
 
110
137
  const questions = [
111
138
  {
@@ -119,13 +146,7 @@ async function initCommand(options) {
119
146
  type: 'select',
120
147
  name: 'projectType',
121
148
  message: 'Project type',
122
- choices: [
123
- { title: 'Plugin', value: 'plugin', description: 'WordPress plugin development' },
124
- { title: 'MU Plugin', value: 'mu-plugin', description: 'Must-use plugin development' },
125
- { title: 'Theme', value: 'theme', description: 'WordPress theme development' },
126
- { title: 'Sandbox', value: 'sandbox', description: 'Base WordPress environment (add code mappings later)' },
127
- { title: 'Custom', value: 'custom', description: 'Custom mapping configuration' }
128
- ],
149
+ choices: projectTypeChoices,
129
150
  initial: defaultTypeIndex
130
151
  },
131
152
  {
@@ -245,31 +266,12 @@ async function initCommand(options) {
245
266
  }
246
267
  };
247
268
 
248
- // Add mapping based on project type
249
- if (answers.projectType === 'plugin') {
250
- config.mappings.push({
251
- local: './',
252
- container: `/var/www/html/wp-content/plugins/${answers.projectName}`
253
- });
254
- } else if (answers.projectType === 'mu-plugin') {
255
- config.mappings.push({
256
- local: './',
257
- container: `/var/www/html/wp-content/mu-plugins/${answers.projectName}`
258
- });
259
- } else if (answers.projectType === 'theme') {
260
- config.mappings.push({
261
- local: './',
262
- container: `/var/www/html/wp-content/themes/${answers.projectName}`
263
- });
264
- } else if (answers.projectType === 'sandbox') {
265
- // Sandbox type: no initial mappings
266
- // User can add mappings manually to config.mappings array
267
- } else {
268
- // Custom type
269
+ // Add mapping based on project type using template
270
+ const template = MAPPING_TEMPLATES[answers.projectType];
271
+ if (template) {
269
272
  config.mappings.push({
270
273
  local: './',
271
- container: '/var/www/html/wp-content/plugins/my-plugin',
272
- comment: 'Customize this mapping for your project'
274
+ container: template.replace('{name}', answers.projectName)
273
275
  });
274
276
  }
275
277
 
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Update command - Updates Docker images and recreates containers
3
+ */
4
+
5
+ import chalk from 'chalk';
6
+ import { execSync } from 'child_process';
7
+ import path from 'path';
8
+ import fs from 'fs';
9
+ import { loadConfig, loadKeychainCredentials, createSecureTempEnvFile, secureDeleteTempEnvFile, ENV_FILE_NAME } from '../config.js';
10
+
11
+ async function updateCommand(options = {}) {
12
+ console.log(chalk.blue('🔄 Updating Docker images...\n'));
13
+
14
+ try {
15
+ const projectPath = process.cwd();
16
+ const composePath = path.join(projectPath, '.buwp-local', 'docker-compose.yml');
17
+ const composeDir = path.dirname(composePath);
18
+ const envFilePath = path.join(projectPath, ENV_FILE_NAME);
19
+
20
+ // Check if docker-compose.yml exists
21
+ if (!fs.existsSync(composePath)) {
22
+ console.log(chalk.yellow('⚠️ No environment found.'));
23
+ console.log(chalk.gray('Run "buwp-local start" to create an environment.\n'));
24
+ return;
25
+ }
26
+
27
+ // Load config to get project name
28
+ const config = loadConfig(projectPath);
29
+ const projectName = config.projectName || 'buwp-local';
30
+
31
+ // Check if Docker is running
32
+ try {
33
+ execSync('docker info', { stdio: 'ignore' });
34
+ } catch (err) {
35
+ console.error(chalk.red('❌ Docker is not running'));
36
+ console.log(chalk.gray('Please start Docker Desktop and try again.\n'));
37
+ process.exit(1);
38
+ }
39
+
40
+ // Determine which services to pull
41
+ const pullAll = options.all || false;
42
+ const imageFilter = pullAll ? '' : 'wordpress';
43
+
44
+ // Step 1: Pull images (WordPress only by default, or all with --all flag)
45
+ console.log(chalk.cyan(pullAll ? '📥 Pulling all Docker images...' : '📥 Pulling WordPress image...'));
46
+ try {
47
+ execSync(
48
+ `docker compose -p ${projectName} -f "${composePath}" pull ${imageFilter}`,
49
+ {
50
+ cwd: composeDir,
51
+ stdio: 'inherit'
52
+ }
53
+ );
54
+ } catch (err) {
55
+ console.error(chalk.red('\n❌ Failed to pull Docker images'));
56
+ console.log(chalk.gray('Check your Docker registry credentials and network connection.\n'));
57
+ process.exit(1);
58
+ }
59
+
60
+ // Step 2: Recreate containers with new images (preserves volumes)
61
+ // Need to pass environment variables just like start command does
62
+ console.log(chalk.cyan('\n🔨 Recreating containers with new images...'));
63
+
64
+ // Load keychain credentials and create secure temp env file if available
65
+ let tempEnvPath = null;
66
+ const finalKeychainCredentials = loadKeychainCredentials();
67
+ const keychainCredCount = Object.keys(finalKeychainCredentials).length;
68
+
69
+ if (keychainCredCount > 0) {
70
+ try {
71
+ tempEnvPath = createSecureTempEnvFile(finalKeychainCredentials, projectName);
72
+ } catch (err) {
73
+ console.warn(chalk.yellow(`⚠️ Could not load keychain credentials: ${err.message}`));
74
+ }
75
+ }
76
+
77
+ // Build env-file flags
78
+ const envFileFlag = fs.existsSync(envFilePath) ? `--env-file ${envFilePath}` : '';
79
+ const tempEnvFileFlag = tempEnvPath ? `--env-file ${tempEnvPath}` : '';
80
+
81
+ try {
82
+ execSync(
83
+ `docker compose -p ${projectName} ${tempEnvFileFlag} ${envFileFlag} -f "${composePath}" up -d --force-recreate`,
84
+ {
85
+ cwd: composeDir,
86
+ stdio: 'inherit'
87
+ }
88
+ );
89
+ } catch (err) {
90
+ console.error(chalk.red('\n❌ Failed to recreate containers'));
91
+ process.exit(1);
92
+ } finally {
93
+ // Always clean up temp env file
94
+ if (tempEnvPath) {
95
+ secureDeleteTempEnvFile(tempEnvPath);
96
+ }
97
+ }
98
+
99
+ // Success message
100
+ console.log(chalk.green('\n✅ Update complete!\n'));
101
+ console.log(chalk.gray('Preserved:'));
102
+ console.log(chalk.gray(' ✓ Database and WordPress data'));
103
+ console.log(chalk.gray(' ✓ Volume mappings and configuration\n'));
104
+ console.log(chalk.cyan('Access your site at:'));
105
+ console.log(chalk.white(` https://${config.hostname}\n`));
106
+
107
+ } catch (err) {
108
+ console.error(chalk.red('\n❌ Error:'), err.message);
109
+ process.exit(1);
110
+ }
111
+ }
112
+
113
+ export default updateCommand;
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@bostonuniversity/buwp-local",
3
- "version": "0.6.3",
3
+ "version": "0.7.1",
4
4
  "description": "Local WordPress development environment for Boston University projects",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "bin": {
8
- "buwp-local": "./bin/buwp-local.js"
8
+ "buwp-local": "bin/buwp-local.js"
9
9
  },
10
10
  "scripts": {
11
11
  "test": "echo \"Error: no test specified\" && exit 1",
package/.buwp-local.json DELETED
@@ -1,22 +0,0 @@
1
- {
2
- "projectName": "buwp-local",
3
- "image": "bu-wordpress:latest",
4
- "hostname": "jaydub.local",
5
- "multisite": true,
6
- "services": {
7
- "redis": true,
8
- "s3proxy": true,
9
- "shibboleth": true
10
- },
11
- "ports": {
12
- "http": 80,
13
- "https": 443,
14
- "db": 3306,
15
- "redis": 6379
16
- },
17
- "mappings": [],
18
- "env": {
19
- "WP_DEBUG": true,
20
- "XDEBUG": false
21
- }
22
- }