UNPKG

@glyphtek/scriptit

Version:

A cross-runtime CLI and library for running scripts with environment management, TUI, and support for lambda functions. Optimized for Bun with compatibility for Node.js and Deno.

711 lines (546 loc) 18.1 kB
# Environment Variables ScriptIt provides comprehensive environment variable management, allowing you to configure scripts for different environments, manage secrets securely, and access environment data within your scripts. ## Overview Environment variables in ScriptIt can be set through: 1. **Interactive prompts** - `--env-prompts` flag or script `variables` export 2. **Command-line flags** - `--env VAR=value` 3. **Configuration files** - `scriptit.config.js` defaultParams 4. **`.env` files** - Loaded automatically 5. **System environment variables** - `export VAR=value` ## Environment Variable Priority Variables are applied in the following order (highest to lowest priority): 1. **Interactive prompts** - Variables collected via `--env-prompts` or script `variables` export 2. **Command-line flags** - `--env VAR=value` 3. **Configuration file** - `defaultParams` property in config 4. **`.env` files** - Loaded from project root and other configured files 5. **System environment variables** - `export VAR=value` **Note:** Variables that are already set (from any source) will not be prompted for interactively, ensuring no accidental overwrites. ## Using .env Files ### Basic .env File Create a `.env` file in your project root: ```env # .env NODE_ENV=development DEBUG=true API_KEY=your-api-key-here DATABASE_URL=postgresql://localhost:5432/mydb PORT=3000 # Comments are supported # Use quotes for values with spaces APP_NAME="My ScriptIt App" DESCRIPTION="A powerful script runner" # Multi-line values (use quotes) PRIVATE_KEY="-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7... -----END PRIVATE KEY-----" ``` ### Environment-Specific .env Files Create different `.env` files for different environments: ``` project/ ├── .env # Default/development ├── .env.local # Local overrides (gitignored) ├── .env.development # Development environment ├── .env.staging # Staging environment ├── .env.production # Production environment └── .env.test # Test environment ``` #### .env.development ```env NODE_ENV=development DEBUG=true API_URL=http://localhost:3000 DATABASE_URL=postgresql://localhost:5432/myapp_dev LOG_LEVEL=debug ``` #### .env.production ```env NODE_ENV=production DEBUG=false API_URL=https://api.myapp.com DATABASE_URL=postgresql://prod-server:5432/myapp_prod LOG_LEVEL=warn ``` ### Loading Environment-Specific Files ```bash # Load development environment NODE_ENV=development scriptit exec script.js # Load production environment NODE_ENV=production scriptit exec script.js # Load staging environment NODE_ENV=staging scriptit exec script.js ``` ## Accessing Environment Variables ### In Scripts Access environment variables through the `context.env` object: ```javascript // script.js export async function execute(context) { const console = context.console || global.console; // Access environment variables const nodeEnv = context.env.NODE_ENV; const apiKey = context.env.API_KEY; const dbUrl = context.env.DATABASE_URL; console.log('Environment:', nodeEnv); console.log('API Key:', apiKey ? '***' : 'Not set'); console.log('Database URL:', dbUrl ? '***' : 'Not set'); // Validate required variables if (!apiKey) { throw new Error('API_KEY environment variable is required'); } return { environment: nodeEnv, hasApiKey: !!apiKey }; } ``` ### Environment Validation ```javascript // validate-env.js export const description = "Validate required environment variables"; const REQUIRED_VARS = [ 'NODE_ENV', 'API_KEY', 'DATABASE_URL' ]; const OPTIONAL_VARS = [ 'DEBUG', 'PORT', 'LOG_LEVEL' ]; export async function execute(context) { const console = context.console || global.console; console.info('🔍 Validating environment variables...'); const missing = []; const present = []; // Check required variables for (const varName of REQUIRED_VARS) { if (context.env[varName]) { present.push(varName); console.log(`✅ ${varName}: Set`); } else { missing.push(varName); console.error(`❌ ${varName}: Missing`); } } // Check optional variables for (const varName of OPTIONAL_VARS) { if (context.env[varName]) { console.log(`✅ ${varName}: ${context.env[varName]}`); } else { console.warn(`⚠️ ${varName}: Not set (optional)`); } } if (missing.length > 0) { console.error(`❌ Missing required variables: ${missing.join(', ')}`); throw new Error(`Missing required environment variables: ${missing.join(', ')}`); } console.info('✅ All required environment variables are set'); return { required: present, missing, environment: context.env.NODE_ENV }; } ``` ## Command-Line Environment Variables ### Setting Variables ```bash # Single variable scriptit exec --env NODE_ENV=production script.js # Multiple variables scriptit exec --env NODE_ENV=production --env DEBUG=false --env PORT=8080 script.js # With spaces (use quotes) scriptit exec --env APP_NAME="My App" --env DESCRIPTION="A great app" script.js ``` ### Interactive Environment Prompts ScriptIt supports interactive collection of environment variables, providing a secure way to input sensitive data without exposing it in command history or scripts. #### Using --env-prompts Flag ```bash # Prompt for specific variables before execution scriptit exec --env-prompts API_KEY,SECRET_TOKEN script.js # Space-separated format also works scriptit exec --env-prompts API_KEY SECRET_TOKEN script.js # Mix static and prompted variables scriptit exec --env NODE_ENV=production --env-prompts API_KEY,DATABASE_PASSWORD script.js # Works with TUI as well scriptit run --env-prompts DATABASE_URL,API_KEY ``` #### Declarative Variable Definition in Scripts Scripts can declare their required environment variables using the `variables` export: ```javascript // deployment-script.js export const description = "Deploy application with secure prompts"; // Full definition with custom prompts and types export const variables = [ { name: 'API_KEY', message: 'Enter your API key:', type: 'password' }, { name: 'DEPLOY_ENV', message: 'Target environment (prod/staging):', type: 'input' }, 'CONFIRM_DEPLOY' // Shorthand - generates default prompt ]; export async function execute(context) { const console = context.console || global.console; // Variables are automatically prompted and available in context.env console.log(`Deploying to: ${context.env.DEPLOY_ENV}`); console.log(`API Key: ${context.env.API_KEY ? '***hidden***' : 'Not set'}`); if (context.env.CONFIRM_DEPLOY?.toLowerCase() !== 'yes') { console.warn('Deployment cancelled by user'); return { cancelled: true }; } // Proceed with deployment... return { success: true }; } ``` #### Variable Types - **`input`** (default): Regular text input, content visible as typed - **`password`**: Hidden input with masked characters (`*`) #### Smart Variable Detection ScriptIt automatically detects which variables need to be collected: - **Skip existing** - Variables already set in environment are not prompted - **Combine sources** - CLI `--env-prompts` + script `variables` export - **Precedence handling** - CLI prompts take precedence over script declarations - **Security first** - Prompted values are never logged or displayed ### Combining with Other Options ```bash # With runtime selection scriptit --runtime=bun exec --env NODE_ENV=production script.js # With config file scriptit exec --config prod.config.js --env API_KEY=secret script.js # With working directory scriptit --pwd /app exec --env NODE_ENV=production deploy.js ``` ## Configuration File Environment Variables ### Basic Configuration ```javascript // scriptit.config.js export default { env: { NODE_ENV: 'development', DEBUG: 'true', API_URL: 'http://localhost:3000', LOG_LEVEL: 'debug' } } ``` ### Dynamic Configuration ```javascript // scriptit.config.js export default { env: { NODE_ENV: process.env.NODE_ENV || 'development', DEBUG: process.env.DEBUG || 'false', API_URL: process.env.API_URL || 'http://localhost:3000', // Computed values IS_PRODUCTION: process.env.NODE_ENV === 'production', IS_DEVELOPMENT: process.env.NODE_ENV === 'development', // Default values with fallbacks PORT: process.env.PORT || '3000', HOST: process.env.HOST || 'localhost' } } ``` ### Environment-Specific Configuration ```javascript // scriptit.config.js const environments = { development: { env: { NODE_ENV: 'development', DEBUG: 'true', API_URL: 'http://localhost:3000', LOG_LEVEL: 'debug' } }, staging: { env: { NODE_ENV: 'staging', DEBUG: 'false', API_URL: 'https://staging-api.myapp.com', LOG_LEVEL: 'info' } }, production: { env: { NODE_ENV: 'production', DEBUG: 'false', API_URL: 'https://api.myapp.com', LOG_LEVEL: 'warn' } } }; const currentEnv = process.env.NODE_ENV || 'development'; export default environments[currentEnv]; ``` ## ScriptIt-Specific Environment Variables ### Built-in Variables | Variable | Description | Example | |----------|-------------|---------| | `SCRIPTIT_RUNTIME` | Default runtime to use | `bun`, `deno`, `node` | | `SCRIPTIT_DEBUG` | Enable debug mode | `true`, `false` | | `SCRIPTIT_CONFIG` | Path to config file | `./custom.config.js` | | `SCRIPTIT_SCRIPTS_DIR` | Scripts directory | `./src/scripts` | | `SCRIPTIT_TMP_DIR` | Temporary directory | `./temp` | ### Usage Examples ```bash # Set default runtime export SCRIPTIT_RUNTIME=bun scriptit exec script.js # Enable debug mode export SCRIPTIT_DEBUG=true scriptit run # Custom config file export SCRIPTIT_CONFIG=./prod.config.js scriptit exec deploy.js # Custom directories export SCRIPTIT_SCRIPTS_DIR=./src/scripts export SCRIPTIT_TMP_DIR=./temp scriptit run ``` ## Environment Variable Patterns ### 1. Configuration Pattern ```javascript // config-script.js export const description = "Configuration management script"; export async function execute(context) { const console = context.console || global.console; const config = { environment: context.env.NODE_ENV || 'development', debug: context.env.DEBUG === 'true', api: { url: context.env.API_URL || 'http://localhost:3000', key: context.env.API_KEY, timeout: parseInt(context.env.API_TIMEOUT || '5000') }, database: { url: context.env.DATABASE_URL, pool: { min: parseInt(context.env.DB_POOL_MIN || '2'), max: parseInt(context.env.DB_POOL_MAX || '10') } } }; console.info('📋 Configuration loaded:'); console.log('Environment:', config.environment); console.log('Debug mode:', config.debug); console.log('API URL:', config.api.url); console.log('API Key:', config.api.key ? '***' : 'Not set'); return config; } ``` ### 2. Feature Flags Pattern ```javascript // feature-flags.js export const description = "Feature flags management"; export async function execute(context) { const console = context.console || global.console; const features = { newUI: context.env.FEATURE_NEW_UI === 'true', betaFeatures: context.env.FEATURE_BETA === 'true', analytics: context.env.FEATURE_ANALYTICS !== 'false', // Default true debugging: context.env.FEATURE_DEBUG === 'true' }; console.info('🚩 Feature flags:'); Object.entries(features).forEach(([name, enabled]) => { const status = enabled ? '✅ Enabled' : '❌ Disabled'; console.log(`${name}: ${status}`); }); return features; } ``` ### 3. Secrets Management Pattern ```javascript // secrets.js export const description = "Secure secrets management"; export async function execute(context) { const console = context.console || global.console; const secrets = { apiKey: context.env.API_KEY, dbPassword: context.env.DB_PASSWORD, jwtSecret: context.env.JWT_SECRET, encryptionKey: context.env.ENCRYPTION_KEY }; // Validate all secrets are present const missingSecrets = Object.entries(secrets) .filter(([name, value]) => !value) .map(([name]) => name); if (missingSecrets.length > 0) { console.error('❌ Missing secrets:', missingSecrets.join(', ')); throw new Error(`Missing required secrets: ${missingSecrets.join(', ')}`); } console.info('🔐 All secrets loaded successfully'); // Return without exposing actual values return { secretsLoaded: Object.keys(secrets), allPresent: missingSecrets.length === 0 }; } ``` ## Environment Variable Security ### Best Practices 1. **Never commit secrets to version control:** ```bash # .gitignore .env.local .env.*.local .env.production *.key *.pem ``` 2. **Use different files for different environments:** ```javascript // ✅ Good - environment-specific files .env.development # Development secrets .env.staging # Staging secrets .env.production # Production secrets (never committed) // ❌ Bad - single file with all secrets .env # Contains production secrets ``` 3. **Validate required variables:** ```javascript // ✅ Good - validate at startup export async function execute(context) { const required = ['API_KEY', 'DATABASE_URL', 'JWT_SECRET']; const missing = required.filter(name => !context.env[name]); if (missing.length > 0) { throw new Error(`Missing: ${missing.join(', ')}`); } } // ❌ Bad - fail silently export async function execute(context) { const apiKey = context.env.API_KEY || 'default-key'; } ``` ### Secret Rotation ```javascript // secret-rotation.js export const description = "Check for secret rotation needs"; export async function execute(context) { const console = context.console || global.console; const secrets = [ { name: 'API_KEY', lastRotated: context.env.API_KEY_ROTATED }, { name: 'JWT_SECRET', lastRotated: context.env.JWT_SECRET_ROTATED }, { name: 'DB_PASSWORD', lastRotated: context.env.DB_PASSWORD_ROTATED } ]; const now = new Date(); const rotationThreshold = 90 * 24 * 60 * 60 * 1000; // 90 days secrets.forEach(secret => { if (!secret.lastRotated) { console.warn(`⚠️ ${secret.name}: No rotation date set`); return; } const lastRotated = new Date(secret.lastRotated); const daysSince = Math.floor((now - lastRotated) / (24 * 60 * 60 * 1000)); if (daysSince > 90) { console.error(`❌ ${secret.name}: Needs rotation (${daysSince} days old)`); } else { console.log(`✅ ${secret.name}: OK (${daysSince} days old)`); } }); return { secrets, checkDate: now.toISOString() }; } ``` ## Environment Variable Templates ### Development Template ```env # .env.development NODE_ENV=development DEBUG=true # API Configuration API_URL=http://localhost:3000 API_KEY=dev-api-key-123 API_TIMEOUT=10000 # Database Configuration DATABASE_URL=postgresql://localhost:5432/myapp_dev DB_POOL_MIN=2 DB_POOL_MAX=10 # Feature Flags FEATURE_NEW_UI=true FEATURE_BETA=true FEATURE_ANALYTICS=false FEATURE_DEBUG=true # Logging LOG_LEVEL=debug LOG_FORMAT=pretty # Development Tools HOT_RELOAD=true SOURCE_MAPS=true ``` ### Production Template ```env # .env.production (DO NOT COMMIT) NODE_ENV=production DEBUG=false # API Configuration API_URL=https://api.myapp.com API_KEY=prod-api-key-secure API_TIMEOUT=5000 # Database Configuration DATABASE_URL=postgresql://prod-server:5432/myapp_prod DB_POOL_MIN=5 DB_POOL_MAX=50 # Feature Flags FEATURE_NEW_UI=true FEATURE_BETA=false FEATURE_ANALYTICS=true FEATURE_DEBUG=false # Logging LOG_LEVEL=warn LOG_FORMAT=json # Security JWT_SECRET=super-secure-jwt-secret ENCRYPTION_KEY=32-char-encryption-key-here ``` ## Troubleshooting ### Common Issues **Variable not found:** ```bash ❌ ScriptIt Error: Required environment variable API_KEY not found 💡 Set the variable: export API_KEY=your-key 💡 Or add to .env file: API_KEY=your-key ``` **Invalid .env file:** ```bash ❌ ScriptIt Error: Invalid .env file syntax at line 5 💡 Check for missing quotes or invalid characters ``` **Variable override conflicts:** ```bash ⚠️ Warning: API_KEY overridden by command line 💡 Command-line --env flags take precedence over .env files ``` ### Debug Environment Variables Use debug mode to see environment variable loading: ```bash scriptit --debug exec script.js ``` Output includes: - Loaded .env files - Environment variable sources - Final merged environment - Variable precedence information ### Environment Variable Inspection ```javascript // inspect-env.js export const description = "Inspect environment variables"; export async function execute(context) { const console = context.console || global.console; console.info('🔍 Environment Variables:'); // Group by prefix const groups = {}; Object.keys(context.env).forEach(key => { const prefix = key.split('_')[0]; if (!groups[prefix]) groups[prefix] = []; groups[prefix].push(key); }); // Display grouped variables Object.entries(groups).forEach(([prefix, keys]) => { console.log(`\n📁 ${prefix}:`); keys.forEach(key => { const value = context.env[key]; const displayValue = key.toLowerCase().includes('secret') || key.toLowerCase().includes('key') || key.toLowerCase().includes('password') ? '***' : value; console.log(` ${key}: ${displayValue}`); }); }); return { totalVariables: Object.keys(context.env).length, groups }; } ``` ## Related Documentation - [CLI Commands](/cli/commands) - Command-line environment options - [Configuration](/cli/configuration) - Configuration file environment settings - [Runtime Selection](/cli/runtime) - Runtime-specific environment variables - [Examples](/examples/cli) - Environment variable examples