UNPKG

@xec-sh/cli

Version:

Xec: The Universal Shell for TypeScript

302 lines 12.1 kB
import { EventEmitter } from 'events'; import { TaskParser } from './task-parser.js'; import { TargetResolver } from './target-resolver.js'; import { VariableInterpolator } from './variable-interpolator.js'; import { TaskExecutor } from './task-executor.js'; export class TaskManager extends EventEmitter { constructor(options) { super(); this.options = options; this.parsedTasks = new Map(); this.config = null; this.configManager = options.configManager; this.parser = new TaskParser(); this.interpolator = new VariableInterpolator(); this.targetResolver = null; this.executor = new TaskExecutor({ interpolator: this.interpolator, targetResolver: this.targetResolver, defaultTimeout: options.defaultTimeout, debug: options.debug, dryRun: options.dryRun, }); this.executor.on('task:start', event => this.emit('task:start', event)); this.executor.on('task:complete', event => this.emit('task:complete', event)); this.executor.on('task:error', event => this.emit('task:error', event)); this.executor.on('step:retry', event => this.emit('step:retry', event)); this.executor.on('event', event => this.emit('event', event)); } async load() { this.config = await this.configManager.load(); this.targetResolver = new TargetResolver(this.config); this.executor = new TaskExecutor({ interpolator: this.interpolator, targetResolver: this.targetResolver, defaultTimeout: this.executor.options.defaultTimeout, debug: this.executor.options.debug || this.options.debug, dryRun: this.executor.options.dryRun }); this.executor.on('task:start', event => this.emit('task:start', event)); this.executor.on('task:complete', event => this.emit('task:complete', event)); this.executor.on('task:error', event => this.emit('task:error', event)); this.executor.on('step:retry', event => this.emit('step:retry', event)); this.executor.on('event', event => this.emit('event', event)); if (!this.config.tasks) { return; } const parsed = this.parser.parseTasks(this.config.tasks); for (const [name, task] of Object.entries(parsed)) { this.parsedTasks.set(name, task); } } async list() { await this.ensureLoaded(); const tasks = []; for (const [name, task] of this.parsedTasks) { if (task.private && !this.options.debug) { continue; } tasks.push({ name, description: task.description, params: task.params, isPrivate: task.private, hasSteps: !!task.steps, hasCommand: !!task.command, hasScript: !!task.script, target: task.target, targets: task.targets, }); } return tasks.sort((a, b) => a.name.localeCompare(b.name)); } async get(taskName) { await this.ensureLoaded(); return this.parsedTasks.get(taskName) || null; } async exists(taskName) { await this.ensureLoaded(); return this.parsedTasks.has(taskName); } async run(taskName, params, options) { await this.ensureLoaded(); const task = this.parsedTasks.get(taskName); if (!task) { throw new Error(`Task '${taskName}' not found`); } let finalParams = params || {}; if (task.params) { this.validateParameters(taskName, task.params, finalParams); finalParams = this.applyParameterDefaults(task.params, finalParams); } return this.executor.execute(taskName, task, { ...options, params: finalParams, vars: this.config?.vars || {}, }); } async runOnTarget(taskName, target, params, options) { return this.run(taskName, params, { ...options, target, }); } async create(taskName, config) { const task = this.parser.parseTask(taskName, config); if (!task) { const errors = this.parser.getErrors(); throw new Error(`Invalid task configuration: ${errors[0]?.message}`); } this.parsedTasks.set(taskName, task); if (!this.config) { await this.load(); } const currentConfig = this.config; currentConfig.tasks = currentConfig.tasks || {}; currentConfig.tasks[taskName] = config; await this.configManager.save(); } async update(taskName, config) { if (!this.parsedTasks.has(taskName)) { throw new Error(`Task '${taskName}' not found`); } await this.create(taskName, config); } async delete(taskName) { if (!this.parsedTasks.has(taskName)) { throw new Error(`Task '${taskName}' not found`); } this.parsedTasks.delete(taskName); if (!this.config) { await this.load(); } const currentConfig = this.config; if (currentConfig.tasks) { delete currentConfig.tasks[taskName]; } await this.configManager.save(); } async explain(taskName, params) { await this.ensureLoaded(); const task = this.parsedTasks.get(taskName); if (!task) { throw new Error(`Task '${taskName}' not found`); } const explanation = []; if (task.description) { explanation.push(`Task: ${task.description}`); } else { explanation.push(`Task: ${taskName}`); } if (task.params && task.params.length > 0) { explanation.push(''); explanation.push('Parameters:'); for (const param of task.params) { const value = params?.[param.name] ?? param.default; const required = param.required ? ' (required)' : ''; explanation.push(` ${param.name}: ${value}${required}`); if (param.description) { explanation.push(` ${param.description}`); } } } explanation.push(''); explanation.push('Execution plan:'); if (task.command) { const interpolated = this.interpolator.interpolate(task.command, { params: params || {}, vars: await this.configManager.get('vars') || {}, }); explanation.push(` Execute: ${interpolated}`); } else if (task.steps) { const parallel = task.parallel ? ' (in parallel)' : ''; explanation.push(` Execute ${task.steps.length} steps${parallel}:`); for (let i = 0; i < task.steps.length; i++) { const step = task.steps[i]; if (!step) continue; const prefix = ` ${i + 1}. `; if (step.command) { const interpolated = this.interpolator.interpolate(step.command, { params: params || {}, vars: await this.configManager.get('vars') || {}, }); explanation.push(`${prefix}${step.name || 'Command'}: ${interpolated}`); } else if (step.task) { explanation.push(`${prefix}${step.name || 'Task'}: Run task '${step.task}'`); } else if (step.script) { explanation.push(`${prefix}${step.name || 'Script'}: Execute ${step.script}`); } if (step.when) { explanation.push(` When: ${step.when}`); } if (step.target || step.targets) { const targets = step.targets || [step.target]; explanation.push(` On: ${targets.join(', ')}`); } } } else if (task.script) { explanation.push(` Execute script: ${task.script}`); } if (task.target || task.targets) { explanation.push(''); const targets = task.targets || [task.target]; explanation.push(`Target${targets.length > 1 ? 's' : ''}: ${targets.join(', ')}`); } if (task.timeout) { explanation.push(''); explanation.push(`Timeout: ${task.timeout}`); } if (task.cache) { explanation.push(''); explanation.push(`Cached with key: ${task.cache.key}`); } return explanation; } applyParameterDefaults(params, provided) { const result = { ...provided }; for (const param of params) { if (!(param.name in result) && param.default !== undefined) { result[param.name] = param.default; } } return result; } validateParameters(taskName, params, provided) { const errors = []; for (const param of params) { const value = provided[param.name]; if (param.required && value === undefined) { errors.push(`Missing required parameter: ${param.name}`); continue; } if (value === undefined) { continue; } if (param.type) { const valid = this.validateParameterType(param, value); if (!valid) { errors.push(`Invalid type for parameter '${param.name}': expected ${param.type}`); } } if (param.pattern && typeof value === 'string') { const regex = new RegExp(param.pattern); if (!regex.test(value)) { errors.push(`Parameter '${param.name}' does not match pattern: ${param.pattern}`); } } if (param.values && !param.values.includes(value)) { errors.push(`Parameter '${param.name}' must be one of: ${param.values.join(', ')}`); } if (typeof value === 'number') { if (param.min !== undefined && value < param.min) { errors.push(`Parameter '${param.name}' must be at least ${param.min}`); } if (param.max !== undefined && value > param.max) { errors.push(`Parameter '${param.name}' must be at most ${param.max}`); } } if (Array.isArray(value)) { if (param.minItems !== undefined && value.length < param.minItems) { errors.push(`Parameter '${param.name}' must have at least ${param.minItems} items`); } if (param.maxItems !== undefined && value.length > param.maxItems) { errors.push(`Parameter '${param.name}' must have at most ${param.maxItems} items`); } } } if (errors.length > 0) { throw new Error(`Task '${taskName}' validation failed:\n${errors.join('\n')}`); } } validateParameterType(param, value) { switch (param.type) { case 'string': return typeof value === 'string'; case 'number': return typeof value === 'number'; case 'boolean': return typeof value === 'boolean'; case 'array': return Array.isArray(value); case 'enum': return param.values?.includes(value) ?? false; default: return true; } } async ensureLoaded() { if (this.parsedTasks.size === 0) { await this.load(); } } clearCache() { this.parsedTasks.clear(); } } //# sourceMappingURL=task-manager.js.map