Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:48:52 +08:00
commit 6ec3196ecc
434 changed files with 125248 additions and 0 deletions

View File

@@ -0,0 +1,269 @@
#!/usr/bin/env python3
"""
Cloudflare Worker Deployment Utility
Automates Cloudflare Worker deployments with wrangler.toml configuration handling,
multi-environment support, and comprehensive error handling.
Usage:
python cloudflare-deploy.py --env production --dry-run
python cloudflare-deploy.py --project ./my-worker --env staging
"""
import argparse
import json
import subprocess
import sys
from pathlib import Path
from typing import Dict, List, Optional, Tuple
class CloudflareDeployError(Exception):
"""Custom exception for Cloudflare deployment errors."""
pass
class CloudflareDeploy:
"""Handle Cloudflare Worker deployments with wrangler CLI."""
def __init__(self, project_dir: Path, env: Optional[str] = None,
dry_run: bool = False, verbose: bool = False):
"""
Initialize CloudflareDeploy.
Args:
project_dir: Path to Worker project directory
env: Environment name (production, staging, dev)
dry_run: Preview deployment without actually deploying
verbose: Enable verbose output
"""
self.project_dir = Path(project_dir).resolve()
self.env = env
self.dry_run = dry_run
self.verbose = verbose
self.wrangler_toml = self.project_dir / "wrangler.toml"
def validate_project(self) -> bool:
"""
Validate project directory and wrangler.toml existence.
Returns:
True if valid, False otherwise
Raises:
CloudflareDeployError: If validation fails
"""
if not self.project_dir.exists():
raise CloudflareDeployError(
f"Project directory does not exist: {self.project_dir}"
)
if not self.wrangler_toml.exists():
raise CloudflareDeployError(
f"wrangler.toml not found in: {self.project_dir}"
)
return True
def check_wrangler_installed(self) -> bool:
"""
Check if wrangler CLI is installed.
Returns:
True if installed, False otherwise
"""
try:
result = subprocess.run(
["wrangler", "--version"],
capture_output=True,
text=True,
check=True
)
if self.verbose:
print(f"Wrangler version: {result.stdout.strip()}")
return True
except (subprocess.CalledProcessError, FileNotFoundError):
return False
def run_command(self, cmd: List[str], check: bool = True) -> Tuple[int, str, str]:
"""
Run shell command and capture output.
Args:
cmd: Command and arguments as list
check: Raise exception on non-zero exit code
Returns:
Tuple of (exit_code, stdout, stderr)
Raises:
CloudflareDeployError: If command fails and check=True
"""
if self.verbose:
print(f"Running: {' '.join(cmd)}")
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
cwd=self.project_dir,
check=check
)
return result.returncode, result.stdout, result.stderr
except subprocess.CalledProcessError as e:
if check:
raise CloudflareDeployError(
f"Command failed: {' '.join(cmd)}\n{e.stderr}"
)
return e.returncode, e.stdout, e.stderr
def get_worker_name(self) -> str:
"""
Extract worker name from wrangler.toml.
Returns:
Worker name
Raises:
CloudflareDeployError: If name cannot be extracted
"""
try:
with open(self.wrangler_toml, 'r') as f:
for line in f:
if line.strip().startswith('name'):
# Parse: name = "worker-name"
return line.split('=')[1].strip().strip('"\'')
except Exception as e:
raise CloudflareDeployError(f"Failed to read worker name: {e}")
raise CloudflareDeployError("Worker name not found in wrangler.toml")
def build_deploy_command(self) -> List[str]:
"""
Build wrangler deploy command with appropriate flags.
Returns:
Command as list of strings
"""
cmd = ["wrangler", "deploy"]
if self.env:
cmd.extend(["--env", self.env])
if self.dry_run:
cmd.append("--dry-run")
return cmd
def deploy(self) -> bool:
"""
Execute deployment.
Returns:
True if successful
Raises:
CloudflareDeployError: If deployment fails
"""
# Validate
self.validate_project()
if not self.check_wrangler_installed():
raise CloudflareDeployError(
"wrangler CLI not installed. Install: npm install -g wrangler"
)
worker_name = self.get_worker_name()
env_suffix = f" ({self.env})" if self.env else ""
mode = "DRY RUN" if self.dry_run else "DEPLOY"
print(f"\n{mode}: {worker_name}{env_suffix}")
print(f"Project: {self.project_dir}\n")
# Build and run command
cmd = self.build_deploy_command()
exit_code, stdout, stderr = self.run_command(cmd)
# Output results
if stdout:
print(stdout)
if stderr:
print(stderr, file=sys.stderr)
if exit_code == 0:
status = "would be deployed" if self.dry_run else "deployed successfully"
print(f"\n✓ Worker {status}")
return True
else:
raise CloudflareDeployError("Deployment failed")
def main():
"""CLI entry point."""
parser = argparse.ArgumentParser(
description="Deploy Cloudflare Worker with wrangler",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python cloudflare-deploy.py
python cloudflare-deploy.py --env production
python cloudflare-deploy.py --project ./my-worker --env staging
python cloudflare-deploy.py --dry-run
python cloudflare-deploy.py --env prod --verbose
"""
)
parser.add_argument(
"--project",
type=str,
default=".",
help="Path to Worker project directory (default: current directory)"
)
parser.add_argument(
"--env",
type=str,
choices=["production", "staging", "dev"],
help="Environment to deploy to (production, staging, dev)"
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Preview deployment without actually deploying"
)
parser.add_argument(
"--verbose",
"-v",
action="store_true",
help="Enable verbose output"
)
args = parser.parse_args()
try:
deployer = CloudflareDeploy(
project_dir=args.project,
env=args.env,
dry_run=args.dry_run,
verbose=args.verbose
)
success = deployer.deploy()
sys.exit(0 if success else 1)
except CloudflareDeployError as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
except KeyboardInterrupt:
print("\nDeployment cancelled by user", file=sys.stderr)
sys.exit(130)
except Exception as e:
print(f"Unexpected error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()