@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.
- package/.buwp-local.json +22 -0
- package/.env.local.example +26 -0
- package/USAGE.md +61 -9
- package/bin/buwp-local.js +12 -0
- package/lib/commands/init.js +312 -0
- package/lib/commands/start.js +7 -2
- package/lib/compose-generator.js +21 -28
- package/package.json +5 -3
- package/plan-environmentBasedCredentials.prompt.md +57 -0
- package/SHARED_ENVIRONMENT_EXAMPLES.md +0 -578
package/.buwp-local.json
ADDED
|
@@ -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
|
|
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. **
|
|
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;
|
package/lib/commands/start.js
CHANGED
|
@@ -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
|
|
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'
|
package/lib/compose-generator.js
CHANGED
|
@@ -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:
|
|
71
|
-
MYSQL_ROOT_PASSWORD:
|
|
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:
|
|
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 =
|
|
108
|
-
environment.IDP_ENTITY_ID =
|
|
109
|
-
environment.SHIB_IDP_LOGOUT =
|
|
110
|
-
environment.SHIB_SP_KEY =
|
|
111
|
-
environment.SHIB_SP_CERT =
|
|
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
|
|
134
|
-
wpConfigExtra +=
|
|
135
|
-
wpConfigExtra +=
|
|
136
|
-
wpConfigExtra +=
|
|
137
|
-
wpConfigExtra +=
|
|
138
|
-
wpConfigExtra +=
|
|
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}
|
|
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(
|
|
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
|
-
|
|
195
|
+
'${OLAP_REGION:-us-east-1}',
|
|
203
196
|
'--no-verify-ssl',
|
|
204
197
|
'--host',
|
|
205
|
-
|
|
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:
|
|
210
|
-
AWS_SECRET_ACCESS_KEY:
|
|
211
|
-
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
|
+
"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
|
|
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
|