@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
Markdown
# 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