@bostonuniversity/buwp-local 0.5.2 → 0.6.0

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/ROADMAP.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # buwp-local Development Roadmap
2
2
 
3
+ This is a historical document, see the current roadmap in the `docs/ROADMAP.md` file.
4
+
3
5
  ## ✅ Phase 1: Core Infrastructure (Complete)
4
6
 
5
7
  **Status:** Shipped ✅
@@ -359,4 +361,3 @@ We've built a **solid foundation** for BU WordPress local development. The tool
359
361
  3. Expand adoption gradually
360
362
  4. Iterate and improve based on real usage
361
363
 
362
- The path forward is clear, and the foundation is strong! 🚀
@@ -0,0 +1,691 @@
1
+ # Architecture
2
+
3
+ Technical overview of how buwp-local works under the hood.
4
+
5
+ ## System Overview
6
+
7
+ buwp-local is a CLI tool that generates and manages Docker Compose configurations for BU WordPress development environments.
8
+
9
+ ```
10
+ ┌─────────────────────────────────────────────────────────────┐
11
+ │ buwp-local CLI │
12
+ │ (Node.js/Commander) │
13
+ └─────────────────────────────────────────────────────────────┘
14
+
15
+ ├─ Read Configuration
16
+ │ (.buwp-local.json)
17
+
18
+ ├─ Load Credentials
19
+ │ (Keychain or .env.local)
20
+
21
+ ├─ Generate Docker Compose
22
+ │ (docker-compose.yml)
23
+
24
+ └─ Execute Docker Compose
25
+ (via child process)
26
+
27
+ ┌─────────────────────────────────────────────────────────────┐
28
+ │ Docker Compose │
29
+ │ │
30
+ │ ┌──────────┐ ┌──────────┐ ┌─────────┐ ┌─────────┐ │
31
+ │ │WordPress │ │ MySQL │ │ Redis │ │S3 Proxy │ │
32
+ │ │ Container│ │Container │ │Container│ │Container│ │
33
+ │ └──────────┘ └──────────┘ └─────────┘ └─────────┘ │
34
+ │ │
35
+ │ ┌──────────────────┐ ┌──────────────────┐ │
36
+ │ │ db_data volume │ │ wp_build volume │ │
37
+ │ └──────────────────┘ └──────────────────┘ │
38
+ └─────────────────────────────────────────────────────────────┘
39
+ ```
40
+
41
+ ## Core Components
42
+
43
+ ### 1. CLI Framework
44
+
45
+ **Technology:** Commander.js
46
+
47
+ **Entry Point:** `bin/buwp-local.js`
48
+
49
+ **Structure:**
50
+ ```
51
+ bin/
52
+ buwp-local.js # CLI entry point, command routing
53
+
54
+ lib/
55
+ commands/
56
+ init.js # Interactive setup wizard
57
+ start.js # Start Docker Compose environment
58
+ stop.js # Stop containers
59
+ destroy.js # Remove containers and volumes
60
+ logs.js # View container logs
61
+ keychain.js # Keychain credential management
62
+ wp.js # WP-CLI proxy command
63
+
64
+ config.js # Configuration loading/validation
65
+ keychain.js # macOS Keychain integration
66
+ docker-compose.js # Docker Compose generation
67
+ utils.js # Utility functions
68
+ ```
69
+
70
+ ### 2. Configuration Management
71
+
72
+ **Config File:** `.buwp-local.json`
73
+
74
+ **Schema:**
75
+ ```json
76
+ {
77
+ "projectName": "string", // Unique project identifier
78
+ "hostname": "string", // Local hostname (e.g., project.local)
79
+ "multisite": "boolean", // Enable WordPress multisite
80
+ "image": "string", // Docker image to use
81
+ "services": {
82
+ "redis": "boolean", // Enable Redis cache
83
+ "s3proxy": "boolean", // Enable S3 proxy service
84
+ "shibboleth": "boolean" // Enable Shibboleth SSO
85
+ },
86
+ "ports": {
87
+ "http": "number", // HTTP port (default: 80)
88
+ "https": "number", // HTTPS port (default: 443)
89
+ "db": "number", // MySQL port (default: 3306)
90
+ "redis": "number" // Redis port (default: 6379)
91
+ },
92
+ "mappings": [
93
+ {
94
+ "local": "string", // Local path (relative or absolute)
95
+ "container": "string" // Container path (absolute)
96
+ }
97
+ ],
98
+ "env": {
99
+ "KEY": "value" // Custom environment variables
100
+ }
101
+ }
102
+ ```
103
+
104
+ **Loading Process:**
105
+
106
+ 1. Read `.buwp-local.json` from project directory
107
+ 2. Apply defaults for missing values
108
+ 3. Validate schema and paths
109
+ 4. Sanitize project name for Docker compatibility
110
+ 5. Merge with command-line options
111
+
112
+ **Module:** `lib/config.js`
113
+
114
+ ### 3. Credential Management
115
+
116
+ #### macOS Keychain Integration
117
+
118
+ **Service:** `buwp-local`
119
+ **Account:** `<CREDENTIAL_NAME>`
120
+ **Access:** Via `security` command-line tool
121
+
122
+ **Implementation:**
123
+ The current implementation uses the macOS `security` CLI to store, retrieve, and delete credentials securely in the Keychain. It is essentially a wrapper around these commands:
124
+
125
+ ```bash
126
+ # Set credential
127
+ security add-generic-password \
128
+ -s "buwp-local" \
129
+ -a "WORDPRESS_DB_PASSWORD" \
130
+ -w "password123" \
131
+ -U
132
+
133
+ # Get credential
134
+ security find-generic-password \
135
+ -s "buwp-local" \
136
+ -a "WORDPRESS_DB_PASSWORD" \
137
+ -w
138
+
139
+ # Delete credential
140
+ security delete-generic-password \
141
+ -s "buwp-local" \
142
+ -a "WORDPRESS_DB_PASSWORD"
143
+ ```
144
+
145
+ **Features:**
146
+ - Stores 15 credential types
147
+ - Automatic hex decoding for legacy multiline credentials
148
+ - Secure storage with macOS encryption
149
+ - Global access across all projects
150
+
151
+ #### Credential Loading Flow
152
+
153
+ ```
154
+ Start Command
155
+
156
+ ├─ Check .env.local exists?
157
+ │ ├─ Yes → Load from .env.local (dotenv)
158
+ │ └─ No → Check Keychain
159
+ │ ├─ Found → Load from Keychain
160
+ │ └─ Not found → Prompt for setup
161
+
162
+ ├─ Validate required credentials
163
+ │ └─ Missing? → Offer interactive setup
164
+
165
+ └─ Create secure temp env file
166
+ └─ Pass to Docker Compose
167
+ ```
168
+
169
+ **Priority:**
170
+ 1. `.env.local` (highest - overrides Keychain)
171
+ 2. macOS Keychain (fallback)
172
+ 3. Error if not found
173
+
174
+ **Module:** `lib/config.js` (`loadKeychainCredentials()`)
175
+
176
+ ### 4. Secure Temporary Files
177
+
178
+ **Purpose:** Pass credentials to Docker Compose securely
179
+
180
+ **Location:** `.buwp-local/.env.XXXXXX` (random suffix)
181
+
182
+ **Permissions:** `0600` (owner read/write only)
183
+
184
+ **Lifecycle:**
185
+ 1. **Create:** Before starting containers
186
+ 2. **Use:** Docker Compose reads via `env_file` directive
187
+ 3. **Delete:** After stopping containers or on process exit
188
+
189
+ **Implementation:**
190
+ ```javascript
191
+ // lib/config.js
192
+
193
+ export function createSecureTempEnvFile(credentials) {
194
+ const tmpDir = path.join(process.cwd(), '.buwp-local');
195
+ fs.mkdirSync(tmpDir, { recursive: true });
196
+
197
+ const tmpFile = fs.mkdtempSync(path.join(tmpDir, '.env.'));
198
+ const envFilePath = path.join(tmpFile, '.env');
199
+
200
+ // Write credentials with escaped newlines
201
+ const content = Object.entries(credentials)
202
+ .map(([key, value]) => {
203
+ const escaped = value.replace(/\n/g, '\\n');
204
+ return `${key}=${escaped}`;
205
+ })
206
+ .join('\n');
207
+
208
+ fs.writeFileSync(envFilePath, content, { mode: 0o600 });
209
+
210
+ return envFilePath;
211
+ }
212
+
213
+ export function secureDeleteTempEnvFile(filePath) {
214
+ if (fs.existsSync(filePath)) {
215
+ fs.unlinkSync(filePath);
216
+ fs.rmdirSync(path.dirname(filePath));
217
+ }
218
+ }
219
+ ```
220
+
221
+ **Why Not Process Environment?**
222
+ - ❌ Process env vars visible in `ps` output
223
+ - ❌ Can't handle multiline values reliably
224
+ - ✅ Temp files are more secure
225
+ - ✅ Docker Compose `env_file` is standard pattern
226
+
227
+ ### 5. Docker Compose Generation
228
+
229
+ **Template:** Generated dynamically in memory
230
+
231
+ **Generation Process:**
232
+
233
+ 1. **Base Services** (always included):
234
+ ```yaml
235
+ services:
236
+ wordpress:
237
+ image: ghcr.io/bu-ist/bu-wp-docker-mod_shib:arm64-latest
238
+ environment:
239
+ WORDPRESS_DB_HOST: db
240
+ WORDPRESS_DB_USER: wordpress
241
+ env_file: .buwp-local/.env.XXXXXX
242
+ volumes:
243
+ - wp_build:/var/www/html
244
+
245
+ db:
246
+ image: mysql:8.0
247
+ environment:
248
+ MYSQL_DATABASE: wordpress
249
+ MYSQL_USER: wordpress
250
+ env_file: .buwp-local/.env.XXXXXX
251
+ volumes:
252
+ - db_data:/var/lib/mysql
253
+ ```
254
+
255
+ 2. **Add Optional Services** (if enabled):
256
+ ```yaml
257
+ redis:
258
+ image: redis:alpine
259
+
260
+ s3proxy:
261
+ image: ghcr.io/bu-ist/s3-proxy:latest
262
+ ```
263
+
264
+ 3. **Add Volume Mappings**:
265
+ ```yaml
266
+ wordpress:
267
+ volumes:
268
+ - ./:/var/www/html/wp-content/plugins/my-plugin
269
+ ```
270
+
271
+ 4. **Add Port Mappings**:
272
+ ```yaml
273
+ wordpress:
274
+ ports:
275
+ - "80:80"
276
+ - "443:443"
277
+ ```
278
+
279
+ 5. **Add Named Volumes**:
280
+ ```yaml
281
+ volumes:
282
+ db_data:
283
+ name: ${projectName}_db_data
284
+ wp_build:
285
+ name: ${projectName}_wp_build
286
+ ```
287
+
288
+ **Module:** `lib/docker-compose.js`
289
+
290
+ **Output:** `.buwp-local/docker-compose.yml`
291
+
292
+ **Important:** Generated file contains **variable references** (`${VAR_NAME}`), not actual credential values.
293
+
294
+ ### 6. Docker Compose Execution
295
+
296
+ **Process:** Node.js child process spawning `docker compose` command
297
+
298
+ Once the docker-compose.yml is generated, buwp-local executes Docker Compose commands using the following pattern:
299
+
300
+ **Commands:**
301
+ ```bash
302
+ # Start
303
+ docker compose -f .buwp-local/docker-compose.yml \
304
+ --project-name ${projectName} \
305
+ --env-file .buwp-local/.env.XXXXXX \
306
+ up -d
307
+
308
+ # Stop
309
+ docker compose -f .buwp-local/docker-compose.yml \
310
+ --project-name ${projectName} \
311
+ down
312
+
313
+ # Destroy
314
+ docker compose -f .buwp-local/docker-compose.yml \
315
+ --project-name ${projectName} \
316
+ down -v
317
+
318
+ # Logs
319
+ docker compose -f .buwp-local/docker-compose.yml \
320
+ --project-name ${projectName} \
321
+ logs
322
+ ```
323
+
324
+ **Project Names:**
325
+ - Each project gets unique Docker Compose project name
326
+ - Format: sanitized directory name (lowercase, alphanumeric)
327
+ - Enables running multiple projects simultaneously
328
+ - All containers/volumes prefixed with project name
329
+
330
+ **Module:** `lib/commands/start.js`, `lib/commands/stop.js`
331
+
332
+ ### 7. Volume Management
333
+
334
+ **Per-Project Volumes:**
335
+
336
+ ```yaml
337
+ volumes:
338
+ db_data:
339
+ name: ${projectName}_db_data # MySQL data
340
+ wp_build:
341
+ name: ${projectName}_wp_build # WordPress files
342
+ ```
343
+
344
+ **Bind Mounts (Volume Mappings):**
345
+
346
+ ```yaml
347
+ volumes:
348
+ - ./:/var/www/html/wp-content/plugins/my-plugin
349
+ - ../theme:/var/www/html/wp-content/themes/my-theme
350
+ ```
351
+
352
+ **Volume Isolation:**
353
+ - Each project name gets unique volumes
354
+ - Prevents database conflicts between projects
355
+ - Allows multiple projects to run simultaneously
356
+ - Volumes persist until `destroy` command
357
+
358
+ **Shared Environment:**
359
+ - Multiple repos with same `projectName` share volumes
360
+ - Enables integration testing
361
+ - All mappings accumulate
362
+
363
+ ### 8. WP-CLI Proxy
364
+
365
+ **Implementation:**
366
+ ```bash
367
+ docker compose --project-name ${projectName} \
368
+ -f .buwp-local/docker-compose.yml \
369
+ exec wordpress \
370
+ wp ${args}
371
+ ```
372
+
373
+ **Usage:**
374
+ ```bash
375
+ npx buwp-local wp plugin list
376
+ npx buwp-local wp user create username user@bu.edu
377
+ ```
378
+
379
+ **Features:**
380
+ - Passes all arguments through to WP-CLI
381
+ - Runs inside WordPress container
382
+ - Requires containers to be running
383
+
384
+ **Module:** `lib/commands/wp.js`
385
+
386
+ ## Security Model
387
+
388
+ ### Credential Security
389
+
390
+ **Storage:**
391
+ - **Keychain:** Encrypted by macOS, protected by user authentication
392
+ - **`.env.local`:** File permissions should be `0600` (user-managed)
393
+
394
+ **In Transit:**
395
+ - Loaded in Node.js process memory
396
+ - Written to temp file with `0600` permissions
397
+ - Never logged or displayed (except explicit `keychain get` command)
398
+
399
+ **In Docker:**
400
+ - Passed via `env_file` directive (not command line)
401
+ - Not visible in `docker ps` or process list
402
+ - Not written to generated `docker-compose.yml`
403
+
404
+ **Cleanup:**
405
+ - Temp files deleted on stop/destroy
406
+ - Process exit handlers ensure cleanup
407
+
408
+ ### File Permissions
409
+
410
+ ```
411
+ .buwp-local/
412
+ docker-compose.yml # 0644 (safe - no credentials)
413
+ .env.XXXXXX/ # 0700 directory
414
+ .env # 0600 file (credentials)
415
+ ```
416
+
417
+ ### Git Ignore
418
+
419
+ **Must be ignored:**
420
+ ```gitignore
421
+ .env.local
422
+ .buwp-local/
423
+ ```
424
+
425
+ **Safe to commit:**
426
+ ```
427
+ .buwp-local.json # Configuration template (no secrets)
428
+ .env.local.example # Example with placeholder values
429
+ ```
430
+
431
+ ## Multi-Project Architecture
432
+
433
+ ### Isolated Projects
434
+
435
+ ```
436
+ Project A (bu-custom-analytics):
437
+ Volumes:
438
+ - bu-custom-analytics_db_data
439
+ - bu-custom-analytics_wp_build
440
+ Containers:
441
+ - bu-custom-analytics-wordpress-1
442
+ - bu-custom-analytics-db-1
443
+ Hostname: bu-custom-analytics.local
444
+ Ports: 8080, 8443
445
+
446
+ Project B (bu-slideshow):
447
+ Volumes:
448
+ - bu-slideshow_db_data
449
+ - bu-slideshow_wp_build
450
+ Containers:
451
+ - bu-slideshow-wordpress-1
452
+ - bu-slideshow-db-1
453
+ Hostname: bu-slideshow.local
454
+ Ports: 8081, 8444
455
+ ```
456
+
457
+ **Key:** Different `projectName` = Complete isolation
458
+
459
+ ### Shared Projects
460
+
461
+ ```
462
+ Shared Environment (bu-sandbox):
463
+ Volumes:
464
+ - bu-sandbox_db_data # Shared
465
+ - bu-sandbox_wp_build # Shared
466
+ Containers:
467
+ - bu-sandbox-wordpress-1
468
+ - bu-sandbox-db-1
469
+ Mappings:
470
+ - ~/projects/plugin-a → /wp-content/plugins/plugin-a
471
+ - ~/projects/plugin-b → /wp-content/plugins/plugin-b
472
+ - ~/projects/theme → /wp-content/themes/theme
473
+ Hostname: bu-sandbox.local
474
+ Ports: 80, 443
475
+ ```
476
+
477
+ **Key:** Same `projectName` = Shared volumes and containers
478
+
479
+ ## Performance Considerations
480
+
481
+ ### Volume Performance
482
+
483
+ **Bind Mounts:**
484
+ - Direct host filesystem access
485
+ - Fast on macOS (Docker Desktop optimizations)
486
+ - Real-time code changes
487
+
488
+ **Named Volumes:**
489
+ - Better performance than bind mounts
490
+ - Used for database and WordPress core
491
+ - Persist between starts
492
+
493
+ ### Resource Usage
494
+
495
+ **Typical Project:**
496
+ - WordPress container: ~200-500 MB RAM
497
+ - MySQL container: ~200-400 MB RAM
498
+ - Redis container: ~10-20 MB RAM
499
+ - S3 Proxy container: ~50-100 MB RAM
500
+ - Total: ~500 MB - 1 GB per project
501
+
502
+ **Multiple Projects:**
503
+ - Linear scaling per project
504
+ - 3 projects ≈ 1.5-3 GB RAM
505
+ - Docker Desktop overhead: ~500 MB
506
+
507
+ ## Extensibility
508
+
509
+ ### Adding New Services
510
+
511
+ Edit `lib/docker-compose.js`:
512
+
513
+ ```javascript
514
+ if (config.services.myservice) {
515
+ compose.services.myservice = {
516
+ image: 'my/service:latest',
517
+ environment: {
518
+ SERVICE_CONFIG: '${MY_SERVICE_CONFIG}'
519
+ }
520
+ };
521
+ }
522
+ ```
523
+
524
+ Add credential type to `lib/keychain.js`:
525
+
526
+ ```javascript
527
+ const CREDENTIAL_TYPES = [
528
+ // ... existing types
529
+ 'MY_SERVICE_CONFIG'
530
+ ];
531
+ ```
532
+
533
+ ### Adding New Commands
534
+
535
+ Create `lib/commands/mycommand.js`:
536
+
537
+ ```javascript
538
+ import { Command } from 'commander';
539
+
540
+ export function myCommand(program) {
541
+ program
542
+ .command('mycommand')
543
+ .description('Description of my command')
544
+ .action(async (options) => {
545
+ // Implementation
546
+ });
547
+ }
548
+ ```
549
+
550
+ Register in `bin/buwp-local.js`:
551
+
552
+ ```javascript
553
+ import { myCommand } from '../lib/commands/mycommand.js';
554
+
555
+ myCommand(program);
556
+ ```
557
+
558
+ ### Custom Docker Images
559
+
560
+ Override in `.buwp-local.json`:
561
+
562
+ ```json
563
+ {
564
+ "image": "ghcr.io/bu-ist/custom-image:latest"
565
+ }
566
+ ```
567
+
568
+ ## Error Handling
569
+
570
+ ### Configuration Errors
571
+
572
+ **Validation:** Runs before Docker Compose generation
573
+
574
+ **Common Issues:**
575
+ - Missing required fields → Prompt with defaults
576
+ - Invalid JSON → Clear error message with line number
577
+ - Invalid paths → Resolve and validate before use
578
+
579
+ ### Docker Errors
580
+
581
+ **Detection:** Check exit codes from `docker compose` commands
582
+
583
+ **Common Issues:**
584
+ - Docker not running → Check `docker info`
585
+ - Port conflicts → Suggest alternate ports
586
+ - Image pull failures → Check authentication
587
+ - Volume permission issues → Suggest cleanup
588
+
589
+ ### Credential Errors
590
+
591
+ **Validation:** Before starting containers
592
+
593
+ **Common Issues:**
594
+ - Missing credentials → Offer interactive setup
595
+ - Keychain access denied → Provide Keychain Access instructions
596
+ - Invalid format → Clear error about expected format
597
+
598
+ ### Cleanup on Failure
599
+
600
+ These are the handlers to ensure temporary files are deleted on process exit:
601
+
602
+ **Process Exit Handlers:**
603
+ ```javascript
604
+ process.on('SIGINT', cleanup);
605
+ process.on('SIGTERM', cleanup);
606
+ process.on('exit', cleanup);
607
+
608
+ function cleanup() {
609
+ secureDeleteTempEnvFile(tempEnvPath);
610
+ }
611
+ ```
612
+
613
+ **Graceful Shutdown:**
614
+ - Delete temporary credential files
615
+ - Log cleanup actions
616
+ - Preserve volumes (unless `destroy`)
617
+
618
+ ## Dependencies
619
+
620
+ ### Runtime Dependencies
621
+
622
+ ```json
623
+ {
624
+ "commander": "^12.x", // CLI framework
625
+ "js-yaml": "^4.x", // YAML generation
626
+ "chalk": "^5.x", // Terminal colors
627
+ "prompts": "^2.x", // Interactive prompts
628
+ "dotenv": "^16.x" // .env.local parsing
629
+ }
630
+ ```
631
+
632
+ ### External Requirements
633
+
634
+ - **Node.js:** >=18.0.0 (ESM support)
635
+ - **Docker Desktop:** Latest stable
636
+ - **macOS:** 10.15+ (for Keychain integration)
637
+ - **Shell:** zsh or bash
638
+
639
+ ### Platform Support
640
+
641
+ **Current:**
642
+ - ✅ macOS (Intel and Apple Silicon)
643
+ - ✅ Keychain integration (macOS only)
644
+
645
+ **Future:**
646
+ - 🔄 Linux (credential storage TBD)
647
+ - 🔄 Windows (WSL2 + credential storage TBD)
648
+
649
+ ## Testing Strategy
650
+
651
+ ### Manual Testing
652
+
653
+ **Test Commands:**
654
+
655
+ Unit tests not implemented yet, recommend vitest as with bu-protected-s3-object-lambda.
656
+
657
+ ### Validation
658
+
659
+ **Config Validation:**
660
+ ```bash
661
+ npx buwp-local config --validate
662
+ ```
663
+
664
+ **Credential Validation:**
665
+ ```bash
666
+ npx buwp-local keychain status
667
+ ```
668
+
669
+ **Docker Validation:**
670
+ ```bash
671
+ docker compose -f .buwp-local/docker-compose.yml config
672
+ ```
673
+
674
+ ## Future Enhancements
675
+
676
+ ### Planned Features
677
+
678
+ - **Cross-platform credential storage** (Linux, Windows)
679
+ - **Automatic /etc/hosts management** (detect missing entries)
680
+ - **SSL certificate generation** (local HTTPS)
681
+ - **Central registry** (shared team configurations)
682
+ - **Health checks** (verify services are running)
683
+ - **Performance monitoring** (container resource usage)
684
+ - **Unit tests** vitest
685
+
686
+ ## See Also
687
+
688
+ - [Getting Started](GETTING_STARTED.md) - User guide
689
+ - [Commands Reference](COMMANDS.md) - Full command list
690
+ - [Credentials Management](CREDENTIALS.md) - Security details
691
+ - [Multi-Project Setup](MULTI_PROJECT.md) - Running multiple projects