@meldscience/meld
Version:
pipeable one-shot prompt scripting toolkit
145 lines • 5.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Meld = void 0;
const fs_1 = require("fs");
const child_process_1 = require("child_process");
const errors_1 = require("./errors");
class Meld {
constructor(options) {
this.options = options;
}
/**
* Main entry point. Orchestrates reading file, extracting commands,
* executing them, and replacing placeholders.
*/
async process() {
if (!(0, fs_1.existsSync)(this.options.inputFile)) {
throw new errors_1.ToolError(`Meld: Input file does not exist: ${this.options.inputFile}`, 'FILE_NOT_FOUND');
}
const rawContent = (0, fs_1.readFileSync)(this.options.inputFile, 'utf-8');
const commands = await this.extractCommands(rawContent);
// If dry-run, skip actual execution
let commandResults = [];
if (!this.options.dryRun) {
for (const cmd of commands) {
const result = await this.executeCommand(cmd);
commandResults.push(result);
}
}
else {
commandResults = commands.map((c) => ({
command: c,
output: `# [DRY-RUN] Would have executed: ${c.command}`
}));
}
const replacedContent = await this.replaceCommands(rawContent, commandResults);
// Write to output file if specified
if (this.options.outputFile) {
(0, fs_1.writeFileSync)(this.options.outputFile, replacedContent);
}
return replacedContent;
}
/**
* Scans markdown content for lines containing @cmd[...]
* @param content entire markdown file text
* @returns array of Command objects
*/
async extractCommands(content) {
const cmdRegex = /@cmd\[(?<command>[^\]]+)\]/g;
const lines = content.split('\n');
const commands = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
let match;
while ((match = cmdRegex.exec(line)) !== null) {
const commandStr = match.groups?.command || '';
commands.push({
raw: match[0],
command: commandStr.trim(),
lineNumber: i + 1
});
}
}
return commands;
}
/**
* Executes a single shell command synchronously.
* @param cmd object containing the command string
* @returns command result with output
*/
async executeCommand(cmd) {
try {
const output = (0, child_process_1.execSync)(cmd.command, {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'] // capture both stdout and stderr
});
return { command: cmd, output };
}
catch (error) {
// For exec errors, error.stderr contains the stderr output
const errorOutput = error.stderr ? error.stderr.toString() : '';
const output = error.stdout ? error.stdout.toString() : '';
// If command failed with no output at all
if (!output && !errorOutput) {
return {
command: cmd,
output: '',
error
};
}
// If we have stderr, format it nicely
let formattedOutput = '';
if (errorOutput) {
// Extract command name for the error header
const cmdName = cmd.command.split(' ')[0];
const header = `${cmdName} error:`;
const separator = '-'.repeat(header.length);
// Log to terminal
console.error(`\n${header}\n${separator}\n${errorOutput}\n${separator}\n`);
// Format for file output
formattedOutput = [
'Command produced error output (included below)',
'-'.repeat('Command produced error output (included below)'.length),
'' // Add blank line before actual output
].join('\n');
}
// Combine all output
const combinedOutput = [
formattedOutput,
output,
errorOutput
].filter(Boolean).join('\n');
return {
command: cmd,
output: combinedOutput,
// Include error if command actually failed
...(error.code !== 0 && { error })
};
}
}
/**
* Replaces each @cmd[...] in the original content with its execution result.
* If there's an error, it inserts the error message into a fenced code block.
* @param content original markdown
* @param results command results
* @returns final processed markdown
*/
async replaceCommands(content, results) {
let finalContent = content;
for (const result of results) {
const placeholder = result.command.raw;
let replacement;
if (result.error && !result.output) {
// Only show error message if we have no output at all
replacement = `Command failed: ${result.command.command}\nError: ${result.error.message}`;
}
else {
replacement = result.output.trim();
}
finalContent = finalContent.replace(placeholder, replacement);
}
return finalContent;
}
}
exports.Meld = Meld;
//# sourceMappingURL=meld.js.map