alvamind-workflow
Version:
A lightweight and flexible workflow automation library for JavaScript/TypeScript projects
162 lines • 6.19 kB
JavaScript
import { executeCommand, createDefaultContext } from "./runner.js";
import { executeChildProcess } from "./utils/executeChildProcess.js";
import chalk from "chalk";
class WorkflowBuilderImpl {
config;
lastCommandIndex = -1;
constructor(name = "Programmatic Workflow") {
this.config = {
version: "1.0",
name,
commands: []
};
}
name(name) {
this.config.name = name;
return this;
}
execute(command, name, skippable = false) {
this.config.commands.push({ command, name, skippable });
this.lastCommandIndex = this.config.commands.length - 1;
return this;
}
executeWithId(id, command, name, skippable = false) {
this.config.commands.push({ id, command, name, skippable });
this.lastCommandIndex = this.config.commands.length - 1;
return this;
}
executeWith(command, name, callback, skippable = false) {
this.config.commands.push({ command, name, callback, skippable });
this.lastCommandIndex = this.config.commands.length - 1;
return this;
}
when(condition, name) {
// This acts as a marker for the next command that will be added
if (this.lastCommandIndex >= 0) {
this.config.commands[this.lastCommandIndex].condition = condition;
}
return this;
}
dependsOn(...ids) {
if (this.lastCommandIndex >= 0 && ids.length > 0) {
this.config.commands[this.lastCommandIndex].dependsOn = ids;
}
return this;
}
addParallelCommands(name, commands) {
this.config.commands.push({
name,
parallel: commands.map(cmd => ({
command: cmd.command,
name: cmd.name,
skippable: cmd.skippable
}))
});
this.lastCommandIndex = this.config.commands.length - 1;
return this;
}
build() {
return {
...this.config,
commands: [...this.config.commands]
};
}
// Store the config for later use by runWorkflow
getConfig() {
return this.config;
}
async run(options = {}) {
const commands = this.config.commands.map(cmd => {
// Convert string conditions to functions
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(p => {
// Handle conditions in parallel commands too
let pCondition = p.condition;
if (typeof pCondition === 'string') {
pCondition = new Function('context', `return ${pCondition}`);
}
return {
name: p.name,
originalCmd: p.command,
command: p.command ? () => executeChildProcess(p.command) : undefined,
skippable: p.skippable,
condition: pCondition,
id: p.id,
dependsOn: p.dependsOn
};
}),
callback: cmd.callback,
condition,
id: cmd.id,
dependsOn: cmd.dependsOn
};
});
let currentStep = 1;
const totalSteps = this.countTotalSteps(commands);
const context = createDefaultContext();
try {
for (let i = 0; i < commands.length; i++) {
const cmd = commands[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 ${currentStep}: ${chalk.cyan(cmd.name)} (condition not met)`);
continue;
}
}
if (cmd.parallel) {
await Promise.all(cmd.parallel.map(async (parallelCmd) => {
if (parallelCmd.command) {
const { result } = await executeCommand(parallelCmd, currentStep++, totalSteps, options.interactive, context);
return result;
}
}));
}
else if (cmd.command) {
const { branchResult, result } = await executeCommand(cmd, currentStep++, totalSteps, options.interactive, context);
if (cmd.callback && branchResult) {
// Handle branching based on callback result
continue;
}
}
}
return true;
}
catch (error) {
if (options.testMode) {
throw error;
}
return false;
}
}
countTotalSteps(commands) {
return commands.reduce((total, cmd) => {
if (cmd.parallel) {
return total + this.countTotalSteps(cmd.parallel);
}
return total + (cmd.command ? 1 : 0);
}, 0);
}
}
export function createWorkflow(options = {}) {
return new WorkflowBuilderImpl(options.name);
}
// Export the class for index.js to use
export { WorkflowBuilderImpl };
//# sourceMappingURL=workflow.js.map