First Init
Deploy to Production / Build & Verify (push) Failing after 5m56s
Ping Search Engines / Notify Search Engines (push) Successful in 2s
Deploy to Production / Pre-Deploy Tests (push) Has been skipped
Deploy to Production / Deploy to Railway (push) Has been skipped
Deploy to Production / Deploy to Render (push) Has been skipped
Deploy to Production / Deploy to VPS (PM2) (push) Has been skipped
Deploy to Production / Deploy to Fly.io (push) Has been skipped
Deploy to Production / Post-Deploy Verification (push) Has been skipped
Deploy to Production / Notify on Failure (push) Successful in 2s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
E2E Test Suite / Smoke Tests (P0) (push) Failing after 11m26s
E2E Test Suite / Form Interaction Tests (push) Failing after 11m42s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 12m2s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 16m14s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 17m45s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 25m23s
E2E Test Suite / Security Header Tests (push) Failing after 7m55s
E2E Test Suite / Test Report Summary (push) Failing after 20s
E2E Test Suite / Mobile Device Tests (push) Failing after 2h49m9s
Uptime Monitor / Health & Response Time (push) Failing after 2s
Uptime Monitor / SSL Certificate (push) Successful in 2s
Uptime Monitor / Send Alerts (push) Failing after 3s
Uptime Monitor / Record Uptime Success (push) Has been skipped

This commit is contained in:
2026-03-21 16:46:46 +05:30
commit d402256547
216 changed files with 48375 additions and 0 deletions
+270
View File
@@ -0,0 +1,270 @@
# Performance Testing Scripts
Quick reference for performance testing and monitoring.
---
## Quick Start
### Test Current Server Performance
```bash
# Quick check (recommended)
curl -w "Time: %{time_total}s\n" -o /dev/null -s http://localhost:10000/
# Comprehensive test
python scripts/performance_test.py
# Shell script (Unix/Mac)
bash scripts/quick-perf-check.sh
```
---
## Available Scripts
### 1. `performance_test.py`
**Purpose:** Comprehensive Python-based performance testing
**Usage:**
```bash
python scripts/performance_test.py
```
**Features:**
- Tests multiple endpoints (pages + APIs)
- Statistical analysis (mean, median, std dev)
- Domain verification
- Performance recommendations
- JSON output support
**Output:**
- Response time statistics per endpoint
- Overall performance summary
- Recommendations for slow endpoints
- Pass/fail status
---
### 2. `quick-perf-check.sh`
**Purpose:** Fast performance spot-check
**Usage:**
```bash
# Default (localhost:10000)
bash scripts/quick-perf-check.sh
# Custom URL
bash scripts/quick-perf-check.sh https://workroot.in
```
**Features:**
- Quick response time checks
- Color-coded status indicators
- Minimal dependencies (just curl)
- Production-ready
---
### 3. `lighthouse_audit.py`
**Purpose:** Lighthouse performance audit
**Usage:**
```bash
python scripts/lighthouse_audit.py <url>
# Example
python scripts/lighthouse_audit.py http://localhost:10000
```
**Features:**
- Full Lighthouse audit
- Performance, accessibility, SEO scores
- Best practices analysis
- Detailed issue reporting
**Note:** May have encoding issues on Windows. Use WSL or fix Unicode output.
---
## Performance Targets
| Metric | Excellent | Good | Needs Work |
|--------|-----------|------|------------|
| **SSR Page Load** | < 200ms | < 500ms | > 500ms |
| **API Response** | < 100ms | < 200ms | > 200ms |
| **TTFB** | < 200ms | < 300ms | > 300ms |
---
## Core Web Vitals (Production)
| Metric | Good | Needs Improvement | Poor |
|--------|------|-------------------|------|
| **LCP** | < 2.5s | 2.5s - 4.0s | > 4.0s |
| **INP** | < 200ms | 200ms - 500ms | > 500ms |
| **CLS** | < 0.1 | 0.1 - 0.25 | > 0.25 |
---
## Manual Testing with curl
### Basic Response Time
```bash
curl -w "\nTime: %{time_total}s\n" -o /dev/null -s http://localhost:10000/
```
### Detailed Timing
```bash
curl -w "\n
Total Time: %{time_total}s
DNS Lookup: %{time_namelookup}s
TCP Connect: %{time_connect}s
TLS Handshake: %{time_appconnect}s
Time to First Byte: %{time_starttransfer}s
HTTP Code: %{http_code}
\n" -o /dev/null -s http://localhost:10000/
```
### Test Multiple Endpoints
```bash
for endpoint in / /contact /about /api/health.json; do
echo "Testing $endpoint"
curl -w "Time: %{time_total}s\n" -o /dev/null -s http://localhost:10000$endpoint
echo ""
done
```
---
## CI/CD Integration
### GitHub Actions Example
```yaml
- name: Performance Test
run: |
npm run build
npm run preview &
sleep 5
python scripts/performance_test.py
```
### Pre-deployment Check
```bash
# Build and test
npm run build
npm run preview &
sleep 5
python scripts/performance_test.py
# If all tests pass, deploy
if [ $? -eq 0 ]; then
echo "Performance tests passed"
# Deploy command here
else
echo "Performance tests failed"
exit 1
fi
```
---
## Troubleshooting
### Slow Response Times (> 500ms)
**Possible Causes:**
1. Development server (not optimized)
2. Cold start (first request)
3. Large bundle size
4. Unoptimized images
5. Missing caching
**Solutions:**
```bash
# 1. Build for production
npm run build
# 2. Analyze bundle
npm run build -- --analyze
# 3. Check bundle size
du -sh dist/
# 4. Test production build
npm run preview
```
### High Variance in Response Times
**Possible Causes:**
1. Network issues
2. System load
3. Garbage collection
4. Cache inconsistency
**Solutions:**
- Run multiple iterations (10+)
- Test during low system load
- Close other applications
- Clear caches between tests
---
## Monitoring in Production
### Recommended Tools
- **DataDog RUM:** Real User Monitoring
- **New Relic:** Application Performance Monitoring
- **Google Analytics:** Core Web Vitals
- **Sentry:** Performance tracking + errors
### Custom Performance Monitoring
```javascript
// Add to your app
if (typeof window !== 'undefined') {
window.addEventListener('load', () => {
const perfData = performance.getEntriesByType('navigation')[0];
console.log('Page Load Time:', perfData.loadEventEnd - perfData.fetchStart, 'ms');
console.log('DOM Interactive:', perfData.domInteractive - perfData.fetchStart, 'ms');
console.log('TTFB:', perfData.responseStart - perfData.requestStart, 'ms');
});
}
```
---
## Best Practices
1. **Test Regularly:** Run performance tests before each deployment
2. **Set Baselines:** Document baseline performance metrics
3. **Monitor Trends:** Track performance over time
4. **Test Production:** Dev server performance != production performance
5. **Real Users:** Use RUM to understand actual user experience
6. **Set Budgets:** Define performance budgets and enforce them
---
## Resources
- [Web.dev Performance](https://web.dev/performance/)
- [Lighthouse Scoring](https://web.dev/performance-scoring/)
- [Core Web Vitals](https://web.dev/vitals/)
- [Astro Performance](https://docs.astro.build/en/guides/performance/)
---
## Quick Reference
```bash
# Test homepage
curl -w "%{time_total}s\n" -o /dev/null -s http://localhost:10000/
# Comprehensive test
python scripts/performance_test.py
# Lighthouse audit
npx lighthouse http://localhost:10000 --view
# Bundle analysis
npm run build -- --analyze
```
+119
View File
@@ -0,0 +1,119 @@
# Backup Scripts
Automated backup system for WorkRoot website.
## Quick Start
```bash
# First-time setup
mkdir -p backups/{daily,weekly,monthly,pre-deploy,incremental,safety}
# Create your first backup
npm run backup:full
# Verify it worked
npm run backup:verify
```
## Available Commands
| Command | Description |
|---------|-------------|
| `npm run backup:full` | Full backup of all critical files |
| `npm run backup:content` | Quick backup of content only |
| `npm run backup:config` | Backup configs and env files |
| `npm run backup:verify` | Check backup health and integrity |
| `npm run backup:list` | List all available backups |
| `npm run backup:restore` | Restore from backup (interactive) |
| `npm run backup:cleanup` | Remove old backups per retention policy |
## Automation Setup
### Linux/macOS (cron)
```bash
# Edit crontab
crontab -e
# Daily backup at 2 AM
0 2 * * * cd /path/to/project && npm run backup:full
# Cleanup weekly on Sunday at 3 AM
0 3 * * 0 cd /path/to/project && npm run backup:cleanup
```
### Windows (Task Scheduler)
```powershell
# Daily backup
schtasks /create /tn "WorkRoot-DailyBackup" /tr "npm run backup:full" /sc daily /st 02:00
# Weekly cleanup
schtasks /create /tn "WorkRoot-CleanupBackup" /tr "npm run backup:cleanup" /sc weekly /d SUN /st 03:00
```
## Restore Examples
```bash
# List available backups
npm run backup:list
# Restore from latest
npm run backup:restore backups/daily/latest-full.tar.gz
# Verify specific backup
npm run backup:verify backups/daily/full-backup-2026-03-21.tar.gz
```
## Storage Structure
```
backups/
├── daily/ # 7 days retention
├── weekly/ # 4 weeks retention
├── monthly/ # 3 months retention
├── incremental/ # 72 hours retention
├── pre-deploy/ # Last 10 deployments
└── safety/ # Pre-restore backups (30 days)
```
## Pre-Deployment
Always backup before deployment:
```bash
# Manual
npm run backup:config
# Automatic (in deployment script)
npm run deploy # Includes automatic config backup
```
## Troubleshooting
### Backup fails
```bash
# Check disk space
df -h
# Check permissions
ls -la backups/
# Make scripts executable
chmod +x scripts/backup/*.sh
```
### Restore fails
```bash
# Verify backup integrity
npm run backup:verify backups/daily/backup-file.tar.gz
# Check backup contents
tar -tzf backups/daily/backup-file.tar.gz
```
## Documentation
See [BACKUP_STRATEGY.md](../../.agents/devops-engineer/BACKUP_STRATEGY.md) for complete documentation.
+73
View File
@@ -0,0 +1,73 @@
#!/bin/bash
# Configuration Backup Script for WorkRoot Website
# Backs up critical config files and environment variables
set -e
# Configuration
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
BACKUP_DIR="$PROJECT_ROOT/backups/pre-deploy"
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
BACKUP_FILE="$BACKUP_DIR/config-$TIMESTAMP.tar.gz"
LOG_FILE="$PROJECT_ROOT/backups/backup.log"
# Colors
GREEN='\033[0;32m'
NC='\033[0m'
log() {
echo "[$(date +"%Y-%m-%d %H:%M:%S")] $1" | tee -a "$LOG_FILE"
}
mkdir -p "$BACKUP_DIR"
log "${GREEN}Starting configuration backup...${NC}"
# Configuration files to backup
CONFIG_FILES=(
"astro.config.mjs"
"tailwind.config.mjs"
"playwright.config.ts"
"tsconfig.json"
"package.json"
"package-lock.json"
".env"
".env.example"
"src/middleware.ts"
"src/content/config.ts"
)
# Filter out non-existent files
EXISTING_FILES=()
for file in "${CONFIG_FILES[@]}"; do
if [ -e "$PROJECT_ROOT/$file" ]; then
EXISTING_FILES+=("$file")
fi
done
# Create backup
cd "$PROJECT_ROOT"
tar -czf "$BACKUP_FILE" "${EXISTING_FILES[@]}" 2>/dev/null
# Verify
if [ ! -f "$BACKUP_FILE" ]; then
log "ERROR: Config backup failed"
exit 1
fi
BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
log "${GREEN}Configuration backup completed${NC}"
log "Location: $BACKUP_FILE"
log "Size: $BACKUP_SIZE"
# Create latest symlink
ln -sf "$BACKUP_FILE" "$BACKUP_DIR/latest-config.tar.gz"
# Cleanup (keep last 10 config backups)
cd "$BACKUP_DIR"
ls -t config-*.tar.gz 2>/dev/null | tail -n +11 | xargs -r rm -f
log "Old config backups cleaned up (kept last 10)"
echo -e "${GREEN}✓ Configuration backup completed${NC}"
echo -e " Location: $BACKUP_FILE"
echo -e " Size: $BACKUP_SIZE"
+60
View File
@@ -0,0 +1,60 @@
#!/bin/bash
# Content-Only Backup Script for WorkRoot Website
# Quick incremental backup of content files (blog posts, markdown)
set -e
# Configuration
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
BACKUP_DIR="$PROJECT_ROOT/backups/incremental"
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
BACKUP_FILE="$BACKUP_DIR/content-$TIMESTAMP.tar.gz"
LOG_FILE="$PROJECT_ROOT/backups/backup.log"
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log() {
echo "[$(date +"%Y-%m-%d %H:%M:%S")] $1" | tee -a "$LOG_FILE"
}
mkdir -p "$BACKUP_DIR"
log "${GREEN}Starting content backup...${NC}"
# Content directories to backup
CONTENT_ITEMS=(
"src/content/"
"public/images/"
"public/assets/"
)
# Create backup
cd "$PROJECT_ROOT"
tar -czf "$BACKUP_FILE" "${CONTENT_ITEMS[@]}" 2>/dev/null || {
log "${YELLOW}Warning: Some content directories may not exist${NC}"
}
# Verify backup
if [ ! -f "$BACKUP_FILE" ]; then
log "ERROR: Content backup failed"
exit 1
fi
BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
log "${GREEN}Content backup completed${NC}"
log "Location: $BACKUP_FILE"
log "Size: $BACKUP_SIZE"
# Create latest symlink
ln -sf "$BACKUP_FILE" "$BACKUP_DIR/latest-content.tar.gz"
# Cleanup old incremental backups (keep last 72 hours)
find "$BACKUP_DIR" -name "content-*.tar.gz" -type f -mtime +3 -delete 2>/dev/null || true
log "Old backups cleaned up"
echo -e "${GREEN}✓ Content backup completed${NC}"
echo -e " Location: $BACKUP_FILE"
echo -e " Size: $BACKUP_SIZE"
+150
View File
@@ -0,0 +1,150 @@
#!/bin/bash
# Full Backup Script for WorkRoot Website
# Backs up all critical files: content, source, configs, environment
set -e # Exit on error
# Configuration
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
BACKUP_DIR="$PROJECT_ROOT/backups/daily"
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
BACKUP_FILE="$BACKUP_DIR/full-backup-$TIMESTAMP.tar.gz"
LOG_FILE="$PROJECT_ROOT/backups/backup.log"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Logging function
log() {
echo "[$(date +"%Y-%m-%d %H:%M:%S")] $1" | tee -a "$LOG_FILE"
}
# Create backup directory if it doesn't exist
mkdir -p "$BACKUP_DIR"
log "${GREEN}Starting full backup...${NC}"
# Files and directories to backup
BACKUP_ITEMS=(
"src/"
"public/"
"astro.config.mjs"
"tailwind.config.mjs"
"playwright.config.ts"
"package.json"
"package-lock.json"
".env"
".env.example"
"tsconfig.json"
"DEPLOYMENT.md"
"README.md"
".agents/"
)
# Files to exclude
EXCLUDE_PATTERNS=(
"node_modules"
"dist"
".astro"
"test-results"
".git"
"backups"
"*.log"
)
# Build exclude arguments
EXCLUDE_ARGS=""
for pattern in "${EXCLUDE_PATTERNS[@]}"; do
EXCLUDE_ARGS="$EXCLUDE_ARGS --exclude=$pattern"
done
# Verify critical files exist
log "Verifying critical files..."
CRITICAL_FILES=("src/" "package.json" "astro.config.mjs")
for file in "${CRITICAL_FILES[@]}"; do
if [ ! -e "$PROJECT_ROOT/$file" ]; then
log "${RED}ERROR: Critical file/directory missing: $file${NC}"
exit 1
fi
done
# Create backup
log "Creating backup archive: $BACKUP_FILE"
cd "$PROJECT_ROOT"
tar -czf "$BACKUP_FILE" $EXCLUDE_ARGS "${BACKUP_ITEMS[@]}" 2>/dev/null || {
log "${RED}ERROR: Backup creation failed${NC}"
exit 1
}
# Verify backup was created
if [ ! -f "$BACKUP_FILE" ]; then
log "${RED}ERROR: Backup file was not created${NC}"
exit 1
fi
# Get backup size
BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
log "${GREEN}Backup created successfully${NC}"
log "Backup file: $BACKUP_FILE"
log "Backup size: $BACKUP_SIZE"
# Verify backup integrity
log "Verifying backup integrity..."
tar -tzf "$BACKUP_FILE" > /dev/null 2>&1 || {
log "${RED}ERROR: Backup verification failed - archive is corrupted${NC}"
rm "$BACKUP_FILE"
exit 1
}
log "${GREEN}Backup integrity verified${NC}"
# Count files in backup
FILE_COUNT=$(tar -tzf "$BACKUP_FILE" | wc -l)
log "Files backed up: $FILE_COUNT"
# Create latest symlink
LATEST_LINK="$BACKUP_DIR/latest-full.tar.gz"
ln -sf "$BACKUP_FILE" "$LATEST_LINK"
log "Latest backup link created: $LATEST_LINK"
# Cleanup old backups (keep last 7 days)
log "Cleaning up old daily backups (keeping last 7)..."
cd "$BACKUP_DIR"
ls -t full-backup-*.tar.gz 2>/dev/null | tail -n +8 | xargs -r rm -f
log "Cleanup completed"
# Weekly backup (if Sunday)
if [ "$(date +%u)" -eq 7 ]; then
WEEKLY_DIR="$PROJECT_ROOT/backups/weekly"
mkdir -p "$WEEKLY_DIR"
WEEKLY_FILE="$WEEKLY_DIR/weekly-backup-$(date +%Y-%m-%d).tar.gz"
cp "$BACKUP_FILE" "$WEEKLY_FILE"
log "${GREEN}Weekly backup created: $WEEKLY_FILE${NC}"
# Cleanup old weekly backups (keep last 4 weeks)
cd "$WEEKLY_DIR"
ls -t weekly-backup-*.tar.gz 2>/dev/null | tail -n +5 | xargs -r rm -f
fi
# Monthly backup (if 1st of month)
if [ "$(date +%d)" -eq 1 ]; then
MONTHLY_DIR="$PROJECT_ROOT/backups/monthly"
mkdir -p "$MONTHLY_DIR"
MONTHLY_FILE="$MONTHLY_DIR/monthly-backup-$(date +%Y-%m).tar.gz"
cp "$BACKUP_FILE" "$MONTHLY_FILE"
log "${GREEN}Monthly backup created: $MONTHLY_FILE${NC}"
# Cleanup old monthly backups (keep last 3 months)
cd "$MONTHLY_DIR"
ls -t monthly-backup-*.tar.gz 2>/dev/null | tail -n +4 | xargs -r rm -f
fi
log "${GREEN}Full backup completed successfully${NC}"
echo ""
echo -e "${GREEN}✓ Backup completed${NC}"
echo -e " Location: $BACKUP_FILE"
echo -e " Size: $BACKUP_SIZE"
echo -e " Files: $FILE_COUNT"
+100
View File
@@ -0,0 +1,100 @@
#!/bin/bash
# Cleanup Old Backups Script
# Enforces retention policy across all backup types
set -e
# Configuration
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
BACKUP_ROOT="$PROJECT_ROOT/backups"
LOG_FILE="$BACKUP_ROOT/cleanup.log"
# Retention periods (in days)
DAILY_RETENTION=7
INCREMENTAL_RETENTION=3
SAFETY_RETENTION=30
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
log() {
echo "[$(date +"%Y-%m-%d %H:%M:%S")] $1" | tee -a "$LOG_FILE"
}
log "${YELLOW}Starting backup cleanup...${NC}"
# Daily backups - keep last 7
if [ -d "$BACKUP_ROOT/daily" ]; then
log "Cleaning daily backups (keeping last $DAILY_RETENTION)..."
cd "$BACKUP_ROOT/daily"
DELETED=$(ls -t full-backup-*.tar.gz 2>/dev/null | tail -n +$((DAILY_RETENTION + 1)) | wc -l)
ls -t full-backup-*.tar.gz 2>/dev/null | tail -n +$((DAILY_RETENTION + 1)) | xargs -r rm -f
log " Deleted $DELETED daily backups"
fi
# Weekly backups - keep last 4
if [ -d "$BACKUP_ROOT/weekly" ]; then
log "Cleaning weekly backups (keeping last 4)..."
cd "$BACKUP_ROOT/weekly"
DELETED=$(ls -t weekly-backup-*.tar.gz 2>/dev/null | tail -n +5 | wc -l)
ls -t weekly-backup-*.tar.gz 2>/dev/null | tail -n +5 | xargs -r rm -f
log " Deleted $DELETED weekly backups"
fi
# Monthly backups - keep last 3
if [ -d "$BACKUP_ROOT/monthly" ]; then
log "Cleaning monthly backups (keeping last 3)..."
cd "$BACKUP_ROOT/monthly"
DELETED=$(ls -t monthly-backup-*.tar.gz 2>/dev/null | tail -n +4 | wc -l)
ls -t monthly-backup-*.tar.gz 2>/dev/null | tail -n +4 | xargs -r rm -f
log " Deleted $DELETED monthly backups"
fi
# Incremental backups - keep last 3 days
if [ -d "$BACKUP_ROOT/incremental" ]; then
log "Cleaning incremental backups (keeping last $INCREMENTAL_RETENTION days)..."
cd "$BACKUP_ROOT/incremental"
DELETED=$(find . -name "content-*.tar.gz" -type f -mtime +$INCREMENTAL_RETENTION 2>/dev/null | wc -l)
find . -name "content-*.tar.gz" -type f -mtime +$INCREMENTAL_RETENTION -delete 2>/dev/null || true
log " Deleted $DELETED incremental backups"
fi
# Pre-deploy config backups - keep last 10
if [ -d "$BACKUP_ROOT/pre-deploy" ]; then
log "Cleaning pre-deploy backups (keeping last 10)..."
cd "$BACKUP_ROOT/pre-deploy"
DELETED=$(ls -t config-*.tar.gz 2>/dev/null | tail -n +11 | wc -l)
ls -t config-*.tar.gz 2>/dev/null | tail -n +11 | xargs -r rm -f
log " Deleted $DELETED pre-deploy backups"
fi
# Safety backups - keep last 30 days
if [ -d "$BACKUP_ROOT/safety" ]; then
log "Cleaning safety backups (keeping last $SAFETY_RETENTION days)..."
cd "$BACKUP_ROOT/safety"
DELETED=$(find . -name "pre-restore-*.tar.gz" -type f -mtime +$SAFETY_RETENTION 2>/dev/null | wc -l)
find . -name "pre-restore-*.tar.gz" -type f -mtime +$SAFETY_RETENTION -delete 2>/dev/null || true
log " Deleted $DELETED safety backups"
fi
# Report storage usage
log "Current backup storage usage:"
du -sh "$BACKUP_ROOT"/* 2>/dev/null | tee -a "$LOG_FILE" || true
# Check for large backups (>500MB)
log "Checking for large backups (>500MB)..."
find "$BACKUP_ROOT" -name "*.tar.gz" -type f -size +500M -exec ls -lh {} \; 2>/dev/null | tee -a "$LOG_FILE" || true
log "${GREEN}Cleanup completed${NC}"
# Summary
echo ""
echo -e "${GREEN}=== Cleanup Summary ===${NC}"
echo -e "Total backup storage: $(du -sh $BACKUP_ROOT | cut -f1)"
echo -e "Daily backups: $(ls $BACKUP_ROOT/daily/*.tar.gz 2>/dev/null | wc -l)"
echo -e "Weekly backups: $(ls $BACKUP_ROOT/weekly/*.tar.gz 2>/dev/null | wc -l)"
echo -e "Monthly backups: $(ls $BACKUP_ROOT/monthly/*.tar.gz 2>/dev/null | wc -l)"
echo -e "Pre-deploy backups: $(ls $BACKUP_ROOT/pre-deploy/*.tar.gz 2>/dev/null | wc -l)"
+54
View File
@@ -0,0 +1,54 @@
# WorkRoot Backup Automation - Cron Configuration
#
# To install:
# 1. Edit this file and replace /path/to/project with your actual project path
# 2. Add to your crontab: crontab -e
# 3. Paste the lines below (excluding comments)
# 4. Save and exit
#
# Verify cron jobs: crontab -l
# ============================================
# Daily Full Backup - 2:00 AM every day
# ============================================
0 2 * * * cd /path/to/project && /usr/bin/npm run backup:full >> /path/to/project/backups/cron.log 2>&1
# ============================================
# Incremental Content Backup - Every 6 hours
# ============================================
0 */6 * * * cd /path/to/project && /usr/bin/npm run backup:content >> /path/to/project/backups/cron.log 2>&1
# ============================================
# Cleanup Old Backups - Sunday at 3:00 AM
# ============================================
0 3 * * 0 cd /path/to/project && /usr/bin/npm run backup:cleanup >> /path/to/project/backups/cron.log 2>&1
# ============================================
# Verify Backup Health - Daily at 9:00 AM
# ============================================
0 9 * * * cd /path/to/project && /usr/bin/npm run backup:verify >> /path/to/project/backups/verify.log 2>&1
# ============================================
# Optional: Monthly backup verification drill
# First day of each month at 10:00 AM
# ============================================
# 0 10 1 * * cd /path/to/project && /path/to/project/scripts/backup/test-restore.sh >> /path/to/project/backups/drill.log 2>&1
# ============================================
# Cron Syntax Reference
# ============================================
# * * * * * command
# │ │ │ │ │
# │ │ │ │ └─── Day of week (0-7, Sunday = 0 or 7)
# │ │ │ └───── Month (1-12)
# │ │ └─────── Day of month (1-31)
# │ └───────── Hour (0-23)
# └─────────── Minute (0-59)
#
# Examples:
# 0 2 * * * - Daily at 2:00 AM
# */15 * * * * - Every 15 minutes
# 0 */6 * * * - Every 6 hours
# 0 3 * * 0 - Sunday at 3:00 AM
# 0 0 1 * * - First day of month at midnight
+161
View File
@@ -0,0 +1,161 @@
#!/bin/bash
# Restore Script for WorkRoot Website
# Restores from backup with safety checks
set -e
# Configuration
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
LOG_FILE="$PROJECT_ROOT/backups/restore.log"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log() {
echo "[$(date +"%Y-%m-%d %H:%M:%S")] $1" | tee -a "$LOG_FILE"
}
# Function to list available backups
list_backups() {
echo -e "${BLUE}Available Backups:${NC}\n"
echo -e "${YELLOW}Full Backups (Daily):${NC}"
ls -lht "$PROJECT_ROOT/backups/daily/"*.tar.gz 2>/dev/null | head -n 10 || echo " No daily backups found"
echo -e "\n${YELLOW}Weekly Backups:${NC}"
ls -lht "$PROJECT_ROOT/backups/weekly/"*.tar.gz 2>/dev/null | head -n 5 || echo " No weekly backups found"
echo -e "\n${YELLOW}Monthly Backups:${NC}"
ls -lht "$PROJECT_ROOT/backups/monthly/"*.tar.gz 2>/dev/null | head -n 3 || echo " No monthly backups found"
echo -e "\n${YELLOW}Config Backups (Pre-deploy):${NC}"
ls -lht "$PROJECT_ROOT/backups/pre-deploy/"*.tar.gz 2>/dev/null | head -n 5 || echo " No config backups found"
}
# Function to verify backup integrity
verify_backup() {
local backup_file="$1"
if [ ! -f "$backup_file" ]; then
echo -e "${RED}ERROR: Backup file not found: $backup_file${NC}"
return 1
fi
echo -e "${YELLOW}Verifying backup integrity...${NC}"
if tar -tzf "$backup_file" > /dev/null 2>&1; then
echo -e "${GREEN}✓ Backup integrity verified${NC}"
return 0
else
echo -e "${RED}✗ Backup is corrupted${NC}"
return 1
fi
}
# Function to create safety backup before restore
safety_backup() {
echo -e "${YELLOW}Creating safety backup of current state...${NC}"
SAFETY_DIR="$PROJECT_ROOT/backups/safety"
mkdir -p "$SAFETY_DIR"
SAFETY_FILE="$SAFETY_DIR/pre-restore-$(date +%Y-%m-%d_%H-%M-%S).tar.gz"
tar -czf "$SAFETY_FILE" \
--exclude=node_modules \
--exclude=dist \
--exclude=.astro \
--exclude=backups \
src/ public/ *.config.* package.json .env 2>/dev/null || true
echo -e "${GREEN}✓ Safety backup created: $SAFETY_FILE${NC}"
}
# Main restore function
restore_backup() {
local backup_file="$1"
local restore_path="${2:-$PROJECT_ROOT}"
log "${BLUE}Starting restore process...${NC}"
log "Backup file: $backup_file"
log "Restore path: $restore_path"
# Verify backup
verify_backup "$backup_file" || exit 1
# Show backup contents
echo -e "\n${YELLOW}Backup contains:${NC}"
tar -tzf "$backup_file" | head -n 20
echo "..."
# Confirm restore
echo -e "\n${RED}WARNING: This will overwrite current files!${NC}"
read -p "Continue with restore? (yes/no): " confirm
if [ "$confirm" != "yes" ]; then
echo "Restore cancelled"
exit 0
fi
# Create safety backup
safety_backup
# Extract backup
echo -e "\n${YELLOW}Extracting backup...${NC}"
cd "$restore_path"
tar -xzf "$backup_file" || {
echo -e "${RED}ERROR: Restore failed${NC}"
exit 1
}
echo -e "${GREEN}✓ Files extracted successfully${NC}"
# Verify critical files
echo -e "\n${YELLOW}Verifying restored files...${NC}"
CRITICAL_FILES=("src/" "package.json" "astro.config.mjs")
for file in "${CRITICAL_FILES[@]}"; do
if [ -e "$file" ]; then
echo -e " ${GREEN}${NC} $file"
else
echo -e " ${RED}${NC} $file (missing)"
fi
done
# Post-restore instructions
echo -e "\n${GREEN}=== Restore Completed ===${NC}"
echo -e "\n${YELLOW}Next steps:${NC}"
echo "1. Verify .env file and update secrets if needed"
echo "2. Run: npm install"
echo "3. Run: npm run build"
echo "4. Run: npm run test"
echo "5. Check logs for any issues"
log "${GREEN}Restore completed successfully${NC}"
}
# Main script logic
case "${1:-}" in
list|--list|-l)
list_backups
;;
verify|--verify|-v)
if [ -z "$2" ]; then
echo "Usage: $0 verify <backup-file>"
exit 1
fi
verify_backup "$2"
;;
*)
if [ -z "$1" ]; then
echo -e "${YELLOW}Usage:${NC}"
echo " $0 list - List available backups"
echo " $0 verify <backup-file> - Verify backup integrity"
echo " $0 <backup-file> - Restore from backup"
echo ""
echo -e "${YELLOW}Quick restore from latest:${NC}"
echo " $0 backups/daily/latest-full.tar.gz"
exit 1
fi
restore_backup "$1" "$2"
;;
esac
+72
View File
@@ -0,0 +1,72 @@
#!/bin/bash
# Backup System Setup Script
# One-time setup for the automated backup system
set -e
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
echo -e "${BLUE}=== WorkRoot Backup System Setup ===${NC}\n"
# Step 1: Create directory structure
echo -e "${YELLOW}Step 1: Creating backup directories...${NC}"
mkdir -p "$PROJECT_ROOT/backups"/{daily,weekly,monthly,pre-deploy,incremental,safety}
echo -e "${GREEN}✓ Directories created${NC}\n"
# Step 2: Make scripts executable
echo -e "${YELLOW}Step 2: Making scripts executable...${NC}"
chmod +x "$PROJECT_ROOT/scripts/backup"/*.sh
echo -e "${GREEN}✓ Scripts are now executable${NC}\n"
# Step 3: Test backup
echo -e "${YELLOW}Step 3: Running test backup...${NC}"
cd "$PROJECT_ROOT"
bash scripts/backup/backup-config.sh
echo -e "${GREEN}✓ Test backup completed${NC}\n"
# Step 4: Verify backup
echo -e "${YELLOW}Step 4: Verifying backup system...${NC}"
bash scripts/backup/verify.sh
echo ""
# Step 5: Instructions
echo -e "${BLUE}=== Setup Complete! ===${NC}\n"
echo -e "${GREEN}Next Steps:${NC}"
echo "1. Test full backup:"
echo -e " ${YELLOW}npm run backup:full${NC}\n"
echo "2. Set up automation (choose one):"
echo -e " ${YELLOW}Linux/macOS:${NC} Edit cron jobs"
echo " crontab -e"
echo " (See scripts/backup/cron.example for template)"
echo ""
echo -e " ${YELLOW}Windows:${NC} Run PowerShell as Administrator"
echo " .\\scripts\\backup\\windows-tasks.ps1"
echo ""
echo "3. Verify automated backups work:"
echo -e " ${YELLOW}npm run backup:verify${NC}\n"
echo -e "${GREEN}Available Commands:${NC}"
echo " npm run backup:full - Full backup"
echo " npm run backup:content - Content only"
echo " npm run backup:config - Config files only"
echo " npm run backup:verify - Check health"
echo " npm run backup:list - List backups"
echo " npm run backup:restore - Restore from backup"
echo " npm run backup:cleanup - Remove old backups"
echo ""
echo -e "${BLUE}Documentation:${NC}"
echo " scripts/backup/README.md"
echo " .agents/devops-engineer/BACKUP_STRATEGY.md"
echo ""
echo -e "${GREEN}Setup completed successfully!${NC}"
+177
View File
@@ -0,0 +1,177 @@
#!/bin/bash
# Backup Verification Script
# Checks backup health, integrity, and provides status report
set -e
# Configuration
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
BACKUP_ROOT="$PROJECT_ROOT/backups"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Counters
TOTAL_BACKUPS=0
VALID_BACKUPS=0
CORRUPTED_BACKUPS=0
WARNINGS=0
echo -e "${BLUE}=== Backup Verification Report ===${NC}\n"
echo "Generated: $(date)"
echo "Backup location: $BACKUP_ROOT"
echo ""
# Function to verify a single backup
verify_single_backup() {
local backup_file="$1"
TOTAL_BACKUPS=$((TOTAL_BACKUPS + 1))
if tar -tzf "$backup_file" > /dev/null 2>&1; then
VALID_BACKUPS=$((VALID_BACKUPS + 1))
return 0
else
CORRUPTED_BACKUPS=$((CORRUPTED_BACKUPS + 1))
echo -e " ${RED}✗ CORRUPTED: $backup_file${NC}"
return 1
fi
}
# Check daily backups
echo -e "${YELLOW}Daily Backups:${NC}"
if [ -d "$BACKUP_ROOT/daily" ]; then
DAILY_COUNT=$(ls "$BACKUP_ROOT/daily"/*.tar.gz 2>/dev/null | wc -l)
echo " Found: $DAILY_COUNT backups"
if [ "$DAILY_COUNT" -eq 0 ]; then
echo -e " ${RED}⚠ WARNING: No daily backups found${NC}"
WARNINGS=$((WARNINGS + 1))
else
# Check latest backup age
LATEST=$(ls -t "$BACKUP_ROOT/daily"/*.tar.gz 2>/dev/null | head -n 1)
AGE_HOURS=$(( ($(date +%s) - $(stat -c %Y "$LATEST" 2>/dev/null || stat -f %m "$LATEST")) / 3600 ))
echo " Latest backup: $(basename "$LATEST")"
echo " Age: ${AGE_HOURS} hours ago"
if [ "$AGE_HOURS" -gt 24 ]; then
echo -e " ${RED}⚠ WARNING: Latest backup is >24 hours old${NC}"
WARNINGS=$((WARNINGS + 1))
fi
# Verify integrity
for backup in "$BACKUP_ROOT/daily"/*.tar.gz; do
verify_single_backup "$backup" > /dev/null
done
echo -e " ${GREEN}✓ All daily backups verified${NC}"
fi
else
echo -e " ${RED}✗ Daily backup directory not found${NC}"
WARNINGS=$((WARNINGS + 1))
fi
echo ""
# Check weekly backups
echo -e "${YELLOW}Weekly Backups:${NC}"
if [ -d "$BACKUP_ROOT/weekly" ]; then
WEEKLY_COUNT=$(ls "$BACKUP_ROOT/weekly"/*.tar.gz 2>/dev/null | wc -l)
echo " Found: $WEEKLY_COUNT backups"
if ls "$BACKUP_ROOT/weekly"/*.tar.gz 1> /dev/null 2>&1; then
for backup in "$BACKUP_ROOT/weekly"/*.tar.gz; do
verify_single_backup "$backup" > /dev/null
done
fi
if [ "$WEEKLY_COUNT" -gt 0 ]; then
echo -e " ${GREEN}✓ All weekly backups verified${NC}"
fi
else
echo " No weekly backups directory"
fi
echo ""
# Check monthly backups
echo -e "${YELLOW}Monthly Backups:${NC}"
if [ -d "$BACKUP_ROOT/monthly" ]; then
MONTHLY_COUNT=$(ls "$BACKUP_ROOT/monthly"/*.tar.gz 2>/dev/null | wc -l)
echo " Found: $MONTHLY_COUNT backups"
if ls "$BACKUP_ROOT/monthly"/*.tar.gz 1> /dev/null 2>&1; then
for backup in "$BACKUP_ROOT/monthly"/*.tar.gz; do
verify_single_backup "$backup" > /dev/null
done
fi
if [ "$MONTHLY_COUNT" -gt 0 ]; then
echo -e " ${GREEN}✓ All monthly backups verified${NC}"
fi
else
echo " No monthly backups directory"
fi
echo ""
# Storage usage
echo -e "${YELLOW}Storage Usage:${NC}"
if [ -d "$BACKUP_ROOT" ]; then
TOTAL_SIZE=$(du -sh "$BACKUP_ROOT" 2>/dev/null | cut -f1)
echo " Total backup size: $TOTAL_SIZE"
# Check if approaching limits (>5GB)
SIZE_MB=$(du -sm "$BACKUP_ROOT" 2>/dev/null | cut -f1)
if [ "$SIZE_MB" -gt 5120 ]; then
echo -e " ${YELLOW}⚠ WARNING: Backup storage >5GB, consider cleanup${NC}"
WARNINGS=$((WARNINGS + 1))
fi
fi
echo ""
# Check disk space
echo -e "${YELLOW}Disk Space:${NC}"
DISK_USAGE=$(df -h "$BACKUP_ROOT" | tail -1)
DISK_PERCENT=$(echo "$DISK_USAGE" | awk '{print $5}' | sed 's/%//')
echo " Disk usage: $DISK_PERCENT%"
if [ "$DISK_PERCENT" -gt 80 ]; then
echo -e " ${RED}⚠ WARNING: Disk usage >80%${NC}"
WARNINGS=$((WARNINGS + 1))
fi
echo ""
# Summary
echo -e "${BLUE}=== Summary ===${NC}"
echo "Total backups checked: $TOTAL_BACKUPS"
echo -e "Valid backups: ${GREEN}$VALID_BACKUPS${NC}"
if [ "$CORRUPTED_BACKUPS" -gt 0 ]; then
echo -e "Corrupted backups: ${RED}$CORRUPTED_BACKUPS${NC}"
fi
if [ "$WARNINGS" -gt 0 ]; then
echo -e "Warnings: ${YELLOW}$WARNINGS${NC}"
fi
echo ""
# Final status
if [ "$CORRUPTED_BACKUPS" -eq 0 ] && [ "$WARNINGS" -eq 0 ] && [ "$VALID_BACKUPS" -gt 0 ]; then
echo -e "${GREEN}✓ Backup system healthy${NC}"
exit 0
elif [ "$CORRUPTED_BACKUPS" -gt 0 ]; then
echo -e "${RED}✗ CRITICAL: Corrupted backups detected${NC}"
exit 2
elif [ "$WARNINGS" -gt 0 ]; then
echo -e "${YELLOW}⚠ Backup system has warnings${NC}"
exit 1
else
echo -e "${YELLOW}⚠ No backups found - run initial backup${NC}"
exit 1
fi
+67
View File
@@ -0,0 +1,67 @@
# WorkRoot Backup Automation - Windows Task Scheduler Setup
#
# Run this PowerShell script as Administrator to set up automated backups on Windows
#
# Usage: .\windows-tasks.ps1
# Get the project root directory
$ProjectRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
$LogPath = Join-Path $ProjectRoot "backups"
Write-Host "Setting up Windows Task Scheduler for backups..." -ForegroundColor Cyan
Write-Host "Project root: $ProjectRoot" -ForegroundColor Yellow
# Ensure log directory exists
if (-not (Test-Path $LogPath)) {
New-Item -ItemType Directory -Path $LogPath -Force | Out-Null
}
# Daily Full Backup at 2:00 AM
Write-Host "`nCreating daily backup task..." -ForegroundColor Green
$DailyAction = New-ScheduledTaskAction -Execute "npm" -Argument "run backup:full" -WorkingDirectory $ProjectRoot
$DailyTrigger = New-ScheduledTaskTrigger -Daily -At 2am
$DailySettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask -TaskName "WorkRoot-DailyBackup" -Action $DailyAction -Trigger $DailyTrigger -Settings $DailySettings -Description "WorkRoot daily full backup" -Force | Out-Null
Write-Host "✓ Daily backup scheduled for 2:00 AM" -ForegroundColor Green
# Incremental Backup Every 6 Hours
Write-Host "`nCreating incremental backup task..." -ForegroundColor Green
$IncrementalAction = New-ScheduledTaskAction -Execute "npm" -Argument "run backup:content" -WorkingDirectory $ProjectRoot
$IncrementalTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Hours 6) -RepetitionDuration ([TimeSpan]::MaxValue)
Register-ScheduledTask -TaskName "WorkRoot-IncrementalBackup" -Action $IncrementalAction -Trigger $IncrementalTrigger -Settings $DailySettings -Description "WorkRoot incremental content backup" -Force | Out-Null
Write-Host "✓ Incremental backup scheduled every 6 hours" -ForegroundColor Green
# Weekly Cleanup on Sunday at 3:00 AM
Write-Host "`nCreating cleanup task..." -ForegroundColor Green
$CleanupAction = New-ScheduledTaskAction -Execute "npm" -Argument "run backup:cleanup" -WorkingDirectory $ProjectRoot
$CleanupTrigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -At 3am
Register-ScheduledTask -TaskName "WorkRoot-BackupCleanup" -Action $CleanupAction -Trigger $CleanupTrigger -Settings $DailySettings -Description "WorkRoot backup cleanup" -Force | Out-Null
Write-Host "✓ Cleanup scheduled for Sunday 3:00 AM" -ForegroundColor Green
# Daily Verification at 9:00 AM
Write-Host "`nCreating verification task..." -ForegroundColor Green
$VerifyAction = New-ScheduledTaskAction -Execute "npm" -Argument "run backup:verify" -WorkingDirectory $ProjectRoot
$VerifyTrigger = New-ScheduledTaskTrigger -Daily -At 9am
Register-ScheduledTask -TaskName "WorkRoot-BackupVerify" -Action $VerifyAction -Trigger $VerifyTrigger -Settings $DailySettings -Description "WorkRoot backup verification" -Force | Out-Null
Write-Host "✓ Verification scheduled for 9:00 AM daily" -ForegroundColor Green
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host "Backup automation setup complete!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "`nScheduled Tasks Created:" -ForegroundColor Yellow
Write-Host " • Daily Full Backup - Every day at 2:00 AM"
Write-Host " • Incremental Backup - Every 6 hours"
Write-Host " • Weekly Cleanup - Sunday at 3:00 AM"
Write-Host " • Daily Verification - Every day at 9:00 AM"
Write-Host "`nTo view scheduled tasks:" -ForegroundColor Yellow
Write-Host " Get-ScheduledTask | Where-Object {`$_.TaskName -like 'WorkRoot-*'}"
Write-Host "`nTo remove all tasks:" -ForegroundColor Yellow
Write-Host " Get-ScheduledTask | Where-Object {`$_.TaskName -like 'WorkRoot-*'} | Unregister-ScheduledTask -Confirm:`$false"
Write-Host "`nTo run a backup now (test):" -ForegroundColor Yellow
Write-Host " npm run backup:full"
Write-Host "`n" -ForegroundColor Green
+154
View File
@@ -0,0 +1,154 @@
#!/usr/bin/env python3
"""
Lighthouse Performance Audit Script
Runs Lighthouse audits on specified URLs and reports results
"""
import subprocess
import json
import sys
from pathlib import Path
def run_lighthouse(url, output_file=None):
"""Run Lighthouse audit on a URL"""
cmd = [
'npx', 'lighthouse', url,
'--output=json',
'--output-path=stdout',
'--chrome-flags="--headless --no-sandbox"',
'--quiet'
]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
if result.returncode != 0:
print(f"Error running Lighthouse on {url}: {result.stderr}")
return None
data = json.loads(result.stdout)
return data
except subprocess.TimeoutExpired:
print(f"Timeout running Lighthouse on {url}")
return None
except json.JSONDecodeError:
print(f"Failed to parse Lighthouse output for {url}")
return None
except Exception as e:
print(f"Unexpected error for {url}: {e}")
return None
def extract_scores(lighthouse_data):
"""Extract key scores from Lighthouse data"""
if not lighthouse_data or 'categories' not in lighthouse_data:
return None
categories = lighthouse_data['categories']
return {
'performance': int(categories.get('performance', {}).get('score', 0) * 100),
'accessibility': int(categories.get('accessibility', {}).get('score', 0) * 100),
'best-practices': int(categories.get('best-practices', {}).get('score', 0) * 100),
'seo': int(categories.get('seo', {}).get('score', 0) * 100),
}
def get_issues(lighthouse_data):
"""Extract issues from Lighthouse audit"""
if not lighthouse_data or 'audits' not in lighthouse_data:
return []
issues = []
audits = lighthouse_data['audits']
# Key audits to check
important_audits = [
'first-contentful-paint',
'largest-contentful-paint',
'cumulative-layout-shift',
'total-blocking-time',
'speed-index',
'interactive',
'image-alt',
'document-title',
'meta-description',
'link-text',
'crawlable-anchors',
'color-contrast',
'tap-targets',
'viewport',
'uses-responsive-images',
'modern-image-formats',
'offscreen-images',
'render-blocking-resources',
'unminified-css',
'unminified-javascript',
'unused-css-rules',
'unused-javascript',
]
for audit_id in important_audits:
if audit_id in audits:
audit = audits[audit_id]
if audit.get('score') is not None and audit['score'] < 0.9:
issues.append({
'id': audit_id,
'title': audit.get('title', audit_id),
'score': audit.get('score'),
'displayValue': audit.get('displayValue', ''),
'description': audit.get('description', ''),
})
return issues
def main():
if len(sys.argv) < 2:
print("Usage: python lighthouse_audit.py <url>")
sys.exit(1)
url = sys.argv[1]
print(f"\n🔍 Running Lighthouse audit on: {url}\n")
data = run_lighthouse(url)
if not data:
print("❌ Failed to run Lighthouse audit")
sys.exit(1)
scores = extract_scores(data)
if not scores:
print("❌ Failed to extract scores")
sys.exit(1)
# Print results
print("=" * 60)
print("LIGHTHOUSE SCORES")
print("=" * 60)
for category, score in scores.items():
emoji = "" if score >= 90 else "⚠️" if score >= 50 else ""
print(f"{emoji} {category.upper():20s}: {score:3d}/100")
print("\n" + "=" * 60)
print("ISSUES FOUND")
print("=" * 60 + "\n")
issues = get_issues(data)
if issues:
for issue in issues[:10]: # Show top 10 issues
score_str = f"{int(issue['score'] * 100)}/100" if issue['score'] is not None else "N/A"
print(f"⚠️ {issue['title']}")
print(f" Score: {score_str}")
if issue['displayValue']:
print(f" Value: {issue['displayValue']}")
print()
else:
print("✅ No major issues found!\n")
# Check if all scores >= 90
all_passed = all(score >= 90 for score in scores.values())
if all_passed:
print("🎉 All scores are 90+! Great job!\n")
sys.exit(0)
else:
print("⚠️ Some scores are below 90. Review issues above.\n")
sys.exit(1)
if __name__ == '__main__':
main()
+188
View File
@@ -0,0 +1,188 @@
#!/usr/bin/env python3
"""
Performance Test Script
Tests SSR response times and API endpoint performance
"""
import time
import requests
import statistics
import json
from typing import Dict, List, Tuple
# Test configuration
BASE_URL = "http://localhost:10000"
NUM_ITERATIONS = 10
TIMEOUT = 10
def measure_response_time(url: str, iterations: int = NUM_ITERATIONS) -> Tuple[List[float], bool]:
"""Measure response time for a URL over multiple iterations"""
times = []
success = True
for i in range(iterations):
try:
start = time.time()
response = requests.get(url, timeout=TIMEOUT)
end = time.time()
if response.status_code != 200:
print(f" Warning: {url} returned status {response.status_code}")
success = False
times.append((end - start) * 1000) # Convert to ms
time.sleep(0.1) # Small delay between requests
except Exception as e:
print(f" Error testing {url}: {e}")
success = False
return times, success
def print_stats(name: str, times: List[float]):
"""Print statistics for response times"""
if not times:
print(f"\n{name}: No data")
return
avg = statistics.mean(times)
median = statistics.median(times)
min_time = min(times)
max_time = max(times)
# Performance thresholds
status = "OK" if avg < 200 else "WARN" if avg < 500 else "SLOW"
print(f"\n{name}:")
print(f" Average: {avg:.2f}ms [{status}]")
print(f" Median: {median:.2f}ms")
print(f" Min: {min_time:.2f}ms")
print(f" Max: {max_time:.2f}ms")
if len(times) > 1:
stddev = statistics.stdev(times)
print(f" Std Dev: {stddev:.2f}ms")
def main():
print("=" * 60)
print("PERFORMANCE TEST REPORT")
print("=" * 60)
print(f"Base URL: {BASE_URL}")
print(f"Iterations per endpoint: {NUM_ITERATIONS}")
print()
# Define test endpoints
test_pages = {
"Homepage": "/",
"Case Studies": "/case-studies",
"Contact": "/contact",
"About": "/about",
}
test_apis = {
"Health Check": "/api/health.json",
"Sitemap": "/api/sitemap.xml",
}
all_passed = True
results = {}
# Test pages
print("\n" + "=" * 60)
print("TESTING SSR PAGES")
print("=" * 60)
for name, path in test_pages.items():
url = f"{BASE_URL}{path}"
print(f"\nTesting {name} ({url})...")
times, success = measure_response_time(url)
print_stats(name, times)
results[name] = times
all_passed = all_passed and success
# Test API endpoints
print("\n\n" + "=" * 60)
print("TESTING API ENDPOINTS")
print("=" * 60)
for name, path in test_apis.items():
url = f"{BASE_URL}{path}"
print(f"\nTesting {name} ({url})...")
times, success = measure_response_time(url)
print_stats(name, times)
results[name] = times
all_passed = all_passed and success
# Verify domain in sitemap
print("\n\n" + "=" * 60)
print("DOMAIN VERIFICATION")
print("=" * 60)
try:
response = requests.get(f"{BASE_URL}/api/sitemap.xml", timeout=TIMEOUT)
if "workroot.in" in response.text:
print("\nOK: Sitemap contains 'workroot.in'")
else:
print("\nWARN: Sitemap does not contain 'workroot.in'")
all_passed = False
if "workroot.com" in response.text:
print("ERROR: Sitemap still contains old domain 'workroot.com'")
all_passed = False
except Exception as e:
print(f"\nError checking sitemap: {e}")
all_passed = False
# Overall summary
print("\n\n" + "=" * 60)
print("PERFORMANCE SUMMARY")
print("=" * 60)
# Calculate average response time across all endpoints
all_times = []
for times in results.values():
all_times.extend(times)
if all_times:
overall_avg = statistics.mean(all_times)
print(f"\nOverall Average Response Time: {overall_avg:.2f}ms")
if overall_avg < 200:
print("Performance: EXCELLENT (< 200ms)")
elif overall_avg < 500:
print("Performance: GOOD (< 500ms)")
else:
print("Performance: NEEDS IMPROVEMENT (>= 500ms)")
# Recommendations
print("\n" + "=" * 60)
print("RECOMMENDATIONS")
print("=" * 60)
slow_endpoints = {name: statistics.mean(times)
for name, times in results.items()
if times and statistics.mean(times) > 500}
if slow_endpoints:
print("\nSlow endpoints detected (>500ms):")
for name, avg_time in slow_endpoints.items():
print(f" - {name}: {avg_time:.2f}ms")
print("\nConsider:")
print(" - Implementing caching")
print(" - Optimizing SSR hydration")
print(" - Reducing bundle size")
else:
print("\nAll endpoints performing well! No immediate optimizations needed.")
print("\n" + "=" * 60)
if all_passed:
print("RESULT: PASS")
print("=" * 60)
return 0
else:
print("RESULT: ISSUES DETECTED")
print("=" * 60)
return 1
if __name__ == "__main__":
exit(main())
+40
View File
@@ -0,0 +1,40 @@
#!/bin/bash
# Sitemap Ping Script
# Notifies search engines of sitemap updates after deployment
# Usage: ./scripts/ping-sitemap.sh
set -e
SITE_URL="https://workroot.in"
SITEMAP_URL="${SITE_URL}/sitemap.xml"
echo "🔔 Pinging search engines about sitemap update..."
echo "Sitemap: $SITEMAP_URL"
echo ""
# Google
echo "📍 Pinging Google..."
GOOGLE_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "https://www.google.com/ping?sitemap=${SITEMAP_URL}")
if [ "$GOOGLE_RESPONSE" = "200" ]; then
echo "✅ Google pinged successfully"
else
echo "⚠️ Google ping returned HTTP $GOOGLE_RESPONSE"
fi
# Bing
echo "📍 Pinging Bing..."
BING_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "https://www.bing.com/ping?sitemap=${SITEMAP_URL}")
if [ "$BING_RESPONSE" = "200" ]; then
echo "✅ Bing pinged successfully"
else
echo "⚠️ Bing ping returned HTTP $BING_RESPONSE"
fi
echo ""
echo "✨ Sitemap ping complete!"
echo ""
echo "Next steps:"
echo "1. Check Google Search Console for indexing status"
echo "2. Check Bing Webmaster Tools for crawl stats"
echo "3. Monitor coverage reports in 24-48 hours"
+51
View File
@@ -0,0 +1,51 @@
#!/bin/bash
# Quick Performance Check Script
# Tests key endpoints and reports response times
echo "============================================================"
echo "QUICK PERFORMANCE CHECK"
echo "============================================================"
echo ""
BASE_URL="${1:-http://localhost:10000}"
echo "Testing: $BASE_URL"
echo ""
# Test endpoints
ENDPOINTS=(
"/"
"/contact"
"/about"
"/api/health.json"
)
echo "Endpoint Performance:"
echo "------------------------------------------------------------"
for endpoint in "${ENDPOINTS[@]}"; do
url="${BASE_URL}${endpoint}"
# Run curl and capture timing
time_total=$(curl -w "%{time_total}" -o /dev/null -s "$url")
http_code=$(curl -w "%{http_code}" -o /dev/null -s "$url")
# Convert to milliseconds
time_ms=$(echo "$time_total * 1000" | bc)
# Determine status
if (( $(echo "$time_total < 0.2" | bc -l) )); then
status="EXCELLENT"
elif (( $(echo "$time_total < 0.5" | bc -l) )); then
status="GOOD"
else
status="SLOW"
fi
printf "%-30s %6.2fms [%s] (HTTP %s)\n" "$endpoint" "$time_ms" "$status" "$http_code"
done
echo ""
echo "============================================================"
echo "Performance Target: < 500ms"
echo "Optimal: < 200ms"
echo "============================================================"
+146
View File
@@ -0,0 +1,146 @@
#!/usr/bin/env bash
# =============================================================================
# UptimeRobot Monitor Setup Script
# Usage: UPTIMEROBOT_API_KEY=your_key ./scripts/setup-uptimerobot.sh
#
# Creates monitors for:
# - Main site health check (every 5 min)
# - Homepage
# - API health endpoint
# - SSL certificate expiry alert
# =============================================================================
set -euo pipefail
API_KEY="${UPTIMEROBOT_API_KEY:-}"
ALERT_EMAIL="${ALERT_EMAIL:-}"
BASE_URL="https://workroot.in"
API_BASE="https://api.uptimerobot.com/v2"
# ── Validation ────────────────────────────────────────────────────────────────
if [ -z "$API_KEY" ]; then
echo "ERROR: UPTIMEROBOT_API_KEY environment variable is required"
echo "Get your API key from: https://uptimerobot.com/dashboard → My Settings → API Settings"
exit 1
fi
# ── Helper function ───────────────────────────────────────────────────────────
uptimerobot_api() {
local endpoint="$1"
shift
curl -s -X POST "${API_BASE}/${endpoint}" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "api_key=${API_KEY}&format=json&$*"
}
# ── Get or create alert contact ───────────────────────────────────────────────
get_alert_contact_id() {
if [ -z "$ALERT_EMAIL" ]; then
# Use first existing contact
CONTACTS=$(uptimerobot_api "getAlertContacts")
CONTACT_ID=$(echo "$CONTACTS" | python3 -c "
import sys, json
data = json.load(sys.stdin)
contacts = data.get('alert_contacts', [])
if contacts:
print(contacts[0]['id'])
" 2>/dev/null || echo "")
echo "$CONTACT_ID"
return
fi
# Create new contact for the email
RESULT=$(uptimerobot_api "newAlertContact" \
"type=2&value=${ALERT_EMAIL}&friendly_name=WorkRoot+Alerts")
CONTACT_ID=$(echo "$RESULT" | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(data.get('alertcontact', {}).get('id', ''))
" 2>/dev/null || echo "")
echo "$CONTACT_ID"
}
# ── Create monitor ────────────────────────────────────────────────────────────
create_monitor() {
local name="$1"
local url="$2"
local type="${3:-1}" # 1=HTTP, 2=keyword, 3=ping
local interval="${4:-300}" # seconds
local alert_id="$5"
local keyword="${6:-}"
echo "Creating monitor: $name$url"
local data="friendly_name=${name}&url=${url}&type=${type}&interval=${interval}"
if [ -n "$alert_id" ]; then
data="${data}&alert_contacts=${alert_id}_0_0"
fi
if [ "$type" = "2" ] && [ -n "$keyword" ]; then
data="${data}&keyword_type=1&keyword_value=${keyword}"
fi
RESULT=$(uptimerobot_api "newMonitor" "$data")
STATUS=$(echo "$RESULT" | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(data.get('stat', 'fail'))
" 2>/dev/null || echo "fail")
if [ "$STATUS" = "ok" ]; then
MONITOR_ID=$(echo "$RESULT" | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(data.get('monitor', {}).get('id', ''))
" 2>/dev/null || echo "")
echo " Created monitor ID: $MONITOR_ID"
else
ERROR=$(echo "$RESULT" | python3 -c "
import sys, json
data = json.load(sys.stdin)
err = data.get('error', {})
print(err.get('message', 'unknown error'))
" 2>/dev/null || echo "unknown")
echo " WARNING: Failed to create monitor - $ERROR"
fi
}
# ── Main ──────────────────────────────────────────────────────────────────────
echo "============================================"
echo " WorkRoot UptimeRobot Monitor Setup"
echo "============================================"
echo ""
echo "Getting alert contact..."
ALERT_ID=$(get_alert_contact_id)
if [ -z "$ALERT_ID" ]; then
echo "WARNING: No alert contact found/created. Monitors will be created without alerts."
echo "Add an alert contact at: https://uptimerobot.com/dashboard → Alert Contacts"
fi
echo "Alert contact ID: ${ALERT_ID:-none}"
echo ""
# HTTP monitors (every 5 minutes)
create_monitor "WorkRoot - Health Check" "${BASE_URL}/api/health.json" "1" "300" "$ALERT_ID"
create_monitor "WorkRoot - Homepage" "${BASE_URL}/" "1" "300" "$ALERT_ID"
create_monitor "WorkRoot - Services Page" "${BASE_URL}/services" "1" "300" "$ALERT_ID"
create_monitor "WorkRoot - Contact Page" "${BASE_URL}/contact" "1" "300" "$ALERT_ID"
# Keyword monitor - verify health endpoint returns "ok" status
create_monitor "WorkRoot - Health Status OK" "${BASE_URL}/api/health.json" "2" "300" "$ALERT_ID" "\"status\":\"ok\""
# SSL certificate monitor (UptimeRobot checks SSL with HTTP monitors, but
# we add explicit keyword check for https redirect)
create_monitor "WorkRoot - SSL/HTTPS" "https://workroot.in/" "1" "300" "$ALERT_ID"
echo ""
echo "============================================"
echo " Setup complete!"
echo ""
echo "Next steps:"
echo " 1. Login to UptimeRobot: https://uptimerobot.com/dashboard"
echo " 2. Enable SSL certificate expiry alerts for each HTTPS monitor"
echo " Monitor → Edit → Enable SSL monitoring → Set alert threshold to 14 days"
echo " 3. Configure response time alert threshold:"
echo " Monitor → Edit → Response Time Threshold → 3000ms"
echo " 4. Set up status page: Dashboard → Status Pages → Create"
echo "============================================"
+82
View File
@@ -0,0 +1,82 @@
#!/bin/bash
# Sitemap Testing Script
# Validates sitemap.xml structure and content
# Usage: ./scripts/test-sitemap.sh
set -e
SITEMAP_URL="http://localhost:10000/sitemap.xml"
PROD_SITEMAP_URL="https://workroot.in/sitemap.xml"
echo "🧪 Testing Sitemap..."
echo ""
# Test 1: Sitemap accessible
echo "1️⃣ Testing sitemap accessibility..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$SITEMAP_URL")
if [ "$HTTP_CODE" = "200" ]; then
echo " ✅ Sitemap is accessible (HTTP $HTTP_CODE)"
else
echo " ❌ Sitemap returned HTTP $HTTP_CODE"
exit 1
fi
# Test 2: Valid XML
echo "2️⃣ Testing XML validity..."
if curl -s "$SITEMAP_URL" | xmllint --noout - 2>/dev/null; then
echo " ✅ Sitemap is valid XML"
else
echo " ⚠️ XML validation failed (xmllint not installed or XML invalid)"
fi
# Test 3: Count URLs
echo "3️⃣ Counting URLs..."
URL_COUNT=$(curl -s "$SITEMAP_URL" | grep -c '<loc>' || echo "0")
echo " 📊 Found $URL_COUNT URLs in sitemap"
# Test 4: Check for blog posts
echo "4️⃣ Checking for blog posts..."
BLOG_COUNT=$(curl -s "$SITEMAP_URL" | grep -c 'workroot.in/blog/[^<]*</loc>' || echo "0")
if [ "$BLOG_COUNT" -gt 0 ]; then
echo " ✅ Found $BLOG_COUNT blog posts"
else
echo " ⚠️ No blog posts found (might be normal if no published posts)"
fi
# Test 5: Verify domain
echo "5️⃣ Verifying domain..."
if curl -s "$SITEMAP_URL" | grep -q 'workroot.in'; then
echo " ✅ Correct domain (workroot.in)"
else
echo " ❌ Wrong domain found in sitemap"
fi
# Test 6: Check lastmod dates
echo "6️⃣ Checking lastmod dates..."
LASTMOD_COUNT=$(curl -s "$SITEMAP_URL" | grep -c '<lastmod>' || echo "0")
if [ "$LASTMOD_COUNT" -gt 0 ]; then
echo " ✅ Found $LASTMOD_COUNT lastmod dates"
else
echo " ⚠️ No lastmod dates found"
fi
# Test 7: Verify robots.txt reference
echo "7️⃣ Checking robots.txt..."
if curl -s "http://localhost:10000/robots.txt" | grep -q 'Sitemap:'; then
echo " ✅ Sitemap referenced in robots.txt"
else
echo " ❌ Sitemap not in robots.txt"
fi
echo ""
echo "📋 Summary:"
echo " Total URLs: $URL_COUNT"
echo " Blog posts: $BLOG_COUNT"
echo " Lastmod dates: $LASTMOD_COUNT"
echo ""
echo "✨ Sitemap test complete!"
echo ""
echo "To view sitemap:"
echo " Local: curl $SITEMAP_URL"
echo " Prod: curl $PROD_SITEMAP_URL"
+341
View File
@@ -0,0 +1,341 @@
#!/usr/bin/env node
/**
* Schema Validation Script
*
* Validates JSON-LD structured data on all pages
* Checks for common issues and provides recommendations
*
* Usage: node scripts/validate-schema.js
*/
import { JSDOM } from 'jsdom';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const COLORS = {
reset: '\x1b[0m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
cyan: '\x1b[36m',
};
const log = {
success: (msg) => console.log(`${COLORS.green}${COLORS.reset} ${msg}`),
error: (msg) => console.log(`${COLORS.red}${COLORS.reset} ${msg}`),
warning: (msg) => console.log(`${COLORS.yellow}${COLORS.reset} ${msg}`),
info: (msg) => console.log(`${COLORS.blue}${COLORS.reset} ${msg}`),
section: (msg) => console.log(`\n${COLORS.cyan}${msg}${COLORS.reset}\n`),
};
/**
* Extract JSON-LD scripts from HTML
*/
function extractJsonLd(html) {
const dom = new JSDOM(html);
const scripts = dom.window.document.querySelectorAll('script[type="application/ld+json"]');
return Array.from(scripts).map(script => {
try {
return JSON.parse(script.textContent);
} catch (e) {
log.error(`Failed to parse JSON-LD: ${e.message}`);
return null;
}
}).filter(Boolean);
}
/**
* Validate required fields for each schema type
*/
function validateSchema(schema) {
const issues = [];
const warnings = [];
if (!schema['@context']) {
issues.push('Missing @context');
}
if (!schema['@type']) {
issues.push('Missing @type');
}
const type = schema['@type'];
// Validate based on type
switch (type) {
case 'Organization':
if (!schema.name) issues.push('Organization: Missing name');
if (!schema.url) issues.push('Organization: Missing url');
if (!schema.logo) warnings.push('Organization: Consider adding logo');
if (!schema.contactPoint) warnings.push('Organization: Consider adding contactPoint');
break;
case 'BlogPosting':
if (!schema.headline) issues.push('BlogPosting: Missing headline');
if (!schema.author) issues.push('BlogPosting: Missing author');
if (!schema.datePublished) issues.push('BlogPosting: Missing datePublished');
if (!schema.publisher) issues.push('BlogPosting: Missing publisher');
if (!schema.image) warnings.push('BlogPosting: Consider adding image');
break;
case 'BreadcrumbList':
if (!schema.itemListElement || !Array.isArray(schema.itemListElement)) {
issues.push('BreadcrumbList: Missing or invalid itemListElement');
} else {
schema.itemListElement.forEach((item, idx) => {
if (!item.position) issues.push(`BreadcrumbList: Item ${idx} missing position`);
if (!item.name) issues.push(`BreadcrumbList: Item ${idx} missing name`);
if (!item.item) issues.push(`BreadcrumbList: Item ${idx} missing item URL`);
});
}
break;
case 'WebPage':
if (!schema.name) issues.push('WebPage: Missing name');
if (!schema.url) issues.push('WebPage: Missing url');
if (!schema.description) warnings.push('WebPage: Consider adding description');
break;
case 'FAQPage':
if (!schema.mainEntity || !Array.isArray(schema.mainEntity)) {
issues.push('FAQPage: Missing or invalid mainEntity');
} else {
schema.mainEntity.forEach((item, idx) => {
if (item['@type'] !== 'Question') {
issues.push(`FAQPage: Item ${idx} should be type Question`);
}
if (!item.name) issues.push(`FAQPage: Question ${idx} missing name`);
if (!item.acceptedAnswer) {
issues.push(`FAQPage: Question ${idx} missing acceptedAnswer`);
}
});
}
break;
case 'Service':
if (!schema.name) issues.push('Service: Missing name');
if (!schema.description) issues.push('Service: Missing description');
if (!schema.provider) warnings.push('Service: Consider adding provider');
break;
case 'WebSite':
if (!schema.name) issues.push('WebSite: Missing name');
if (!schema.url) issues.push('WebSite: Missing url');
if (!schema.potentialAction) warnings.push('WebSite: Consider adding search action');
break;
}
return { issues, warnings };
}
/**
* Validate Open Graph tags
*/
function validateOpenGraph(html) {
const dom = new JSDOM(html);
const doc = dom.window.document;
const issues = [];
const warnings = [];
const requiredOgTags = ['og:title', 'og:description', 'og:image', 'og:url', 'og:type'];
requiredOgTags.forEach(tag => {
const meta = doc.querySelector(`meta[property="${tag}"]`);
if (!meta) {
issues.push(`Missing required Open Graph tag: ${tag}`);
}
});
const ogImage = doc.querySelector('meta[property="og:image"]');
if (ogImage) {
const imageUrl = ogImage.getAttribute('content');
if (!imageUrl.startsWith('http')) {
warnings.push('og:image should be an absolute URL');
}
if (!doc.querySelector('meta[property="og:image:width"]')) {
warnings.push('Consider adding og:image:width');
}
if (!doc.querySelector('meta[property="og:image:height"]')) {
warnings.push('Consider adding og:image:height');
}
}
return { issues, warnings };
}
/**
* Validate Twitter Card tags
*/
function validateTwitterCard(html) {
const dom = new JSDOM(html);
const doc = dom.window.document;
const issues = [];
const warnings = [];
const requiredTwitterTags = ['twitter:card', 'twitter:title', 'twitter:description', 'twitter:image'];
requiredTwitterTags.forEach(tag => {
const meta = doc.querySelector(`meta[name="${tag}"]`);
if (!meta) {
issues.push(`Missing required Twitter Card tag: ${tag}`);
}
});
const twitterCard = doc.querySelector('meta[name="twitter:card"]');
if (twitterCard && twitterCard.getAttribute('content') === 'summary_large_image') {
const twitterImage = doc.querySelector('meta[name="twitter:image"]');
if (twitterImage) {
const imageUrl = twitterImage.getAttribute('content');
if (!imageUrl.startsWith('http')) {
warnings.push('twitter:image should be an absolute URL');
}
}
}
return { issues, warnings };
}
/**
* Main validation function
*/
async function validatePage(filePath, pageName) {
log.section(`Validating: ${pageName}`);
try {
const html = fs.readFileSync(filePath, 'utf-8');
// Extract and validate JSON-LD
const schemas = extractJsonLd(html);
if (schemas.length === 0) {
log.warning('No JSON-LD structured data found');
} else {
log.info(`Found ${schemas.length} JSON-LD schema(s)`);
schemas.forEach((schema, idx) => {
console.log(`\n Schema ${idx + 1}: ${schema['@type'] || 'Unknown'}`);
const { issues, warnings } = validateSchema(schema);
if (issues.length === 0) {
log.success('No critical issues');
} else {
issues.forEach(issue => log.error(` ${issue}`));
}
if (warnings.length > 0) {
warnings.forEach(warning => log.warning(` ${warning}`));
}
});
}
// Validate Open Graph
console.log('\n Open Graph:');
const ogValidation = validateOpenGraph(html);
if (ogValidation.issues.length === 0) {
log.success('All required Open Graph tags present');
} else {
ogValidation.issues.forEach(issue => log.error(` ${issue}`));
}
if (ogValidation.warnings.length > 0) {
ogValidation.warnings.forEach(warning => log.warning(` ${warning}`));
}
// Validate Twitter Cards
console.log('\n Twitter Card:');
const twitterValidation = validateTwitterCard(html);
if (twitterValidation.issues.length === 0) {
log.success('All required Twitter Card tags present');
} else {
twitterValidation.issues.forEach(issue => log.error(` ${issue}`));
}
if (twitterValidation.warnings.length > 0) {
twitterValidation.warnings.forEach(warning => log.warning(` ${warning}`));
}
return {
schemas: schemas.length,
issues: ogValidation.issues.length + twitterValidation.issues.length,
warnings: ogValidation.warnings.length + twitterValidation.warnings.length,
};
} catch (error) {
log.error(`Failed to validate ${pageName}: ${error.message}`);
return { schemas: 0, issues: 1, warnings: 0 };
}
}
/**
* Main execution
*/
async function main() {
console.log('\n╔════════════════════════════════════════════════════════╗');
console.log('║ Schema Validation for WorkRoot IT Solutions ║');
console.log('╚════════════════════════════════════════════════════════╝\n');
const distDir = path.join(__dirname, '..', 'dist');
if (!fs.existsSync(distDir)) {
log.error('Build directory not found. Please run "npm run build" first.');
process.exit(1);
}
// Pages to validate
const pages = [
{ path: path.join(distDir, 'index.html'), name: 'Homepage' },
{ path: path.join(distDir, 'about', 'index.html'), name: 'About' },
{ path: path.join(distDir, 'services', 'index.html'), name: 'Services' },
{ path: path.join(distDir, 'contact', 'index.html'), name: 'Contact' },
{ path: path.join(distDir, 'blog', 'index.html'), name: 'Blog Index' },
];
// Find a blog post to validate
const blogDir = path.join(distDir, 'blog');
if (fs.existsSync(blogDir)) {
const blogPosts = fs.readdirSync(blogDir)
.filter(file => fs.statSync(path.join(blogDir, file)).isDirectory());
if (blogPosts.length > 0) {
pages.push({
path: path.join(blogDir, blogPosts[0], 'index.html'),
name: `Blog Post (${blogPosts[0]})`
});
}
}
let totalSchemas = 0;
let totalIssues = 0;
let totalWarnings = 0;
for (const page of pages) {
if (fs.existsSync(page.path)) {
const result = await validatePage(page.path, page.name);
totalSchemas += result.schemas;
totalIssues += result.issues;
totalWarnings += result.warnings;
} else {
log.warning(`Page not found: ${page.name}`);
}
}
// Summary
log.section('Validation Summary');
console.log(`Total schemas found: ${COLORS.cyan}${totalSchemas}${COLORS.reset}`);
console.log(`Total issues: ${totalIssues > 0 ? COLORS.red : COLORS.green}${totalIssues}${COLORS.reset}`);
console.log(`Total warnings: ${totalWarnings > 0 ? COLORS.yellow : COLORS.green}${totalWarnings}${COLORS.reset}`);
if (totalIssues === 0) {
log.success('\nAll validations passed! 🎉');
} else {
log.error('\nPlease fix the issues above before deployment.');
}
console.log('\n');
process.exit(totalIssues > 0 ? 1 : 0);
}
main();