@bostonuniversity/buwp-local 0.7.2 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/buwp-local.js CHANGED
@@ -27,6 +27,7 @@ import destroyCommand from '../lib/commands/destroy.js';
27
27
  import updateCommand from '../lib/commands/update.js';
28
28
  import logsCommand from '../lib/commands/logs.js';
29
29
  import wpCommand from '../lib/commands/wp.js';
30
+ import watchJobsCommand from '../lib/commands/watch-jobs.js';
30
31
  import shellCommand from '../lib/commands/shell.js';
31
32
  import configCommand from '../lib/commands/config.js';
32
33
  import initCommand from '../lib/commands/init.js';
@@ -108,6 +109,14 @@ program
108
109
  .allowUnknownOption()
109
110
  .action(wpCommand);
110
111
 
112
+ // Watch Jobs command
113
+ program
114
+ .command('watch-jobs')
115
+ .description('Watch and automatically process site-manager jobs')
116
+ .option('--interval <seconds>', `Polling interval in seconds (default: 300, min: 30)`)
117
+ .option('--quiet', 'Suppress output unless jobs are found')
118
+ .action(watchJobsCommand);
119
+
111
120
  // Shell command
112
121
  program
113
122
  .command('shell')
package/docs/CHANGELOG.md CHANGED
@@ -5,6 +5,13 @@ All notable changes to buwp-local will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.7.3]
9
+
10
+ ### Added
11
+ - `watch-jobs` command for automatic processing of site-manager jobs at regular intervals
12
+ - Configurable polling interval with `--interval` flag (default: 60 seconds, minimum: 10 seconds), and through `jobWatchInterval` in configuration file
13
+ - `--quiet` flag to suppress routine output for long-running background use
14
+
8
15
  ## [0.7.2]
9
16
 
10
17
  ### Breaking Changes
package/docs/COMMANDS.md CHANGED
@@ -421,6 +421,82 @@ npx buwp-local keychain clear [--force]
421
421
 
422
422
  ---
423
423
 
424
+ ### `watch-jobs`
425
+
426
+ Watch for and automatically process site-manager jobs at regular intervals.
427
+
428
+ ```bash
429
+ npx buwp-local watch-jobs [options]
430
+ ```
431
+
432
+ **Options:**
433
+ - `--interval <seconds>` - Polling interval in seconds (default: 60, min: 10)
434
+ - `--quiet` - Suppress all routine output (for long-running background use)
435
+
436
+ **Examples:**
437
+ ```bash
438
+ # Watch with default 1 minute interval
439
+ npx buwp-local watch-jobs
440
+
441
+ # Watch with custom 2 minute interval
442
+ npx buwp-local watch-jobs --interval 120
443
+
444
+ # Quiet mode (silent operation, check web UI for job status)
445
+ npx buwp-local watch-jobs --quiet
446
+
447
+ # Quiet mode with short interval for active monitoring
448
+ npx buwp-local watch-jobs --interval 20 --quiet
449
+ ```
450
+
451
+ **What it does:**
452
+ - Runs `wp site-manager process-jobs` inside the WordPress container at configured intervals
453
+ - **Default mode**: Shows timestamped output for all checks and job results
454
+ - **Quiet mode**: Silently processes jobs (check site-manager web UI for status)
455
+ - Continues running until stopped (Ctrl+C)
456
+ - Mirrors production cron/EventBridge behavior locally
457
+
458
+ **Use cases:**
459
+ - Automatic processing of jobs created via web UI
460
+
461
+ **Configuration:**
462
+
463
+ Optionally configure default interval in `.buwp-local.json`:
464
+ ```json
465
+ {
466
+ "projectName": "my-project",
467
+ "jobWatchInterval": 200
468
+ }
469
+ ```
470
+
471
+ Command-line `--interval` flag overrides config file setting.
472
+
473
+ **Requirements:**
474
+ - WordPress container must be running (`start` first)
475
+ - Site-manager plugin must be installed and activated
476
+
477
+ **Quiet mode behavior:**
478
+ - **Startup**: Shows configuration banner
479
+ - **Routine operation**: Completely silent - no output for checks or job processing
480
+ - **Critical errors**: Shows only failures requiring intervention (Docker stopped, environment missing)
481
+ - **Job status**: Check site-manager plugin web UI to see job results and history
482
+ - **Best for**: Long-running background monitoring (hours/days) without terminal noise
483
+
484
+ **Error handling:**
485
+ - **Default mode**: Shows all errors and warnings with timestamps
486
+ - **Quiet mode**: Silently retries transient errors (container restarts), only shows critical failures
487
+ - Graceful shutdown on SIGINT/SIGTERM
488
+
489
+ **Tips:**
490
+ - Run in separate terminal window for visibility
491
+ - Use **default mode** during active development to see what's happening
492
+ - Use **quiet mode** for set-it-and-forget-it background monitoring
493
+ - Reduce interval during active testing (e.g., `--interval 60`)
494
+ - Increase interval for background monitoring (e.g., `--interval 600`)
495
+
496
+ **⚠️ Note:** This is a development tool. Production environments should continue using cron/AWS EventBridge for job processing.
497
+
498
+ ---
499
+
424
500
  ## Global Options
425
501
 
426
502
  These options work with all commands:
package/docs/ROADMAP.md CHANGED
@@ -172,8 +172,51 @@ hostile.remove('127.0.0.1', config.hostname);
172
172
  - Update command now properly releases volume locks before deletion
173
173
  - Added `--preserve-wpbuild` flag for opt-out of WordPress volume refresh
174
174
 
175
+ ### Shipped in v0.7.3
176
+
177
+ - **Job Watcher Command** 🚧
178
+ - New `watch-jobs` command to periodically run `wp site-manager process-jobs`
179
+ - Configurable polling interval (default: 5 minutes)
180
+ - Runs as standalone process in terminal window
181
+ - Timestamped output for job processing visibility
182
+ - Graceful shutdown (Ctrl+C)
183
+
184
+ **Problem:** Production environments use cron/AWS EventBridge to automatically process site-manager jobs (content migration, deployments). Local developers currently must manually run `npx buwp-local wp site-manager process-jobs` to see queued jobs complete.
185
+
186
+ **Solution:** Standalone `watch-jobs` command that runs indefinitely, polling for jobs at configurable intervals. Mirrors production behavior without requiring cron setup. Enables developers to use the site-manager web UI for content operations and see jobs complete automatically.
187
+
188
+ **Implementation location:** `lib/commands/watch-jobs.js`
189
+
190
+ **Configuration support:**
191
+ ```json
192
+ {
193
+ "jobWatchInterval": 60 // seconds, default 60 seconds
194
+ }
195
+ ```
196
+
197
+ **Command syntax:**
198
+ ```bash
199
+ buwp-local watch-jobs [--interval 200] [--quiet]
200
+ ```
201
+
202
+ **Technical considerations:**
203
+ - Requires WordPress container to be running
204
+ - Uses `docker compose exec` to run WP-CLI command
205
+ - Handles container stop/restart gracefully
206
+ - Minimal resource usage (sleeps between checks)
207
+ - Output includes timestamps for audit trail
208
+
209
+ **Future enhancement (v0.8.0+):** If widely adopted, consider adding `--watch-jobs` flag to `start` command for automatic background execution.
210
+
175
211
  ### Potential Features
176
212
 
213
+ - **Ability to add custom WORDPRESS_CONFIG_EXTRA environment variables**
214
+ - Support for adding custom WP config snippets via env vars
215
+
216
+ - **Credential Export**
217
+ - Commands to export credentials to JSON file
218
+ - Useful for migrating between machines or sharing setup
219
+
177
220
  - **Database Security**
178
221
  - Check database access on db port (e.g. `localhost:3306`)
179
222
  - Consider more stringent default database passwords
@@ -207,9 +250,6 @@ hostile.remove('127.0.0.1', config.hostname);
207
250
  - Credential issues → clear next steps
208
251
  - Port conflicts → suggest alternatives
209
252
 
210
- - **Multi project experience**
211
- - There is a problem when starting a new project when an existing project exists in docker but is stopped. When starting the new project, docker first starts the container for the stopped project for unknown reasons. If the new project uses the same ports, this causes conflicts. Need to investigate and resolve, projects should be isolated and not interfere with each other.
212
-
213
253
  - **Docker Volume management assistant**
214
254
  - listing and cleanup of unused volumes
215
255
 
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Watch Jobs command - Periodically process site-manager jobs
3
+ * Mirrors production cron/EventBridge behavior for local development
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+ import { execSync, exec } from 'child_process';
8
+ import path from 'path';
9
+ import fs from 'fs';
10
+ import { loadConfig } from '../config.js';
11
+
12
+ const DEFAULT_INTERVAL = 60; // 1 minute in seconds
13
+ const MIN_INTERVAL = 10; // Minimum 10 seconds
14
+
15
+ /**
16
+ * Format timestamp for log output
17
+ */
18
+ function timestamp() {
19
+ const now = new Date();
20
+ return now.toLocaleString('en-US', {
21
+ year: 'numeric',
22
+ month: '2-digit',
23
+ day: '2-digit',
24
+ hour: '2-digit',
25
+ minute: '2-digit',
26
+ second: '2-digit',
27
+ hour12: false
28
+ }).replace(',', '');
29
+ }
30
+
31
+ /**
32
+ * Check if container is running
33
+ */
34
+ function isContainerRunning(projectName, composePath) {
35
+ try {
36
+ const result = execSync(
37
+ `docker compose -p ${projectName} -f "${composePath}" ps --status running --services`,
38
+ { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }
39
+ );
40
+ return result.includes('wordpress');
41
+ } catch (err) {
42
+ return false;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Process site-manager jobs
48
+ */
49
+ async function processJobs(projectName, composePath, quiet) {
50
+ return new Promise((resolve) => {
51
+ const composeDir = path.dirname(composePath);
52
+ const command = `docker compose -p ${projectName} -f "${composePath}" exec -T wordpress wp site-manager process-jobs`;
53
+
54
+ if (!quiet) {
55
+ console.log(chalk.gray(`[${timestamp()}] Checking for jobs...`));
56
+ }
57
+
58
+ exec(command, { cwd: composeDir }, (error, stdout, stderr) => {
59
+ if (error) {
60
+ // In quiet mode, silently retry on transient errors (container might restart)
61
+ // Only verbose mode shows these errors
62
+ if (!quiet) {
63
+ if (stderr.includes('container') || stderr.includes('not running')) {
64
+ console.log(chalk.yellow(`[${timestamp()}] ⚠️ Container not running. Waiting...`));
65
+ } else {
66
+ console.log(chalk.red(`[${timestamp()}] ❌ Error executing command:`));
67
+ console.log(chalk.red(stderr || error.message));
68
+ }
69
+ }
70
+ resolve(false);
71
+ return;
72
+ }
73
+
74
+ const output = stdout.trim();
75
+
76
+ // Check if jobs were found and processed
77
+ if (output && output.length > 0) {
78
+ // In quiet mode, suppress all output (user checks web UI for job status)
79
+ // In verbose mode, show full job output
80
+ if (!quiet) {
81
+ console.log(chalk.green(`[${timestamp()}] ✓ Processing jobs:`));
82
+ console.log(output);
83
+ }
84
+ resolve(true);
85
+ } else if (!quiet) {
86
+ // No jobs found - only show in verbose mode
87
+ console.log(chalk.gray(`[${timestamp()}] No jobs found.`));
88
+ resolve(false);
89
+ } else {
90
+ // Quiet mode and no jobs - silent
91
+ resolve(false);
92
+ }
93
+ });
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Watch jobs command
99
+ */
100
+ async function watchJobsCommand(options) {
101
+ try {
102
+ const projectPath = process.cwd();
103
+ const composePath = path.join(projectPath, '.buwp-local', 'docker-compose.yml');
104
+
105
+ // Check if docker-compose.yml exists
106
+ if (!fs.existsSync(composePath)) {
107
+ console.log(chalk.yellow('⚠️ No running environment found.'));
108
+ console.log(chalk.gray('Run "buwp-local start" to create an environment.\n'));
109
+ return;
110
+ }
111
+
112
+ // Load config to get project name and optional interval setting
113
+ const config = loadConfig(projectPath);
114
+ const projectName = config.projectName || 'buwp-local';
115
+
116
+ // Determine interval (priority: CLI flag > config file > default)
117
+ let interval = DEFAULT_INTERVAL;
118
+ if (options.interval) {
119
+ interval = parseInt(options.interval, 10);
120
+ if (isNaN(interval) || interval < MIN_INTERVAL) {
121
+ console.log(chalk.red(`❌ Invalid interval. Minimum is ${MIN_INTERVAL} seconds.`));
122
+ process.exit(1);
123
+ }
124
+ } else if (config.jobWatchInterval) {
125
+ interval = config.jobWatchInterval;
126
+ if (interval < MIN_INTERVAL) {
127
+ console.log(chalk.yellow(`⚠️ Config interval too low. Using minimum: ${MIN_INTERVAL}s`));
128
+ interval = MIN_INTERVAL;
129
+ }
130
+ }
131
+
132
+ const quiet = options.quiet || false;
133
+
134
+ // Check if Docker is running
135
+ try {
136
+ execSync('docker info', { stdio: 'ignore' });
137
+ } catch (err) {
138
+ console.error(chalk.red('❌ Docker is not running.'));
139
+ console.log(chalk.gray('Start Docker Desktop and try again.'));
140
+ process.exit(1);
141
+ }
142
+
143
+ // Check if container is running initially
144
+ if (!isContainerRunning(projectName, composePath)) {
145
+ console.log(chalk.yellow('⚠️ WordPress container is not running.'));
146
+ console.log(chalk.gray('Run "buwp-local start" first, then try again.\n'));
147
+ return;
148
+ }
149
+
150
+ // Display startup message
151
+ console.log(chalk.cyan('🔍 Watching for site-manager jobs...'));
152
+ console.log(chalk.gray(` Interval: ${interval}s`));
153
+ console.log(chalk.gray(` Project: ${projectName}`));
154
+ console.log(chalk.gray(` Mode: ${quiet ? 'quiet' : 'verbose'}`));
155
+ console.log(chalk.gray('\nPress Ctrl+C to stop\n'));
156
+
157
+ // Set up graceful shutdown
158
+ let isShuttingDown = false;
159
+ const shutdown = () => {
160
+ if (isShuttingDown) return;
161
+ isShuttingDown = true;
162
+ console.log(chalk.cyan('\n\n👋 Stopping job watcher...'));
163
+ process.exit(0);
164
+ };
165
+
166
+ process.on('SIGINT', shutdown);
167
+ process.on('SIGTERM', shutdown);
168
+
169
+ // Main watch loop
170
+ const watch = async () => {
171
+ // Check if we should stop
172
+ if (isShuttingDown) return;
173
+
174
+ // Process jobs
175
+ await processJobs(projectName, composePath, quiet);
176
+
177
+ // Schedule next check
178
+ if (!isShuttingDown) {
179
+ setTimeout(watch, interval * 1000);
180
+ }
181
+ };
182
+
183
+ // Start watching
184
+ await watch();
185
+
186
+ } catch (err) {
187
+ console.error(chalk.red('\n❌ Error:'), err.message);
188
+ process.exit(1);
189
+ }
190
+ }
191
+
192
+ export default watchJobsCommand;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bostonuniversity/buwp-local",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "description": "Local WordPress development environment for Boston University projects",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",