@bostonuniversity/buwp-local 0.5.3 → 0.6.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.
@@ -0,0 +1,237 @@
1
+ # Roadmap
2
+
3
+ Strategic direction and development priorities for buwp-local.
4
+
5
+ ## Release History
6
+
7
+ ### ✅ v0.5.x - Keychain & Credential Management (Complete)
8
+
9
+ **Status:** Shipped
10
+
11
+ **Key Features:**
12
+ - macOS Keychain integration for secure credential storage
13
+ - Automatic hex decoding for legacy multiline credentials
14
+ - Credential validation on startup with interactive setup
15
+ - Multi-project support with isolated Docker volumes
16
+ - Smart initialization for plugins, themes, and mu-plugins
17
+
18
+ **Result:** Ready for production use by small development teams.
19
+
20
+ ---
21
+
22
+ ## v0.6.0 - Quality & Robustness
23
+ **Status:** Shipped
24
+ **Focus:** Foundation improvements before team rollout
25
+
26
+ ### Features in Development
27
+
28
+ 1. **Credential Validation** ✅ (Complete)
29
+ - Validates credentials before starting containers
30
+ - Offers interactive setup if credentials missing
31
+ - Clear error messages and guidance
32
+
33
+ 2. **Documentation Consolidation** ✅ (Complete)
34
+ - Reorganized docs in `/docs` directory
35
+ - Comprehensive guides for all user levels
36
+ - Architecture documentation for contributors
37
+
38
+ 3. **Robust /etc/hosts Detection** ✅ (Complete)
39
+ - Detects if hostname exists in `/etc/hosts` during start
40
+ - Provides copy-paste sudo command if missing
41
+ - Smart messaging (only shows once per project)
42
+ - Non-blocking (user can continue without adding)
43
+
44
+ ### Success Criteria
45
+ - ✅ Zero setup confusion for new users
46
+ - ✅ All common workflows documented
47
+ - ✅ Hostname setup guidance clear and actionable
48
+
49
+ **Status:** All v0.6.0 features complete. Ready for initial user rollout.
50
+
51
+ ---
52
+
53
+ ## Lessons Learned: /etc/hosts Management
54
+
55
+ ### What We Built (v0.6.0)
56
+ The detection-only approach proved to be the right choice:
57
+ - **Non-intrusive** - Checks once, shows clear instructions
58
+ - **No sudo required** - Detection runs without elevated permissions
59
+ - **Smart persistence** - Doesn't nag on every start
60
+ - **User control** - Provides copy-paste command, user decides when to run
61
+
62
+ ### Future Consideration: Automatic Management
63
+
64
+ **Library Identified:** [`hostile`](https://www.npmjs.com/package/hostile) npm package
65
+ - ~500k weekly downloads, well-maintained
66
+ - Cross-platform (macOS, Linux, Windows)
67
+ - Programmatic API: `hostile.set('127.0.0.1', 'hostname')`
68
+ - Sync and async methods available
69
+
70
+ **Why We're Not Implementing It Now:**
71
+
72
+ 1. **Requires sudo** - Password prompt interrupts smooth startup flow
73
+ 2. **Security sensitivity** - Modifying system files needs user trust
74
+ 3. **Current solution works** - Detection + copy-paste is clear and effective
75
+ 4. **Premature optimization** - Need real user feedback first
76
+
77
+ **If We Revisit (v0.8.0+):**
78
+
79
+ Implementation would be straightforward:
80
+ ```javascript
81
+ import hostile from 'hostile';
82
+
83
+ // Add hostname (requires sudo, prompts for password)
84
+ hostile.set('127.0.0.1', config.hostname, (err) => {
85
+ if (err) {
86
+ // Fall back to manual instructions
87
+ }
88
+ });
89
+
90
+ // Remove on destroy
91
+ hostile.remove('127.0.0.1', config.hostname);
92
+ ```
93
+
94
+ **Decision criteria for future:**
95
+ - Do >50% of users request automatic management?
96
+ - Is sudo prompt acceptable to most users?
97
+ - Would opt-in flag (`autoManageHosts: true`) address concerns?
98
+ - Does it meaningfully improve onboarding over current approach?
99
+
100
+ **Current recommendation:** Gather feedback from Stage 1 users (2-3 developers) in December 2024. If hostname setup proves to be a friction point, revisit automatic management in Q1 2025.
101
+
102
+ ---
103
+
104
+ ## Incremental Functional Improvements: v0.6.1
105
+ **Status:** Currently Ongoing
106
+ **Focus:** Enhancements based on initial user experience
107
+
108
+ - **Container Registry Assistance**
109
+ - Guide users on setting up access to private registries (ghcr.io)
110
+ - Automatic check for registry login or existing image on `start`
111
+
112
+ - **Basic docs on existing Xdebug features**
113
+ - Quickstart guide for enabling and using Xdebug in containers
114
+
115
+ - **Volume Mapping pattern guide**
116
+ - Documentation on different volume mapping strategies for various development workflows
117
+
118
+ ## Next Phase: v0.7.0 - Developer Experience
119
+
120
+ **Status:** Planned
121
+ **Timeline:** After team onboarding feedback
122
+ **Focus:** Ease of use and visibility
123
+
124
+ ### Potential Features
125
+
126
+ - **Security Checks**
127
+ - Check database access on db port (e.g. `localhost:3306`)
128
+ - Consider more stringent default database passwords
129
+ - The database can have restricted content in it, so we need to ensure that users are aware of this and take appropriate measures.
130
+
131
+ - **Xdebug Integration**
132
+ - Command to help generate Xdebug configuration for IDEs (VSCode, Zed)
133
+ - Documentation on usage patterns
134
+
135
+ - **Improved Windows and Linux support**
136
+ - Multiplatform /etc/hosts hostname management
137
+ - Evaluate credential storage solutions for non-macOS platforms (https://www.npmjs.com/package/keytar)
138
+
139
+ - **Project Status & Listing**
140
+ - Central tracking of all buwp-local projects in `~/.buwp-local/projects.json`
141
+ - View all running projects: `buwp-local list`
142
+ - Quick status checks: `buwp-local status`
143
+
144
+ - **Health Checks**
145
+ - Verify services are running properly
146
+ - Database connectivity validation
147
+ - Clear diagnostics on failures
148
+
149
+ - **Improved Error Messages**
150
+ - Docker startup failures → actionable solutions
151
+ - Credential issues → clear next steps
152
+ - Port conflicts → suggest alternatives
153
+
154
+ - **Multi project experience**
155
+ - 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.
156
+
157
+ - **Docker Volume management assistant**
158
+ - listing and cleanup of unused volumes
159
+
160
+ ### Prioritization
161
+ Will be informed by feedback from initial 2-3 users and actual pain points observed during rollout.
162
+
163
+ ---
164
+
165
+ ## Future Phases: v0.8.0+
166
+
167
+ **Status:** Conceptual
168
+ **Timeline:** TBD based on team feedback
169
+
170
+ ### Potential Features (Lower Priority)
171
+
172
+ - **SSL Certificate Generation** - Local HTTPS with mkcert
173
+ - **Database Backup/Restore** - Simplified snapshots and recovery
174
+ - **Performance Monitoring** - Real-time resource usage tracking
175
+ - **Team Configuration Sync** - Share project configurations across team
176
+ - **Cross-Platform Support** - Windows WSL2 and Linux credential storage
177
+
178
+ **Note:** Automatic `/etc/hosts` management deferred pending user feedback. See "Lessons Learned" section above for details on the `hostile` library approach.
179
+
180
+ ---
181
+
182
+ ## Roadmap by User Stage
183
+
184
+ ### Stage 1: Initial Users
185
+ **Users:** 1-3 developers
186
+ **Goal:** Validate core functionality
187
+ **Release:** v0.6.0
188
+ **Focus:** Robustness, clear setup, good documentation
189
+
190
+ ### Stage 2: Team Rollout
191
+ **Users:** 10-15 developers
192
+ **Goal:** Find and fix real-world issues
193
+ **Release:** v0.7.0+
194
+ **Focus:** Developer experience, error handling
195
+
196
+ ### Stage 3: Broader Adoption
197
+ **Users:** 20+ developers
198
+ **Goal:** Self-service onboarding
199
+ **Release:** v1.0.0+
200
+ **Focus:** Advanced features, automation
201
+
202
+ ---
203
+
204
+ ## Technical Debt
205
+
206
+ ### Testing
207
+ - Unit tests for core modules (config, keychain, docker-compose)
208
+ - Integration tests for Docker operations
209
+ - E2E tests for full workflows
210
+
211
+ ### Documentation
212
+ - Troubleshooting guide for common issues
213
+ - FAQ section
214
+ - Video tutorials for setup
215
+
216
+ ### Quality
217
+ - Standardized help for all CLI commands
218
+
219
+ ---
220
+
221
+ ## Decision Framework
222
+
223
+ Features will be prioritized based on:
224
+
225
+ 1. **User Feedback** - What's actually blocking users?
226
+ 2. **Adoption Impact** - Does it help onboard new users?
227
+ 3. **Implementation Effort** - Can it be done quickly?
228
+ 4. **Maintenance Burden** - Will it create ongoing support overhead?
229
+
230
+ ---
231
+
232
+ ## Technical Details
233
+
234
+ For detailed information about planned features, implementation approaches, and architecture decisions, see:
235
+
236
+ - **[ARCHITECTURE.md](ARCHITECTURE.md)** - Planned Features section
237
+ - **[CHANGELOG.md](CHANGELOG.md)** - Version history and release notes
@@ -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)\n'));
88
+ console.log(chalk.yellow(' - Delete all volumes (including database data) (except maybe not, please fix)\n'));
89
89
 
90
90
  rl.question(chalk.red('Are you sure you want to continue? (yes/no): '), (answer) => {
91
91
  rl.close();
@@ -295,7 +295,8 @@ async function initCommand(options) {
295
295
  // Show next steps
296
296
  console.log(chalk.cyan('📝 Next steps:'));
297
297
  console.log(chalk.gray(' 1. Check keychain credentials or add .env.local with your credentials'));
298
- console.log(chalk.gray(` 2. Add to /etc/hosts: 127.0.0.1 ${answers.hostname}`));
298
+ console.log(chalk.gray(' 2. Add to /etc/hosts with this command:'));
299
+ console.log(chalk.cyan(` echo "127.0.0.1 ${answers.hostname}" | sudo tee -a /etc/hosts\n`));
299
300
 
300
301
  if (answers.projectType === 'sandbox') {
301
302
  console.log(chalk.gray(' 3. Edit .buwp-local.json and add volume mappings to the mappings array'));
@@ -635,7 +635,7 @@ function showHelp() {
635
635
  console.log(' # Interactive setup (prompts for each credential)');
636
636
  console.log(' buwp-local keychain setup\n');
637
637
  console.log(' # Bulk import from JSON file');
638
- console.log(' buwp-local keychain setup --file .buwp-credentials.json\n');
638
+ console.log(' buwp-local keychain setup --file buwp-local-credentials.json\n');
639
639
  console.log(' # Interactive prompt for single-line credential');
640
640
  console.log(' buwp-local keychain set WORDPRESS_DB_PASSWORD\n');
641
641
  console.log(' # Set single-line credential directly');
@@ -6,8 +6,164 @@ import chalk from 'chalk';
6
6
  import { execSync } from 'child_process';
7
7
  import path from 'path';
8
8
  import fs from 'fs';
9
+ import prompts from 'prompts';
9
10
  import { loadConfig, validateConfig, ENV_FILE_NAME, loadKeychainCredentials, createSecureTempEnvFile, secureDeleteTempEnvFile } from '../config.js';
10
11
  import { generateComposeFile } from '../compose-generator.js';
12
+ import keychainCommand from './keychain.js';
13
+
14
+ /**
15
+ * Required credentials that must be present for WordPress to function
16
+ */
17
+ const REQUIRED_CREDENTIALS = [
18
+ 'WORDPRESS_DB_PASSWORD',
19
+ 'DB_ROOT_PASSWORD'
20
+ ];
21
+
22
+ /**
23
+ * Check if hostname exists in /etc/hosts
24
+ * @param {string} hostname - Hostname to check
25
+ * @returns {object} { found: boolean, error?: string }
26
+ */
27
+ function checkHostsFile(hostname) {
28
+ try {
29
+ const hostsPath = '/etc/hosts';
30
+ const content = fs.readFileSync(hostsPath, 'utf8');
31
+ const lines = content.split('\n');
32
+
33
+ // Check each line for hostname
34
+ for (const line of lines) {
35
+ // Skip comments and empty lines
36
+ if (line.trim().startsWith('#') || !line.trim()) continue;
37
+
38
+ // Parse: "127.0.0.1 hostname.local" or "127.0.0.1\thostname.local"
39
+ const parts = line.trim().split(/\s+/);
40
+ if (parts.length >= 2) {
41
+ // Check if hostname appears in any position after IP
42
+ const hostnames = parts.slice(1);
43
+ if (hostnames.includes(hostname)) {
44
+ return { found: true };
45
+ }
46
+ }
47
+ }
48
+
49
+ return { found: false };
50
+ } catch (error) {
51
+ // /etc/hosts not readable (unlikely on macOS)
52
+ return { found: false, error: error.message };
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Check if we've already shown the hosts warning for this project
58
+ * @param {string} projectPath - Project directory path
59
+ * @returns {boolean} true if warning already shown
60
+ */
61
+ function hasShownHostsWarning(projectPath) {
62
+ const warningFile = path.join(projectPath, '.buwp-local', '.hosts-warning-shown');
63
+ return fs.existsSync(warningFile);
64
+ }
65
+
66
+ /**
67
+ * Mark that we've shown the hosts warning for this project
68
+ * @param {string} projectPath - Project directory path
69
+ */
70
+ function markHostsWarningShown(projectPath) {
71
+ const dir = path.join(projectPath, '.buwp-local');
72
+ fs.mkdirSync(dir, { recursive: true });
73
+ const warningFile = path.join(dir, '.hosts-warning-shown');
74
+ fs.writeFileSync(warningFile, Date.now().toString());
75
+ }
76
+
77
+ /**
78
+ * Check if required credentials are available in keychain or .env.local
79
+ * @param {object} keychainCreds - Credentials loaded from keychain
80
+ * @param {string} envFilePath - Path to .env.local file
81
+ * @returns {object} { isValid, missing }
82
+ */
83
+ function validateCredentials(keychainCreds, envFilePath) {
84
+ const missing = [];
85
+
86
+ for (const key of REQUIRED_CREDENTIALS) {
87
+ // Check if credential exists in keychain
88
+ if (keychainCreds[key]) {
89
+ continue; // Found in keychain
90
+ }
91
+
92
+ // Check if credential exists in .env.local
93
+ if (fs.existsSync(envFilePath)) {
94
+ const envContent = fs.readFileSync(envFilePath, 'utf8');
95
+ if (envContent.includes(`${key}=`)) {
96
+ continue; // Found in .env.local
97
+ }
98
+ }
99
+
100
+ // Credential not found anywhere
101
+ missing.push(key);
102
+ }
103
+
104
+ return {
105
+ isValid: missing.length === 0,
106
+ missing
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Prompt user to set up missing credentials
112
+ * @param {string[]} missingCreds - List of missing credential keys
113
+ * @returns {Promise<boolean>} true if user wants to set up, false otherwise
114
+ */
115
+ async function promptCredentialSetup(missingCreds) {
116
+ console.log(chalk.yellow('\n⚠️ Missing Required Credentials\n'));
117
+ console.log(chalk.gray('The following credentials are not configured:\n'));
118
+ missingCreds.forEach(key => {
119
+ console.log(chalk.gray(` - ${key}`));
120
+ });
121
+ console.log('');
122
+
123
+ console.log(chalk.cyan('You can set up credentials in two ways:\n'));
124
+ console.log(chalk.white(' 1. Use macOS Keychain (recommended):'));
125
+ console.log(chalk.gray(' npx buwp-local keychain setup\n'));
126
+ console.log(chalk.white(' 2. Create .env.local file in your project:'));
127
+ console.log(chalk.gray(' cp .env.local.example .env.local'));
128
+ console.log(chalk.gray(' # Edit .env.local with your credentials\n'));
129
+
130
+ const { shouldSetup } = await prompts({
131
+ type: 'confirm',
132
+ name: 'shouldSetup',
133
+ message: 'Would you like to set up credentials now using Keychain?',
134
+ initial: true
135
+ });
136
+
137
+ return shouldSetup;
138
+ }
139
+
140
+ // Check if image is accessible
141
+ async function checkImageAccess(imageName) {
142
+ try {
143
+ // Try to inspect image locally
144
+ execSync(`docker image inspect ${imageName}`, { stdio: 'pipe' });
145
+ return true; // Image exists locally
146
+ } catch {
147
+ // Image doesn't exist, try to pull
148
+ try {
149
+ console.log(chalk.gray(`Checking access to ${imageName}...`));
150
+ execSync(`docker pull ${imageName}`, { stdio: 'pipe' });
151
+ return true;
152
+ } catch (pullError) {
153
+ if (pullError.message.includes('unauthorized') ||
154
+ pullError.message.includes('denied')) {
155
+ console.log(chalk.yellow('\n⚠️ Cannot access container image\n'));
156
+ console.log('The BU WordPress image requires GitHub Packages authentication.\n');
157
+ console.log('Run this command to authenticate:\n');
158
+ console.log(chalk.cyan(' docker login ghcr.io\n'));
159
+ console.log('You will need a GitHub personal access token with "read:packages" scope.');
160
+ console.log('See: https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry\n');
161
+ return false;
162
+ }
163
+ throw pullError; // Other error, let it surface
164
+ }
165
+ }
166
+ }
11
167
 
12
168
  async function startCommand(options) {
13
169
  console.log(chalk.blue('🚀 Starting BU WordPress local environment...\n'));
@@ -43,6 +199,80 @@ async function startCommand(options) {
43
199
  process.exit(1);
44
200
  }
45
201
 
202
+ // Load and validate credentials early
203
+ const envFilePath = path.join(projectPath, ENV_FILE_NAME);
204
+ const keychainCredentials = loadKeychainCredentials();
205
+ const credentialValidation = validateCredentials(keychainCredentials, envFilePath);
206
+
207
+ if (!credentialValidation.isValid) {
208
+ const shouldSetup = await promptCredentialSetup(credentialValidation.missing);
209
+
210
+ if (shouldSetup) {
211
+ // Run keychain setup command
212
+ console.log(chalk.gray('\nLaunching Keychain Setup...\n'));
213
+ await keychainCommand('setup', [], {});
214
+
215
+ // Reload credentials after setup
216
+ const reloadedCreds = loadKeychainCredentials();
217
+ const revalidation = validateCredentials(reloadedCreds, envFilePath);
218
+
219
+ if (!revalidation.isValid) {
220
+ console.log(chalk.red('\n❌ Setup incomplete. Required credentials still missing:\n'));
221
+ revalidation.missing.forEach(key => {
222
+ console.log(chalk.red(` - ${key}`));
223
+ });
224
+ console.log(chalk.gray('\nPlease complete setup or add credentials to .env.local\n'));
225
+ process.exit(1);
226
+ }
227
+ } else {
228
+ console.log(chalk.red('\n❌ Cannot start without required credentials.\n'));
229
+ process.exit(1);
230
+ }
231
+ } else {
232
+ console.log(chalk.green('✓ Required credentials validated\n'));
233
+ }
234
+
235
+ // Check image accessibility
236
+ console.log(chalk.gray('Checking container image access...'));
237
+ const imageAccessible = await checkImageAccess(config.image);
238
+
239
+ if (!imageAccessible) {
240
+ console.log(chalk.red('\n❌ Cannot proceed without access to container image.'));
241
+ console.log(chalk.gray('Please authenticate with GitHub Packages and try again.\n'));
242
+ process.exit(1);
243
+ }
244
+
245
+ console.log(chalk.green('✓ Container image accessible\n'));
246
+
247
+ // Check /etc/hosts for hostname entry
248
+ if (!hasShownHostsWarning(projectPath)) {
249
+ const hostsCheck = checkHostsFile(config.hostname);
250
+
251
+ if (!hostsCheck.found) {
252
+ console.log(chalk.yellow('⚠️ Hostname not found in /etc/hosts\n'));
253
+ console.log(`Your site won't be accessible at ${chalk.cyan(`http://${config.hostname}`)}`);
254
+ console.log('until you add this entry:\n');
255
+ console.log(chalk.green(` 127.0.0.1 ${config.hostname}\n`));
256
+ console.log('Run this command to add it (copy and paste, and then enter your password):\n');
257
+ console.log(chalk.cyan(` echo "127.0.0.1 ${config.hostname}" | sudo tee -a /etc/hosts\n`));
258
+
259
+ const { continueStart } = await prompts({
260
+ type: 'confirm',
261
+ name: 'continueStart',
262
+ message: 'Continue starting containers?',
263
+ initial: true
264
+ });
265
+
266
+ if (!continueStart) {
267
+ console.log(chalk.gray('\nStart cancelled. Add hostname to /etc/hosts and try again.'));
268
+ process.exit(0);
269
+ }
270
+
271
+ // Mark warning as shown so we don't annoy user on every start
272
+ markHostsWarningShown(projectPath);
273
+ }
274
+ }
275
+
46
276
  // Generate docker-compose.yml
47
277
  console.log(chalk.gray('Generating docker-compose.yml...'));
48
278
  const composePath = generateComposeFile(config, projectPath);
@@ -63,12 +293,12 @@ async function startCommand(options) {
63
293
 
64
294
  // Load keychain credentials and create secure temp env file if available
65
295
  let tempEnvPath = null;
66
- const keychainCredentials = loadKeychainCredentials();
67
- const keychainCredCount = Object.keys(keychainCredentials).length;
296
+ const finalKeychainCredentials = loadKeychainCredentials();
297
+ const keychainCredCount = Object.keys(finalKeychainCredentials).length;
68
298
 
69
299
  if (keychainCredCount > 0) {
70
300
  try {
71
- tempEnvPath = createSecureTempEnvFile(keychainCredentials, projectName);
301
+ tempEnvPath = createSecureTempEnvFile(finalKeychainCredentials, projectName);
72
302
  console.log(chalk.gray(`✓ Loaded ${keychainCredCount} credentials from keychain\n`));
73
303
  } catch (err) {
74
304
  console.warn(chalk.yellow(`⚠️ Could not load keychain credentials: ${err.message}`));
@@ -76,7 +306,6 @@ async function startCommand(options) {
76
306
  }
77
307
 
78
308
  // Check if .env.local exists and build env-file flags
79
- const envFilePath = path.join(projectPath, ENV_FILE_NAME);
80
309
  const envFileFlag = fs.existsSync(envFilePath) ? `--env-file ${envFilePath}` : '';
81
310
  const tempEnvFileFlag = tempEnvPath ? `--env-file ${tempEnvPath}` : '';
82
311
 
@@ -111,10 +340,6 @@ async function startCommand(options) {
111
340
  console.log(chalk.white(' buwp-local stop - Stop environment'));
112
341
  console.log(chalk.white(' buwp-local destroy - Remove environment\n'));
113
342
 
114
- // Reminder about /etc/hosts
115
- console.log(chalk.yellow('⚠️ Remember to add this to your /etc/hosts file:'));
116
- console.log(chalk.white(` 127.0.0.1 ${config.hostname}\n`));
117
-
118
343
  } catch (err) {
119
344
  console.error(chalk.red('\n❌ Error:'), err.message);
120
345
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bostonuniversity/buwp-local",
3
- "version": "0.5.3",
3
+ "version": "0.6.1",
4
4
  "description": "Local WordPress development environment for Boston University projects",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
package/readme.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  This repository contains resources and instructions for setting up a local WordPress development environment for Boston University projects. It uses the BU WordPress container image and provides the additional resources needed to run it locally with Docker.
4
4
 
5
+ The package can be installed in a specific repo for development of that one package, or standalone for more general use, mapping local code into the container as needed.
6
+
7
+ > **Why buwp-local over VM sandboxes?**
8
+ >
9
+ > Traditional VM sandboxes require **monthly rebuilds that wipe your development code**. With buwp-local's Docker architecture, your code lives on your local filesystem while only WordPress core updates. This means:
10
+ > - ✅ **Keep your work** - No more monthly rebuild cycles that erase local changes
11
+ > - ✅ **Update independently** - Pull WordPress updates on your schedule, not a global calendar
12
+ > - ✅ **Instant rollback** - Switch between WordPress versions without losing work
13
+ >
14
+ > Learn more: [Migration from VM Sandboxes](docs/MIGRATION_FROM_VM.md)
5
15
 
6
16
  ## Quickstart for plugin or theme development
7
17
 
@@ -84,3 +94,19 @@ Your local WordPress site should now be accessible at the hostname you configure
84
94
  ```
85
95
 
86
96
  This will download the latest snapshot from the specified source and import it into your local WordPress environment.
97
+
98
+ ## Documentation
99
+
100
+ - 📘 [Getting Started Guide](docs/GETTING_STARTED.md)
101
+ - 📖 [Command Reference](docs/COMMANDS.md)
102
+ - 🔐 [Credential Management](docs/CREDENTIALS.md)
103
+ - 🏗️ [Architecture](docs/ARCHITECTURE.md) (for contributors)
104
+
105
+ ## Features
106
+
107
+ - ✅ One-time credential setup with macOS Keychain
108
+ - ✅ Isolated environments for multiple projects
109
+ - ✅ Pre-configured BU infrastructure (Shibboleth, S3, Redis)
110
+ - ✅ Smart initialization for plugins, themes, and mu-plugins
111
+ - ✅ Volume mapping for live code sync
112
+ - ✅ Xdebug support for step debugging