Local Target Overview
Implementation Reference
Source Files:
packages/core/src/adapters/local-adapter.ts
- Local execution adapter implementationpackages/core/src/utils/shell.ts
- Shell detection and escaping utilitiesapps/xec/src/config/types.ts
- Target type definitions (lines 45-52)packages/core/src/core/execution-engine.ts
- Execution engine integration
Key Classes:
LocalAdapter
- Implements local command executionExecutionEngine
- Core execution orchestrator
Key Functions:
LocalAdapter.execute()
- Main execution method (lines 25-68)LocalAdapter.spawn()
- Process spawning (lines 70-112)detectShell()
- Shell detection logicescapeShellArg()
- Shell argument escaping
Overview
Local targets execute commands directly on the machine where Xec is running. This is the default and simplest execution environment, providing direct access to the local filesystem, processes, and system resources.
Target Configuration
Basic Configuration
# .xec/config.yaml
targets:
local:
type: local
shell: /bin/bash # Optional: override default shell
env: # Optional: environment variables
NODE_ENV: development
PATH: /usr/local/bin:/usr/bin:/bin
cwd: /project # Optional: working directory
Default Local Target
When no target is specified, Xec uses an implicit local target:
// Implicit local target (from LocalAdapter constructor)
{
type: 'local',
shell: process.env.SHELL || '/bin/sh',
env: process.env,
cwd: process.cwd()
}
Execution Model
Process Spawning
Local execution uses Node.js child_process.spawn()
internally:
// From LocalAdapter.spawn() implementation
const child = spawn(command, args, {
cwd: options.cwd || process.cwd(),
env: { ...process.env, ...options.env },
shell: options.shell || true,
stdio: options.stdio || 'pipe',
detached: options.detached || false
});
Shell Detection
The adapter automatically detects the available shell (from utils/shell.ts
):
- Environment Variable:
$SHELL
environment variable - Windows Detection:
process.platform === 'win32'
→ cmd.exe or PowerShell - Unix Fallback:
/bin/sh
as universal fallback
// Shell detection priority
const shell = options.shell
|| process.env.SHELL
|| (isWindows ? 'cmd.exe' : '/bin/sh');
Command Execution
Template Literal API
import { $ } from '@xec-sh/core';
// Simple command
const result = await $`ls -la`;
console.log(result.stdout);
// With error handling
const { ok, stdout, stderr, exitCode } = await $`test -f file.txt`.nothrow();
if (!ok) {
console.error('File not found');
}
// Piping and chaining
await $`cat file.txt`.pipe($`grep pattern`).pipe($`wc -l`);
Direct Execution
const adapter = new LocalAdapter();
// Execute with options
const result = await adapter.execute('npm install', {
cwd: '/project',
env: { NODE_ENV: 'production' },
timeout: 60000
});
Features
Environment Variables
Local execution inherits and can override environment variables:
// Inherits process.env by default
await $`echo $HOME`; // Uses current HOME
// Override specific variables
await $.env({ NODE_ENV: 'production' })`npm run build`;
// Clear environment
await $.env({})`printenv`; // Empty environment
Working Directory
Control the execution directory:
// Change directory for execution
await $.cwd('/tmp')`pwd`; // Outputs: /tmp
// Temporary directory change
await $.within(async () => {
await $.cd('/project');
await $`npm install`;
await $`npm test`;
}); // Returns to original directory
Input/Output Handling
Stream Processing
// Capture output
const { stdout, stderr } = await $`ls -la`;
// Stream to console
await $`npm install`.pipe(process.stdout);
// Redirect to file
await $`echo "content"`.pipe(fs.createWriteStream('output.txt'));
// Process line by line
await $`tail -f log.txt`.lines(async (line) => {
console.log(`Log: ${line}`);
});
Input Redirection
// From string
await $`cat`.stdin('Hello, World\n');
// From file
await $`wc -l`.stdin(fs.createReadStream('input.txt'));
// From another command
await $`echo "test"`.pipe($`cat`);
Signal Handling
// Handle process signals
const proc = $`sleep 100`;
// Send signal
setTimeout(() => proc.kill('SIGTERM'), 1000);
// Handle termination
proc.on('exit', (code, signal) => {
console.log(`Process exited: ${code || signal}`);
});
Performance Characteristics
Execution Overhead
Based on implementation analysis:
- Process Spawn: 5-10ms (Node.js child_process overhead)
- Shell Invocation: +2-5ms (shell interpreter startup)
- Environment Setup: <1ms (environment variable copying)
- Working Directory Change: <1ms (process.chdir)
Memory Usage
- Per Process: 5-10MB (child process overhead)
- Output Buffering: Variable (depends on stdout/stderr size)
- Stream Mode: Constant memory (no buffering)
Optimization Strategies
- Avoid Shell When Possible:
// Slower (invokes shell)
await $`echo hello`;
// Faster (direct execution)
await $.noshell()`echo`, ['hello']);
- Use Streaming for Large Output:
// Memory intensive (buffers all output)
const { stdout } = await $`find / -type f`;
// Memory efficient (streams output)
await $`find / -type f`.pipe(process.stdout);
- Batch Operations:
// Inefficient (multiple shell invocations)
await $`mkdir dir1`;
await $`mkdir dir2`;
await $`mkdir dir3`;
// Efficient (single shell invocation)
await $`mkdir dir1 dir2 dir3`;
Error Handling
Exit Codes
Local adapter preserves process exit codes:
try {
await $`exit 42`;
} catch (error) {
console.log(error.exitCode); // 42
}
Error Types
Error Class | Condition | Exit Code |
---|---|---|
ExecutionError | Non-zero exit code | Process exit code |
TimeoutError | Execution timeout | 10 |
FileSystemError | Command not found | 127 |
PermissionError | Permission denied | 126 |