Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96251ec2cc | ||
| fe79522ea4 | |||
|
|
743216ef1b | ||
|
|
c53295a371 | ||
| c18efb1b60 | |||
|
|
822805e4c4 | ||
|
|
6fd690890b | ||
|
|
5fd836190c | ||
| 441467eb8a | |||
| 59bfc859d7 | |||
|
|
b989405a53 |
@@ -86,6 +86,12 @@ jobs:
|
||||
echo "✅ Schema is up to date. No changes detected."
|
||||
fi
|
||||
|
||||
- name: Generate TSOA OpenAPI Spec and Routes
|
||||
run: |
|
||||
echo "Generating TSOA OpenAPI specification and route handlers..."
|
||||
npm run tsoa:build
|
||||
echo "✅ TSOA files generated successfully"
|
||||
|
||||
- name: Build React Application for Production
|
||||
# Source Maps (ADR-015): If SENTRY_AUTH_TOKEN is set, the @sentry/vite-plugin will:
|
||||
# 1. Generate hidden source maps during build
|
||||
@@ -295,6 +301,10 @@ jobs:
|
||||
}
|
||||
"
|
||||
|
||||
# Save PM2 process list after cleanup to persist deletions
|
||||
echo "Saving PM2 process list after cleanup..."
|
||||
pm2 save
|
||||
|
||||
# === POST-CLEANUP VERIFICATION ===
|
||||
echo "=== POST-CLEANUP VERIFICATION ==="
|
||||
pm2 jlist | node -e "
|
||||
|
||||
@@ -173,6 +173,10 @@ jobs:
|
||||
}
|
||||
" || true
|
||||
|
||||
# Save PM2 process list after cleanup to persist deletions
|
||||
echo "Saving PM2 process list after cleanup..."
|
||||
pm2 save || true
|
||||
|
||||
# === POST-CLEANUP VERIFICATION ===
|
||||
echo "=== POST-CLEANUP VERIFICATION ==="
|
||||
pm2 jlist 2>/dev/null | node -e "
|
||||
@@ -732,6 +736,10 @@ jobs:
|
||||
}
|
||||
"
|
||||
|
||||
# Save PM2 process list after cleanup to persist deletions
|
||||
echo "Saving PM2 process list after cleanup..."
|
||||
pm2 save
|
||||
|
||||
# === POST-CLEANUP VERIFICATION ===
|
||||
echo "=== POST-CLEANUP VERIFICATION ==="
|
||||
pm2 jlist | node -e "
|
||||
|
||||
@@ -58,7 +58,8 @@ jobs:
|
||||
run: |
|
||||
echo "Stopping PRODUCTION PM2 processes to release database connections..."
|
||||
pm2 stop flyer-crawler-api flyer-crawler-worker flyer-crawler-analytics-worker || echo "Production PM2 processes were not running."
|
||||
echo "✅ Production application server stopped."
|
||||
pm2 save
|
||||
echo "✅ Production application server stopped and saved."
|
||||
|
||||
- name: Step 2 - Drop and Recreate Database
|
||||
run: |
|
||||
|
||||
@@ -85,6 +85,12 @@ jobs:
|
||||
echo "✅ Schema is up to date. No changes detected."
|
||||
fi
|
||||
|
||||
- name: Generate TSOA OpenAPI Spec and Routes
|
||||
run: |
|
||||
echo "Generating TSOA OpenAPI specification and route handlers..."
|
||||
npm run tsoa:build
|
||||
echo "✅ TSOA files generated successfully"
|
||||
|
||||
- name: Build React Application for Production
|
||||
run: |
|
||||
if [ -z "${{ secrets.VITE_GOOGLE_GENAI_API_KEY }}" ]; then
|
||||
@@ -199,6 +205,10 @@ jobs:
|
||||
}
|
||||
"
|
||||
|
||||
# Save PM2 process list after cleanup to persist deletions
|
||||
echo "Saving PM2 process list after cleanup..."
|
||||
pm2 save
|
||||
|
||||
# === POST-CLEANUP VERIFICATION ===
|
||||
echo "=== POST-CLEANUP VERIFICATION ==="
|
||||
pm2 jlist | node -e "
|
||||
|
||||
100
.gitea/workflows/sync-test-version.yml
Normal file
100
.gitea/workflows/sync-test-version.yml
Normal file
@@ -0,0 +1,100 @@
|
||||
# .gitea/workflows/sync-test-version.yml
|
||||
#
|
||||
# Lightweight workflow to sync version numbers from production to test environment.
|
||||
# This runs after successful production deployments to update test PM2 metadata
|
||||
# without re-running the full test suite, build, and deployment pipeline.
|
||||
#
|
||||
# Duration: ~30 seconds (vs 5+ minutes for full test deployment)
|
||||
name: Sync Test Version
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Deploy to Production"]
|
||||
types:
|
||||
- completed
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
sync-version:
|
||||
runs-on: projectium.com
|
||||
# Only run if the production deployment succeeded
|
||||
if: ${{ gitea.event.workflow_run.conclusion == 'success' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Latest Code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 1 # Shallow clone, we only need latest commit
|
||||
|
||||
- name: Update Test Package Version
|
||||
run: |
|
||||
echo "========================================="
|
||||
echo "SYNCING VERSION TO TEST ENVIRONMENT"
|
||||
echo "========================================="
|
||||
|
||||
APP_PATH="/var/www/flyer-crawler-test.projectium.com"
|
||||
|
||||
# Get version from this repo's package.json
|
||||
NEW_VERSION=$(node -p "require('./package.json').version")
|
||||
echo "Production version: $NEW_VERSION"
|
||||
|
||||
# Get current test version
|
||||
if [ -f "$APP_PATH/package.json" ]; then
|
||||
CURRENT_VERSION=$(node -p "require('$APP_PATH/package.json').version")
|
||||
echo "Current test version: $CURRENT_VERSION"
|
||||
else
|
||||
CURRENT_VERSION="unknown"
|
||||
echo "Test package.json not found"
|
||||
fi
|
||||
|
||||
# Only update if versions differ
|
||||
if [ "$NEW_VERSION" != "$CURRENT_VERSION" ]; then
|
||||
echo "Updating test package.json to version $NEW_VERSION..."
|
||||
|
||||
# Update just the version field in test's package.json
|
||||
cd "$APP_PATH"
|
||||
npm version "$NEW_VERSION" --no-git-tag-version --allow-same-version
|
||||
|
||||
echo "✅ Test package.json updated to $NEW_VERSION"
|
||||
else
|
||||
echo "ℹ️ Versions already match, no update needed"
|
||||
fi
|
||||
|
||||
- name: Restart Test PM2 Processes
|
||||
run: |
|
||||
echo "Restarting test PM2 processes to refresh version metadata..."
|
||||
|
||||
# Restart with --update-env to pick up new package.json version
|
||||
pm2 restart flyer-crawler-api-test flyer-crawler-worker-test flyer-crawler-analytics-worker-test --update-env && pm2 save
|
||||
|
||||
echo "✅ Test PM2 processes restarted and saved"
|
||||
|
||||
# Show current state
|
||||
echo ""
|
||||
echo "--- Current PM2 State ---"
|
||||
pm2 list
|
||||
|
||||
# Verify version in PM2 metadata
|
||||
echo ""
|
||||
echo "--- Verifying Version in PM2 ---"
|
||||
pm2 jlist | node -e "
|
||||
try {
|
||||
const list = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
|
||||
const testProcesses = list.filter(p => p.name && p.name.endsWith('-test'));
|
||||
testProcesses.forEach(p => {
|
||||
console.log(p.name + ': v' + (p.pm2_env.version || 'unknown') + ' (' + p.pm2_env.status + ')');
|
||||
});
|
||||
} catch(e) {
|
||||
console.error('Failed to parse PM2 output');
|
||||
}
|
||||
"
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "========================================="
|
||||
echo "VERSION SYNC COMPLETE"
|
||||
echo "========================================="
|
||||
echo "Test environment version updated to match production"
|
||||
echo "No tests run, no builds performed"
|
||||
echo "Duration: ~30 seconds"
|
||||
48
CLAUDE.md
48
CLAUDE.md
@@ -89,6 +89,54 @@ if (p.pm2_env.status === 'errored') {
|
||||
}
|
||||
```
|
||||
|
||||
### PM2 Save Requirement (CRITICAL)
|
||||
|
||||
**CRITICAL**: Every `pm2 start`, `pm2 restart`, `pm2 stop`, or `pm2 delete` command MUST be immediately followed by `pm2 save`.
|
||||
|
||||
Without `pm2 save`, processes become ephemeral and will disappear on:
|
||||
|
||||
- PM2 daemon restarts
|
||||
- Server reboots
|
||||
- Internal PM2 reconciliation events
|
||||
|
||||
**Pattern:**
|
||||
|
||||
```bash
|
||||
# ✅ CORRECT - Save after every state change
|
||||
pm2 start ecosystem.config.cjs && pm2 save
|
||||
pm2 restart my-app && pm2 save
|
||||
pm2 stop my-app && pm2 save
|
||||
pm2 delete my-app && pm2 save
|
||||
|
||||
# ❌ WRONG - Missing save (processes become ephemeral)
|
||||
pm2 start ecosystem.config.cjs
|
||||
pm2 restart my-app
|
||||
```
|
||||
|
||||
**In Cleanup Scripts:**
|
||||
|
||||
```javascript
|
||||
// ✅ CORRECT - Save after cleanup loop completes
|
||||
targetProcesses.forEach((p) => {
|
||||
exec('pm2 delete ' + p.pm2_env.pm_id);
|
||||
});
|
||||
exec('pm2 save'); // Persist all deletions
|
||||
|
||||
// ❌ WRONG - Missing save
|
||||
targetProcesses.forEach((p) => {
|
||||
exec('pm2 delete ' + p.pm2_env.pm_id);
|
||||
});
|
||||
```
|
||||
|
||||
**Why This Matters:**
|
||||
|
||||
PM2 maintains an in-memory process list. The `pm2 save` command writes this list to `~/.pm2/dump.pm2`, which PM2 uses to resurrect processes after daemon restarts. Without it, your carefully managed process state is lost.
|
||||
|
||||
**See Also:**
|
||||
|
||||
- [ADR-014: Containerization and Deployment Strategy](docs/adr/0014-containerization-and-deployment-strategy.md)
|
||||
- [ADR-061: PM2 Process Isolation Safeguards](docs/adr/0061-pm2-process-isolation-safeguards.md)
|
||||
|
||||
### Communication Style
|
||||
|
||||
Ask before assuming. Never assume:
|
||||
|
||||
@@ -249,26 +249,39 @@ module.exports = {
|
||||
|
||||
### PM2 Commands Reference
|
||||
|
||||
**CRITICAL**: Every `pm2 start`, `pm2 restart`, `pm2 stop`, or `pm2 delete` command MUST be immediately followed by `pm2 save`. Without this, processes become ephemeral and will disappear on PM2 daemon restarts, server reboots, or internal reconciliation events.
|
||||
|
||||
```bash
|
||||
# Start/reload with environment
|
||||
# ✅ CORRECT - Start/reload with environment and save
|
||||
pm2 startOrReload ecosystem.config.cjs --env production --update-env && pm2 save
|
||||
|
||||
# ✅ CORRECT - Restart and save
|
||||
pm2 restart flyer-crawler-api && pm2 save
|
||||
|
||||
# ✅ CORRECT - Stop and save
|
||||
pm2 stop flyer-crawler-api && pm2 save
|
||||
|
||||
# ✅ CORRECT - Delete and save
|
||||
pm2 delete flyer-crawler-api && pm2 save
|
||||
|
||||
# ❌ WRONG - Missing save (processes become ephemeral)
|
||||
pm2 startOrReload ecosystem.config.cjs --env production --update-env
|
||||
|
||||
# Save process list for startup
|
||||
pm2 save
|
||||
|
||||
# View logs
|
||||
# View logs (read-only operation, no save needed)
|
||||
pm2 logs flyer-crawler-api --lines 50
|
||||
|
||||
# Monitor processes
|
||||
# Monitor processes (read-only operation, no save needed)
|
||||
pm2 monit
|
||||
|
||||
# List all processes
|
||||
# List all processes (read-only operation, no save needed)
|
||||
pm2 list
|
||||
|
||||
# Describe process details
|
||||
# Describe process details (read-only operation, no save needed)
|
||||
pm2 describe flyer-crawler-api
|
||||
```
|
||||
|
||||
**Why This Matters**: PM2 maintains an in-memory process list. The `pm2 save` command writes this list to `~/.pm2/dump.pm2`, which PM2 uses to resurrect processes after daemon restarts. Without it, your carefully managed process state is lost.
|
||||
|
||||
### Resource Limits
|
||||
|
||||
| Process | Memory Limit | Restart Delay | Kill Timeout |
|
||||
|
||||
@@ -115,6 +115,31 @@ echo "=== END POST-CLEANUP VERIFICATION ==="
|
||||
|
||||
**Purpose**: Immediately identifies cross-environment contamination.
|
||||
|
||||
#### Layer 6: PM2 Process List Persistence
|
||||
|
||||
**CRITICAL**: Save the PM2 process list after every state-changing operation:
|
||||
|
||||
```bash
|
||||
# After any pm2 start/stop/restart/delete operation
|
||||
pm2 save
|
||||
|
||||
# Example: After cleanup loop completes
|
||||
targetProcesses.forEach(p => {
|
||||
exec('pm2 delete ' + p.pm2_env.pm_id);
|
||||
});
|
||||
exec('pm2 save'); // Persist all deletions
|
||||
```
|
||||
|
||||
**Purpose**: Ensures PM2 process state persists across daemon restarts, server reboots, and internal reconciliation events.
|
||||
|
||||
**Why This Matters**: PM2 maintains an in-memory process list. Without `pm2 save`, processes become ephemeral:
|
||||
|
||||
- Daemon restart → All unsaved processes disappear
|
||||
- Server reboot → Process list reverts to last saved state
|
||||
- PM2 internal reconciliation → Unsaved processes may be lost
|
||||
|
||||
**Pattern**: Every `pm2 start`, `pm2 restart`, `pm2 stop`, or `pm2 delete` MUST be followed by `pm2 save`.
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
203
docs/adr/0062-lightweight-version-sync-workflow.md
Normal file
203
docs/adr/0062-lightweight-version-sync-workflow.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# ADR-0062: Lightweight Version Sync Workflow
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-02-18
|
||||
**Decision Makers:** Development Team
|
||||
**Related:** ADR-061 (PM2 Process Isolation Safeguards)
|
||||
|
||||
## Context
|
||||
|
||||
After successful production deployments, the version number in `package.json` is bumped and pushed back to the `main` branch. This triggered the full test deployment workflow (`deploy-to-test.yml`), which includes:
|
||||
|
||||
- `npm ci` (dependency installation)
|
||||
- TypeScript type-checking
|
||||
- Prettier formatting
|
||||
- ESLint linting
|
||||
- Unit tests (~150 tests)
|
||||
- Integration tests (~50 tests)
|
||||
- React build with source maps
|
||||
- Full rsync deployment
|
||||
- PM2 process restart
|
||||
|
||||
**Problem:** Running the complete 5-7 minute test suite just to update a 10KB `package.json` file was wasteful:
|
||||
|
||||
| Resource | Waste |
|
||||
| -------------- | ------------------------------------- |
|
||||
| **Duration** | 5-7 minutes (vs 30 seconds needed) |
|
||||
| **CPU** | ~95% unnecessary (full test suite) |
|
||||
| **File I/O** | 99.8% unnecessary (only need 1 file) |
|
||||
| **CI Queue** | Blocked other workflows unnecessarily |
|
||||
| **Time/month** | ~20 minutes wasted on version syncs |
|
||||
|
||||
The code being tested had already passed the full test suite when originally pushed to `main`. Re-running tests for a version number change provided no additional value.
|
||||
|
||||
## Decision
|
||||
|
||||
Implement a lightweight **version-sync-only workflow** that:
|
||||
|
||||
1. **Triggers automatically** after successful production deployments (via `workflow_run`)
|
||||
2. **Updates only** the `package.json` version in the test deployment directory
|
||||
3. **Restarts PM2** with `--update-env` to refresh version metadata
|
||||
4. **Completes in ~30 seconds** instead of 5-7 minutes
|
||||
|
||||
### Architecture
|
||||
|
||||
```yaml
|
||||
# .gitea/workflows/sync-test-version.yml
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ['Deploy to Production']
|
||||
types: [completed]
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
sync-version:
|
||||
if: ${{ gitea.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- Checkout latest main
|
||||
- Update test package.json version
|
||||
- PM2 restart with --update-env
|
||||
- Verify version in PM2 metadata
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
|
||||
- `deploy-to-test.yml` remains **unchanged** (runs normally for code changes)
|
||||
- `deploy-to-prod.yml` remains **unchanged** (no explicit trigger needed)
|
||||
- `workflow_run` automatically triggers after production deployment
|
||||
- Non-blocking: production success doesn't depend on version sync
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
✅ **90% faster** version synchronization (30 sec vs 5-7 min)
|
||||
✅ **95% less CPU** usage (no test suite execution)
|
||||
✅ **99.8% less file I/O** (only package.json updated)
|
||||
✅ **Cleaner separation** of concerns (version sync vs full deployment)
|
||||
✅ **No workflow file pollution** with conditionals
|
||||
✅ **Saves ~20 minutes/month** of CI time
|
||||
|
||||
### Negative
|
||||
|
||||
⚠️ Test environment version could briefly lag behind production (30 second window)
|
||||
⚠️ Additional workflow file to maintain
|
||||
⚠️ PM2 version metadata relies on `--update-env` working correctly
|
||||
|
||||
### Neutral
|
||||
|
||||
- Full test deployment still runs for actual code changes (as designed)
|
||||
- Version numbers remain synchronized (just via different workflow)
|
||||
- Same end state, different path
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### 1. Add Conditionals to `deploy-to-test.yml`
|
||||
|
||||
**Approach:** Detect version bump commits and skip most steps
|
||||
|
||||
**Rejected because:**
|
||||
|
||||
- Pollutes workflow with 20+ `if:` conditionals
|
||||
- Makes workflow complex and brittle
|
||||
- Still queues a workflow run (even if it exits early)
|
||||
- Harder to maintain and debug
|
||||
|
||||
### 2. Manual Production Deployments
|
||||
|
||||
**Approach:** Make production `workflow_dispatch` only (manual trigger)
|
||||
|
||||
**Rejected because:**
|
||||
|
||||
- Removes automation benefits
|
||||
- Requires human intervention for every production deploy
|
||||
- Doesn't align with CI/CD best practices
|
||||
- Slows down deployment velocity
|
||||
|
||||
### 3. Don't Sync Test Versions
|
||||
|
||||
**Approach:** Accept that test version lags behind production
|
||||
|
||||
**Rejected because:**
|
||||
|
||||
- Version numbers become meaningless in test
|
||||
- Harder to correlate test issues with production releases
|
||||
- Loses visibility into what's deployed where
|
||||
|
||||
### 4. Release Tag-Based Production Deployments
|
||||
|
||||
**Approach:** Only deploy production on release tags, not on every push
|
||||
|
||||
**Rejected because:**
|
||||
|
||||
- Changes current deployment cadence
|
||||
- Adds manual release step overhead
|
||||
- Doesn't solve the fundamental problem (version sync still needed)
|
||||
|
||||
## Implementation
|
||||
|
||||
### Files Created
|
||||
|
||||
- `.gitea/workflows/sync-test-version.yml` - Lightweight version sync workflow
|
||||
|
||||
### Files Modified
|
||||
|
||||
- None (clean separation, no modifications to existing workflows)
|
||||
|
||||
### Configuration
|
||||
|
||||
No additional secrets or environment variables required. Uses existing PM2 configuration and test deployment paths.
|
||||
|
||||
## Verification
|
||||
|
||||
After implementation:
|
||||
|
||||
1. **Production deployment** completes and bumps version
|
||||
2. **Version sync workflow** triggers automatically within seconds
|
||||
3. **Test PM2 processes** restart with updated version metadata
|
||||
4. **PM2 list** shows matching versions across environments
|
||||
|
||||
```bash
|
||||
# Verify version sync worked
|
||||
pm2 jlist | jq '.[] | select(.name | endsWith("-test")) | {name, version: .pm2_env.version}'
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "flyer-crawler-api-test",
|
||||
"version": "0.16.2"
|
||||
}
|
||||
{
|
||||
"name": "flyer-crawler-worker-test",
|
||||
"version": "0.16.2"
|
||||
}
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
Track workflow execution times:
|
||||
|
||||
- Before: `deploy-to-test.yml` duration after prod deploys (~5-7 min)
|
||||
- After: `sync-test-version.yml` duration (~30 sec)
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If version sync fails or causes issues:
|
||||
|
||||
1. Remove `sync-test-version.yml`
|
||||
2. Previous behavior automatically resumes (full test deployment on version bumps)
|
||||
3. No data loss or configuration changes needed
|
||||
|
||||
## References
|
||||
|
||||
- Inspired by similar optimization in `stock-alert` project (commit `021f9c8`)
|
||||
- Related: ADR-061 for PM2 process isolation and deployment safety
|
||||
- Workflow pattern: GitHub Actions `workflow_run` trigger documentation
|
||||
|
||||
## Notes
|
||||
|
||||
This optimization follows the principle: **"Don't test what you've already tested."**
|
||||
|
||||
The code was validated when originally pushed to `main`. Version number changes are metadata updates, not code changes. Treating them differently is an architectural improvement, not a shortcut.
|
||||
@@ -57,6 +57,7 @@ This directory contains a log of the architectural decisions made for the Flyer
|
||||
**[ADR-053](./0053-worker-health-checks.md)**: Worker Health Checks and Stalled Job Monitoring (Accepted)
|
||||
**[ADR-054](./0054-bugsink-gitea-issue-sync.md)**: Bugsink to Gitea Issue Synchronization (Proposed)
|
||||
**[ADR-061](./0061-pm2-process-isolation-safeguards.md)**: PM2 Process Isolation Safeguards (Accepted)
|
||||
**[ADR-062](./0062-lightweight-version-sync-workflow.md)**: Lightweight Version Sync Workflow (Accepted)
|
||||
|
||||
## 7. Frontend / User Interface
|
||||
|
||||
|
||||
@@ -17,15 +17,15 @@ This guide covers deploying Flyer Crawler to a production server.
|
||||
|
||||
### Command Reference Table
|
||||
|
||||
| Task | Command |
|
||||
| -------------------- | ----------------------------------------------------------------------- |
|
||||
| Deploy to production | Gitea Actions workflow (manual trigger) |
|
||||
| Deploy to test | Automatic on push to `main` |
|
||||
| Check PM2 status | `pm2 list` |
|
||||
| View logs | `pm2 logs flyer-crawler-api --lines 100` |
|
||||
| Restart all | `pm2 restart all` |
|
||||
| Check NGINX | `sudo nginx -t && sudo systemctl status nginx` |
|
||||
| Check health | `curl -s https://flyer-crawler.projectium.com/api/health/ready \| jq .` |
|
||||
| Task | Command |
|
||||
| -------------------- | ----------------------------------------------------------------------------------------------- |
|
||||
| Deploy to production | Gitea Actions workflow (manual trigger) |
|
||||
| Deploy to test | Automatic on push to `main` |
|
||||
| Check PM2 status | `pm2 list` |
|
||||
| View logs | `pm2 logs flyer-crawler-api --lines 100` |
|
||||
| Restart all | `pm2 restart flyer-crawler-api flyer-crawler-worker flyer-crawler-analytics-worker && pm2 save` |
|
||||
| Check NGINX | `sudo nginx -t && sudo systemctl status nginx` |
|
||||
| Check health | `curl -s https://flyer-crawler.projectium.com/api/health/ready \| jq .` |
|
||||
|
||||
### Deployment URLs
|
||||
|
||||
@@ -274,7 +274,40 @@ sudo systemctl reload nginx
|
||||
|
||||
---
|
||||
|
||||
## PM2 Log Management
|
||||
## PM2 Process Management
|
||||
|
||||
### Critical: Always Save After State Changes
|
||||
|
||||
**CRITICAL**: Every `pm2 start`, `pm2 restart`, `pm2 stop`, or `pm2 delete` command MUST be immediately followed by `pm2 save`.
|
||||
|
||||
Without `pm2 save`, processes become ephemeral and will disappear on:
|
||||
|
||||
- PM2 daemon restarts
|
||||
- Server reboots
|
||||
- Internal PM2 reconciliation events
|
||||
|
||||
**Correct Pattern:**
|
||||
|
||||
```bash
|
||||
# ✅ CORRECT - Always save after state changes
|
||||
pm2 restart flyer-crawler-api && pm2 save
|
||||
pm2 stop flyer-crawler-worker && pm2 save
|
||||
pm2 delete flyer-crawler-analytics-worker && pm2 save
|
||||
pm2 startOrReload ecosystem.config.cjs --env production --update-env && pm2 save
|
||||
|
||||
# ❌ WRONG - Missing save (processes become ephemeral)
|
||||
pm2 restart flyer-crawler-api
|
||||
pm2 stop flyer-crawler-worker
|
||||
|
||||
# ✅ Read-only operations don't need save
|
||||
pm2 list
|
||||
pm2 logs flyer-crawler-api
|
||||
pm2 monit
|
||||
```
|
||||
|
||||
**Why This Matters**: PM2 maintains an in-memory process list. The `pm2 save` command writes this list to `~/.pm2/dump.pm2`, which PM2 uses to resurrect processes after daemon restarts. Without it, your carefully managed process state is lost.
|
||||
|
||||
### PM2 Log Management
|
||||
|
||||
Install and configure pm2-logrotate to manage log files:
|
||||
|
||||
|
||||
@@ -42,25 +42,35 @@
|
||||
|
||||
### Critical Commands
|
||||
|
||||
**IMPORTANT**: Every `pm2 start`, `pm2 restart`, `pm2 stop`, or `pm2 delete` command MUST be immediately followed by `pm2 save` to persist changes.
|
||||
|
||||
```bash
|
||||
# Check PM2 status
|
||||
# Check PM2 status (read-only, no save needed)
|
||||
pm2 list
|
||||
|
||||
# Check specific process
|
||||
# Check specific process (read-only, no save needed)
|
||||
pm2 show flyer-crawler-api
|
||||
|
||||
# View recent logs
|
||||
# View recent logs (read-only, no save needed)
|
||||
pm2 logs --lines 50
|
||||
|
||||
# Restart specific processes (SAFE)
|
||||
pm2 restart flyer-crawler-api flyer-crawler-worker flyer-crawler-analytics-worker
|
||||
# ✅ Restart specific processes (SAFE - includes save)
|
||||
pm2 restart flyer-crawler-api flyer-crawler-worker flyer-crawler-analytics-worker && pm2 save
|
||||
|
||||
# DO NOT USE (affects ALL apps)
|
||||
# ✅ Start processes from config (SAFE - includes save)
|
||||
pm2 startOrReload /var/www/flyer-crawler.projectium.com/ecosystem.config.cjs --update-env && pm2 save
|
||||
|
||||
# ❌ DO NOT USE (affects ALL apps)
|
||||
# pm2 restart all <-- DANGEROUS
|
||||
# pm2 stop all <-- DANGEROUS
|
||||
# pm2 delete all <-- DANGEROUS
|
||||
|
||||
# ❌ DO NOT FORGET pm2 save after state changes
|
||||
# pm2 restart flyer-crawler-api <-- WRONG: Missing save, process becomes ephemeral
|
||||
```
|
||||
|
||||
**Why `pm2 save` Matters**: Without it, processes become ephemeral and disappear on daemon restarts, server reboots, or internal PM2 reconciliation events.
|
||||
|
||||
### Severity Classification
|
||||
|
||||
| Severity | Criteria | Response Time | Example |
|
||||
|
||||
@@ -77,6 +77,7 @@ module.exports = {
|
||||
min_uptime: '10s',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
PORT: '3001',
|
||||
WORKER_LOCK_DURATION: '120000',
|
||||
...sharedEnv,
|
||||
},
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "flyer-crawler",
|
||||
"version": "0.16.4",
|
||||
"version": "0.19.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "flyer-crawler",
|
||||
"version": "0.16.4",
|
||||
"version": "0.19.1",
|
||||
"dependencies": {
|
||||
"@bull-board/api": "^6.14.2",
|
||||
"@bull-board/express": "^6.14.2",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "flyer-crawler",
|
||||
"private": true,
|
||||
"version": "0.16.4",
|
||||
"version": "0.19.1",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
|
||||
@@ -149,6 +149,9 @@ pm2 delete all 2>/dev/null || true
|
||||
# Start PM2 with the dev ecosystem config
|
||||
pm2 start /app/ecosystem.dev.config.cjs
|
||||
|
||||
# Save PM2 process list to persist across daemon restarts
|
||||
pm2 save
|
||||
|
||||
# Show PM2 status
|
||||
echo ""
|
||||
echo "--- PM2 Process Status ---"
|
||||
|
||||
@@ -108,7 +108,14 @@ describe('useAppInitialization Hook', () => {
|
||||
});
|
||||
|
||||
it('should open "What\'s New" modal if version is new', () => {
|
||||
vi.spyOn(window.localStorage, 'getItem').mockReturnValue('1.0.0');
|
||||
// The hook only shows "What's New" if:
|
||||
// 1. The app version differs from lastSeenVersion
|
||||
// 2. The onboarding tour has been completed (flyer_crawler_onboarding_completed === 'true')
|
||||
vi.spyOn(window.localStorage, 'getItem').mockImplementation((key) => {
|
||||
if (key === 'lastSeenVersion') return '1.0.0';
|
||||
if (key === 'flyer_crawler_onboarding_completed') return 'true';
|
||||
return null;
|
||||
});
|
||||
renderHook(() => useAppInitialization(), { wrapper });
|
||||
expect(mockOpenModal).toHaveBeenCalledWith('whatsNew');
|
||||
expect(window.localStorage.setItem).toHaveBeenCalledWith('lastSeenVersion', '1.0.1');
|
||||
|
||||
@@ -68,12 +68,18 @@ vi.mock('bullmq', () => ({
|
||||
UnrecoverableError: class UnrecoverableError extends Error {},
|
||||
}));
|
||||
|
||||
// Mock redis.server to prevent real Redis connection attempts
|
||||
// Mock redis.server to prevent real Redis connection attempts.
|
||||
// The workers.server.ts imports `bullmqConnection` from redis.server,
|
||||
// so we must export both `connection` and `bullmqConnection` from this mock.
|
||||
vi.mock('./redis.server', () => ({
|
||||
connection: {
|
||||
on: vi.fn(),
|
||||
quit: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
bullmqConnection: {
|
||||
on: vi.fn(),
|
||||
quit: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock queues.server to provide mock queue instances
|
||||
|
||||
@@ -19,8 +19,10 @@ vi.mock('bullmq', () => ({
|
||||
}));
|
||||
|
||||
// Mock our internal redis connection module to export our mock connection object.
|
||||
// The queues.server.ts imports `bullmqConnection` (aliased as `connection`),
|
||||
// so we must export `bullmqConnection` from this mock.
|
||||
vi.mock('./redis.server', () => ({
|
||||
connection: mocks.mockConnection,
|
||||
bullmqConnection: mocks.mockConnection,
|
||||
}));
|
||||
|
||||
describe('Queue Definitions', () => {
|
||||
|
||||
Reference in New Issue
Block a user