@xec-sh/core
Version:
Universal shell execution engine
144 lines • 4.55 kB
JavaScript
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