UNPKG

@xec-sh/core

Version:

Universal shell execution engine

144 lines 4.55 kB
export class CommandTemplate { constructor(template, options = {}) { this.template = template; this.options = options; this.requiredParams = this.extractRequiredParams(template); } extractRequiredParams(template) { const params = new Set(); const regex = /\{\{(\w+)\}\}/g; let match; while ((match = regex.exec(template)) !== null) { if (match[1]) { params.add(match[1]); } } return params; } interpolate(params) { const mergedParams = { ...this.options.defaults, ...params }; return this.template.replace(/\{\{(\w+)\}\}/g, (match, key) => { if (!(key in mergedParams)) { throw new Error(`Missing required parameter: ${key}`); } const value = mergedParams[key]; if (typeof value === 'string') { return this.escapeShellArg(value); } return String(value); }); } escapeShellArg(arg) { if (!arg || /[^a-zA-Z0-9_\-./]/.test(arg)) { return '"' + arg.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'; } return arg; } async execute(engine, params = {}) { if (this.options.validate) { await this.options.validate(params); } const command = this.interpolate(params); const engineWithOptions = this.options ? engine.with(this.options) : engine; const result = await engineWithOptions.execute({ command, shell: true }); if (this.options.transform) { return this.options.transform(result); } return result; } bind(engine) { return new BoundTemplate(this, engine); } getRequiredParams() { return Array.from(this.requiredParams); } describe() { return `Template: ${this.template}\nRequired params: ${this.getRequiredParams().join(', ')}`; } } export class BoundTemplate { constructor(template, engine) { this.template = template; this.engine = engine; } async execute(params = {}) { return this.template.execute(this.engine, params); } with(config) { return new BoundTemplate(this.template, this.engine.with(config)); } } export class TemplateRegistry { constructor() { this.templates = new Map(); } register(name, template, options) { const cmdTemplate = typeof template === 'string' ? new CommandTemplate(template, options) : template; this.templates.set(name, cmdTemplate); } get(name) { return this.templates.get(name); } has(name) { return this.templates.has(name); } list() { return Array.from(this.templates.keys()); } bind(engine) { return new BoundRegistry(this, engine); } } export class BoundRegistry { constructor(registry, engine) { this.registry = registry; this.engine = engine; } async execute(name, params) { const template = this.registry.get(name); if (!template) { throw new Error(`Template not found: ${name}`); } return template.execute(this.engine, params); } get(name) { const template = this.registry.get(name); return template ? template.bind(this.engine) : undefined; } } export const commonTemplates = { gitClone: new CommandTemplate('git clone {{repo}} {{dir}}', { defaults: { dir: '.' }, validate: (params) => { if (!params['repo'] || !params['repo'].startsWith('http')) { throw new Error('Invalid repository URL'); } } }), dockerRun: new CommandTemplate('docker run {{options}} {{image}} {{command}}', { defaults: { options: '', command: '' }, }), curl: new CommandTemplate('curl {{options}} {{url}}', { defaults: { options: '-s' }, transform: (result) => { try { return JSON.parse(result.stdout); } catch { return result.stdout; } } }), mkdir: new CommandTemplate('mkdir -p {{path}}'), rsync: new CommandTemplate('rsync {{options}} {{source}} {{destination}}', { defaults: { options: '-avz' } }), tar: new CommandTemplate('tar {{operation}} {{file}} {{path}}', { defaults: { operation: '-czf' } }) }; //# sourceMappingURL=templates.js.map