Initial commit
This commit is contained in:
11
.claude-plugin/plugin.json
Normal file
11
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "deployment",
|
||||
"description": "Cloudflare Workers and Pages deployment with integrated debugging and troubleshooting chains",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Grey Haven Studio"
|
||||
},
|
||||
"skills": [
|
||||
"./skills/deployment-cloudflare"
|
||||
]
|
||||
}
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# deployment
|
||||
|
||||
Cloudflare Workers and Pages deployment with integrated debugging and troubleshooting chains
|
||||
69
plugin.lock.json
Normal file
69
plugin.lock.json
Normal file
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||
"pluginId": "gh:greyhaven-ai/claude-code-config:grey-haven-plugins/deployment",
|
||||
"normalized": {
|
||||
"repo": null,
|
||||
"ref": "refs/tags/v20251128.0",
|
||||
"commit": "d968ce3992c04fbe1017e52826b17ef053439784",
|
||||
"treeHash": "acdc75a68c6f0ff0bec92578b029945776afbe354a669d6c82c981c1af96eeee",
|
||||
"generatedAt": "2025-11-28T10:17:04.293548Z",
|
||||
"toolVersion": "publish_plugins.py@0.2.0"
|
||||
},
|
||||
"origin": {
|
||||
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||
"branch": "master",
|
||||
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||
},
|
||||
"manifest": {
|
||||
"name": "deployment",
|
||||
"description": "Cloudflare Workers and Pages deployment with integrated debugging and troubleshooting chains",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"content": {
|
||||
"files": [
|
||||
{
|
||||
"path": "README.md",
|
||||
"sha256": "375fffa8ad704c8a21a397ac730d70af51d3f72ad5f361029101821fed787b61"
|
||||
},
|
||||
{
|
||||
"path": ".claude-plugin/plugin.json",
|
||||
"sha256": "0f8fc3e95b33b5efb1455b86e819090a23f181e4a6fac61df753dbca001c508c"
|
||||
},
|
||||
{
|
||||
"path": "skills/deployment-cloudflare/SKILL.md",
|
||||
"sha256": "3bea75b9d6331ad7d605ade33981bd991e36671530ef5f813effced5b33e6a14"
|
||||
},
|
||||
{
|
||||
"path": "skills/deployment-cloudflare/checklists/deployment-checklist.md",
|
||||
"sha256": "5f7ea4265cedc70764c7559a06db526a3c58245b86bc84ad957365cc2c272e10"
|
||||
},
|
||||
{
|
||||
"path": "skills/deployment-cloudflare/examples/INDEX.md",
|
||||
"sha256": "c18993e7fa3e2d96fe6d0249c6096e82bb465e8fe3345a14fce361364cd9cf8f"
|
||||
},
|
||||
{
|
||||
"path": "skills/deployment-cloudflare/scripts/deploy.py",
|
||||
"sha256": "b33b4972ff81999eeb7a63d56141bbc1ee528d95de6d713ca49bd1ce85446dfe"
|
||||
},
|
||||
{
|
||||
"path": "skills/deployment-cloudflare/scripts/rollback.py",
|
||||
"sha256": "b98e96e02422580dd636b28cbda1349a2b61f0933fe0add2a6c00b484862e7ee"
|
||||
},
|
||||
{
|
||||
"path": "skills/deployment-cloudflare/scripts/migrate.py",
|
||||
"sha256": "34c466de7d9225ef97020e1b27b73e29e3078b34037c0c100c3119782ceb7286"
|
||||
},
|
||||
{
|
||||
"path": "skills/deployment-cloudflare/reference/INDEX.md",
|
||||
"sha256": "6102aff62f2b938f6da2b67bcb5696ea306573979d3692bdd7175f17675f01e6"
|
||||
}
|
||||
],
|
||||
"dirSha256": "acdc75a68c6f0ff0bec92578b029945776afbe354a669d6c82c981c1af96eeee"
|
||||
},
|
||||
"security": {
|
||||
"scannedAt": null,
|
||||
"scannerVersion": null,
|
||||
"flags": []
|
||||
}
|
||||
}
|
||||
317
skills/deployment-cloudflare/SKILL.md
Normal file
317
skills/deployment-cloudflare/SKILL.md
Normal file
@@ -0,0 +1,317 @@
|
||||
---
|
||||
name: grey-haven-deployment-cloudflare
|
||||
description: Deploy TanStack Start applications to Cloudflare Workers/Pages with GitHub Actions, Doppler, Wrangler, database migrations, and rollback procedures. Use when deploying Grey Haven applications.
|
||||
---
|
||||
|
||||
# Grey Haven Cloudflare Deployment
|
||||
|
||||
Deploy **TanStack Start** applications to Cloudflare Workers using GitHub Actions, Doppler for secrets, and Wrangler CLI.
|
||||
|
||||
## Deployment Architecture
|
||||
|
||||
### TanStack Start on Cloudflare Workers
|
||||
- **SSR**: Server-side rendering with TanStack Start server functions
|
||||
- **Edge Runtime**: Global deployment on Cloudflare's edge network
|
||||
- **Database**: PostgreSQL (PlanetScale) with connection pooling
|
||||
- **Cache**: Cloudflare KV for sessions, R2 for file uploads
|
||||
- **Secrets**: Managed via Doppler, injected in GitHub Actions
|
||||
|
||||
### Core Infrastructure
|
||||
- **Workers**: Edge compute (TanStack Start)
|
||||
- **KV Storage**: Session management
|
||||
- **R2 Storage**: File uploads and assets
|
||||
- **D1 Database**: Edge data (optional)
|
||||
- **Queues**: Background jobs (optional)
|
||||
|
||||
## Wrangler Configuration
|
||||
|
||||
### Basic `wrangler.toml`
|
||||
```toml
|
||||
name = "grey-haven-app"
|
||||
main = "dist/server/index.js"
|
||||
compatibility_date = "2025-01-15"
|
||||
node_compat = true
|
||||
|
||||
[vars]
|
||||
ENVIRONMENT = "production"
|
||||
DATABASE_POOL_MIN = "2"
|
||||
DATABASE_POOL_MAX = "10"
|
||||
|
||||
# KV namespace for session storage
|
||||
[[kv_namespaces]]
|
||||
binding = "SESSIONS"
|
||||
id = "your-kv-namespace-id"
|
||||
preview_id = "your-preview-kv-namespace-id"
|
||||
|
||||
# R2 bucket for file uploads
|
||||
[[r2_buckets]]
|
||||
binding = "UPLOADS"
|
||||
bucket_name = "grey-haven-uploads"
|
||||
preview_bucket_name = "grey-haven-uploads-preview"
|
||||
|
||||
# Routes
|
||||
routes = [
|
||||
{ pattern = "app.greyhaven.studio", zone_name = "greyhaven.studio" }
|
||||
]
|
||||
```
|
||||
|
||||
### Environment-Specific Configs
|
||||
- **Development**: `wrangler.toml` with `ENVIRONMENT = "development"`
|
||||
- **Staging**: `wrangler.staging.toml` with staging routes
|
||||
- **Production**: `wrangler.production.toml` with production routes
|
||||
|
||||
## Doppler Integration
|
||||
|
||||
### Required GitHub Secrets
|
||||
- `DOPPLER_TOKEN`: Doppler service token for CI/CD
|
||||
- `CLOUDFLARE_API_TOKEN`: Wrangler deployment token
|
||||
|
||||
### Required Doppler Secrets (Production)
|
||||
```bash
|
||||
# Application
|
||||
BETTER_AUTH_SECRET=<random-secret>
|
||||
BETTER_AUTH_URL=https://app.greyhaven.studio
|
||||
JWT_SECRET_KEY=<random-secret>
|
||||
|
||||
# Database (PlanetScale)
|
||||
DATABASE_URL=postgresql://user:pass@host/db
|
||||
DATABASE_URL_ADMIN=postgresql://admin:pass@host/db
|
||||
|
||||
# Redis (Upstash)
|
||||
REDIS_URL=redis://user:pass@host:port
|
||||
|
||||
# Email (Resend)
|
||||
RESEND_API_KEY=re_...
|
||||
|
||||
# OAuth Providers
|
||||
GOOGLE_CLIENT_ID=...
|
||||
GOOGLE_CLIENT_SECRET=...
|
||||
GITHUB_CLIENT_ID=...
|
||||
GITHUB_CLIENT_SECRET=...
|
||||
|
||||
# Cloudflare
|
||||
CLOUDFLARE_ACCOUNT_ID=...
|
||||
CLOUDFLARE_API_TOKEN=...
|
||||
|
||||
# Monitoring (optional)
|
||||
SENTRY_DSN=https://...@sentry.io/...
|
||||
AXIOM_TOKEN=xaat-...
|
||||
```
|
||||
|
||||
## GitHub Actions Deployment
|
||||
|
||||
### Production Deployment Flow
|
||||
```yaml
|
||||
# .github/workflows/deploy-production.yml
|
||||
- Checkout code
|
||||
- Setup Node.js 22 with cache
|
||||
- Install dependencies (npm ci)
|
||||
- Install Doppler CLI
|
||||
- Run tests (doppler run --config test)
|
||||
- Build (doppler run --config production)
|
||||
- Run database migrations
|
||||
- Deploy to Cloudflare Workers
|
||||
- Inject secrets from Doppler
|
||||
- Run smoke tests
|
||||
- Rollback on failure
|
||||
```
|
||||
|
||||
### Key Deployment Commands
|
||||
```bash
|
||||
# Build with Doppler secrets
|
||||
doppler run --config production -- npm run build
|
||||
|
||||
# Run migrations before deployment
|
||||
doppler run --config production -- npm run db:migrate
|
||||
|
||||
# Deploy to Cloudflare
|
||||
npx wrangler deploy --config wrangler.production.toml
|
||||
|
||||
# Inject secrets into Workers
|
||||
doppler secrets download --config production --format json > secrets.json
|
||||
cat secrets.json | jq -r 'to_entries | .[] | "\(.key)=\(.value)"' | while read -r line; do
|
||||
key=$(echo "$line" | cut -d= -f1)
|
||||
value=$(echo "$line" | cut -d= -f2-)
|
||||
echo "$value" | npx wrangler secret put "$key"
|
||||
done
|
||||
rm secrets.json
|
||||
```
|
||||
|
||||
## Database Migrations
|
||||
|
||||
### Drizzle Migrations (TanStack Start)
|
||||
```typescript
|
||||
// scripts/migrate.ts
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
||||
import { Pool } from "pg";
|
||||
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL_ADMIN,
|
||||
});
|
||||
|
||||
const db = drizzle(pool);
|
||||
|
||||
async function main() {
|
||||
console.log("Running migrations...");
|
||||
await migrate(db, { migrationsFolder: "./drizzle/migrations" });
|
||||
console.log("Migrations complete!");
|
||||
await pool.end();
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("Migration failed:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
```
|
||||
|
||||
**package.json scripts**:
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"db:migrate": "tsx scripts/migrate.ts",
|
||||
"db:migrate:production": "doppler run --config production -- tsx scripts/migrate.ts"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Rollback Procedures
|
||||
|
||||
### Wrangler Rollback
|
||||
```bash
|
||||
# List recent deployments
|
||||
npx wrangler deployments list --config wrangler.production.toml
|
||||
|
||||
# Rollback to previous deployment
|
||||
npx wrangler rollback --config wrangler.production.toml
|
||||
|
||||
# Rollback to specific deployment ID
|
||||
npx wrangler rollback --deployment-id abc123 --config wrangler.production.toml
|
||||
```
|
||||
|
||||
### Database Rollback
|
||||
```bash
|
||||
# Drizzle - rollback last migration
|
||||
doppler run --config production -- drizzle-kit migrate:rollback
|
||||
|
||||
# Alembic - rollback one migration
|
||||
doppler run --config production -- alembic downgrade -1
|
||||
```
|
||||
|
||||
### Emergency Rollback Playbook
|
||||
1. **Identify issue**: Check Cloudflare Workers logs, Sentry
|
||||
2. **Rollback Workers**: `npx wrangler rollback`
|
||||
3. **Rollback database** (if needed): `drizzle-kit migrate:rollback`
|
||||
4. **Verify rollback**: Run smoke tests
|
||||
5. **Notify team**: Update Linear issue
|
||||
6. **Root cause analysis**: Create postmortem
|
||||
|
||||
## Cloudflare Resources Setup
|
||||
|
||||
### KV Namespace (Session Storage)
|
||||
```bash
|
||||
# Create KV namespace
|
||||
npx wrangler kv:namespace create "SESSIONS" --config wrangler.production.toml
|
||||
npx wrangler kv:namespace create "SESSIONS" --preview --config wrangler.production.toml
|
||||
|
||||
# List KV namespaces
|
||||
npx wrangler kv:namespace list
|
||||
```
|
||||
|
||||
### R2 Bucket (File Uploads)
|
||||
```bash
|
||||
# Create R2 bucket
|
||||
npx wrangler r2 bucket create grey-haven-uploads
|
||||
npx wrangler r2 bucket create grey-haven-uploads-preview
|
||||
|
||||
# List R2 buckets
|
||||
npx wrangler r2 bucket list
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Wrangler Tail (Real-time Logs)
|
||||
```bash
|
||||
# Stream production logs
|
||||
npx wrangler tail --config wrangler.production.toml
|
||||
|
||||
# Filter by status code
|
||||
npx wrangler tail --status error --config wrangler.production.toml
|
||||
```
|
||||
|
||||
### Sentry Integration (Error Tracking)
|
||||
```typescript
|
||||
import * as Sentry from "@sentry/browser";
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.SENTRY_DSN,
|
||||
environment: process.env.ENVIRONMENT,
|
||||
tracesSampleRate: 1.0,
|
||||
});
|
||||
```
|
||||
|
||||
## Local Development
|
||||
|
||||
### Wrangler Dev (Local Workers)
|
||||
```bash
|
||||
# Run Workers locally with Doppler
|
||||
doppler run --config dev -- npx wrangler dev
|
||||
|
||||
# Run with remote mode (uses production KV/R2)
|
||||
doppler run --config dev -- npx wrangler dev --remote
|
||||
```
|
||||
|
||||
## Supporting Documentation
|
||||
|
||||
All supporting files are under 500 lines per Anthropic best practices:
|
||||
|
||||
- **[examples/](examples/)** - Complete deployment examples
|
||||
- [github-actions-workflow.md](examples/github-actions-workflow.md) - Full CI/CD workflows
|
||||
- [wrangler-config.md](examples/wrangler-config.md) - Complete wrangler.toml examples
|
||||
- [doppler-secrets.md](examples/doppler-secrets.md) - Secret management patterns
|
||||
- [migrations.md](examples/migrations.md) - Database migration examples
|
||||
- [INDEX.md](examples/INDEX.md) - Examples navigation
|
||||
|
||||
- **[reference/](reference/)** - Deployment references
|
||||
- [rollback-procedures.md](reference/rollback-procedures.md) - Rollback strategies
|
||||
- [monitoring.md](reference/monitoring.md) - Monitoring and alerting
|
||||
- [troubleshooting.md](reference/troubleshooting.md) - Common issues and fixes
|
||||
- [INDEX.md](reference/INDEX.md) - Reference navigation
|
||||
|
||||
- **[templates/](templates/)** - Copy-paste ready templates
|
||||
- [wrangler.toml](templates/wrangler.toml) - Basic wrangler config
|
||||
- [deploy-production.yml](templates/deploy-production.yml) - GitHub Actions workflow
|
||||
|
||||
- **[checklists/](checklists/)** - Deployment checklists
|
||||
- [deployment-checklist.md](checklists/deployment-checklist.md) - Pre-deployment validation
|
||||
|
||||
## When to Apply This Skill
|
||||
|
||||
Use this skill when:
|
||||
- Deploying TanStack Start to Cloudflare Workers
|
||||
- Setting up CI/CD with GitHub Actions
|
||||
- Configuring Doppler multi-environment secrets
|
||||
- Running database migrations in production
|
||||
- Rolling back failed deployments
|
||||
- Setting up KV namespaces or R2 buckets
|
||||
- Troubleshooting deployment failures
|
||||
- Configuring monitoring and alerting
|
||||
|
||||
## Template Reference
|
||||
|
||||
These patterns are from Grey Haven's production templates:
|
||||
- **cvi-template**: TanStack Start + Cloudflare Workers
|
||||
- **cvi-backend-template**: FastAPI + Python Workers
|
||||
|
||||
## Critical Reminders
|
||||
|
||||
1. **Doppler for ALL secrets**: Never commit secrets to git
|
||||
2. **Migrations BEFORE deployment**: Run `db:migrate` before `wrangler deploy`
|
||||
3. **Smoke tests AFTER deployment**: Validate production after deploy
|
||||
4. **Automated rollback**: GitHub Actions rolls back on failure
|
||||
5. **Connection pooling**: Match wrangler.toml pool settings with database
|
||||
6. **Environment-specific configs**: Separate wrangler files per environment
|
||||
7. **KV/R2 bindings**: Configure in wrangler.toml, create with CLI
|
||||
8. **Custom domains**: Use Cloudflare Proxy for DDoS protection
|
||||
9. **Monitoring**: Set up Sentry + Axiom + Wrangler tail
|
||||
10. **Emergency playbook**: Know how to rollback both Workers and database
|
||||
100
skills/deployment-cloudflare/checklists/deployment-checklist.md
Normal file
100
skills/deployment-cloudflare/checklists/deployment-checklist.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Deployment Checklist
|
||||
|
||||
**Use before deploying to production.**
|
||||
|
||||
## Pre-Deployment
|
||||
|
||||
### Doppler Configuration
|
||||
- [ ] All required secrets set in Doppler production environment
|
||||
- [ ] DOPPLER_TOKEN added to GitHub repository secrets
|
||||
- [ ] CLOUDFLARE_API_TOKEN added to GitHub repository secrets
|
||||
- [ ] Test Doppler access: `doppler secrets --config production`
|
||||
|
||||
### Wrangler Configuration
|
||||
- [ ] wrangler.production.toml configured with correct routes
|
||||
- [ ] KV namespaces created and IDs added to wrangler.toml
|
||||
- [ ] R2 buckets created and names added to wrangler.toml
|
||||
- [ ] Custom domain DNS configured in Cloudflare
|
||||
- [ ] Connection pool settings match database capacity
|
||||
|
||||
### Database
|
||||
- [ ] Migrations tested locally
|
||||
- [ ] Migration reversible (has downgrade)
|
||||
- [ ] Seed data prepared for production (if needed)
|
||||
- [ ] DATABASE_URL_ADMIN has elevated permissions for migrations
|
||||
- [ ] Connection pooling configured (PlanetScale)
|
||||
|
||||
### Application
|
||||
- [ ] All tests passing locally
|
||||
- [ ] All tests passing in CI
|
||||
- [ ] Build completes without errors
|
||||
- [ ] No TypeScript errors
|
||||
- [ ] No console.log statements in production code
|
||||
- [ ] Environment variables validated
|
||||
|
||||
### GitHub Actions
|
||||
- [ ] Workflow file present (.github/workflows/deploy-production.yml)
|
||||
- [ ] Workflow triggers configured (push to main)
|
||||
- [ ] Node.js version matches local (22)
|
||||
- [ ] Doppler CLI action configured
|
||||
- [ ] Smoke tests configured
|
||||
|
||||
## During Deployment
|
||||
|
||||
### Deployment Steps
|
||||
- [ ] Merge PR to main branch
|
||||
- [ ] GitHub Actions workflow triggered
|
||||
- [ ] Tests pass in CI
|
||||
- [ ] Build completes successfully
|
||||
- [ ] Database migrations run successfully
|
||||
- [ ] Workers deployment completes
|
||||
- [ ] Secrets injected into Workers
|
||||
- [ ] Smoke tests pass
|
||||
|
||||
### Monitoring
|
||||
- [ ] Watch deployment logs in GitHub Actions
|
||||
- [ ] Monitor Cloudflare Workers dashboard
|
||||
- [ ] Check Wrangler tail for errors
|
||||
- [ ] Verify no Sentry errors
|
||||
- [ ] Verify application loads at production URL
|
||||
|
||||
## Post-Deployment
|
||||
|
||||
### Validation
|
||||
- [ ] Production URL loads successfully
|
||||
- [ ] Authentication works (login/logout)
|
||||
- [ ] Database queries work (tenant isolation)
|
||||
- [ ] File uploads work (R2 storage)
|
||||
- [ ] Session management works (KV storage)
|
||||
- [ ] API endpoints respond correctly
|
||||
|
||||
### Smoke Tests
|
||||
- [ ] Critical user flows tested
|
||||
- [ ] Multi-tenant isolation verified
|
||||
- [ ] Performance acceptable (< 500ms response time)
|
||||
- [ ] No console errors in browser
|
||||
- [ ] Mobile responsive (if applicable)
|
||||
|
||||
### Rollback Readiness
|
||||
- [ ] Know how to rollback Workers: `npx wrangler rollback`
|
||||
- [ ] Know how to rollback database: `drizzle-kit migrate:rollback`
|
||||
- [ ] Emergency contacts notified
|
||||
- [ ] Linear issue updated with deployment status
|
||||
|
||||
## Rollback Triggers
|
||||
|
||||
Rollback immediately if:
|
||||
- [ ] Smoke tests fail
|
||||
- [ ] Critical user flow broken
|
||||
- [ ] 500 errors in production
|
||||
- [ ] Database connection failures
|
||||
- [ ] Authentication broken
|
||||
- [ ] Multi-tenant isolation breach
|
||||
|
||||
## Post-Deployment Documentation
|
||||
|
||||
- [ ] Update Linear issue with deployment notes
|
||||
- [ ] Document any manual steps taken
|
||||
- [ ] Update team on deployment status
|
||||
- [ ] Schedule postmortem if issues occurred
|
||||
- [ ] Update runbook with any new learnings
|
||||
41
skills/deployment-cloudflare/examples/INDEX.md
Normal file
41
skills/deployment-cloudflare/examples/INDEX.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Deployment Examples
|
||||
|
||||
Complete Cloudflare Workers deployment examples for Grey Haven applications.
|
||||
|
||||
## Available Examples
|
||||
|
||||
### [github-actions-workflow.md](github-actions-workflow.md)
|
||||
Full GitHub Actions CI/CD workflows.
|
||||
- Production deployment workflow
|
||||
- Staging deployment workflow
|
||||
- PR preview deployments
|
||||
- Automated rollback on failure
|
||||
|
||||
### [wrangler-config.md](wrangler-config.md)
|
||||
Complete wrangler.toml configuration examples.
|
||||
- Environment-specific configurations
|
||||
- KV namespace bindings
|
||||
- R2 bucket bindings
|
||||
- D1 database bindings
|
||||
- Custom domain routing
|
||||
|
||||
### [doppler-secrets.md](doppler-secrets.md)
|
||||
Secret management patterns with Doppler.
|
||||
- Environment setup (dev, staging, production)
|
||||
- Secret injection in CI/CD
|
||||
- Local development with Doppler
|
||||
- Rotating secrets safely
|
||||
|
||||
### [migrations.md](migrations.md)
|
||||
Database migration examples.
|
||||
- Drizzle migration scripts
|
||||
- Alembic migration scripts
|
||||
- Running migrations in CI/CD
|
||||
- Migration rollback procedures
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**Need GitHub Actions?** → [github-actions-workflow.md](github-actions-workflow.md)
|
||||
**Need wrangler.toml?** → [wrangler-config.md](wrangler-config.md)
|
||||
**Need Doppler setup?** → [doppler-secrets.md](doppler-secrets.md)
|
||||
**Need migrations?** → [migrations.md](migrations.md)
|
||||
34
skills/deployment-cloudflare/reference/INDEX.md
Normal file
34
skills/deployment-cloudflare/reference/INDEX.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Deployment Reference
|
||||
|
||||
Configuration and operational references for Cloudflare Workers deployment.
|
||||
|
||||
## Available References
|
||||
|
||||
### [rollback-procedures.md](rollback-procedures.md)
|
||||
Rollback strategies and emergency procedures.
|
||||
- Wrangler rollback commands
|
||||
- Database rollback procedures
|
||||
- Emergency rollback playbook
|
||||
- Recovery validation
|
||||
|
||||
### [monitoring.md](monitoring.md)
|
||||
Monitoring and alerting configuration.
|
||||
- Cloudflare Workers analytics
|
||||
- Wrangler tail (real-time logs)
|
||||
- Sentry error tracking
|
||||
- Axiom structured logging
|
||||
- Performance metrics
|
||||
|
||||
### [troubleshooting.md](troubleshooting.md)
|
||||
Common deployment issues and fixes.
|
||||
- Deployment failures
|
||||
- Secret management issues
|
||||
- Database connection errors
|
||||
- KV/R2 binding errors
|
||||
- Performance problems
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**Need rollback?** → [rollback-procedures.md](rollback-procedures.md)
|
||||
**Need monitoring?** → [monitoring.md](monitoring.md)
|
||||
**Need troubleshooting?** → [troubleshooting.md](troubleshooting.md)
|
||||
252
skills/deployment-cloudflare/scripts/deploy.py
Executable file
252
skills/deployment-cloudflare/scripts/deploy.py
Executable file
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Deploy Grey Haven applications to Cloudflare Workers with Doppler secrets.
|
||||
|
||||
This script automates the deployment workflow:
|
||||
1. Runs tests with Doppler environment
|
||||
2. Builds the application
|
||||
3. Runs database migrations
|
||||
4. Deploys to Cloudflare Workers with Wrangler
|
||||
5. Injects secrets from Doppler
|
||||
6. Runs smoke tests
|
||||
7. Automatically rolls back on failure
|
||||
|
||||
Usage:
|
||||
# Development deployment
|
||||
python scripts/deploy.py --env dev
|
||||
|
||||
# Staging deployment
|
||||
python scripts/deploy.py --env staging
|
||||
|
||||
# Production deployment (with confirmation)
|
||||
python scripts/deploy.py --env production
|
||||
|
||||
# Skip tests (not recommended)
|
||||
python scripts/deploy.py --env staging --skip-tests
|
||||
|
||||
# Dry run (show what would happen)
|
||||
python scripts/deploy.py --env production --dry-run
|
||||
|
||||
Always run with --help first to see all options.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
import json
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def run_command(cmd: str, description: str, dry_run: bool = False) -> bool:
|
||||
"""Run a shell command and return success status."""
|
||||
print(f"\n{'[DRY RUN] ' if dry_run else ''}→ {description}")
|
||||
print(f" Command: {cmd}")
|
||||
|
||||
if dry_run:
|
||||
return True
|
||||
|
||||
result = subprocess.run(cmd, shell=True, capture_output=False)
|
||||
return result.returncode == 0
|
||||
|
||||
|
||||
def confirm_production_deploy() -> bool:
|
||||
"""Ask for confirmation before production deployment."""
|
||||
print("\nWARNING: WARNING: You are about to deploy to PRODUCTION")
|
||||
print("This will affect live users.")
|
||||
response = input("Type 'deploy to production' to confirm: ")
|
||||
return response.strip().lower() == "deploy to production"
|
||||
|
||||
|
||||
def get_wrangler_config(env: str) -> str:
|
||||
"""Get the appropriate wrangler config file for environment."""
|
||||
if env == "production":
|
||||
return "wrangler.production.toml"
|
||||
elif env == "staging":
|
||||
return "wrangler.staging.toml"
|
||||
else:
|
||||
return "wrangler.toml"
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Deploy Grey Haven application to Cloudflare Workers",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
python scripts/deploy.py --env dev
|
||||
python scripts/deploy.py --env staging --skip-migrations
|
||||
python scripts/deploy.py --env production --dry-run
|
||||
|
||||
Environments:
|
||||
dev - Development (wrangler.toml)
|
||||
staging - Staging (wrangler.staging.toml)
|
||||
production - Production (wrangler.production.toml)
|
||||
|
||||
Doppler Configuration:
|
||||
Requires DOPPLER_TOKEN environment variable or doppler CLI configured.
|
||||
Secrets are injected from Doppler config matching the environment.
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--env",
|
||||
required=True,
|
||||
choices=["dev", "staging", "production"],
|
||||
help="Deployment environment"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-tests",
|
||||
action="store_true",
|
||||
help="Skip running tests (not recommended)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-migrations",
|
||||
action="store_true",
|
||||
help="Skip database migrations"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-smoke-tests",
|
||||
action="store_true",
|
||||
help="Skip smoke tests after deployment"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Show what would happen without executing"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Production confirmation
|
||||
if args.env == "production" and not args.dry_run:
|
||||
if not confirm_production_deploy():
|
||||
print("\nERROR: Production deployment cancelled")
|
||||
sys.exit(1)
|
||||
|
||||
env = args.env
|
||||
wrangler_config = get_wrangler_config(env)
|
||||
|
||||
print(f"\n{'=' * 70}")
|
||||
print(f" Grey Haven Deployment to {env.upper()}")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
# Step 1: Run tests
|
||||
if not args.skip_tests:
|
||||
success = run_command(
|
||||
f"doppler run --config test -- npm run test",
|
||||
"Running tests",
|
||||
args.dry_run
|
||||
)
|
||||
if not success:
|
||||
print("\nERROR: Tests failed. Deployment aborted.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("\nWARNING: Skipping tests (--skip-tests)")
|
||||
|
||||
# Step 2: Build application
|
||||
success = run_command(
|
||||
f"doppler run --config {env} -- npm run build",
|
||||
f"Building application for {env}",
|
||||
args.dry_run
|
||||
)
|
||||
if not success:
|
||||
print("\nERROR: Build failed. Deployment aborted.")
|
||||
sys.exit(1)
|
||||
|
||||
# Step 3: Run database migrations
|
||||
if not args.skip_migrations:
|
||||
success = run_command(
|
||||
f"doppler run --config {env} -- npm run db:migrate",
|
||||
"Running database migrations",
|
||||
args.dry_run
|
||||
)
|
||||
if not success:
|
||||
print("\nERROR: Migrations failed. Deployment aborted.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("\nWARNING: Skipping migrations (--skip-migrations)")
|
||||
|
||||
# Step 4: Deploy to Cloudflare Workers
|
||||
success = run_command(
|
||||
f"npx wrangler deploy --config {wrangler_config}",
|
||||
f"Deploying to Cloudflare Workers ({wrangler_config})",
|
||||
args.dry_run
|
||||
)
|
||||
if not success:
|
||||
print("\nERROR: Deployment failed.")
|
||||
sys.exit(1)
|
||||
|
||||
# Step 5: Inject Doppler secrets to Cloudflare Workers
|
||||
if not args.dry_run:
|
||||
print("\n→ Injecting Doppler secrets to Cloudflare Workers")
|
||||
print(" This may take a minute...")
|
||||
|
||||
# Download secrets from Doppler
|
||||
result = subprocess.run(
|
||||
f"doppler secrets download --config {env} --format json",
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
print("\nERROR: Failed to download Doppler secrets")
|
||||
sys.exit(1)
|
||||
|
||||
secrets = json.loads(result.stdout)
|
||||
|
||||
# Inject each secret to Cloudflare Workers
|
||||
for key, value in secrets.items():
|
||||
# Skip non-secret env vars (like NODE_ENV, ENVIRONMENT)
|
||||
if key in ["NODE_ENV", "ENVIRONMENT", "CI"]:
|
||||
continue
|
||||
|
||||
cmd = f'echo "{value}" | npx wrangler secret put {key} --config {wrangler_config}'
|
||||
subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
print(f" ✓ Injected {len(secrets)} secrets")
|
||||
else:
|
||||
print("\n[DRY RUN] → Would inject Doppler secrets to Cloudflare Workers")
|
||||
|
||||
# Step 6: Run smoke tests
|
||||
if not args.skip_smoke_tests:
|
||||
success = run_command(
|
||||
f"doppler run --config {env} -- npm run test:e2e:smoke",
|
||||
"Running smoke tests",
|
||||
args.dry_run
|
||||
)
|
||||
if not success:
|
||||
print("\nWARNING: Smoke tests failed. Rolling back deployment...")
|
||||
|
||||
if not args.dry_run:
|
||||
subprocess.run(
|
||||
f"npx wrangler rollback --config {wrangler_config}",
|
||||
shell=True
|
||||
)
|
||||
|
||||
print("\nERROR: Deployment rolled back due to smoke test failure")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("\nWARNING: Skipping smoke tests (--skip-smoke-tests)")
|
||||
|
||||
# Success!
|
||||
print(f"\n{'=' * 70}")
|
||||
print(f" SUCCESS: Deployment to {env.upper()} successful!")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
if env == "production":
|
||||
print(f"\n Production URL: https://app.greyhaven.studio")
|
||||
elif env == "staging":
|
||||
print(f"\n Staging URL: https://staging.greyhaven.studio")
|
||||
else:
|
||||
print(f"\n Dev URL: https://dev.greyhaven.studio")
|
||||
|
||||
print("\nNext steps:")
|
||||
print(" • Monitor logs: npx wrangler tail")
|
||||
print(" • Check Sentry for errors")
|
||||
print(" • Verify Axiom logs")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
198
skills/deployment-cloudflare/scripts/migrate.py
Executable file
198
skills/deployment-cloudflare/scripts/migrate.py
Executable file
@@ -0,0 +1,198 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Run database migrations for Grey Haven applications with Doppler.
|
||||
|
||||
Supports both Drizzle (TypeScript) and Alembic (Python) migrations
|
||||
across multiple environments.
|
||||
|
||||
Usage:
|
||||
# Run migrations for development
|
||||
python scripts/migrate.py --env dev
|
||||
|
||||
# Run migrations for staging
|
||||
python scripts/migrate.py --env staging
|
||||
|
||||
# Run migrations for production (with confirmation)
|
||||
python scripts/migrate.py --env production
|
||||
|
||||
# Rollback last migration
|
||||
python scripts/migrate.py --env dev --rollback
|
||||
|
||||
# Rollback to specific migration
|
||||
python scripts/migrate.py --env dev --rollback --to 20250115_add_users
|
||||
|
||||
# Dry run (show what would happen)
|
||||
python scripts/migrate.py --env production --dry-run
|
||||
|
||||
# Use Alembic instead of Drizzle
|
||||
python scripts/migrate.py --env dev --backend alembic
|
||||
|
||||
Always run with --help first to see all options.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def run_command(cmd: str, description: str, dry_run: bool = False) -> bool:
|
||||
"""Run a shell command and return success status."""
|
||||
print(f"\n{'[DRY RUN] ' if dry_run else ''}→ {description}")
|
||||
print(f" Command: {cmd}")
|
||||
|
||||
if dry_run:
|
||||
return True
|
||||
|
||||
result = subprocess.run(cmd, shell=True, capture_output=False)
|
||||
return result.returncode == 0
|
||||
|
||||
|
||||
def confirm_production_migration() -> bool:
|
||||
"""Ask for confirmation before production migration."""
|
||||
print("\nWARNING: WARNING: You are about to run migrations on PRODUCTION database")
|
||||
print("This operation is IRREVERSIBLE and will affect live data.")
|
||||
response = input("Type 'run production migrations' to confirm: ")
|
||||
return response.strip().lower() == "run production migrations"
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run database migrations with Doppler environment variables",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Run Drizzle migrations for development
|
||||
python scripts/migrate.py --env dev
|
||||
|
||||
# Rollback last Drizzle migration
|
||||
python scripts/migrate.py --env dev --rollback
|
||||
|
||||
# Run Alembic migrations for staging
|
||||
python scripts/migrate.py --env staging --backend alembic
|
||||
|
||||
# Rollback to specific Alembic migration
|
||||
python scripts/migrate.py --env staging --backend alembic --rollback --to abc123
|
||||
|
||||
Environments:
|
||||
dev - Local development database
|
||||
test - CI/CD test database
|
||||
staging - Staging database
|
||||
production - Production database (requires confirmation)
|
||||
|
||||
Backends:
|
||||
drizzle - Drizzle Kit (TypeScript/TanStack Start)
|
||||
alembic - Alembic (Python/FastAPI)
|
||||
|
||||
Doppler Configuration:
|
||||
Requires doppler CLI configured with appropriate access.
|
||||
Uses DATABASE_URL_ADMIN from Doppler config.
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--env",
|
||||
required=True,
|
||||
choices=["dev", "test", "staging", "production"],
|
||||
help="Environment to run migrations against"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--backend",
|
||||
default="drizzle",
|
||||
choices=["drizzle", "alembic"],
|
||||
help="Migration backend to use (default: drizzle)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rollback",
|
||||
action="store_true",
|
||||
help="Rollback migrations instead of applying"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--to",
|
||||
type=str,
|
||||
help="Rollback to specific migration (use with --rollback)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Show what would happen without executing"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Production confirmation
|
||||
if args.env == "production" and not args.dry_run and not args.rollback:
|
||||
if not confirm_production_migration():
|
||||
print("\nERROR: Production migration cancelled")
|
||||
sys.exit(1)
|
||||
|
||||
env = args.env
|
||||
backend = args.backend
|
||||
|
||||
print(f"\n{'=' * 70}")
|
||||
print(f" Database Migration - {env.upper()} ({backend.upper()})")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
# Check Doppler configuration
|
||||
if not args.dry_run:
|
||||
result = subprocess.run(
|
||||
f"doppler secrets get DATABASE_URL_ADMIN --config {env}",
|
||||
shell=True,
|
||||
capture_output=True
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"\nERROR: Failed to get DATABASE_URL_ADMIN from Doppler config '{env}'")
|
||||
print(" Make sure Doppler is configured: doppler setup")
|
||||
sys.exit(1)
|
||||
|
||||
# Construct migration command
|
||||
if backend == "drizzle":
|
||||
if args.rollback:
|
||||
if args.to:
|
||||
cmd = f"doppler run --config {env} -- drizzle-kit migrate:rollback --to {args.to}"
|
||||
else:
|
||||
cmd = f"doppler run --config {env} -- drizzle-kit migrate:rollback"
|
||||
description = "Rolling back Drizzle migration"
|
||||
else:
|
||||
cmd = f"doppler run --config {env} -- drizzle-kit push:pg"
|
||||
description = "Applying Drizzle migrations"
|
||||
|
||||
elif backend == "alembic":
|
||||
if args.rollback:
|
||||
if args.to:
|
||||
cmd = f"doppler run --config {env} -- alembic downgrade {args.to}"
|
||||
else:
|
||||
cmd = f"doppler run --config {env} -- alembic downgrade -1"
|
||||
description = "Rolling back Alembic migration"
|
||||
else:
|
||||
cmd = f"doppler run --config {env} -- alembic upgrade head"
|
||||
description = "Applying Alembic migrations"
|
||||
|
||||
# Run migration
|
||||
success = run_command(cmd, description, args.dry_run)
|
||||
|
||||
if not success:
|
||||
print(f"\nERROR: Migration failed for {env}")
|
||||
sys.exit(1)
|
||||
|
||||
# Success!
|
||||
print(f"\n{'=' * 70}")
|
||||
if args.rollback:
|
||||
print(f" SUCCESS: Rollback successful for {env.upper()}")
|
||||
else:
|
||||
print(f" SUCCESS: Migration successful for {env.upper()}")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
if not args.rollback:
|
||||
print("\nNext steps:")
|
||||
print(" • Verify schema changes in database")
|
||||
print(" • Run tests: doppler run --config test -- npm run test")
|
||||
print(" • Deploy application if migrations succeeded")
|
||||
else:
|
||||
print("\nNext steps:")
|
||||
print(" • Verify rollback was successful")
|
||||
print(" • Re-deploy previous application version if needed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
239
skills/deployment-cloudflare/scripts/rollback.py
Executable file
239
skills/deployment-cloudflare/scripts/rollback.py
Executable file
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Rollback Grey Haven Cloudflare Workers deployment to previous version.
|
||||
|
||||
This script handles emergency rollbacks when a deployment fails or causes
|
||||
production issues. It can rollback both the Workers deployment and database
|
||||
migrations.
|
||||
|
||||
Usage:
|
||||
# Rollback to previous Workers deployment
|
||||
python scripts/rollback.py --env production
|
||||
|
||||
# Rollback Workers and database migration
|
||||
python scripts/rollback.py --env production --with-migration
|
||||
|
||||
# Rollback to specific deployment ID
|
||||
python scripts/rollback.py --env production --deployment-id abc123
|
||||
|
||||
# Rollback database only
|
||||
python scripts/rollback.py --env production --migration-only
|
||||
|
||||
# Dry run (show what would happen)
|
||||
python scripts/rollback.py --env production --dry-run
|
||||
|
||||
Always run with --help first to see all options.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def run_command(cmd: str, description: str, dry_run: bool = False, capture: bool = False) -> tuple[bool, Optional[str]]:
|
||||
"""Run a shell command and return success status and output."""
|
||||
print(f"\n{'[DRY RUN] ' if dry_run else ''}→ {description}")
|
||||
print(f" Command: {cmd}")
|
||||
|
||||
if dry_run:
|
||||
return True, None
|
||||
|
||||
result = subprocess.run(cmd, shell=True, capture_output=capture, text=True)
|
||||
return result.returncode == 0, result.stdout if capture else None
|
||||
|
||||
|
||||
def confirm_production_rollback() -> bool:
|
||||
"""Ask for confirmation before production rollback."""
|
||||
print("\nWARNING: WARNING: You are about to ROLLBACK PRODUCTION deployment")
|
||||
print("This will affect live users immediately.")
|
||||
response = input("Type 'rollback production' to confirm: ")
|
||||
return response.strip().lower() == "rollback production"
|
||||
|
||||
|
||||
def get_wrangler_config(env: str) -> str:
|
||||
"""Get the appropriate wrangler config file for environment."""
|
||||
if env == "production":
|
||||
return "wrangler.production.toml"
|
||||
elif env == "staging":
|
||||
return "wrangler.staging.toml"
|
||||
else:
|
||||
return "wrangler.toml"
|
||||
|
||||
|
||||
def list_recent_deployments(wrangler_config: str, dry_run: bool = False) -> None:
|
||||
"""List recent deployments for reference."""
|
||||
if dry_run:
|
||||
print("\n[DRY RUN] → Would list recent deployments")
|
||||
return
|
||||
|
||||
print("\n→ Fetching recent deployments...")
|
||||
subprocess.run(
|
||||
f"npx wrangler deployments list --config {wrangler_config}",
|
||||
shell=True
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Rollback Grey Haven Cloudflare Workers deployment",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Emergency rollback of production Workers deployment
|
||||
python scripts/rollback.py --env production
|
||||
|
||||
# Rollback Workers and database migration
|
||||
python scripts/rollback.py --env production --with-migration
|
||||
|
||||
# Rollback to specific deployment
|
||||
python scripts/rollback.py --env production --deployment-id abc123
|
||||
|
||||
# Rollback database migration only
|
||||
python scripts/rollback.py --env production --migration-only --backend drizzle
|
||||
|
||||
Environments:
|
||||
dev - Development
|
||||
staging - Staging
|
||||
production - Production (requires confirmation)
|
||||
|
||||
Emergency Rollback Procedure:
|
||||
1. Identify the issue (check Sentry, Axiom, Cloudflare logs)
|
||||
2. Run rollback script with appropriate flags
|
||||
3. Verify rollback with smoke tests
|
||||
4. Notify team and update Linear issue
|
||||
5. Create postmortem for root cause analysis
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--env",
|
||||
required=True,
|
||||
choices=["dev", "staging", "production"],
|
||||
help="Environment to rollback"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--deployment-id",
|
||||
type=str,
|
||||
help="Specific deployment ID to rollback to (optional)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--with-migration",
|
||||
action="store_true",
|
||||
help="Also rollback database migration"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--migration-only",
|
||||
action="store_true",
|
||||
help="Rollback database migration only (not Workers)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--backend",
|
||||
default="drizzle",
|
||||
choices=["drizzle", "alembic"],
|
||||
help="Migration backend (default: drizzle)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Show what would happen without executing"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Production confirmation
|
||||
if args.env == "production" and not args.dry_run:
|
||||
if not confirm_production_rollback():
|
||||
print("\nERROR: Production rollback cancelled")
|
||||
sys.exit(1)
|
||||
|
||||
env = args.env
|
||||
wrangler_config = get_wrangler_config(env)
|
||||
|
||||
print(f"\n{'=' * 70}")
|
||||
print(f" Emergency Rollback - {env.upper()}")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
# Rollback Workers deployment (unless migration-only)
|
||||
if not args.migration_only:
|
||||
# List recent deployments first
|
||||
if not args.deployment_id:
|
||||
list_recent_deployments(wrangler_config, args.dry_run)
|
||||
|
||||
# Construct rollback command
|
||||
if args.deployment_id:
|
||||
cmd = f"npx wrangler rollback --deployment-id {args.deployment_id} --config {wrangler_config}"
|
||||
description = f"Rolling back to deployment {args.deployment_id}"
|
||||
else:
|
||||
cmd = f"npx wrangler rollback --config {wrangler_config}"
|
||||
description = "Rolling back to previous deployment"
|
||||
|
||||
success, _ = run_command(cmd, description, args.dry_run)
|
||||
|
||||
if not success:
|
||||
print(f"\nERROR: Workers rollback failed for {env}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"\nSUCCESS: Workers deployment rolled back successfully")
|
||||
|
||||
# Rollback database migration (if requested)
|
||||
if args.with_migration or args.migration_only:
|
||||
backend = args.backend
|
||||
|
||||
print(f"\n→ Rolling back {backend} migration for {env}")
|
||||
|
||||
if backend == "drizzle":
|
||||
cmd = f"doppler run --config {env} -- drizzle-kit migrate:rollback"
|
||||
elif backend == "alembic":
|
||||
cmd = f"doppler run --config {env} -- alembic downgrade -1"
|
||||
|
||||
success, _ = run_command(cmd, f"Rolling back {backend} migration", args.dry_run)
|
||||
|
||||
if not success:
|
||||
print(f"\nERROR: Database migration rollback failed for {env}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"\nSUCCESS: Database migration rolled back successfully")
|
||||
|
||||
# Success!
|
||||
print(f"\n{'=' * 70}")
|
||||
print(f" SUCCESS: Rollback complete for {env.upper()}")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
# Run smoke tests
|
||||
print("\n→ Running smoke tests to verify rollback...")
|
||||
success, _ = run_command(
|
||||
f"doppler run --config {env} -- npm run test:e2e:smoke",
|
||||
"Verifying rollback with smoke tests",
|
||||
args.dry_run
|
||||
)
|
||||
|
||||
if not success:
|
||||
print("\nWARNING: Warning: Smoke tests failed after rollback")
|
||||
print(" Manual verification required!")
|
||||
else:
|
||||
print("\nSUCCESS: Smoke tests passed - rollback verified")
|
||||
|
||||
# Post-rollback checklist
|
||||
print("\n📋 Post-Rollback Checklist:")
|
||||
print(" ✓ Deployment rolled back")
|
||||
if args.with_migration or args.migration_only:
|
||||
print(" ✓ Database migration rolled back")
|
||||
print("\n WARNING: Action Items:")
|
||||
print(" • Check Sentry for errors")
|
||||
print(" • Verify Axiom logs")
|
||||
print(" • Monitor Cloudflare Workers analytics")
|
||||
print(" • Update Linear issue with rollback status")
|
||||
print(" • Create postmortem for root cause analysis")
|
||||
print(" • Fix the issue before re-deploying")
|
||||
|
||||
if env == "production":
|
||||
print(f"\n Production URL: https://app.greyhaven.studio")
|
||||
elif env == "staging":
|
||||
print(f"\n Staging URL: https://staging.greyhaven.studio")
|
||||
else:
|
||||
print(f"\n Dev URL: https://dev.greyhaven.studio")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user