UNPKG

@meldscience/meld

Version:

pipeable one-shot prompt scripting toolkit

145 lines 5.6 kB
"use strict"; 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