commit 8d8103de195f7ca3bc45fe8dee386ef046841765 Author: Zhongwei Li Date: Sat Nov 29 18:29:12 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..e48654a --- /dev/null +++ b/.claude-plugin/plugin.json @@ -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" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..78d954b --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# deployment + +Cloudflare Workers and Pages deployment with integrated debugging and troubleshooting chains diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..be71531 --- /dev/null +++ b/plugin.lock.json @@ -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": [] + } +} \ No newline at end of file diff --git a/skills/deployment-cloudflare/SKILL.md b/skills/deployment-cloudflare/SKILL.md new file mode 100644 index 0000000..06f6fb5 --- /dev/null +++ b/skills/deployment-cloudflare/SKILL.md @@ -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= +BETTER_AUTH_URL=https://app.greyhaven.studio +JWT_SECRET_KEY= + +# 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 diff --git a/skills/deployment-cloudflare/checklists/deployment-checklist.md b/skills/deployment-cloudflare/checklists/deployment-checklist.md new file mode 100644 index 0000000..b538660 --- /dev/null +++ b/skills/deployment-cloudflare/checklists/deployment-checklist.md @@ -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 diff --git a/skills/deployment-cloudflare/examples/INDEX.md b/skills/deployment-cloudflare/examples/INDEX.md new file mode 100644 index 0000000..4bade16 --- /dev/null +++ b/skills/deployment-cloudflare/examples/INDEX.md @@ -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) diff --git a/skills/deployment-cloudflare/reference/INDEX.md b/skills/deployment-cloudflare/reference/INDEX.md new file mode 100644 index 0000000..232aa85 --- /dev/null +++ b/skills/deployment-cloudflare/reference/INDEX.md @@ -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) diff --git a/skills/deployment-cloudflare/scripts/deploy.py b/skills/deployment-cloudflare/scripts/deploy.py new file mode 100755 index 0000000..767d9c5 --- /dev/null +++ b/skills/deployment-cloudflare/scripts/deploy.py @@ -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() diff --git a/skills/deployment-cloudflare/scripts/migrate.py b/skills/deployment-cloudflare/scripts/migrate.py new file mode 100755 index 0000000..ce721e9 --- /dev/null +++ b/skills/deployment-cloudflare/scripts/migrate.py @@ -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() diff --git a/skills/deployment-cloudflare/scripts/rollback.py b/skills/deployment-cloudflare/scripts/rollback.py new file mode 100755 index 0000000..511249c --- /dev/null +++ b/skills/deployment-cloudflare/scripts/rollback.py @@ -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()