diff --git a/docs/operations/PM2-NAMESPACE-COMPLETION-REPORT.md b/docs/operations/PM2-NAMESPACE-COMPLETION-REPORT.md index 7b3c3ed9..127c09eb 100644 --- a/docs/operations/PM2-NAMESPACE-COMPLETION-REPORT.md +++ b/docs/operations/PM2-NAMESPACE-COMPLETION-REPORT.md @@ -12,13 +12,13 @@ The PM2 namespace implementation for the flyer-crawler project is now 100% compl ### Key Achievements -| Metric | Value | -|--------|-------| -| **Namespaces Implemented** | 3 (production, test, development) | -| **Workflow Files Updated** | 6 | -| **Config Files Modified** | 3 | -| **Test Coverage** | 89 tests (all passing) | -| **Race Conditions Eliminated** | `pm2 save` isolation complete | +| Metric | Value | +| ------------------------------ | --------------------------------- | +| **Namespaces Implemented** | 3 (production, test, development) | +| **Workflow Files Updated** | 6 | +| **Config Files Modified** | 3 | +| **Test Coverage** | 89 tests (all passing) | +| **Race Conditions Eliminated** | `pm2 save` isolation complete | --- @@ -40,11 +40,11 @@ Prior to this implementation, the project experienced critical issues with PM2 p ### Namespace Architecture -| Environment | Namespace | Config File | Use Case | -|-------------|-----------|-------------|----------| -| Production | `flyer-crawler-prod` | `ecosystem.config.cjs` | Live production deployment | -| Test | `flyer-crawler-test` | `ecosystem-test.config.cjs` | Staging/test deployment | -| Development | `flyer-crawler-dev` | `ecosystem.dev.config.cjs` | Local development in dev container | +| Environment | Namespace | Config File | Use Case | +| ----------- | -------------------- | --------------------------- | ---------------------------------- | +| Production | `flyer-crawler-prod` | `ecosystem.config.cjs` | Live production deployment | +| Test | `flyer-crawler-test` | `ecosystem-test.config.cjs` | Staging/test deployment | +| Development | `flyer-crawler-dev` | `ecosystem.dev.config.cjs` | Local development in dev container | ### Namespace Definition Pattern @@ -55,10 +55,10 @@ Each ecosystem config defines its namespace at the module.exports level (not ins module.exports = { namespace: 'flyer-crawler-prod', apps: [ - { name: 'flyer-crawler-api', /* ... */ }, - { name: 'flyer-crawler-worker', /* ... */ }, - { name: 'flyer-crawler-analytics-worker', /* ... */ } - ] + { name: 'flyer-crawler-api' /* ... */ }, + { name: 'flyer-crawler-worker' /* ... */ }, + { name: 'flyer-crawler-analytics-worker' /* ... */ }, + ], }; ``` @@ -68,22 +68,22 @@ module.exports = { ### Ecosystem Configuration Files -| File | Change | -|------|--------| -| `ecosystem.config.cjs` | Added `namespace: 'flyer-crawler-prod'` at module.exports level | +| File | Change | +| --------------------------- | --------------------------------------------------------------- | +| `ecosystem.config.cjs` | Added `namespace: 'flyer-crawler-prod'` at module.exports level | | `ecosystem-test.config.cjs` | Added `namespace: 'flyer-crawler-test'` at module.exports level | -| `ecosystem.dev.config.cjs` | Added `namespace: 'flyer-crawler-dev'` at module.exports level | +| `ecosystem.dev.config.cjs` | Added `namespace: 'flyer-crawler-dev'` at module.exports level | ### Workflow Files -| File | Changes | -|------|---------| -| `.gitea/workflows/deploy-to-prod.yml` | Added `--namespace flyer-crawler-prod` to all PM2 commands | -| `.gitea/workflows/deploy-to-test.yml` | Added `--namespace flyer-crawler-test` to all PM2 commands | -| `.gitea/workflows/restart-pm2.yml` | Added `--namespace` flags for both test and production environments | -| `.gitea/workflows/manual-db-restore.yml` | Added `--namespace flyer-crawler-prod` to PM2 stop, save, and startOrReload commands | -| `.gitea/workflows/manual-deploy-major.yml` | Added `--namespace flyer-crawler-prod` to PM2 commands | -| `.gitea/workflows/pm2-diagnostics.yml` | Added namespace-specific sections for both production and test | +| File | Changes | +| ------------------------------------------ | ------------------------------------------------------------------------------------ | +| `.gitea/workflows/deploy-to-prod.yml` | Added `--namespace flyer-crawler-prod` to all PM2 commands | +| `.gitea/workflows/deploy-to-test.yml` | Added `--namespace flyer-crawler-test` to all PM2 commands | +| `.gitea/workflows/restart-pm2.yml` | Added `--namespace` flags for both test and production environments | +| `.gitea/workflows/manual-db-restore.yml` | Added `--namespace flyer-crawler-prod` to PM2 stop, save, and startOrReload commands | +| `.gitea/workflows/manual-deploy-major.yml` | Added `--namespace flyer-crawler-prod` to PM2 commands | +| `.gitea/workflows/pm2-diagnostics.yml` | Added namespace-specific sections for both production and test | ### Session-Specific Modifications (2026-02-18) @@ -103,16 +103,16 @@ The following files were modified in the final session to ensure complete namesp ### Migration Script -| File | Purpose | -|------|---------| +| File | Purpose | +| ----------------------------------- | ------------------------------------------------------------------------------- | | `scripts/migrate-pm2-namespaces.sh` | Zero-downtime migration script for transitioning servers to namespace-based PM2 | ### Documentation -| File | Purpose | -|------|---------| -| `docs/adr/0063-pm2-namespace-implementation.md` | Architecture Decision Record documenting the design | -| `CLAUDE.md` | Updated PM2 Namespace Isolation section with usage examples | +| File | Purpose | +| ----------------------------------------------- | ----------------------------------------------------------- | +| `docs/adr/0063-pm2-namespace-implementation.md` | Architecture Decision Record documenting the design | +| `CLAUDE.md` | Updated PM2 Namespace Isolation section with usage examples | --- @@ -173,6 +173,7 @@ Total: **89 tests** (all passing) ### 1. Race Condition Elimination Before: + ``` Test deploy: pm2 save -> writes to ~/.pm2/dump.pm2 Prod deploy: pm2 save -> overwrites ~/.pm2/dump.pm2 @@ -180,6 +181,7 @@ PM2 daemon restart -> incomplete process list ``` After: + ``` Test deploy: pm2 save --namespace flyer-crawler-test -> writes to ~/.pm2/dump-flyer-crawler-test.pm2 Prod deploy: pm2 save --namespace flyer-crawler-prod -> writes to ~/.pm2/dump-flyer-crawler-prod.pm2 @@ -189,6 +191,7 @@ PM2 daemon restart -> both environments fully restored ### 2. Safe Parallel Deployments Test and production deployments can now run simultaneously without interference. Each namespace operates independently with its own: + - Process list - Dump file - Logs (when using namespace filter) @@ -196,16 +199,18 @@ Test and production deployments can now run simultaneously without interference. ### 3. Simplified Commands Before (with filtering logic): + ```javascript // Complex inline JavaScript filtering const list = JSON.parse(execSync('pm2 jlist').toString()); -const prodProcesses = list.filter(p => - ['flyer-crawler-api', 'flyer-crawler-worker', 'flyer-crawler-analytics-worker'].includes(p.name) +const prodProcesses = list.filter((p) => + ['flyer-crawler-api', 'flyer-crawler-worker', 'flyer-crawler-analytics-worker'].includes(p.name), ); -prodProcesses.forEach(p => execSync(`pm2 delete ${p.pm_id}`)); +prodProcesses.forEach((p) => execSync(`pm2 delete ${p.pm_id}`)); ``` After (simple namespace flag): + ```bash pm2 delete all --namespace flyer-crawler-prod ``` @@ -347,13 +352,13 @@ pm2 list # Should show processes organized by namespace ## Related Documentation -| Document | Purpose | -|----------|---------| -| [ADR-063: PM2 Namespace Implementation](../adr/0063-pm2-namespace-implementation.md) | Architecture decision record | -| [ADR-061: PM2 Process Isolation Safeguards](../adr/0061-pm2-process-isolation-safeguards.md) | Prior safeguards (still active) | -| [CLAUDE.md](../../CLAUDE.md) | PM2 Namespace Isolation section (lines 52-169) | -| [PM2 Incident Response Runbook](./PM2-INCIDENT-RESPONSE.md) | Emergency procedures | -| [Incident Report 2026-02-17](./INCIDENT-2026-02-17-PM2-PROCESS-KILL.md) | Root cause analysis | +| Document | Purpose | +| -------------------------------------------------------------------------------------------- | ---------------------------------------------- | +| [ADR-063: PM2 Namespace Implementation](../adr/0063-pm2-namespace-implementation.md) | Architecture decision record | +| [ADR-061: PM2 Process Isolation Safeguards](../adr/0061-pm2-process-isolation-safeguards.md) | Prior safeguards (still active) | +| [CLAUDE.md](../../CLAUDE.md) | PM2 Namespace Isolation section (lines 52-169) | +| [PM2 Incident Response Runbook](./PM2-INCIDENT-RESPONSE.md) | Emergency procedures | +| [Incident Report 2026-02-17](./INCIDENT-2026-02-17-PM2-PROCESS-KILL.md) | Root cause analysis | --- @@ -373,16 +378,16 @@ pm2 list # Should show processes organized by namespace ## Appendix: Command Quick Reference -| Action | Production | Test | -|--------|------------|------| -| Start | `pm2 start ecosystem.config.cjs --namespace flyer-crawler-prod` | `pm2 start ecosystem-test.config.cjs --namespace flyer-crawler-test` | -| Stop all | `pm2 stop all --namespace flyer-crawler-prod` | `pm2 stop all --namespace flyer-crawler-test` | -| Restart all | `pm2 restart all --namespace flyer-crawler-prod` | `pm2 restart all --namespace flyer-crawler-test` | -| Delete all | `pm2 delete all --namespace flyer-crawler-prod` | `pm2 delete all --namespace flyer-crawler-test` | -| List | `pm2 list --namespace flyer-crawler-prod` | `pm2 list --namespace flyer-crawler-test` | -| Logs | `pm2 logs --namespace flyer-crawler-prod` | `pm2 logs --namespace flyer-crawler-test` | -| Save | `pm2 save --namespace flyer-crawler-prod` | `pm2 save --namespace flyer-crawler-test` | -| Describe | `pm2 describe flyer-crawler-api --namespace flyer-crawler-prod` | `pm2 describe flyer-crawler-api-test --namespace flyer-crawler-test` | +| Action | Production | Test | +| ----------- | --------------------------------------------------------------- | -------------------------------------------------------------------- | +| Start | `pm2 start ecosystem.config.cjs --namespace flyer-crawler-prod` | `pm2 start ecosystem-test.config.cjs --namespace flyer-crawler-test` | +| Stop all | `pm2 stop all --namespace flyer-crawler-prod` | `pm2 stop all --namespace flyer-crawler-test` | +| Restart all | `pm2 restart all --namespace flyer-crawler-prod` | `pm2 restart all --namespace flyer-crawler-test` | +| Delete all | `pm2 delete all --namespace flyer-crawler-prod` | `pm2 delete all --namespace flyer-crawler-test` | +| List | `pm2 list --namespace flyer-crawler-prod` | `pm2 list --namespace flyer-crawler-test` | +| Logs | `pm2 logs --namespace flyer-crawler-prod` | `pm2 logs --namespace flyer-crawler-test` | +| Save | `pm2 save --namespace flyer-crawler-prod` | `pm2 save --namespace flyer-crawler-test` | +| Describe | `pm2 describe flyer-crawler-api --namespace flyer-crawler-prod` | `pm2 describe flyer-crawler-api-test --namespace flyer-crawler-test` | --- diff --git a/tests/pm2-namespace.test.ts b/tests/pm2-namespace.test.ts index cc224e6c..3dc14cd1 100644 --- a/tests/pm2-namespace.test.ts +++ b/tests/pm2-namespace.test.ts @@ -89,8 +89,8 @@ describe('PM2 Namespace Implementation', () => { // Extract just the module.exports block to avoid matching 'apps:' in comments const exportBlock = content.slice(content.indexOf('module.exports')); - const namespaceInExport = exportBlock.indexOf("namespace:"); - const appsInExport = exportBlock.indexOf("apps:"); + const namespaceInExport = exportBlock.indexOf('namespace:'); + const appsInExport = exportBlock.indexOf('apps:'); expect(namespaceInExport).toBeGreaterThan(-1); expect(appsInExport).toBeGreaterThan(-1); @@ -136,8 +136,8 @@ describe('PM2 Namespace Implementation', () => { // Extract just the module.exports block to avoid matching 'apps:' in comments const exportBlock = content.slice(content.indexOf('module.exports')); - const namespaceInExport = exportBlock.indexOf("namespace:"); - const appsInExport = exportBlock.indexOf("apps:"); + const namespaceInExport = exportBlock.indexOf('namespace:'); + const appsInExport = exportBlock.indexOf('apps:'); expect(namespaceInExport).toBeGreaterThan(-1); expect(appsInExport).toBeGreaterThan(-1); @@ -183,8 +183,8 @@ describe('PM2 Namespace Implementation', () => { // Extract just the module.exports block to avoid matching 'apps:' in comments const exportBlock = content.slice(content.indexOf('module.exports')); - const namespaceInExport = exportBlock.indexOf("namespace:"); - const appsInExport = exportBlock.indexOf("apps:"); + const namespaceInExport = exportBlock.indexOf('namespace:'); + const appsInExport = exportBlock.indexOf('apps:'); expect(namespaceInExport).toBeGreaterThan(-1); expect(appsInExport).toBeGreaterThan(-1); @@ -293,10 +293,17 @@ describe('PM2 Namespace Implementation', () => { const problematicLines = lines.filter((line) => { const trimmed = line.trim(); // Skip comments, echo statements (log messages), and inline JS exec/execSync calls - if (trimmed.startsWith('#') || trimmed.startsWith('echo ') || trimmed.includes('exec(') || trimmed.includes('execSync(')) return false; + if ( + trimmed.startsWith('#') || + trimmed.startsWith('echo ') || + trimmed.includes('exec(') || + trimmed.includes('execSync(') + ) + return false; // Check for pm2 commands that should have namespace - const hasPm2Command = /pm2\s+(save|restart|stop|delete|list|jlist|describe|logs|env|ps)(\s|$)/.test(trimmed); + const hasPm2Command = + /pm2\s+(save|restart|stop|delete|list|jlist|describe|logs|env|ps)(\s|$)/.test(trimmed); if (!hasPm2Command) return false; // If it has a pm2 command, it should include --namespace @@ -393,7 +400,7 @@ describe('PM2 Namespace Implementation', () => { it('should support environment selection input', () => { expect(workflow).toContain('environment:'); - expect(workflow).toContain("type: choice"); + expect(workflow).toContain('type: choice'); expect(workflow).toContain('test'); expect(workflow).toContain('production'); expect(workflow).toContain('both'); @@ -720,8 +727,12 @@ describe('PM2 Namespace Implementation', () => { it('should show correct namespace examples', () => { // Check for correct usage examples - expect(claudeMdContent).toContain('pm2 start ecosystem.config.cjs --namespace flyer-crawler-prod'); - expect(claudeMdContent).toContain('pm2 start ecosystem-test.config.cjs --namespace flyer-crawler-test'); + expect(claudeMdContent).toContain( + 'pm2 start ecosystem.config.cjs --namespace flyer-crawler-prod', + ); + expect(claudeMdContent).toContain( + 'pm2 start ecosystem-test.config.cjs --namespace flyer-crawler-test', + ); }); it('should warn against using pm2 commands without namespace', () => { @@ -816,14 +827,20 @@ describe('PM2 Namespace Implementation', () => { // Count PM2 commands vs PM2 commands with namespace // In properly migrated workflows, most PM2 commands should have namespace - const testPm2Commands = testWorkflow.match(/pm2\s+(list|jlist|save|stop|start|delete|restart|logs|describe|env|startOrReload)/g) || []; + const testPm2Commands = + testWorkflow.match( + /pm2\s+(list|jlist|save|stop|start|delete|restart|logs|describe|env|startOrReload)/g, + ) || []; const testNamespacedCommands = testWorkflow.match(/pm2\s+\w+.*--namespace/g) || []; // Most PM2 commands should have namespace (allow some slack for inline JS) const testRatio = testNamespacedCommands.length / testPm2Commands.length; expect(testRatio).toBeGreaterThan(0.5); // At least 50% should have namespace - const prodPm2Commands = prodWorkflow.match(/pm2\s+(list|jlist|save|stop|start|delete|restart|logs|describe|env|startOrReload)/g) || []; + const prodPm2Commands = + prodWorkflow.match( + /pm2\s+(list|jlist|save|stop|start|delete|restart|logs|describe|env|startOrReload)/g, + ) || []; const prodNamespacedCommands = prodWorkflow.match(/pm2\s+\w+.*--namespace/g) || []; const prodRatio = prodNamespacedCommands.length / prodPm2Commands.length;