Script Execution Context
Every Xec script runs within a rich execution context that provides access to targets, configuration, and utility functions. This guide explains the execution context and how to leverage it effectively.
Understanding β
Xec provides two primary execution engines in your scripts:
// $target - Executes on the configured target (local, SSH, Docker, or Kubernetes)
await $target`ls -la`;
// $ - Always executes locally, regardless of target
await $`pwd`;
Global Context Variablesβ
When a script executes, Xec injects several global variables:
Core Execution Variablesβ
// Primary execution engines
$target // ExecutionEngine - Target-specific command execution
$targetInfo // TargetInfo - Information about the current target
$ // ExecutionEngine - Local command execution
// Script metadata
__filename // string - Absolute path to the script file
__dirname // string - Directory containing the script
__script // ScriptInfo - Complete script metadata
// Script arguments
args // string[] - Arguments passed to the script
argv // string[] - Full argv array (includes script path)
params // Record<string, any> - Parsed named parameters
Target Informationβ
The $targetInfo
object provides details about the execution target:
interface TargetInfo {
type: 'local' | 'ssh' | 'docker' | 'k8s';
name?: string;
host?: string; // SSH targets
container?: string; // Docker targets
pod?: string; // Kubernetes targets
namespace?: string; // Kubernetes targets
config: any; // Full target configuration
}
Example usage:
if ($targetInfo) {
console.log(`Executing on ${$targetInfo.type} target: ${$targetInfo.name}`);
switch ($targetInfo.type) {
case 'ssh':
console.log(`Connected to: ${$targetInfo.host}`);
break;
case 'docker':
console.log(`Container: ${$targetInfo.container}`);
break;
case 'k8s':
console.log(`Pod: ${$targetInfo.pod} in namespace ${$targetInfo.namespace}`);
break;
}
}
Configuration Accessβ
Scripts have direct access to the configuration system:
// Access configuration API
const allTargets = config.get('targets');
const tasks = config.get('tasks');
const variables = config.get('vars');
// Get specific configuration values
const apiUrl = config.get('vars.api_url');
const sshConfig = config.get('targets.production');
// Reload configuration
await config.reload();
// Access resolved variables
console.log('Environment:', vars.environment);
console.log('Version:', vars.version);
Task and Target APIsβ
Scripts can interact with tasks and targets programmatically:
// Task API
await tasks.run('build');
await tasks.run('deploy', { environment: 'staging' });
const taskList = await tasks.list();
const exists = await tasks.exists('test');
// Target API
const sshTargets = await targets.list('ssh');
const prodTarget = await targets.get('production');
await targets.execute('staging', 'ls -la');
Utility Functionsβ
Several utility functions are available globally:
// Terminal colors with chalk
console.log(chalk.green('Success!'));
console.log(chalk.red.bold('Error!'));
// File globbing
const files = await glob('**/*.js');
const configs = await glob('config/*.yaml');
// Pattern matching
if (minimatch('src/index.js', '**/*.js')) {
console.log('File matches pattern');
}
Working with Multiple Targetsβ
Scripts can be executed against different targets:
# Execute on SSH target
xec run script.js --target production
# Execute on Docker container
xec run script.js --target my-container
# Execute on Kubernetes pod
xec run script.js --target my-pod
In the script:
// script.js
if ($targetInfo?.type === 'ssh') {
// SSH-specific logic
await $target`sudo systemctl restart nginx`;
} else if ($targetInfo?.type === 'docker') {
// Docker-specific logic
await $target`apt-get update && apt-get install -y curl`;
} else if ($targetInfo?.type === 'k8s') {
// Kubernetes-specific logic
await $target`kubectl get pods`;
} else {
// Local execution
await $`echo "Running locally"`;
}
// Always execute locally regardless of target
await $`echo "This always runs on the host"`;
Parameter Parsingβ
Scripts automatically parse command-line parameters:
// Script called with: xec run deploy.js --env=prod --version=1.2.3 --force
console.log(params.env); // 'prod'
console.log(params.version); // '1.2.3'
console.log(params.force); // true
// Type conversion is automatic
// --port=3000 becomes number 3000
// --enabled=true becomes boolean true
// --config='{"key":"value"}' becomes object
Context Isolationβ
Each script runs in its own context with proper cleanup:
// Variables set in one script don't affect others
globalThis.myVar = 'test';
// After script execution, global variables are cleaned up
// This prevents cross-script contamination
REPL Contextβ
When running in REPL mode, additional helpers are available:
// Start REPL
xec run --repl
// In REPL:
> help() // Show available commands
> clear() // Clear the console
> await $`ls` // Execute commands
> config.get() // Access configuration
Custom Context Extensionβ
Scripts can extend their context programmatically:
// extend-context.js
import { $ } from '@xec-sh/core';
// Add custom utilities to the context
global.utils = {
async deployToAll(targets) {
for (const target of targets) {
console.log(`Deploying to ${target}...`);
await targets.execute(target, 'npm run deploy');
}
},
formatBytes(bytes) {
const sizes = ['B', 'KB', 'MB', 'GB'];
if (bytes === 0) return '0 B';
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
}
};
// Use the extended context
await utils.deployToAll(['staging', 'production']);
Environment Variablesβ
Scripts inherit the process environment with Xec-specific additions:
// Xec environment variables
console.log(process.env.XEC_TARGET); // Current target name
console.log(process.env.XEC_TARGET_TYPE); // Target type (ssh, docker, k8s)
console.log(process.env.XEC_DEBUG); // Debug mode flag
console.log(process.env.XEC_CONFIG_PATH); // Path to config file
// Pass environment variables to commands
process.env.API_KEY = 'secret';
await $`echo $API_KEY`;
// Or use env option
await $`node script.js`.env({ API_KEY: 'secret' });
Script Info Objectβ
The __script
object provides complete script metadata:
interface ScriptInfo {
path: string; // Full path to the script
args: string[]; // Script arguments
target?: Target; // Target configuration if specified
}
Usage example:
console.log('Script:', __script.path);
console.log('Arguments:', __script.args);
if (__script.target) {
console.log('Running on target:', __script.target.name);
}
Best Practicesβ
-
Check for target availability:
if (typeof $target !== 'undefined') {
// Target-specific code
} -
Use type guards for target types:
if ($targetInfo?.type === 'ssh') {
// SSH-specific operations
} -
Provide fallbacks for local execution:
const engine = $target || $;
await engine`ls -la`; -
Clean up resources:
try {
// Your script logic
} finally {
// Cleanup code runs even on error
} -
Document expected parameters:
// deploy.js
// Usage: xec run deploy.js --env=<environment> --version=<version>
if (!params.env || !params.version) {
console.error('Required parameters: --env and --version');
process.exit(1);
}
Complete Exampleβ
Here's a comprehensive example using the execution context:
// multi-target-deploy.js
import { $ } from '@xec-sh/core';
import chalk from 'chalk';
async function main() {
// Check if running against a target
if ($targetInfo) {
console.log(chalk.blue(`Deploying to ${$targetInfo.type} target: ${$targetInfo.name}`));
// Target-specific deployment
switch ($targetInfo.type) {
case 'ssh':
await deployToSSH();
break;
case 'docker':
await deployToDocker();
break;
case 'k8s':
await deployToKubernetes();
break;
default:
await deployLocal();
}
} else {
// No target specified, deploy locally
await deployLocal();
}
// Always run post-deployment tasks locally
await $`echo "Deployment complete" >> deployment.log`;
await $`date >> deployment.log`;
}
async function deployToSSH() {
console.log(`Connecting to ${$targetInfo.host}...`);
await $target`cd /app && git pull`;
await $target`npm install`;
await $target`npm run build`;
await $target`sudo systemctl restart app`;
}
async function deployToDocker() {
console.log(`Deploying to container ${$targetInfo.container}...`);
await $target`apt-get update`;
await $target`cd /app && npm install`;
await $target`npm run build`;
}
async function deployToKubernetes() {
console.log(`Deploying to pod ${$targetInfo.pod}...`);
await $target`cd /app && npm ci`;
await $target`npm run build`;
// Restart the pod
await $`kubectl rollout restart deployment/${$targetInfo.pod}`;
}
async function deployLocal() {
console.log('Deploying locally...');
await $`npm install`;
await $`npm run build`;
await $`npm run start`;
}
// Execute with error handling
try {
await main();
console.log(chalk.green('β
Deployment successful!'));
} catch (error) {
console.error(chalk.red('β Deployment failed:'), error.message);
process.exit(1);
}
Run this script with different targets:
# Deploy locally
xec run multi-target-deploy.js
# Deploy to SSH server
xec run multi-target-deploy.js --target production
# Deploy to Docker container
xec run multi-target-deploy.js --target app-container
# Deploy to Kubernetes pod
xec run multi-target-deploy.js --target app-pod