@bostonuniversity/buwp-local 0.3.0 → 0.4.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,22 @@
1
+ {
2
+ "projectName": "buwp-local",
3
+ "image": "ghcr.io/bu-ist/bu-wp-docker-mod_shib:arm64-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
+ }
@@ -0,0 +1,26 @@
1
+ # BU WordPress Local Development Environment Variables
2
+ # Copy this file to .env.local and fill in your actual credentials
3
+ # NEVER commit .env.local to version control!
4
+
5
+ # Database Credentials
6
+ WORDPRESS_DB_PASSWORD=password
7
+ DB_ROOT_PASSWORD=rootpassword
8
+
9
+ # Shibboleth Configuration (if enabled)
10
+ SP_ENTITY_ID=https://your-sp-entity-id
11
+ IDP_ENTITY_ID=https://shib-test.bu.edu/idp/shibboleth
12
+ SHIB_IDP_LOGOUT=https://shib-test.bu.edu/idp/logout.jsp
13
+ SHIB_SP_KEY=your-private-key-here
14
+ SHIB_SP_CERT=your-certificate-here
15
+
16
+ # AWS S3 Configuration (if enabled)
17
+ S3_UPLOADS_BUCKET=your-bucket-name
18
+ S3_UPLOADS_REGION=us-east-1
19
+ S3_UPLOADS_ACCESS_KEY_ID=AKIA...
20
+ S3_UPLOADS_SECRET_ACCESS_KEY=your-secret-key
21
+ S3_ACCESS_RULES_TABLE=your-access-rules-table
22
+
23
+ # OLAP Configuration (if using S3 Object Lambda)
24
+ OLAP=your-olap-name
25
+ OLAP_ACCT_NBR=123456789
26
+ OLAP_REGION=us-east-1
package/USAGE.md CHANGED
@@ -10,14 +10,23 @@ npm install --save-dev @bostonuniversity/buwp-local
10
10
 
11
11
  ### 2. Initialize configuration
12
12
 
13
+ **Interactive mode (recommended):**
13
14
  ```bash
14
- npx buwp-local config --init
15
+ npx buwp-local init
15
16
  ```
16
17
 
18
+ The interactive setup will guide you through:
19
+ - Project name and type (plugin, theme, mu-plugin)
20
+ - Hostname configuration
21
+ - Port selection
22
+ - Service options (Redis, S3, Shibboleth)
23
+ - Debug settings
24
+
25
+ **Non-interactive mode:**
17
26
  ```bash
18
- buwp-local config --init --plugin # For plugins
19
- buwp-local config --init --mu-plugin # For mu-plugins
20
- buwp-local config --init --theme # For themes
27
+ npx buwp-local config --init --plugin # For plugins
28
+ npx buwp-local config --init --mu-plugin # For mu-plugins
29
+ npx buwp-local config --init --theme # For themes
21
30
  ```
22
31
 
23
32
  This creates `.buwp-local.json` in your project directory.
@@ -42,7 +51,13 @@ Edit `.buwp-local.json` to map your local repository into the container:
42
51
 
43
52
  ### 4. Create `.env.local` for secrets
44
53
 
45
- Create `.env.local` (never commit this file!):
54
+ Create `.env.local` (never commit this file!) or copy from the example:
55
+
56
+ ```bash
57
+ cp .env.local.example .env.local
58
+ ```
59
+
60
+ Then edit `.env.local` with your actual credentials:
46
61
 
47
62
  ```bash
48
63
  # Database
@@ -61,6 +76,7 @@ S3_UPLOADS_BUCKET=your-bucket
61
76
  S3_UPLOADS_REGION=us-east-1
62
77
  S3_UPLOADS_ACCESS_KEY_ID=your-access-key
63
78
  S3_UPLOADS_SECRET_ACCESS_KEY=your-secret-key
79
+ S3_ACCESS_RULES_TABLE=your-access-rules-table
64
80
 
65
81
  # OLAP
66
82
  OLAP=your-olap-name
@@ -68,6 +84,8 @@ OLAP_ACCT_NBR=your-account-number
68
84
  OLAP_REGION=us-east-1
69
85
  ```
70
86
 
87
+ **Important:** Credentials are read directly from `.env.local` by Docker Compose. They are never written to the generated `docker-compose.yml` file, making it safe to review or share the generated compose file without exposing secrets.
88
+
71
89
  ### 5. Add hostname to /etc/hosts
72
90
 
73
91
  ```bash
@@ -86,6 +104,23 @@ Open http://username.local or https://username.local in your browser.
86
104
 
87
105
  ## Commands
88
106
 
107
+ ### Initialize configuration
108
+ ```bash
109
+ npx buwp-local init
110
+
111
+ Options:
112
+ --no-interactive Use non-interactive mode with defaults
113
+ --plugin Non-interactive: initialize as plugin
114
+ --mu-plugin Non-interactive: initialize as mu-plugin
115
+ --theme Non-interactive: initialize as theme
116
+ -f, --force Overwrite existing configuration
117
+ ```
118
+
119
+ The `init` command provides an interactive setup experience with:
120
+ - **Smart defaults** - Detects project type from files
121
+ - **Real-time validation** - Validates inputs as you type
122
+ - **Guided workflow** - Clear prompts for all configuration options
123
+
89
124
  ### Start environment
90
125
  ```bash
91
126
  npx buwp-local start [options]
@@ -200,10 +235,27 @@ Map your local code into the container:
200
235
  ## Security Best Practices
201
236
 
202
237
  1. **Never commit `.env.local`** - This file contains secrets
203
- 2. **Never commit `.buwp-local/`** - This contains generated files
204
- 3. **Do commit `.buwp-local.json`** - This is your configuration template
205
- 4. **Use environment variables for all secrets**
206
- 5. **Consider using macOS Keychain** for even better security (see docs)
238
+ 2. **Never commit `.buwp-local/`** - This directory contains generated files
239
+ 3. **Do commit `.buwp-local.json`** - This is your configuration template (no secrets)
240
+ 4. **Use environment variables for all secrets** - Credentials stay in `.env.local` and are never written to generated files
241
+ 5. **Review generated compose files** - The generated `docker-compose.yml` only contains variable references like `${WORDPRESS_DB_PASSWORD}`, not actual credentials
242
+ 6. **Copy `.env.local.example`** - Use the provided template to create your `.env.local` file
243
+ 7. **Consider using macOS Keychain** for even better security (future feature)
244
+
245
+ ### How Credentials Work
246
+
247
+ buwp-local uses Docker Compose's native environment variable interpolation:
248
+
249
+ 1. **You provide** credentials in `.env.local`
250
+ 2. **buwp-local generates** `docker-compose.yml` with variable references like `${VAR_NAME}`
251
+ 3. **Docker Compose reads** `.env.local` at runtime and substitutes the values
252
+ 4. **Your secrets never** get written to any generated files
253
+
254
+ This means:
255
+ - ✅ Generated compose files are safe to review or share
256
+ - ✅ No credentials in version control (even accidentally)
257
+ - ✅ Industry-standard Docker Compose pattern
258
+ - ✅ Simple and secure
207
259
 
208
260
  ## File Structure
209
261
 
package/bin/buwp-local.js CHANGED
@@ -28,6 +28,7 @@ import logsCommand from '../lib/commands/logs.js';
28
28
  import wpCommand from '../lib/commands/wp.js';
29
29
  import shellCommand from '../lib/commands/shell.js';
30
30
  import configCommand from '../lib/commands/config.js';
31
+ import initCommand from '../lib/commands/init.js';
31
32
 
32
33
  const program = new Command();
33
34
 
@@ -78,6 +79,17 @@ program
78
79
  .option('--show', 'Show resolved configuration')
79
80
  .action(configCommand);
80
81
 
82
+ // Init command (interactive configuration)
83
+ program
84
+ .command('init')
85
+ .description('Initialize configuration interactively')
86
+ .option('--no-interactive', 'Use non-interactive mode with defaults')
87
+ .option('--plugin', 'Non-interactive: initialize as plugin')
88
+ .option('--mu-plugin', 'Non-interactive: initialize as mu-plugin')
89
+ .option('--theme', 'Non-interactive: initialize as theme')
90
+ .option('-f, --force', 'Overwrite existing configuration')
91
+ .action(initCommand);
92
+
81
93
  // WP-CLI proxy command
82
94
  program
83
95
  .command('wp <args...>')
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Init command - Interactive configuration initialization
3
+ */
4
+
5
+ import prompts from 'prompts';
6
+ import chalk from 'chalk';
7
+ import path from 'path';
8
+ import fs from 'fs';
9
+ import os from 'os';
10
+ import { initConfig, CONFIG_FILE_NAME } from '../config.js';
11
+
12
+ /**
13
+ * Detect project type from package.json or directory structure
14
+ * @param {string} projectPath - Path to project directory
15
+ * @returns {string|null} Detected project type or null
16
+ */
17
+ function detectProjectType(projectPath) {
18
+ // Check package.json for hints
19
+ const packageJsonPath = path.join(projectPath, 'package.json');
20
+ if (fs.existsSync(packageJsonPath)) {
21
+ try {
22
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
23
+
24
+ // Check keywords
25
+ if (pkg.keywords) {
26
+ if (pkg.keywords.includes('wordpress-plugin')) return 'plugin';
27
+ if (pkg.keywords.includes('wordpress-theme')) return 'theme';
28
+ if (pkg.keywords.includes('wordpress-muplugin')) return 'mu-plugin';
29
+ }
30
+
31
+ // Check name patterns
32
+ if (pkg.name) {
33
+ if (pkg.name.includes('-plugin')) return 'plugin';
34
+ if (pkg.name.includes('-theme')) return 'theme';
35
+ if (pkg.name.includes('mu-')) return 'mu-plugin';
36
+ }
37
+ } catch (err) {
38
+ // Ignore errors reading package.json
39
+ }
40
+ }
41
+
42
+ // Check for common WordPress files
43
+ if (fs.existsSync(path.join(projectPath, 'style.css'))) {
44
+ const styleContent = fs.readFileSync(path.join(projectPath, 'style.css'), 'utf8');
45
+ if (styleContent.includes('Theme Name:')) return 'theme';
46
+ }
47
+
48
+ // Check for plugin header
49
+ const phpFiles = fs.readdirSync(projectPath).filter(f => f.endsWith('.php'));
50
+ for (const phpFile of phpFiles) {
51
+ const content = fs.readFileSync(path.join(projectPath, phpFile), 'utf8');
52
+ if (content.includes('Plugin Name:')) return 'plugin';
53
+ }
54
+
55
+ return null; // Could not detect
56
+ }
57
+
58
+ /**
59
+ * Interactive initialization command
60
+ * @param {object} options - Command options
61
+ */
62
+ async function initCommand(options) {
63
+ const projectPath = process.cwd();
64
+ const configPath = path.join(projectPath, CONFIG_FILE_NAME);
65
+
66
+ const userName = os.userInfo().username;
67
+
68
+ console.log(chalk.blue(`👋 Hello, ${userName}! Let's set up your WordPress local development environment.\n`));
69
+
70
+ // Check if config already exists
71
+ if (fs.existsSync(configPath) && !options.force) {
72
+ console.log(chalk.yellow(`⚠️ Configuration file already exists: ${configPath}`));
73
+ console.log(chalk.gray('Use --force to overwrite.\n'));
74
+ return;
75
+ }
76
+
77
+ // Check if we should use interactive mode
78
+ const isInteractive = options.interactive !== false && process.stdin.isTTY;
79
+
80
+ if (!isInteractive) {
81
+ // Fall back to non-interactive mode
82
+ console.log(chalk.gray('Non-interactive mode: using defaults\n'));
83
+ const initOptions = {};
84
+ if (options.plugin) initOptions.plugin = true;
85
+ if (options.muPlugin) initOptions.muPlugin = true;
86
+ if (options.theme) initOptions.theme = true;
87
+
88
+ const configPath = initConfig(projectPath, initOptions);
89
+ console.log(chalk.green(`✅ Created configuration file: ${configPath}\n`));
90
+ return;
91
+ }
92
+
93
+ // Interactive mode
94
+ console.log(chalk.blue('🚀 Interactive configuration setup\n'));
95
+ console.log(chalk.gray('Press Ctrl+C to cancel at any time\n'));
96
+
97
+ const detectedName = path.basename(projectPath);
98
+ const detectedType = detectProjectType(projectPath);
99
+
100
+ if (detectedType) {
101
+ console.log(chalk.cyan(`ℹ️ Detected project type: ${detectedType}\n`));
102
+ }
103
+
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;
109
+
110
+ const questions = [
111
+ {
112
+ type: 'text',
113
+ name: 'projectName',
114
+ message: 'Project name',
115
+ initial: detectedName,
116
+ validate: value => value.trim().length > 0 || 'Project name is required'
117
+ },
118
+ {
119
+ type: 'select',
120
+ name: 'projectType',
121
+ 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
+ ],
129
+ initial: defaultTypeIndex
130
+ },
131
+ {
132
+ type: 'text',
133
+ name: 'hostname',
134
+ message: 'Hostname',
135
+ initial: (prev, values) => {
136
+ // For sandbox, use just username.local; for other types include project name
137
+ if (values.projectType === 'sandbox') {
138
+ return `${userName}.local`;
139
+ }
140
+ return `${userName}-${values.projectName}.local`;
141
+ },
142
+ validate: value => {
143
+ if (!value.trim()) return 'Hostname is required';
144
+ if (!value.includes('.')) return 'Hostname should include a domain (e.g., .local)';
145
+ return true;
146
+ }
147
+ },
148
+ {
149
+ type: 'text',
150
+ name: 'image',
151
+ message: 'WordPress Docker image',
152
+ initial: 'ghcr.io/bu-ist/bu-wp-docker-mod_shib:arm64-latest',
153
+ validate: value => {
154
+ if (!value.trim()) return 'Image name is required';
155
+ return true;
156
+ }
157
+ },
158
+ {
159
+ type: 'text',
160
+ name: 'httpPort',
161
+ message: 'HTTP port (default: 80)',
162
+ initial: '80',
163
+ validate: value => {
164
+ const port = parseInt(value);
165
+ if (isNaN(port) || port < 1 || port > 65535) return 'Port must be a number between 1 and 65535';
166
+ return true;
167
+ }
168
+ },
169
+ {
170
+ type: 'text',
171
+ name: 'httpsPort',
172
+ message: 'HTTPS port (default: 443)',
173
+ initial: '443',
174
+ validate: value => {
175
+ const port = parseInt(value);
176
+ if (isNaN(port) || port < 1 || port > 65535) return 'Port must be a number between 1 and 65535';
177
+ return true;
178
+ }
179
+ },
180
+ {
181
+ type: 'text',
182
+ name: 'dbPort',
183
+ message: 'Database port (default: 3306)',
184
+ initial: '3306',
185
+ validate: value => {
186
+ const port = parseInt(value);
187
+ if (isNaN(port) || port < 1 || port > 65535) return 'Port must be a number between 1 and 65535';
188
+ return true;
189
+ }
190
+ },
191
+ {
192
+ type: 'confirm',
193
+ name: 'redis',
194
+ message: 'Enable Redis?',
195
+ initial: true
196
+ },
197
+ {
198
+ type: 'confirm',
199
+ name: 's3proxy',
200
+ message: 'Enable S3 proxy?',
201
+ initial: true
202
+ },
203
+ {
204
+ type: 'confirm',
205
+ name: 'shibboleth',
206
+ message: 'Enable Shibboleth?',
207
+ initial: true
208
+ },
209
+ {
210
+ type: 'confirm',
211
+ name: 'xdebug',
212
+ message: 'Enable Xdebug by default?',
213
+ initial: false
214
+ }
215
+ ];
216
+
217
+ const answers = await prompts(questions, {
218
+ onCancel: () => {
219
+ console.log(chalk.yellow('\n⚠️ Setup cancelled\n'));
220
+ process.exit(0);
221
+ }
222
+ });
223
+
224
+ // Build configuration object
225
+ const config = {
226
+ projectName: answers.projectName,
227
+ image: answers.image,
228
+ hostname: answers.hostname,
229
+ multisite: true,
230
+ services: {
231
+ redis: answers.redis || false,
232
+ s3proxy: answers.s3proxy || false,
233
+ shibboleth: answers.shibboleth || false
234
+ },
235
+ ports: {
236
+ http: parseInt(answers.httpPort),
237
+ https: parseInt(answers.httpsPort),
238
+ db: parseInt(answers.dbPort),
239
+ redis: 6379
240
+ },
241
+ mappings: [],
242
+ env: {
243
+ WP_DEBUG: true,
244
+ XDEBUG: answers.xdebug || false
245
+ }
246
+ };
247
+
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
+ config.mappings.push({
270
+ local: './',
271
+ container: '/var/www/html/wp-content/plugins/my-plugin',
272
+ comment: 'Customize this mapping for your project'
273
+ });
274
+ }
275
+
276
+ // Write configuration file
277
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
278
+
279
+ console.log(chalk.green(`\n✅ Created configuration file: ${configPath}\n`));
280
+
281
+ // Show summary
282
+ console.log(chalk.cyan('📋 Configuration summary:'));
283
+ console.log(chalk.gray(` Project: ${answers.projectName}`));
284
+ console.log(chalk.gray(` Type: ${answers.projectType}`));
285
+ console.log(chalk.gray(` Hostname: ${answers.hostname}`));
286
+ console.log(chalk.gray(` Image: ${answers.image}`));
287
+ console.log(chalk.gray(` HTTP port: ${answers.httpPort}`));
288
+ console.log(chalk.gray(` HTTPS port: ${answers.httpsPort}`));
289
+ console.log(chalk.gray(` Services: ${[
290
+ answers.redis && 'Redis',
291
+ answers.s3proxy && 'S3',
292
+ answers.shibboleth && 'Shibboleth'
293
+ ].filter(Boolean).join(', ') || 'None'}\n`));
294
+
295
+ // Show next steps
296
+ console.log(chalk.cyan('📝 Next steps:'));
297
+ console.log(chalk.gray(' 1. Create .env.local with your credentials'));
298
+ console.log(chalk.gray(` 2. Add to /etc/hosts: 127.0.0.1 ${answers.hostname}`));
299
+
300
+ if (answers.projectType === 'sandbox') {
301
+ console.log(chalk.gray(' 3. Edit .buwp-local.json and add volume mappings to the mappings array'));
302
+ console.log(chalk.gray(' 4. Run: buwp-local start'));
303
+ console.log(chalk.gray(`\n Example mapping for .buwp-local.json mappings array:`));
304
+ console.log(chalk.gray(` { "local": "/path/to/plugin", "container": "/var/www/html/wp-content/plugins/my-plugin" }`));
305
+ } else {
306
+ console.log(chalk.gray(' 3. Run: buwp-local start'));
307
+ }
308
+
309
+ console.log(chalk.gray(`\n Then access: https://${answers.hostname}\n`));
310
+ }
311
+
312
+ export default initCommand;
@@ -5,7 +5,8 @@
5
5
  import chalk from 'chalk';
6
6
  import { execSync } from 'child_process';
7
7
  import path from 'path';
8
- import { loadConfig, validateConfig } from '../config.js';
8
+ import fs from 'fs';
9
+ import { loadConfig, validateConfig, ENV_FILE_NAME } from '../config.js';
9
10
  import { generateComposeFile } from '../compose-generator.js';
10
11
 
11
12
  async function startCommand(options) {
@@ -60,9 +61,13 @@ async function startCommand(options) {
60
61
  const composeDir = path.dirname(composePath);
61
62
  const projectName = config.projectName || 'buwp-local';
62
63
 
64
+ // Check if .env.local exists and build env-file flag
65
+ const envFilePath = path.join(projectPath, ENV_FILE_NAME);
66
+ const envFileFlag = fs.existsSync(envFilePath) ? `--env-file ${envFilePath}` : '';
67
+
63
68
  try {
64
69
  execSync(
65
- `docker compose -p ${projectName} -f ${composePath} up -d`,
70
+ `docker compose -p ${projectName} ${envFileFlag} -f ${composePath} up -d`,
66
71
  {
67
72
  cwd: composeDir,
68
73
  stdio: 'inherit'
@@ -57,9 +57,6 @@ function generateComposeConfig(config) {
57
57
  * @returns {object} Database service config
58
58
  */
59
59
  function generateDbService(config, dbVolumeName) {
60
- const dbPassword = config.db?.password || 'password';
61
- const rootPassword = config.db?.rootPassword || 'rootpassword';
62
-
63
60
  return {
64
61
  image: 'mariadb:latest',
65
62
  restart: 'always',
@@ -67,8 +64,8 @@ function generateDbService(config, dbVolumeName) {
67
64
  environment: {
68
65
  MYSQL_DATABASE: 'wordpress',
69
66
  MYSQL_USER: 'wordpress',
70
- MYSQL_PASSWORD: dbPassword,
71
- MYSQL_ROOT_PASSWORD: rootPassword
67
+ MYSQL_PASSWORD: '${WORDPRESS_DB_PASSWORD:-password}',
68
+ MYSQL_ROOT_PASSWORD: '${DB_ROOT_PASSWORD:-rootpassword}'
72
69
  },
73
70
  ports: [`${config.ports.db}:3306`],
74
71
  networks: ['wp-network']
@@ -91,7 +88,7 @@ function generateWordPressService(config, wpVolumeName) {
91
88
  const environment = {
92
89
  WORDPRESS_DB_HOST: 'db:3306',
93
90
  WORDPRESS_DB_USER: 'wordpress',
94
- WORDPRESS_DB_PASSWORD: config.db?.password || 'password',
91
+ WORDPRESS_DB_PASSWORD: '${WORDPRESS_DB_PASSWORD:-password}',
95
92
  WORDPRESS_DB_NAME: 'wordpress',
96
93
  WORDPRESS_DEBUG: config.env?.WP_DEBUG || '0',
97
94
  SERVER_NAME: config.hostname,
@@ -104,11 +101,11 @@ function generateWordPressService(config, wpVolumeName) {
104
101
 
105
102
  // Add Shibboleth config if enabled
106
103
  if (config.services.shibboleth) {
107
- environment.SP_ENTITY_ID = config.shibboleth?.entityId || '';
108
- environment.IDP_ENTITY_ID = config.shibboleth?.idpEntityId || '';
109
- environment.SHIB_IDP_LOGOUT = config.shibboleth?.idpLogout || '';
110
- environment.SHIB_SP_KEY = config.shibboleth?.spKey || '';
111
- environment.SHIB_SP_CERT = config.shibboleth?.spCert || '';
104
+ environment.SP_ENTITY_ID = '${SP_ENTITY_ID:-}';
105
+ environment.IDP_ENTITY_ID = '${IDP_ENTITY_ID:-}';
106
+ environment.SHIB_IDP_LOGOUT = '${SHIB_IDP_LOGOUT:-}';
107
+ environment.SHIB_SP_KEY = '${SHIB_SP_KEY:-}';
108
+ environment.SHIB_SP_CERT = '${SHIB_SP_CERT:-}';
112
109
  }
113
110
 
114
111
  // Add S3 config if enabled
@@ -130,12 +127,12 @@ function generateWordPressService(config, wpVolumeName) {
130
127
  wpConfigExtra += "define('SUBDOMAIN_INSTALL', false);\n";
131
128
  }
132
129
 
133
- if (config.services.s3proxy && config.s3) {
134
- wpConfigExtra += `define('S3_UPLOADS_BUCKET', '${config.s3.bucket || ''}');\n`;
135
- wpConfigExtra += `define('S3_UPLOADS_REGION', '${config.s3.region || 'us-east-1'}');\n`;
136
- wpConfigExtra += `define('S3_UPLOADS_KEY', '${config.s3.accessKeyId || ''}');\n`;
137
- wpConfigExtra += `define('S3_UPLOADS_SECRET', '${config.s3.secretAccessKey || ''}');\n`;
138
- wpConfigExtra += `define('ACCESS_RULES_TABLE', '${config.s3.accessRulesTable || ''}');\n`;
130
+ if (config.services.s3proxy) {
131
+ wpConfigExtra += "define('S3_UPLOADS_BUCKET', '${S3_UPLOADS_BUCKET}');\n";
132
+ wpConfigExtra += "define('S3_UPLOADS_REGION', '${S3_UPLOADS_REGION:-us-east-1}');\n";
133
+ wpConfigExtra += "define('S3_UPLOADS_KEY', '${S3_UPLOADS_ACCESS_KEY_ID}');\n";
134
+ wpConfigExtra += "define('S3_UPLOADS_SECRET', '${S3_UPLOADS_SECRET_ACCESS_KEY}');\n";
135
+ wpConfigExtra += "define('ACCESS_RULES_TABLE', '${S3_ACCESS_RULES_TABLE}');\n";
139
136
  wpConfigExtra += "define('S3_UPLOADS_OBJECT_ACL', null);\n";
140
137
  wpConfigExtra += "define('S3_UPLOADS_AUTOENABLE', true);\n";
141
138
  wpConfigExtra += "define('S3_UPLOADS_DISABLE_REPLACE_UPLOAD_URL', true);\n";
@@ -183,14 +180,10 @@ function generateWordPressService(config, wpVolumeName) {
183
180
 
184
181
  /**
185
182
  * Generate S3 proxy service configuration
186
- * @param {object} config - buwp-local configuration
183
+ * @param {object} _config - buwp-local configuration (unused - env vars used instead)
187
184
  * @returns {object} S3 proxy service config
188
185
  */
189
- function generateS3ProxyService(config) {
190
- const region = config.olap?.region || config.s3?.region || 'us-east-1';
191
- const olapName = config.olap?.name || '';
192
- const olapAcctNbr = config.olap?.accountNumber || '';
193
-
186
+ function generateS3ProxyService(_config) {
194
187
  return {
195
188
  image: 'public.ecr.aws/bostonuniversity-nonprod/aws-sigv4-proxy',
196
189
  restart: 'always',
@@ -199,16 +192,16 @@ function generateS3ProxyService(config) {
199
192
  '--name',
200
193
  's3-object-lambda',
201
194
  '--region',
202
- region,
195
+ '${OLAP_REGION:-us-east-1}',
203
196
  '--no-verify-ssl',
204
197
  '--host',
205
- `${olapName}-${olapAcctNbr}.s3-object-lambda.${region}.amazonaws.com`
198
+ '${OLAP}-${OLAP_ACCT_NBR}.s3-object-lambda.${OLAP_REGION:-us-east-1}.amazonaws.com'
206
199
  ],
207
200
  environment: {
208
201
  healthcheck_path: '/s3proxy-healthcheck',
209
- AWS_ACCESS_KEY_ID: config.s3?.accessKeyId || '',
210
- AWS_SECRET_ACCESS_KEY: config.s3?.secretAccessKey || '',
211
- REGION: region
202
+ AWS_ACCESS_KEY_ID: '${S3_UPLOADS_ACCESS_KEY_ID}',
203
+ AWS_SECRET_ACCESS_KEY: '${S3_UPLOADS_SECRET_ACCESS_KEY}',
204
+ REGION: '${S3_UPLOADS_REGION:-us-east-1}'
212
205
  },
213
206
  networks: ['wp-network']
214
207
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bostonuniversity/buwp-local",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Local WordPress development environment for Boston University projects",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "scripts": {
11
11
  "test": "echo \"Error: no test specified\" && exit 1",
12
- "buwp-local": "node bin/buwp-local.js config --init --mu-plugin",
12
+ "buwp-local": "node bin/buwp-local.js start",
13
13
  "lint": "eslint ."
14
14
  },
15
15
  "keywords": [
@@ -28,9 +28,11 @@
28
28
  "js-yaml": "^4.1.0",
29
29
  "chalk": "^4.1.2",
30
30
  "commander": "^11.0.0",
31
- "dotenv": "^16.3.1"
31
+ "dotenv": "^16.3.1",
32
+ "prompts": "^2.4.2"
32
33
  },
33
34
  "devDependencies": {
35
+ "@types/prompts": "^2.4.9",
34
36
  "eslint": "^8.50.0"
35
37
  }
36
38
  }
@@ -0,0 +1,57 @@
1
+ ## Plan: Environment-Based Credentials Migration (v0.4.1)
2
+
3
+ We've successfully shipped v0.4.0 with interactive init, sandbox support, configurable Docker images, and smart hostname defaults. The codebase is in excellent shape with real-world testing validated in both plugin and sandbox scenarios.
4
+
5
+ **Current Problem:** Credentials (database passwords, AWS keys, Shibboleth certs) are currently being read from the config/environment and then **written directly into the generated `docker-compose.yml`**. This means sensitive data sits in a generated file, which isn't ideal for security.
6
+
7
+ **Better Approach:** Use Docker Compose's native environment variable interpolation (`${VAR_NAME}`) so credentials stay in `.env.local` and never get written to the compose file.
8
+
9
+ ### Steps
10
+
11
+ 1. **Update `compose-generator.js` to use environment variable references**
12
+ - Database service: Change from `MYSQL_PASSWORD: dbPassword` to `MYSQL_PASSWORD: '${WORDPRESS_DB_PASSWORD:-password}'`
13
+ - WordPress service: Change from `WORDPRESS_DB_PASSWORD: config.db?.password` to `WORDPRESS_DB_PASSWORD: '${WORDPRESS_DB_PASSWORD:-password}'`
14
+ - S3 proxy service: Change from `AWS_ACCESS_KEY_ID: config.s3?.accessKeyId` to `AWS_ACCESS_KEY_ID: '${S3_UPLOADS_ACCESS_KEY_ID}'`
15
+ - WordPress `WORDPRESS_CONFIG_EXTRA`: Change from injecting actual S3 keys to using `${S3_UPLOADS_ACCESS_KEY_ID}` references
16
+
17
+ 2. **Update `start.js` to pass `.env.local` to docker compose**
18
+ - Add `--env-file` flag to `docker compose up` command
19
+ - Ensure `.env.local` path is correctly resolved relative to project
20
+
21
+ 3. **Update config loading to remove credential reading**
22
+ - Keep `extractEnvVars()` for backward compatibility but mark as deprecated
23
+ - Config validation no longer needs to check for credential presence in config object
24
+ - Document that credentials should only live in `.env.local`
25
+
26
+ 4. **Update documentation**
27
+ - `USAGE.md` - Clarify that `.env.local` is the source of truth for credentials
28
+ - Add example showing compose file will have `${VAR}` references
29
+ - Security best practices section emphasizes this approach
30
+
31
+ 5. **Test migration path**
32
+ - Verify existing projects with credentials in config still work (backward compat)
33
+ - Test new projects using only `.env.local`
34
+ - Verify generated compose file contains variable references, not actual values
35
+ - Ensure `docker compose` properly reads `.env.local`
36
+
37
+ ### Further Considerations
38
+
39
+ 1. **Backward compatibility**: Should we warn users if credentials exist in config? Or silently prefer `.env.local`?
40
+ 2. **`.env.local` template**: Should we create `.env.local.example` during `init` command?
41
+ 3. **Validation**: Should `config --validate` check that `.env.local` exists and has required variables?
42
+
43
+ ---
44
+
45
+ **Why This Matters:**
46
+ - **Security**: Credentials never written to files, only referenced
47
+ - **Git safety**: Generated compose files are safe to accidentally commit (no secrets)
48
+ - **Best practices**: Aligns with Docker Compose's standard env var pattern
49
+ - **Simplicity**: Reduces complexity in config merging logic
50
+
51
+ **Effort Estimate:** Low-Medium (4-6 hours)
52
+ - Compose generation changes: ~2 hours
53
+ - Start command changes: ~1 hour
54
+ - Documentation: ~1 hour
55
+ - Testing: ~1-2 hours
56
+
57
+ **Ready to proceed?** This sets up a cleaner foundation before tackling Phase 2 features like keychain integration.
@@ -1,578 +0,0 @@
1
- # Shared Environment - Practical Examples
2
-
3
- ## Example 1: Team Integration Sandbox
4
-
5
- ### Scenario
6
- A team of 4 developers working on BU's WordPress site needs a shared local environment with:
7
- - Custom theme (responsive-framework)
8
- - Navigation plugin (bu-navigation)
9
- - Analytics plugin (bu-custom-analytics)
10
- - Slideshow plugin (bu-slideshow)
11
-
12
- ### Setup
13
-
14
- **Step 1: Create Primary Configuration Repo**
15
- ```bash
16
- mkdir -p ~/projects/bu-team-sandbox
17
- cd ~/projects/bu-team-sandbox
18
-
19
- # Initialize
20
- buwp-local config --init
21
-
22
- # Edit .buwp-local.json:
23
- {
24
- "projectName": "bu-team-sandbox",
25
- "hostname": "buteam.local",
26
- "multisite": true,
27
- "services": {
28
- "redis": true,
29
- "s3proxy": true,
30
- "shibboleth": true
31
- },
32
- "ports": {
33
- "http": 80,
34
- "https": 443,
35
- "db": 3306,
36
- "redis": 6379
37
- },
38
- "mappings": [],
39
- "env": {
40
- "WP_DEBUG": true,
41
- "WP_DEBUG_LOG": true,
42
- "XDEBUG": false
43
- }
44
- }
45
-
46
- # Create .env.local with credentials
47
- # Start environment
48
- buwp-local start
49
- ```
50
-
51
- **Step 2: Each Developer Adds Their Repo**
52
-
53
- **Developer 1 - Theme:**
54
- ```bash
55
- cd ~/projects/responsive-framework
56
- buwp-local config --init --theme
57
-
58
- # Edit .buwp-local.json to join shared sandbox:
59
- {
60
- "projectName": "bu-team-sandbox", # SHARED!
61
- "hostname": "buteam.local",
62
- "multisite": true,
63
- "services": {
64
- "redis": true,
65
- "s3proxy": true,
66
- "shibboleth": true
67
- },
68
- "ports": {
69
- "http": 80,
70
- "https": 443,
71
- "db": 3306,
72
- "redis": 6379
73
- },
74
- "mappings": [
75
- {
76
- "local": "./",
77
- "container": "/var/www/html/wp-content/themes/responsive-framework"
78
- }
79
- ],
80
- "env": {
81
- "WP_DEBUG": true
82
- }
83
- }
84
-
85
- # Symlink shared credentials
86
- ln -s ~/projects/bu-team-sandbox/.env.local .env.local
87
-
88
- # Add theme to shared environment
89
- buwp-local start
90
- ```
91
-
92
- **Developer 2 - Navigation Plugin:**
93
- ```bash
94
- cd ~/projects/bu-navigation
95
- buwp-local config --init --plugin
96
-
97
- # Edit .buwp-local.json (same projectName):
98
- {
99
- "projectName": "bu-team-sandbox",
100
- "hostname": "buteam.local",
101
- # ... same ports/services ...
102
- "mappings": [
103
- {
104
- "local": "./",
105
- "container": "/var/www/html/wp-content/plugins/bu-navigation"
106
- }
107
- ]
108
- }
109
-
110
- ln -s ~/projects/bu-team-sandbox/.env.local .env.local
111
- buwp-local start
112
- ```
113
-
114
- **Developers 3 & 4 repeat for their plugins...**
115
-
116
- **Result:**
117
- - All 4 developers access http://buteam.local
118
- - All plugins and theme are active
119
- - Shared database with realistic test data
120
- - Each developer's file changes are live
121
-
122
- ---
123
-
124
- ## Example 2: Solo Developer - Isolated + Shared
125
-
126
- ### Scenario
127
- One developer working on `bu-custom-analytics` needs:
128
- - **Isolated environment** for feature development
129
- - **Shared environment** for integration testing with other plugins
130
-
131
- ### Setup
132
-
133
- **Isolated Configuration** (`.buwp-local.json`):
134
- ```json
135
- {
136
- "projectName": "bu-custom-analytics",
137
- "hostname": "analytics.local",
138
- "multisite": true,
139
- "services": {
140
- "redis": true,
141
- "s3proxy": false,
142
- "shibboleth": false
143
- },
144
- "ports": {
145
- "http": 80,
146
- "https": 443,
147
- "db": 3306,
148
- "redis": 6379
149
- },
150
- "mappings": [
151
- {
152
- "local": "./",
153
- "container": "/var/www/html/wp-content/plugins/bu-custom-analytics"
154
- }
155
- ],
156
- "env": {
157
- "WP_DEBUG": true,
158
- "XDEBUG": true
159
- }
160
- }
161
- ```
162
-
163
- **Shared Configuration** (`.buwp-local.shared.json`):
164
- ```json
165
- {
166
- "projectName": "bu-integration",
167
- "hostname": "buint.local",
168
- "multisite": true,
169
- "services": {
170
- "redis": true,
171
- "s3proxy": true,
172
- "shibboleth": true
173
- },
174
- "ports": {
175
- "http": 8080,
176
- "https": 8443,
177
- "db": 3307,
178
- "redis": 6380
179
- },
180
- "mappings": [
181
- {
182
- "local": "./",
183
- "container": "/var/www/html/wp-content/plugins/bu-custom-analytics"
184
- }
185
- ],
186
- "env": {
187
- "WP_DEBUG": true,
188
- "XDEBUG": false
189
- }
190
- }
191
- ```
192
-
193
- ### Workflow
194
-
195
- **Morning - Feature Development (Isolated):**
196
- ```bash
197
- cd ~/projects/bu-custom-analytics
198
-
199
- # Use isolated config (default)
200
- buwp-local start
201
-
202
- # Develop at http://analytics.local
203
- # Fast, isolated, clean environment
204
- # Debug with Xdebug enabled
205
- ```
206
-
207
- **Afternoon - Integration Testing (Shared):**
208
- ```bash
209
- cd ~/projects/bu-custom-analytics
210
-
211
- # Stop isolated
212
- buwp-local stop
213
-
214
- # Switch to shared config
215
- cp .buwp-local.shared.json .buwp-local.json
216
-
217
- # Join shared integration environment
218
- buwp-local start
219
-
220
- # Test at http://buint.local:8080
221
- # Now running with other plugins/theme
222
- # Test interactions and integration points
223
- ```
224
-
225
- **End of Day - Back to Isolated:**
226
- ```bash
227
- # Restore isolated config
228
- git checkout .buwp-local.json
229
-
230
- # Back to isolated for tomorrow
231
- buwp-local destroy # Clean up shared
232
- buwp-local start # Start fresh isolated
233
- ```
234
-
235
- ---
236
-
237
- ## Example 3: Multi-Environment Matrix
238
-
239
- ### Scenario
240
- Testing plugin compatibility across different configurations:
241
- - Modern stack (latest everything)
242
- - Legacy stack (older versions)
243
- - Production replica
244
-
245
- ### Setup
246
-
247
- **Create 3 Separate Environments:**
248
-
249
- **Environment 1: Modern Stack**
250
- ```bash
251
- cd ~/projects/bu-custom-analytics
252
-
253
- # .buwp-local.modern.json:
254
- {
255
- "projectName": "modern-stack",
256
- "hostname": "modern.local",
257
- "image": "ghcr.io/bu-ist/bu-wp-docker-mod_shib:arm64-latest",
258
- # ... modern settings ...
259
- }
260
-
261
- cp .buwp-local.modern.json .buwp-local.json
262
- buwp-local start
263
- # Test at http://modern.local
264
- ```
265
-
266
- **Environment 2: Legacy Stack**
267
- ```bash
268
- # .buwp-local.legacy.json:
269
- {
270
- "projectName": "legacy-stack",
271
- "hostname": "legacy.local",
272
- "image": "ghcr.io/bu-ist/bu-wp-docker-mod_shib:legacy-tag",
273
- "ports": {
274
- "http": 8081,
275
- "db": 3307
276
- }
277
- # ... legacy settings ...
278
- }
279
-
280
- cp .buwp-local.legacy.json .buwp-local.json
281
- buwp-local start
282
- # Test at http://legacy.local:8081
283
- ```
284
-
285
- **Environment 3: Production Replica**
286
- ```bash
287
- # .buwp-local.prod.json:
288
- {
289
- "projectName": "prod-replica",
290
- "hostname": "prodlocal.local",
291
- "ports": {
292
- "http": 8082,
293
- "db": 3308
294
- }
295
- # ... production-like settings ...
296
- }
297
-
298
- cp .buwp-local.prod.json .buwp-local.json
299
- buwp-local start
300
- # Test at http://prodlocal.local:8082
301
- ```
302
-
303
- **Result:**
304
- - All 3 environments running simultaneously
305
- - Same plugin code in all 3
306
- - Different WordPress/PHP versions
307
- - Test compatibility across configurations
308
-
309
- ---
310
-
311
- ## Example 4: Department-Wide Shared Sandbox
312
-
313
- ### Scenario
314
- Marketing department (10 people) needs shared sandbox with:
315
- - Content editors
316
- - Designers
317
- - Developers
318
- - QA testers
319
-
320
- ### Setup
321
-
322
- **Central Configuration Repository:**
323
- ```bash
324
- # Shared repo that everyone clones
325
- git clone https://github.com/bu-ist/bu-marketing-sandbox.git
326
- cd bu-marketing-sandbox
327
-
328
- # .buwp-local.json:
329
- {
330
- "projectName": "bu-marketing",
331
- "hostname": "marketing.local",
332
- "multisite": true,
333
- "services": {
334
- "redis": true,
335
- "s3proxy": true,
336
- "shibboleth": true
337
- },
338
- "ports": {
339
- "http": 80,
340
- "https": 443,
341
- "db": 3306,
342
- "redis": 6379
343
- },
344
- "mappings": [
345
- {
346
- "local": "../responsive-framework",
347
- "container": "/var/www/html/wp-content/themes/responsive-framework"
348
- },
349
- {
350
- "local": "../bu-slideshow",
351
- "container": "/var/www/html/wp-content/plugins/bu-slideshow"
352
- },
353
- {
354
- "local": "../bu-analytics",
355
- "container": "/var/www/html/wp-content/plugins/bu-analytics"
356
- }
357
- ],
358
- "env": {
359
- "WP_DEBUG": false # Production-like for QA
360
- }
361
- }
362
-
363
- # .env.local (shared credentials via 1Password/LastPass)
364
- # Includes test user accounts for all team members
365
- ```
366
-
367
- **Each Team Member:**
368
- ```bash
369
- # Clone central config
370
- git clone https://github.com/bu-ist/bu-marketing-sandbox.git ~/projects/bu-marketing-sandbox
371
-
372
- # Clone the repos to develop
373
- cd ~/projects
374
- git clone https://github.com/bu-ist/responsive-framework.git
375
- git clone https://github.com/bu-ist/bu-slideshow.git
376
- git clone https://github.com/bu-ist/bu-analytics.git
377
-
378
- # Start shared environment
379
- cd ~/projects/bu-marketing-sandbox
380
- # Add .env.local with shared credentials
381
- buwp-local start
382
-
383
- # Access http://marketing.local
384
- # All team members work in same environment
385
- ```
386
-
387
- **Benefits:**
388
- - Designers see live changes to theme
389
- - Developers see plugin interactions
390
- - Content editors have realistic environment
391
- - QA tests actual configuration
392
- - Everyone shares same database with test content
393
-
394
- ---
395
-
396
- ## Example 5: Feature Branch Testing
397
-
398
- ### Scenario
399
- Test a feature branch alongside main branch:
400
- - `main` branch in production-like environment
401
- - `feature/new-ui` branch in test environment
402
-
403
- ### Setup
404
-
405
- **Main Branch Environment:**
406
- ```bash
407
- cd ~/projects/bu-custom-analytics
408
- git checkout main
409
-
410
- # .buwp-local.json (default):
411
- {
412
- "projectName": "bu-analytics-main",
413
- "hostname": "analytics-main.local",
414
- "ports": {
415
- "http": 80,
416
- "db": 3306
417
- },
418
- "mappings": [
419
- {
420
- "local": "./",
421
- "container": "/var/www/html/wp-content/plugins/bu-custom-analytics"
422
- }
423
- ]
424
- }
425
-
426
- buwp-local start
427
- # Test main at http://analytics-main.local
428
- ```
429
-
430
- **Feature Branch Environment:**
431
- ```bash
432
- cd ~/projects/bu-custom-analytics
433
- git checkout feature/new-ui
434
-
435
- # Change projectName in .buwp-local.json:
436
- {
437
- "projectName": "bu-analytics-feature",
438
- "hostname": "analytics-feature.local",
439
- "ports": {
440
- "http": 8080,
441
- "db": 3307
442
- },
443
- "mappings": [
444
- {
445
- "local": "./",
446
- "container": "/var/www/html/wp-content/plugins/bu-custom-analytics"
447
- }
448
- ]
449
- }
450
-
451
- buwp-local start
452
- # Test feature at http://analytics-feature.local:8080
453
- ```
454
-
455
- **Result:**
456
- - Both branches running simultaneously
457
- - Compare behavior side-by-side
458
- - Separate databases for each
459
- - No branch switching needed
460
-
461
- ---
462
-
463
- ## Tips for Shared Environments
464
-
465
- ### 1. Use Configuration Management
466
- ```bash
467
- # Store configs in version control
468
- ~/projects/
469
- ├── bu-team-sandbox/ # Primary config repo
470
- │ ├── .buwp-local.json
471
- │ ├── .env.local.example
472
- │ └── README.md
473
- ├── plugin-a/
474
- │ ├── .buwp-local.json # Points to shared
475
- │ └── .env.local -> ../bu-team-sandbox/.env.local
476
- └── plugin-b/
477
- ├── .buwp-local.json # Points to shared
478
- └── .env.local -> ../bu-team-sandbox/.env.local
479
- ```
480
-
481
- ### 2. Document Your Shared Environments
482
- Create a team wiki page:
483
- ```markdown
484
- # Team Shared Environments
485
-
486
- ## bu-team-sandbox
487
- - **URL:** http://buteam.local
488
- - **Purpose:** Integration testing
489
- - **Includes:** theme + all plugins
490
- - **Managed by:** @teamlead
491
- - **Config repo:** github.com/bu-ist/bu-team-sandbox
492
-
493
- ## bu-staging-replica
494
- - **URL:** http://bustaging.local:8080
495
- - **Purpose:** Pre-production testing
496
- - **Includes:** Production plugins only
497
- - **Managed by:** @qa-lead
498
- ```
499
-
500
- ### 3. Use Consistent Naming
501
- ```bash
502
- # Good naming convention:
503
- bu-team-sandbox # Team shared environment
504
- bu-integration # Integration testing
505
- bu-staging-replica # Staging mirror
506
- bu-prod-replica # Production mirror
507
-
508
- # Avoid:
509
- sandbox
510
- test
511
- local
512
- dev
513
- ```
514
-
515
- ### 4. Regular Cleanup
516
- ```bash
517
- # Weekly: Clean shared environments
518
- buwp-local destroy
519
- buwp-local start
520
- # Fresh start with clean database
521
- ```
522
-
523
- ### 5. Backup Shared Databases
524
- ```bash
525
- # Before major changes:
526
- docker exec bu-team-sandbox-db-1 mysqldump -u wordpress -ppassword wordpress > backup-$(date +%Y%m%d).sql
527
-
528
- # Restore if needed:
529
- docker exec -i bu-team-sandbox-db-1 mysql -u wordpress -ppassword wordpress < backup-20251110.sql
530
- ```
531
-
532
- ---
533
-
534
- ## Troubleshooting Common Issues
535
-
536
- ### Issue: "Port already in use"
537
- **Cause:** Another shared environment using same ports
538
- **Solution:** Use different ports in shared config:
539
- ```json
540
- {
541
- "ports": {
542
- "http": 8081,
543
- "db": 3307
544
- }
545
- }
546
- ```
547
-
548
- ### Issue: "My changes don't appear"
549
- **Cause:** Wrong repo's mapping is active
550
- **Solution:** Check which repo last ran `start`:
551
- ```bash
552
- docker compose -p bu-team-sandbox ps
553
- # See which volumes are mounted
554
- ```
555
-
556
- ### Issue: "Database has wrong data"
557
- **Cause:** Another developer made changes
558
- **Solution:** Communicate or use separate environments
559
-
560
- ### Issue: "Config out of sync"
561
- **Cause:** Repos have different settings
562
- **Solution:** Use symlinks or central config repo
563
-
564
- ---
565
-
566
- ## Summary
567
-
568
- Shared environments enable:
569
- - ✅ Team collaboration on integrated stack
570
- - ✅ Integration testing without deploy
571
- - ✅ Multiple development strategies
572
- - ✅ Flexible workflows (solo + team)
573
- - ✅ Realistic production-like testing
574
-
575
- Use the right tool for the job:
576
- - **Isolated** = Solo development, fast iteration
577
- - **Shared** = Integration, team collaboration
578
- - **Both** = Maximum flexibility