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:
$SHELLenvironment variable - Windows Detection:
process.platform === 'win32'→ cmd.exe or PowerShell - Unix Fallback:
/bin/shas 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 |
Signal Handling​
// Handle specific signals
const proc = $`long-running-process`;
process.on('SIGINT', () => {
proc.kill('SIGTERM');
process.exit(130); // Standard SIGINT exit code
});
Security Considerations​
Command Injection Prevention​
The adapter provides automatic escaping:
const userInput = "'; rm -rf /";
// Safe - automatically escaped
await $`echo ${userInput}`;
// Executes: echo ''"'"'; rm -rf /'
// Manual escaping
const escaped = escapeShellArg(userInput);
await $`echo ${escaped}`;
Environment Isolation​
// Sanitize environment
const cleanEnv = {
PATH: '/usr/local/bin:/usr/bin:/bin',
HOME: process.env.HOME,
USER: process.env.USER
};
await $.env(cleanEnv)`sensitive-command`;
Platform Differences​
Unix/Linux/macOS​
- Default Shell:
/bin/shor$SHELL - Path Separator:
: - Null Device:
/dev/null - Temp Directory:
/tmpor$TMPDIR
Windows​
- Default Shell:
cmd.exeor PowerShell - Path Separator:
; - Null Device:
NUL - Temp Directory:
%TEMP%or%TMP%
// Platform-aware execution
const isWindows = process.platform === 'win32';
if (isWindows) {
await $`dir`; // Windows
} else {
await $`ls`; // Unix-like
}
Best Practices​
1. Use Appropriate Shell​
// For simple commands, avoid shell overhead
await $.noshell()`node`, ['script.js'];
// For complex pipelines, use shell
await $`cat file | grep pattern | sort | uniq`;
2. Handle Errors Gracefully​
const result = await $`command`.nothrow();
if (!result.ok) {
// Handle error without throwing
console.error(`Command failed: ${result.stderr}`);
}
3. Set Timeouts for Long Operations​
await $`npm install`.timeout(60000); // 60 seconds
4. Use Streaming for Large Data​
// Stream large files
await $`cat large-file.txt`
.pipe($`grep pattern`)
.pipe(fs.createWriteStream('filtered.txt'));
5. Clean Up Resources​
const proc = $`tail -f log.txt`;
// Ensure cleanup
process.on('exit', () => proc.kill());
Troubleshooting​
See Local Troubleshooting Guide for common issues and solutions.
Related Documentation​
- Shell Configuration - Detailed shell setup
- Local Troubleshooting - Common issues and solutions
- Execution Engine - Core execution architecture
- Local Adapter API - API reference