Extensions
The Xec execution engine provides powerful extension mechanisms to add custom functionality, create new adapters, and integrate with external systems.
Overviewβ
Extension support (packages/core/src/core/extensions.ts
) provides:
- Custom adapter creation for new environments
- Plugin system for modular features
- Hook system for lifecycle events
- Custom commands and methods
- Middleware integration for cross-cutting concerns
- Type-safe extensions with TypeScript
Custom Adaptersβ
Creating an Adapterβ
import { BaseAdapter, AdapterConfig, ExecutionResult } from '@xec-sh/core';
// Custom adapter for a new environment
class CustomAdapter extends BaseAdapter {
constructor(config: AdapterConfig) {
super(config);
}
async execute(command: string, options: any): Promise<ExecutionResult> {
// Custom execution logic
const result = await this.runCommand(command, options);
return {
stdout: result.output,
stderr: result.errors,
exitCode: result.code,
duration: result.time
};
}
async connect(): Promise<void> {
// Establish connection to custom environment
await this.establishConnection();
}
async disconnect(): Promise<void> {
// Clean up resources
await this.cleanup();
}
async healthCheck(): Promise<boolean> {
// Check if adapter is healthy
return await this.ping();
}
}
// Register adapter
$.registerAdapter('custom', CustomAdapter);
// Use custom adapter
await $.custom({ config: 'value' })`command`;
Adapter with Connection Poolingβ
class PooledAdapter extends BaseAdapter {
private pool: ConnectionPool;
constructor(config: AdapterConfig) {
super(config);
this.pool = new ConnectionPool({
min: 2,
max: 10,
idleTimeout: 60000
});
}
async execute(command: string, options: any): Promise<ExecutionResult> {
const connection = await this.pool.acquire();
try {
return await connection.execute(command, options);
} finally {
await this.pool.release(connection);
}
}
async getPoolStats() {
return this.pool.getStats();
}
}
Plugin Systemβ
Creating Pluginsβ
// Plugin interface
interface XecPlugin {
name: string;
version: string;
initialize(engine: ExecutionEngine): void;
destroy?(): Promise<void>;
}
// Example plugin
class LoggingPlugin implements XecPlugin {
name = 'logging';
version = '1.0.0';
initialize(engine: ExecutionEngine) {
// Add logging to all commands
engine.on('command:start', this.logStart);
engine.on('command:complete', this.logComplete);
engine.on('command:error', this.logError);
// Add custom method
engine.addMethod('logged', (command: any) => {
return command
.on('output', (data: any) => console.log('Output:', data));
});
}
private logStart = (event: any) => {
console.log(`[${event.id}] Starting: ${event.command}`);
};
private logComplete = (event: any) => {
console.log(`[${event.id}] Completed in ${event.duration}ms`);
};
private logError = (event: any) => {
console.error(`[${event.id}] Error:`, event.error);
};
async destroy() {
console.log('Logging plugin destroyed');
}
}
// Register and use plugin
$.use(new LoggingPlugin());
// Now all commands have logging
await $`command`.logged();
Plugin with Configurationβ
class MetricsPlugin implements XecPlugin {
name = 'metrics';
version = '1.0.0';
private metrics: Map<string, any> = new Map();
constructor(private config: {
endpoint: string;
interval: number;
tags: Record<string, string>;
}) {}
initialize(engine: ExecutionEngine) {
// Collect metrics
engine.on('command:complete', (event) => {
this.recordMetric({
command: event.command,
duration: event.duration,
exitCode: event.exitCode,
timestamp: Date.now()
});
});
// Send metrics periodically
setInterval(() => this.sendMetrics(), this.config.interval);
}
private recordMetric(metric: any) {
const key = `${metric.command}:${Date.now()}`;
this.metrics.set(key, metric);
}
private async sendMetrics() {
const batch = Array.from(this.metrics.values());
this.metrics.clear();
await fetch(this.config.endpoint, {
method: 'POST',
body: JSON.stringify({
metrics: batch,
tags: this.config.tags
})
});
}
}
// Use configured plugin
$.use(new MetricsPlugin({
endpoint: 'https://metrics.example.com/api',
interval: 60000,
tags: { service: 'xec', environment: 'production' }
}));
Custom Commandsβ
Adding Global Commandsβ
// Add custom command method
$.addCommand('deploy', async function(this: ExecutionEngine, target: string) {
const config = await this`cat deploy.json`.json();
const steps = [
this`git pull`,
this`npm install`,
this`npm run build`,
this`rsync -av dist/ ${target}:/var/www/`
];
for (const step of steps) {
await step;
}
return { success: true, target };
});
// Use custom command
await $.deploy('user@server.com');
Command with Optionsβ
interface DeployOptions {
environment: 'dev' | 'staging' | 'prod';
version?: string;
skipTests?: boolean;
}
$.addCommand('smartDeploy', async function(
this: ExecutionEngine,
options: DeployOptions
) {
// Validate options
if (!options.environment) {
throw new Error('Environment is required');
}
// Build command based on options
let pipeline = [
this`git fetch`,
this`git checkout ${options.version || 'main'}`
];
if (!options.skipTests) {
pipeline.push(this`npm test`);
}
pipeline.push(
this`npm run build:${options.environment}`,
this`deploy-${options.environment}.sh`
);
// Execute pipeline
for (const cmd of pipeline) {
await cmd;
}
});
// Use with options
await $.smartDeploy({
environment: 'staging',
version: 'v1.2.3',
skipTests: false
});