Vercel Env Sync
Commands nextjs-vercel 257
npx claude-code-templates@latest --command nextjs-vercel/vercel-env-sync Content
Vercel Environment Sync
Sync Operation: $ARGUMENTS
Current Environment Analysis
Local Environment
- Environment files:
- @.env.local (if exists)
- @.env.development (if exists)
- @.env.production (if exists)
- @.env (if exists)
- Environment example: @.env.example (if exists)
- Vercel config: @vercel.json (if exists)
Project Status
- Vercel CLI status: !
vercel --version 2>/dev/null || echo "Vercel CLI not installed" - Current project: !
vercel project ls 2>/dev/null | head -5 || echo "Not linked to Vercel project" - Git status: !
git status --porcelain | head -5
Environment Synchronization Strategy
1. Environment File Analysis
typescript
// Environment file structure analysis
interface EnvironmentConfig {
development: Record<string, string>;
preview: Record<string, string>;
production: Record<string, string>;
}
const environmentFiles = {
'.env.local': 'Local development overrides',
'.env.development': 'Development environment',
'.env.staging': 'Staging/preview environment',
'.env.production': 'Production environment',
'.env': 'Default environment (committed to git)',
'.env.example': 'Environment template (safe to commit)',
};2. Vercel Environment Management
bash
# List all environment variables for all environments
vercel env ls
# List environment variables for specific environment
vercel env ls --environment=production
vercel env ls --environment=preview
vercel env ls --environment=development
# Pull environment variables from Vercel
vercel env pull .env.vercel
# Add new environment variable
vercel env add [name] [environment]
# Remove environment variable
vercel env rm [name] [environment]Synchronization Operations
1. Pull Environment Variables from Vercel
bash
#!/bin/bash
# Pull environments from Vercel
echo "🔄 Pulling environment variables from Vercel..."
# Create backup of existing files
if [ -f .env.local ]; then
cp .env.local .env.local.backup.$(date +%Y%m%d_%H%M%S)
echo "📦 Backup created for .env.local"
fi
# Pull from Vercel (creates .env.local by default)
vercel env pull .env.local
if [ $? -eq 0 ]; then
echo "✅ Successfully pulled environment variables"
echo "📁 Variables saved to .env.local"
# Show summary
echo ""
echo "📊 Environment Variables Summary:"
echo "================================"
grep -c "=" .env.local 2>/dev/null && echo "Total variables: $(grep -c "=" .env.local)"
# List variable names (hide values for security)
echo ""
echo "🔑 Variable Names:"
grep "^[A-Z]" .env.local | cut -d'=' -f1 | sort
else
echo "❌ Failed to pull environment variables"
exit 1
fi2. Push Environment Variables to Vercel
bash
#!/bin/bash
# Push environment variables to Vercel
echo "🚀 Pushing environment variables to Vercel..."
# Check if environment files exist
ENV_FILES=(".env.production" ".env.staging" ".env.development")
FOUND_FILES=()
for file in "${ENV_FILES[@]}"; do
if [ -f "$file" ]; then
FOUND_FILES+=("$file")
fi
done
if [ ${#FOUND_FILES[@]} -eq 0 ]; then
echo "❌ No environment files found to push"
echo "💡 Expected files: ${ENV_FILES[*]}"
exit 1
fi
# Push each environment file
for file in "${FOUND_FILES[@]}"; do
echo "📤 Processing $file..."
# Determine target environment
if [[ "$file" == *"production"* ]]; then
ENV="production"
elif [[ "$file" == *"staging"* ]]; then
ENV="preview" # Vercel uses 'preview' for staging
elif [[ "$file" == *"development"* ]]; then
ENV="development"
else
ENV="development" # Default
fi
echo "🎯 Pushing to $ENV environment..."
# Read variables from file and push to Vercel
while IFS='=' read -r key value; do
# Skip empty lines and comments
if [[ -z "$key" || "$key" =~ ^#.* ]]; then
continue
fi
# Remove quotes from value if present
value=$(echo "$value" | sed 's/^"\(.*\)"$/\1/' | sed "s/^'\(.*\)'$/\1/")
echo " 🔑 Setting $key..."
echo "$value" | vercel env add "$key" "$ENV" --force
done < "$file"
echo "✅ Completed $file -> $ENV"
echo ""
done
echo "🎉 All environment variables pushed successfully!"3. Environment Validation
typescript
// Environment validation script
interface ValidationRule {
name: string;
required: boolean;
pattern?: RegExp;
description: string;
}
const validationRules: ValidationRule[] = [
{
name: 'DATABASE_URL',
required: true,
pattern: /^(postgresql|mysql|sqlite):\/\/.+/,
description: 'Database connection string',
},
{
name: 'NEXTAUTH_SECRET',
required: true,
pattern: /.{32,}/,
description: 'NextAuth.js secret key (min 32 characters)',
},
{
name: 'NEXTAUTH_URL',
required: true,
pattern: /^https?:\/\/.+/,
description: 'NextAuth.js canonical URL',
},
{
name: 'API_KEY',
required: false,
pattern: /^[A-Za-z0-9_-]+$/,
description: 'API key for external services',
},
];
function validateEnvironment(envFile: string): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
const env = readEnvironmentFile(envFile);
// Check required variables
validationRules.forEach(rule => {
const value = env[rule.name];
if (rule.required && !value) {
errors.push(`Missing required variable: ${rule.name}`);
return;
}
if (value && rule.pattern && !rule.pattern.test(value)) {
errors.push(`Invalid format for ${rule.name}: ${rule.description}`);
}
});
// Check for common issues
Object.entries(env).forEach(([key, value]) => {
// Check for placeholder values
if (value === 'your-secret-here' || value === 'change-me') {
warnings.push(`Placeholder value detected for ${key}`);
}
// Check for potentially committed secrets
if (key.includes('SECRET') || key.includes('PRIVATE')) {
if (value.length < 16) {
warnings.push(`${key} appears to be too short for a secret`);
}
}
});
return {
valid: errors.length === 0,
errors,
warnings,
};
}
function readEnvironmentFile(filePath: string): Record<string, string> {
// Implementation to read and parse environment file
return {};
}
interface ValidationResult {
valid: boolean;
errors: string[];
warnings: string[];
}4. Environment Backup and Restore
bash
#!/bin/bash
# Backup and restore environment variables
BACKUP_DIR=".env-backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
backup_environment() {
echo "📦 Creating environment backup..."
mkdir -p "$BACKUP_DIR"
# Backup local files
for file in .env.local .env.development .env.staging .env.production; do
if [ -f "$file" ]; then
cp "$file" "$BACKUP_DIR/${file}.${TIMESTAMP}"
echo "✅ Backed up $file"
fi
done
# Backup Vercel environment variables
echo "📤 Backing up Vercel environment variables..."
for env in production preview development; do
vercel env ls --environment="$env" > "$BACKUP_DIR/vercel-${env}.${TIMESTAMP}.txt"
echo "✅ Backed up Vercel $env environment"
done
echo "🎉 Backup completed in $BACKUP_DIR/"
ls -la "$BACKUP_DIR/" | grep "$TIMESTAMP"
}
restore_environment() {
local backup_timestamp="$1"
if [ -z "$backup_timestamp" ]; then
echo "❌ Please specify backup timestamp"
echo "💡 Available backups:"
ls -1 "$BACKUP_DIR/" | grep -E "\.env" | cut -d'.' -f3 | sort -u
exit 1
fi
echo "🔄 Restoring environment from backup $backup_timestamp..."
# Restore local files
for file in .env.local .env.development .env.staging .env.production; do
backup_file="$BACKUP_DIR/${file}.${backup_timestamp}"
if [ -f "$backup_file" ]; then
cp "$backup_file" "$file"
echo "✅ Restored $file"
fi
done
echo "🎉 Environment restored from backup"
}
# Usage functions
case "$1" in
backup)
backup_environment
;;
restore)
restore_environment "$2"
;;
*)
echo "Usage: $0 {backup|restore} [timestamp]"
exit 1
;;
esacAdvanced Synchronization Features
1. Environment Diff and Comparison
typescript
// Environment comparison tool
interface EnvironmentDiff {
added: string[];
removed: string[];
modified: Array<{
key: string;
local: string;
remote: string;
}>;
unchanged: string[];
}
function compareEnvironments(
local: Record<string, string>,
remote: Record<string, string>
): EnvironmentDiff {
const diff: EnvironmentDiff = {
added: [],
removed: [],
modified: [],
unchanged: [],
};
const allKeys = new Set([...Object.keys(local), ...Object.keys(remote)]);
allKeys.forEach(key => {
if (!(key in local)) {
diff.added.push(key);
} else if (!(key in remote)) {
diff.removed.push(key);
} else if (local[key] !== remote[key]) {
diff.modified.push({
key,
local: local[key],
remote: remote[key],
});
} else {
diff.unchanged.push(key);
}
});
return diff;
}
// Generate diff report
function generateDiffReport(diff: EnvironmentDiff): string {
let report = '# Environment Variables Comparison\n\n';
if (diff.added.length > 0) {
report += '## ➕ Variables in Remote (not in Local)\n';
diff.added.forEach(key => {
report += `- \`${key}\`\n`;
});
report += '\n';
}
if (diff.removed.length > 0) {
report += '## ➖ Variables in Local (not in Remote)\n';
diff.removed.forEach(key => {
report += `- \`${key}\`\n`;
});
report += '\n';
}
if (diff.modified.length > 0) {
report += '## 🔄 Modified Variables\n';
diff.modified.forEach(({ key, local, remote }) => {
report += `### \`${key}\`\n`;
report += `- **Local**: \`${maskSensitive(local)}\`\n`;
report += `- **Remote**: \`${maskSensitive(remote)}\`\n\n`;
});
}
if (diff.unchanged.length > 0) {
report += `## ✅ Unchanged Variables (${diff.unchanged.length})\n`;
report += `${diff.unchanged.map(key => `- \`${key}\``).join('\n')}\n\n`;
}
return report;
}
function maskSensitive(value: string): string {
// Mask sensitive values for security
if (value.length <= 8) {
return '*'.repeat(value.length);
}
return `${value.substring(0, 4)}${'*'.repeat(value.length - 8)}${value.substring(value.length - 4)}`;
}2. Environment Template Generation
typescript
// Generate .env.example from existing environment
function generateEnvExample(envFile: string): string {
const env = readEnvironmentFile(envFile);
let template = '# Environment Variables Template\n';
template += '# Copy this file to .env.local and fill in the values\n\n';
const categories = categorizeVariables(env);
Object.entries(categories).forEach(([category, variables]) => {
template += `# ${category.toUpperCase()}\n`;
variables.forEach(({ key, description, example }) => {
if (description) {
template += `# ${description}\n`;
}
template += `${key}=${example || 'your-value-here'}\n\n`;
});
});
return template;
}
function categorizeVariables(env: Record<string, string>) {
const categories: Record<string, Array<{
key: string;
description?: string;
example?: string;
}>> = {
database: [],
authentication: [],
external_apis: [],
configuration: [],
};
Object.keys(env).forEach(key => {
if (key.includes('DATABASE') || key.includes('DB_')) {
categories.database.push({ key, description: getDatabaseDescription(key) });
} else if (key.includes('AUTH') || key.includes('SECRET')) {
categories.authentication.push({ key, description: getAuthDescription(key) });
} else if (key.includes('API_KEY') || key.includes('_TOKEN')) {
categories.external_apis.push({ key, description: getApiDescription(key) });
} else {
categories.configuration.push({ key, description: getConfigDescription(key) });
}
});
return categories;
}
function getDatabaseDescription(key: string): string {
if (key === 'DATABASE_URL') return 'Database connection string';
if (key === 'DB_HOST') return 'Database host';
if (key === 'DB_PORT') return 'Database port';
if (key === 'DB_NAME') return 'Database name';
return 'Database configuration';
}
function getAuthDescription(key: string): string {
if (key === 'NEXTAUTH_SECRET') return 'NextAuth.js secret key';
if (key === 'NEXTAUTH_URL') return 'NextAuth.js canonical URL';
if (key === 'JWT_SECRET') return 'JWT secret key';
return 'Authentication configuration';
}
function getApiDescription(key: string): string {
return `API key for ${key.toLowerCase().replace(/_/g, ' ')}`;
}
function getConfigDescription(key: string): string {
return `Configuration for ${key.toLowerCase().replace(/_/g, ' ')}`;
}3. Security and Validation
bash
#!/bin/bash
# Security checks for environment variables
security_check() {
echo "🔐 Running security checks on environment variables..."
local issues=0
# Check for common security issues
for file in .env.local .env.development .env.staging .env.production; do
if [ ! -f "$file" ]; then
continue
fi
echo "🔍 Checking $file..."
# Check for weak secrets
while IFS='=' read -r key value; do
if [[ -z "$key" || "$key" =~ ^#.* ]]; then
continue
fi
# Remove quotes
value=$(echo "$value" | sed 's/^"\(.*\)"$/\1/' | sed "s/^'\(.*\)'$/\1/")
# Check for placeholder values
if [[ "$value" == *"your-"* || "$value" == *"change-me"* || "$value" == *"replace-me"* ]]; then
echo "⚠️ Placeholder value in $key"
((issues++))
fi
# Check for short secrets
if [[ "$key" =~ (SECRET|PRIVATE|KEY|TOKEN) ]]; then
if [ ${#value} -lt 16 ]; then
echo "⚠️ $key appears to be too short for a secret (${#value} characters)"
((issues++))
fi
fi
# Check for hardcoded URLs in production
if [[ "$file" == *"production"* && "$value" =~ localhost ]]; then
echo "⚠️ $key contains localhost in production environment"
((issues++))
fi
done < "$file"
done
# Check if .env files are in .gitignore
if [ -f .gitignore ]; then
if ! grep -q ".env.local" .gitignore; then
echo "⚠️ .env.local not in .gitignore"
((issues++))
fi
if ! grep -q ".env.production" .gitignore; then
echo "⚠️ .env.production not in .gitignore"
((issues++))
fi
else
echo "⚠️ No .gitignore file found"
((issues++))
fi
echo ""
if [ $issues -eq 0 ]; then
echo "✅ No security issues found"
else
echo "❌ Found $issues security issues"
exit 1
fi
}
security_checkAutomation and Integration
1. GitHub Actions Integration
yaml
# .github/workflows/env-sync.yml
name: Environment Sync
on:
push:
branches: [main, develop]
paths: ['.env.example', '.env.*']
workflow_dispatch:
inputs:
action:
description: 'Sync action'
required: true
default: 'validate'
type: choice
options:
- validate
- pull
- push
jobs:
env-sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Vercel CLI
run: npm i -g vercel@latest
- name: Link to Vercel
run: vercel link --token=${{ secrets.VERCEL_TOKEN }} --yes
- name: Validate Environment
if: github.event.inputs.action == 'validate' || github.event.inputs.action == ''
run: |
# Run environment validation
node scripts/validate-env.js
- name: Pull Environment
if: github.event.inputs.action == 'pull'
run: |
vercel env pull .env.ci --token=${{ secrets.VERCEL_TOKEN }}
# Validate pulled environment
node scripts/validate-env.js .env.ci
- name: Push Environment
if: github.event.inputs.action == 'push'
run: |
# Push environment variables to Vercel
node scripts/push-env.js --token=${{ secrets.VERCEL_TOKEN }}2. Development Workflow Integration
bash
#!/bin/bash
# Pre-commit hook for environment validation
echo "🔍 Validating environment variables..."
# Check if environment files are properly configured
if [ -f .env.local ]; then
echo "✅ .env.local exists"
else
echo "⚠️ .env.local not found"
if [ -f .env.example ]; then
echo "💡 Copy .env.example to .env.local and configure your variables"
fi
fi
# Validate environment structure
node scripts/validate-env.js
if [ $? -ne 0 ]; then
echo "❌ Environment validation failed"
exit 1
fi
echo "✅ Environment validation passed"Comprehensive Sync Operations
Implement complete environment synchronization workflow with validation, backup, security checks, and automation integration for seamless development and deployment processes.