alvamind-workflow
Version:
A lightweight and flexible workflow automation library for JavaScript/TypeScript projects
137 lines ⢠5.41 kB
JavaScript
import { readFile } from "fs/promises";
import chalk from "chalk";
import { executeCommand, isRunning, formatTime, setTestMode, createDefaultContext } from "./runner.js";
import { join, dirname } from "path";
import { existsSync } from "fs";
import { executeChildProcess } from "./utils/executeChildProcess.js";
import { parseWorkflowYaml } from "./yaml-parser.js";
async function findWorkflowFile(startPath = process.cwd()) {
let currentPath = startPath;
while (true) {
const workflowPath = join(currentPath, "workflow.yml");
if (existsSync(workflowPath)) {
return workflowPath;
}
const parentPath = dirname(currentPath);
if (parentPath === currentPath) {
return null;
}
currentPath = parentPath;
}
}
export async function loadWorkflow(path = "workflow.yml") {
try {
// If no explicit path is provided, try to find workflow.yml in parent directories
const workflowPath = path === "workflow.yml" ?
(await findWorkflowFile()) || path :
path;
const content = await readFile(workflowPath, "utf-8");
// Use the specialized YAML parser that handles conditions and dependencies
return parseWorkflowYaml(content);
}
catch (error) {
console.error(chalk.red(`Error loading workflow file: ${path}`));
throw error;
}
}
function createCommand(cmd) {
// Convert string condition to function if needed
let condition = cmd.condition;
if (typeof condition === 'string') {
condition = new Function('context', `return ${condition}`);
}
return {
name: cmd.name,
originalCmd: cmd.command,
command: cmd.command ? () => executeChildProcess(cmd.command) : undefined,
skippable: cmd.skippable,
parallel: cmd.parallel?.map(createCommand),
callback: cmd.callback,
condition,
id: cmd.id,
dependsOn: cmd.dependsOn
};
}
async function executeCommands(cmds, context, options, totalSteps) {
let currentStep = 1;
for (let i = 0; i < cmds.length; i++) {
const cmd = cmds[i];
// Check if this command has dependencies
if (cmd.dependsOn && cmd.dependsOn.length > 0) {
const missingDependencies = cmd.dependsOn.filter(id => !context.results[id]);
if (missingDependencies.length > 0) {
throw new Error(`Command "${cmd.name}" depends on missing results: ${missingDependencies.join(', ')}`);
}
}
// Check conditions (if any)
if (cmd.condition) {
const shouldRun = await cmd.condition(context);
if (!shouldRun) {
console.log(`\nSkipping step: ${chalk.cyan(cmd.name)} (condition not met)`);
continue;
}
}
if (cmd.parallel) {
const results = await Promise.all(cmd.parallel.map(async (parallelCmd) => {
if (parallelCmd.command) {
return executeCommand(parallelCmd, currentStep++, totalSteps, options.interactive, context);
}
}));
// Handle branch results from parallel commands
const branchResults = results
.filter((r) => r?.branchResult)
.map((r) => r?.branchResult)
.filter(Boolean);
if (branchResults.length > 0) {
console.log(chalk.dim(`\nParallel branches: ${branchResults.join(', ')}`));
}
}
else if (cmd.command) {
const { branchResult } = await executeCommand(cmd, currentStep++, totalSteps, options.interactive, context);
if (branchResult) {
// Process commands based on branch result
console.log(chalk.dim(`\nBranching to: ${branchResult}`));
}
}
}
}
export async function runWorkflow(config, options = {}) {
if (options.testMode) {
setTestMode(true);
}
const startTime = performance.now();
const commands = config.commands.map(createCommand);
const context = createDefaultContext();
console.log(chalk.bold(`\nš³ Executing workflow: ${config.name}`));
console.log(chalk.dim("====================================="));
try {
const totalSteps = countTotalSteps(commands);
await executeCommands(commands, context, options, totalSteps);
const totalTime = performance.now() - startTime;
console.log(chalk.dim("\n====================================="));
console.log(`${chalk.green("ā")} Workflow completed in ${chalk.bold(formatTime(totalTime))}`);
return true;
}
catch (error) {
if (options.testMode) {
throw error;
}
process.exit(1);
}
}
function countTotalSteps(commands) {
return commands.reduce((total, cmd) => {
if (cmd.parallel) {
return total + countTotalSteps(cmd.parallel);
}
return total + (cmd.command ? 1 : 0);
}, 0);
}
export { isRunning, setTestMode };
import { createWorkflow, WorkflowBuilderImpl } from "./workflow.js";
WorkflowBuilderImpl.prototype.run = async function (options = {}) {
return runWorkflow(this.getConfig(), options);
};
// Export everything needed for tests and examples
export { createWorkflow, createDefaultContext };
//# sourceMappingURL=index.js.map