UNPKG

scai

Version:

> **AI-powered CLI for local code analysis, commit message suggestions, and natural-language queries.** 100% local, private, GDPR-friendly, made in Denmark/EU with ❤️.

104 lines (103 loc) 4.08 kB
// File: src/workflow/workflowRunner.ts import fs from "fs/promises"; import chalk from "chalk"; import { normalizePath } from "../utils/contentUtils.js"; /** * runWorkflow - orchestrates running a modular I/O pipeline. * * Each module conforms to the `ModuleIO` interface: * input: { query, content? } * output: { data?, mode?, newFilepath? } * * Options: * - modules: ordered modules to run (Module[]) * - filepath: optional file path to read/write * - inputContent: optional string for stdin content * * Behaviours: * - Sequentially runs each module on the full file content * - Supports output modes: overwrite, append, newFile, skip * - Stream mode (no filepath): prints final output to stdout */ export async function runWorkflow(opts) { const { modules } = opts; let filepath = opts.filepath; let fileContent = ""; try { if (filepath) { filepath = normalizePath(filepath); await fs.access(filepath); fileContent = await fs.readFile(filepath, "utf-8"); } else { fileContent = opts.inputContent ?? ""; } for (const mod of modules) { console.log(chalk.cyan(`\n⚙️ Running module: ${mod.name}`)); const io = { query: `Process ${filepath ?? "<stdin>"} with ${mod.name}`, content: fileContent, }; const result = await mod.run(io); if (!result || !result.data || !String(result.data).trim()) { throw new Error(`⚠️ Empty result from module ${mod.name}`); } const output = typeof result.data === "string" ? result.data : JSON.stringify(result.data, null, 2); const mode = result.mode ?? "overwrite"; // default mode if not specified by module const newFilepath = result.newFilepath; // // === Handle stdout vs file modes === // if (!filepath) { // stdin mode: print directly to stdout process.stdout.write(output); fileContent = output; continue; } // // === File-backed mode handling (honor mode semantics) === // switch (mode) { case "overwrite": await fs.writeFile(filepath, output, "utf-8"); console.log(chalk.green(`✅ Overwritten: ${filepath}`)); fileContent = output; break; case "append": await fs.appendFile(filepath, output, "utf-8"); console.log(chalk.green(`✅ Appended: ${filepath}`)); fileContent += output; break; case "newFile": if (!newFilepath) throw new Error(`newFile mode requires a newFilepath`); const resolvedNew = normalizePath(newFilepath); await fs.writeFile(resolvedNew, output, "utf-8"); console.log(chalk.green(`✅ New file created: ${resolvedNew}`)); filepath = resolvedNew; fileContent = output; break; case "skip": console.log(chalk.gray(`⏭️ Skipped writing for module ${mod.name}`)); fileContent = output; break; default: console.log(chalk.yellow(`⚠️ Unknown mode (${String(mode)}). Treating as overwrite.`)); await fs.writeFile(filepath, output, "utf-8"); fileContent = output; break; } } // Final stdout flush if running without filepath if (!opts.filepath) { process.stdout.write("\n"); } } catch (err) { console.error(chalk.red("❌ Error in workflow run:"), err?.message ?? err); throw err; } }