Cloudflare Workers Environment Setup Script
Automated setup tool for Cloudflare Workers resources (KV, R2, D1, Secrets)
Metadata
- Author: ropean
- Version: 1.0.0
- Dependencies: wrangler
Code
javascript
#!/usr/bin/env node
/**
* @title Cloudflare Workers Environment Setup Script
* @description Automated setup tool for Cloudflare Workers resources (KV, R2, D1, Secrets)
* @version 1.0.0
* @author ropean
*
* This script automates the setup of Cloudflare Workers resources including:
* - KV Namespaces (Key-Value storage)
* - R2 Buckets (Object storage)
* - D1 Databases (SQL database)
* - JWT Secrets (Authentication)
*
* @example
* node cloudflare-setup.js
* node cloudflare-setup.js --env production
*
* @requires wrangler
*
* Usage:
* node scripts/cloudflare-setup.js # local (default) - creates .dev.vars
* node scripts/cloudflare-setup.js --env local # same as above
* node scripts/cloudflare-setup.js --env dev # dev environment
* node scripts/cloudflare-setup.js --env preview # preview environment
* node scripts/cloudflare-setup.js --env staging # staging environment
* node scripts/cloudflare-setup.js --env production # production environment
*
* Environment Behavior:
* ┌─────────────┬────────────────────────────────────────────────────────────┐
* │ Environment │ Behavior │
* ├─────────────┼────────────────────────────────────────────────────────────┤
* │ local │ • Generates JWT_SECRET to .dev.vars (for wrangler dev) │
* │ │ • No Cloudflare resources created │
* │ │ • Uses local simulated storage │
* ├─────────────┼────────────────────────────────────────────────────────────┤
* │ dev │ • Creates resources with -dev suffix │
* │ │ • Sets JWT_SECRET via wrangler secret put │
* │ │ • Generates wrangler.toml configuration │
* ├─────────────┼────────────────────────────────────────────────────────────┤
* │ preview │ • Creates resources with -preview suffix │
* │ │ • Temporary environment for testing/PRs │
* │ │ • Sets JWT_SECRET via wrangler secret put │
* ├─────────────┼────────────────────────────────────────────────────────────┤
* │ staging │ • Creates resources with -staging suffix │
* │ │ • Pre-production testing environment │
* │ │ • Sets JWT_SECRET via wrangler secret put │
* ├─────────────┼────────────────────────────────────────────────────────────┤
* │ production │ • Creates resources without suffix │
* │ │ • Production environment │
* │ │ • Sets JWT_SECRET via wrangler secret put │
* └─────────────┴────────────────────────────────────────────────────────────┘
*
* Resource Naming Convention:
* production: api-kit-users
* dev: api-kit-users-dev
* preview: api-kit-users-preview
* staging: api-kit-users-staging
*
* Features:
* • Smart caching: Lists resources once, checks from memory
* • Idempotent: Safe to run multiple times (skips existing resources)
* • Filtered output: Only shows resources defined in RESOURCES config
* • Auto-generates wrangler.toml configuration
* • Optional save to timestamped backup file
*
* Prerequisites:
* • Node.js installed
* • Wrangler CLI installed (pnpm install -g wrangler)
* • Logged in to Cloudflare (wrangler login)
*
* Examples:
* # Local development (no cloud resources)
* node scripts/cloudflare-setup.js
*
* # Setup dev environment
* node scripts/cloudflare-setup.js --env dev
*
* # Setup production
* node scripts/cloudflare-setup.js --env production
*/
import { execSync } from 'child_process';
import { createInterface } from 'readline';
import { existsSync, readFileSync, writeFileSync, appendFileSync } from 'fs';
import { randomBytes } from 'crypto';
// ============================================================================
// Configuration
// ============================================================================
/**
* Resource definitions for the API Kit project
* Only resources defined here will be created and shown in output
*/
const RESOURCES = {
kv: [
{ binding: 'USERS_KV', baseName: 'api-kit-users' },
{ binding: 'CONFIG_KV', baseName: 'api-kit-config' },
{ binding: 'RATE_LIMIT_KV', baseName: 'api-kit-ratelimit' },
{ binding: 'STATS_KV', baseName: 'api-kit-stats' },
],
r2: [
{ binding: 'FILE_BUCKET', baseName: 'api-kit-files' },
{ binding: 'LOG_ARCHIVE', baseName: 'api-kit-logs' },
],
d1: [
{ binding: 'DB', baseName: 'api-kit-logs' },
],
analytics: [
{ binding: 'ANALYTICS' },
],
};
const VALID_ENVIRONMENTS = ['local', 'dev', 'preview', 'staging', 'production'];
// ============================================================================
// Utility Functions
// ============================================================================
/**
* Execute shell command and return output
* @param {string} command - Command to execute
* @returns {string} Command output
*/
function exec(command) {
try {
return execSync(command, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
} catch (error) {
throw new Error(`Command failed: ${command}\n${error.message}`);
}
}
/**
* Execute shell command silently (ignore errors)
* @param {string} command - Command to execute
* @returns {string|null} Command output or null if failed
*/
function execSilent(command) {
try {
return execSync(command, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
} catch (error) {
return null;
}
}
/**
* Generate random base64 secret
* @returns {string} Random secret string
*/
function generateSecret() {
return randomBytes(32).toString('base64');
}
/**
* Get resource name with environment suffix
* @param {string} baseName - Base resource name
* @param {string} env - Environment name
* @returns {string} Resource name with suffix
*/
function getResourceName(baseName, env) {
if (env === 'production') {
return baseName; // production has no suffix
}
return `${baseName}-${env}`;
}
/**
* Parse command line arguments
* @returns {string} Environment name
*/
function parseArgs() {
const args = process.argv.slice(2);
// Default to local if no args
if (args.length === 0) {
return 'local';
}
// Parse --env flag
const envIndex = args.indexOf('--env');
if (envIndex !== -1 && args[envIndex + 1]) {
const env = args[envIndex + 1];
if (!VALID_ENVIRONMENTS.includes(env)) {
console.error(`❌ Invalid environment: "${env}"\n`);
console.error('Valid environments:');
console.error(' • local - Local development (wrangler dev)');
console.error(' • dev - Development environment');
console.error(' • preview - Preview/temporary environment');
console.error(' • staging - Pre-production staging');
console.error(' • production - Production environment\n');
console.error('Usage:');
console.error(' node scripts/cloudflare-setup.js # defaults to local');
console.error(' node scripts/cloudflare-setup.js --env local # local environment');
console.error(' node scripts/cloudflare-setup.js --env dev # dev environment');
console.error(' node scripts/cloudflare-setup.js --env preview # preview environment');
console.error(' node scripts/cloudflare-setup.js --env staging # staging environment');
console.error(' node scripts/cloudflare-setup.js --env production # production environment');
process.exit(1);
}
return env;
}
console.error('❌ Invalid arguments. Use --env flag to specify environment.\n');
console.error('Usage:');
console.error(' node scripts/cloudflare-setup.js --env <environment>\n');
console.error('Example:');
console.error(' node scripts/cloudflare-setup.js --env dev');
process.exit(1);
}
/**
* Prompt user for yes/no input
* @param {string} question - Question to ask
* @returns {Promise<boolean>} True if user answered yes
*/
function prompt(question) {
const rl = createInterface({
input: process.stdin,
output: process.stdout,
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(['y', 'yes'].includes(answer.toLowerCase().trim()));
});
});
}
// ============================================================================
// Local Environment Setup
// ============================================================================
/**
* Setup local environment (.dev.vars)
*/
function setupLocalEnvironment() {
console.log('=======================================');
console.log('API Kit - Local Environment Setup');
console.log('=======================================\n');
console.log('📝 Local environment uses wrangler dev with simulated resources');
console.log('📝 No real Cloudflare resources will be created\n');
const devVarsPath = '.dev.vars';
const jwtSecretLine = 'JWT_SECRET';
// Generate JWT secret
const jwtSecret = generateSecret();
// Check if .dev.vars exists
if (existsSync(devVarsPath)) {
const content = readFileSync(devVarsPath, 'utf-8');
// Check if JWT_SECRET already exists
if (content.includes(jwtSecretLine)) {
console.log('✅ JWT_SECRET already exists in .dev.vars');
console.log(' Skipping to avoid overwriting existing secret\n');
} else {
// Append JWT_SECRET
const newLine = content.endsWith('\n') ? '' : '\n';
appendFileSync(devVarsPath, `${newLine}JWT_SECRET=${jwtSecret}\n`);
console.log('✅ JWT_SECRET added to .dev.vars');
console.log(` Secret: ${jwtSecret}\n`);
}
} else {
// Create .dev.vars from example if it exists
if (existsSync('.dev.vars.example')) {
const exampleContent = readFileSync('.dev.vars.example', 'utf-8');
const updatedContent = exampleContent.replace(
/# JWT_SECRET=.*/,
`JWT_SECRET=${jwtSecret}`
);
writeFileSync(devVarsPath, updatedContent);
console.log('✅ Created .dev.vars from .dev.vars.example');
console.log(`✅ JWT_SECRET set: ${jwtSecret}\n`);
} else {
// Create new .dev.vars
writeFileSync(devVarsPath, `JWT_SECRET=${jwtSecret}\n`);
console.log('✅ Created .dev.vars');
console.log(`✅ JWT_SECRET set: ${jwtSecret}\n`);
}
}
console.log('=======================================');
console.log('✅ Local Setup Complete!');
console.log('=======================================\n');
console.log('Next steps:');
console.log(' 1. Run: pnpm install (or pnpm install)');
console.log(' 2. Run: pnpm run build');
console.log(' 3. Run: wrangler dev\n');
}
// ============================================================================
// Resource Manager (with caching)
// ============================================================================
class ResourceManager {
constructor(env) {
this.env = env;
this.kvCache = null;
this.r2Cache = null;
this.d1Cache = null;
}
/**
* Get KV namespaces list (cached)
* @param {boolean} forceRefresh - Force refresh cache
* @returns {Array} List of KV namespaces
*/
getKVList(forceRefresh = false) {
if (!this.kvCache || forceRefresh) {
console.log('📋 Fetching KV namespaces...');
const output = exec('wrangler kv namespace list');
try {
this.kvCache = JSON.parse(output);
} catch (error) {
// If parsing fails, assume no namespaces exist
console.log(' ⚠️ Warning: Could not parse KV list output');
this.kvCache = [];
}
}
return this.kvCache;
}
/**
* Get R2 buckets list (cached)
* @param {boolean} forceRefresh - Force refresh cache
* @returns {Array} List of R2 buckets
*/
getR2List(forceRefresh = false) {
if (!this.r2Cache || forceRefresh) {
console.log('📋 Fetching R2 buckets...');
try {
const output = exec('wrangler r2 bucket list');
// Parse text output format:
// name: api-kit-files
// creation_date: 2025-10-21T17:18:30.289Z
const buckets = [];
const lines = output.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line.startsWith('name:')) {
const name = line.replace('name:', '').trim();
if (name) {
buckets.push({ name });
}
}
}
this.r2Cache = buckets;
} catch (error) {
console.log(' ⚠️ Warning: Could not fetch R2 buckets');
this.r2Cache = [];
}
}
return this.r2Cache;
}
/**
* Get D1 databases list (cached)
* @param {boolean} forceRefresh - Force refresh cache
* @returns {Array} List of D1 databases
*/
getD1List(forceRefresh = false) {
if (!this.d1Cache || forceRefresh) {
console.log('📋 Fetching D1 databases...');
try {
const output = exec('wrangler d1 list');
// Parse table output format:
// │ uuid │ name │ created_at │ ...
// │ 22bc420e-0672-4f5d-9e6b-8fcdaa34592d │ api-kit-logs │ 2025-10-22T04:02:57.242Z │ ...
const databases = [];
const lines = output.split('\n');
for (const line of lines) {
// Skip header, separator, and empty lines
if (!line.includes('│') || line.includes('uuid') || line.includes('─')) {
continue;
}
// Split by │ and extract uuid and name
const parts = line.split('│').map(p => p.trim()).filter(p => p);
if (parts.length >= 2) {
const uuid = parts[0];
const name = parts[1];
// Validate UUID format (contains hyphens)
if (uuid.includes('-') && name && !name.includes('name')) {
databases.push({
database_id: uuid,
name: name
});
}
}
}
this.d1Cache = databases;
} catch (error) {
console.log(' ⚠️ Warning: Could not fetch D1 databases');
this.d1Cache = [];
}
}
return this.d1Cache;
}
/**
* Check if KV namespace exists
* @param {string} name - KV namespace name
* @returns {object|null} KV namespace object or null
*/
findKV(name) {
const list = this.getKVList();
return list.find(kv => kv.title === name) || null;
}
/**
* Check if R2 bucket exists
* @param {string} name - R2 bucket name
* @returns {object|null} R2 bucket object or null
*/
findR2(name) {
const list = this.getR2List();
return list.find(bucket => bucket.name === name) || null;
}
/**
* Check if D1 database exists
* @param {string} name - D1 database name
* @returns {object|null} D1 database object or null
*/
findD1(name) {
const list = this.getD1List();
return list.find(db => db.name === name) || null;
}
/**
* Create KV namespace
* @param {string} name - KV namespace name
* @returns {object} Created KV namespace
*/
createKV(name) {
console.log(` 🔨 Creating KV namespace: ${name}...`);
const output = exec(`wrangler kv namespace create "${name}"`);
this.kvCache = null; // Clear cache
// Parse ID from output
const match = output.match(/id = "([a-f0-9]+)"/);
if (!match) {
throw new Error('Failed to parse KV namespace ID from output');
}
return { title: name, id: match[1] };
}
/**
* Create R2 bucket
* @param {string} name - R2 bucket name
* @returns {object} Created R2 bucket
*/
createR2(name) {
console.log(` 🔨 Creating R2 bucket: ${name}...`);
exec(`wrangler r2 bucket create "${name}"`);
this.r2Cache = null; // Clear cache
// Fetch to get details
const list = this.getR2List(true);
return list.find(bucket => bucket.name === name);
}
/**
* Create D1 database
* @param {string} name - D1 database name
* @returns {object} Created D1 database
*/
createD1(name) {
console.log(` 🔨 Creating D1 database: ${name}...`);
const output = exec(`wrangler d1 create "${name}"`);
this.d1Cache = null; // Clear cache
// Parse database_id from output
const match = output.match(/database_id = "([a-f0-9-]+)"/);
if (!match) {
throw new Error('Failed to parse D1 database ID from output');
}
return { name: name, database_id: match[1] };
}
/**
* Initialize D1 database schema
* @param {string} name - D1 database name
*/
initD1Schema(name) {
const schemaPath = 'database/schema.sql';
if (!existsSync(schemaPath)) {
console.log(` ⚠️ Schema file not found: ${schemaPath}`);
console.log(` ⚠️ Skipping schema initialization`);
return;
}
console.log(` 🔧 Initializing database schema...`);
// Build command with environment flag if needed
let command = `wrangler d1 execute "${name}" --file="${schemaPath}"`;
if (this.env !== 'production') {
command += ` --env ${this.env}`;
}
const result = execSilent(command);
if (result !== null) {
console.log(` ✅ Schema initialized successfully`);
} else {
console.log(` ⚠️ Schema initialization may have failed (might be normal if tables exist)`);
}
}
}
// ============================================================================
// Cloud Environment Setup
// ============================================================================
/**
* Setup cloud environment (dev/preview/staging/production)
* @param {string} env - Environment name
*/
async function setupCloudEnvironment(env) {
console.log('=======================================');
console.log(`API Kit - Setup Script (${env.toUpperCase()})`);
console.log('=======================================\n');
// Check prerequisites
console.log('🔍 Checking prerequisites...');
// Check if wrangler is installed
if (!execSilent('wrangler --version')) {
console.error('❌ Wrangler CLI is not installed!');
console.error(' Install it with: pnpm install -g wrangler\n');
process.exit(1);
}
console.log(' ✅ Wrangler CLI is installed');
// Check if logged in
if (!execSilent('wrangler whoami')) {
console.error('❌ Not logged in to Cloudflare!');
console.error(' Run: wrangler login\n');
process.exit(1);
}
console.log(' ✅ Logged in to Cloudflare\n');
const manager = new ResourceManager(env);
const createdResources = {
kv: [],
r2: [],
d1: [],
};
// ============================================================
// Setup KV Namespaces
// ============================================================
console.log('📦 Setting up KV Namespaces...\n');
for (const resource of RESOURCES.kv) {
const name = getResourceName(resource.baseName, env);
console.log(`Setting up ${resource.binding} (${name})...`);
const existing = manager.findKV(name);
if (existing) {
console.log(` ✅ Already exists (ID: ${existing.id})`);
createdResources.kv.push({
binding: resource.binding,
name: name,
id: existing.id,
});
} else {
const created = manager.createKV(name);
console.log(` ✅ Created successfully (ID: ${created.id})`);
createdResources.kv.push({
binding: resource.binding,
name: created.title,
id: created.id,
});
}
console.log('');
}
// ============================================================
// Setup R2 Buckets
// ============================================================
console.log('📦 Setting up R2 Buckets...\n');
for (const resource of RESOURCES.r2) {
const name = getResourceName(resource.baseName, env);
console.log(`Setting up ${resource.binding} (${name})...`);
const existing = manager.findR2(name);
if (existing) {
console.log(` ✅ Already exists`);
createdResources.r2.push({
binding: resource.binding,
bucket_name: existing.name,
});
} else {
const created = manager.createR2(name);
console.log(` ✅ Created successfully`);
createdResources.r2.push({
binding: resource.binding,
bucket_name: created.name,
});
}
console.log('');
}
// ============================================================
// Setup D1 Databases
// ============================================================
console.log('📦 Setting up D1 Databases...\n');
for (const resource of RESOURCES.d1) {
const name = getResourceName(resource.baseName, env);
console.log(`Setting up ${resource.binding} (${name})...`);
const existing = manager.findD1(name);
if (existing) {
console.log(` ✅ Already exists (ID: ${existing.database_id})`);
createdResources.d1.push({
binding: resource.binding,
database_name: existing.name,
database_id: existing.database_id,
});
} else {
const created = manager.createD1(name);
console.log(` ✅ Created successfully (ID: ${created.database_id})`);
createdResources.d1.push({
binding: resource.binding,
database_name: created.name,
database_id: created.database_id,
});
// Initialize schema for newly created database
manager.initD1Schema(name);
}
console.log('');
}
// ============================================================
// Setup JWT Secret
// ============================================================
console.log('🔐 Setting up JWT Secret...\n');
// Check if secret already exists
const existingSecrets = execSilent('wrangler secret list');
if (existingSecrets && existingSecrets.includes('JWT_SECRET')) {
console.log('✅ JWT_SECRET already exists');
console.log(' Skipping to avoid overwriting existing secret\n');
} else {
const jwtSecret = generateSecret();
console.log(`Generated secret: ${jwtSecret}`);
const command = `wrangler secret put JWT_SECRET${env !== 'production' ? ` --env ${env}` : ''}`;
console.log(' Setting secret...');
try {
execSync(command, { input: jwtSecret, encoding: 'utf-8' });
console.log(' ✅ JWT_SECRET set successfully\n');
} catch (error) {
console.log(' ⚠️ Failed to set secret (you may need to set it manually)');
console.log(` Run: echo "${jwtSecret}" | wrangler secret put JWT_SECRET${env !== 'production' ? ` --env ${env}` : ''}\n`);
}
}
// ============================================================
// Generate wrangler.toml configuration
// ============================================================
console.log('📋 Generated Configuration:');
console.log('=======================================\n');
const config = generateWranglerConfig(env, createdResources);
console.log(config);
console.log('=======================================\n');
// Ask if user wants to save to file
const shouldSave = await prompt('💾 Save configuration to file? (y/n): ');
if (shouldSave) {
const timestamp = Date.now();
const filename = `wrangler.${env}.${timestamp}.toml`;
writeFileSync(filename, config);
console.log(`✅ Configuration saved to: ${filename}\n`);
}
// ============================================================
// Summary
// ============================================================
console.log('=======================================');
console.log('✅ Setup Complete!');
console.log('=======================================\n');
console.log('📊 Summary:');
console.log(` ✅ ${createdResources.kv.length} KV Namespaces`);
console.log(` ✅ ${createdResources.r2.length} R2 Buckets`);
console.log(` ✅ ${createdResources.d1.length} D1 Databases`);
console.log(` ✅ JWT_SECRET configured\n`);
console.log('Next steps:');
console.log(' 1. Copy the configuration above into your wrangler.toml');
console.log(' 2. Run: pnpm install (or pnpm install)');
console.log(' 3. Run: pnpm run build');
console.log(` 4. Deploy: wrangler deploy${env !== 'production' ? ` --env ${env}` : ''}\n`);
}
/**
* Generate wrangler.toml configuration
* @param {string} env - Environment name
* @param {object} resources - Created resources
* @returns {string} wrangler.toml configuration
*/
function generateWranglerConfig(env, resources) {
const lines = [];
// Add environment header for non-production
if (env !== 'production') {
lines.push(`[env.${env}]`);
lines.push('');
}
const prefix = env === 'production' ? '' : `env.${env}.`;
// KV Namespaces
for (const kv of resources.kv) {
lines.push(`[[${prefix}kv_namespaces]]`);
lines.push(`binding = "${kv.binding}"`);
lines.push(`id = "${kv.id}"`);
lines.push('');
}
// R2 Buckets
for (const r2 of resources.r2) {
lines.push(`[[${prefix}r2_buckets]]`);
lines.push(`binding = "${r2.binding}"`);
lines.push(`bucket_name = "${r2.bucket_name}"`);
lines.push('');
}
// D1 Databases
for (const d1 of resources.d1) {
lines.push(`[[${prefix}d1_databases]]`);
lines.push(`binding = "${d1.binding}"`);
lines.push(`database_name = "${d1.database_name}"`);
lines.push(`database_id = "${d1.database_id}"`);
lines.push('');
}
// Analytics Engine Datasets (binding only, no ID needed)
for (const analytics of RESOURCES.analytics) {
lines.push(`[[${prefix}analytics_engine_datasets]]`);
lines.push(`binding = "${analytics.binding}${env === 'production' ? '' : `-${env}`}"`);
lines.push('');
}
return lines.join('\n');
}
// ============================================================================
// Main Entry Point
// ============================================================================
async function main() {
const env = parseArgs();
if (env === 'local') {
setupLocalEnvironment();
} else {
await setupCloudEnvironment(env);
}
}
// Run the script
main().catch((error) => {
console.error('\n❌ Setup failed!');
console.error(error.message);
process.exit(1);
});File Information
- Filename:
cloudflare-setup.js - Category: serverless
- Language: JAVASCRIPT