@cascade-flow/runner 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -9,8 +9,158 @@ var __export = (target, all) => {
9
9
  });
10
10
  };
11
11
 
12
- // src/index.ts
13
- import { join as join2 } from "node:path";
12
+ // src/subprocess-executor.ts
13
+ import { spawn } from "node:child_process";
14
+ import { resolve, dirname } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+ import { mkdir, readFile, unlink } from "node:fs/promises";
17
+ import { getMicrosecondTimestamp, ensureErrorMessage } from "@cascade-flow/backend-interface";
18
+ function createStreamHandler(streamType, attemptNumber, emitLog) {
19
+ let buffer = "";
20
+ const handler = (chunk) => {
21
+ buffer += chunk.toString();
22
+ const lines = buffer.split(`
23
+ `);
24
+ buffer = lines.pop() || "";
25
+ for (const line of lines) {
26
+ if (!line.trim())
27
+ continue;
28
+ const timestamp = getMicrosecondTimestamp();
29
+ emitLog({
30
+ timestamp,
31
+ stream: streamType,
32
+ message: line,
33
+ attemptNumber
34
+ });
35
+ }
36
+ };
37
+ const getBuffer = () => buffer;
38
+ const flushBuffer = () => {
39
+ if (buffer.trim()) {
40
+ emitLog({
41
+ timestamp: getMicrosecondTimestamp(),
42
+ stream: streamType,
43
+ message: buffer,
44
+ attemptNumber
45
+ });
46
+ buffer = "";
47
+ }
48
+ };
49
+ return { handler, getBuffer, flushBuffer };
50
+ }
51
+ async function executeStepInSubprocess(stepFile, stepId, dependencies, ctx, attemptNumber, outputPath, onLog, options) {
52
+ const executorPath = resolve(dirname(fileURLToPath(import.meta.url)), "step-executor");
53
+ await mkdir(dirname(outputPath), { recursive: true });
54
+ return new Promise((resolve2, reject) => {
55
+ const child = spawn("bun", [executorPath], {
56
+ stdio: ["pipe", "pipe", "pipe"],
57
+ env: {
58
+ ...process.env,
59
+ STEP_OUTPUT_FILE: outputPath
60
+ }
61
+ });
62
+ const logs = [];
63
+ const logWritePromises = [];
64
+ let logError = null;
65
+ const emitLog = (entry) => {
66
+ logs.push(entry);
67
+ if (!onLog)
68
+ return;
69
+ const trackedPromise = Promise.resolve(onLog(entry)).catch((err) => {
70
+ if (!logError) {
71
+ logError = err instanceof Error ? err : new Error(String(err));
72
+ }
73
+ });
74
+ logWritePromises.push(trackedPromise);
75
+ };
76
+ const signal = options?.signal;
77
+ let aborted = false;
78
+ let abortReason;
79
+ const abortHandler = signal ? () => {
80
+ if (aborted)
81
+ return;
82
+ aborted = true;
83
+ abortReason = signal?.reason ?? new Error("Step execution aborted");
84
+ try {
85
+ child.kill("SIGKILL");
86
+ } catch {}
87
+ } : null;
88
+ const cleanup = () => {
89
+ if (signal && abortHandler) {
90
+ signal.removeEventListener("abort", abortHandler);
91
+ }
92
+ };
93
+ if (signal) {
94
+ if (signal.aborted) {
95
+ abortHandler?.();
96
+ } else {
97
+ signal.addEventListener("abort", abortHandler);
98
+ }
99
+ }
100
+ const stdoutHandler = createStreamHandler("stdout", attemptNumber, emitLog);
101
+ const stderrHandler = createStreamHandler("stderr", attemptNumber, emitLog);
102
+ child.stdout.on("data", stdoutHandler.handler);
103
+ child.stderr.on("data", stderrHandler.handler);
104
+ child.on("error", (err) => {
105
+ cleanup();
106
+ reject(err);
107
+ });
108
+ child.on("close", async (code, signal2) => {
109
+ cleanup();
110
+ stdoutHandler.flushBuffer();
111
+ stderrHandler.flushBuffer();
112
+ try {
113
+ await Promise.all(logWritePromises);
114
+ } catch {}
115
+ if (logError) {
116
+ reject(logError);
117
+ return;
118
+ }
119
+ if (aborted) {
120
+ const reason = abortReason instanceof Error ? abortReason : new Error(String(abortReason ?? "Step execution aborted"));
121
+ reject(reason);
122
+ return;
123
+ }
124
+ if (code === 0) {
125
+ try {
126
+ const outputContent = await readFile(outputPath, "utf-8");
127
+ const result = JSON.parse(outputContent);
128
+ try {
129
+ await unlink(outputPath);
130
+ } catch {}
131
+ resolve2({ result, logs });
132
+ } catch (err) {
133
+ reject(new Error(`Failed to read/parse output file ${outputPath}: ${err}`));
134
+ }
135
+ } else if (signal2) {
136
+ const error = new Error(`Step process killed by signal: ${signal2}`);
137
+ error.isCrash = true;
138
+ reject(error);
139
+ } else {
140
+ const lastStderrLog = logs.filter((l) => l.stream === "stderr").pop();
141
+ if (lastStderrLog) {
142
+ try {
143
+ const errorObj = JSON.parse(lastStderrLog.message);
144
+ const errorMessage = ensureErrorMessage(errorObj.message);
145
+ const error = new Error(errorMessage);
146
+ error.stack = errorObj.stack;
147
+ error.name = errorObj.name || "Error";
148
+ reject(error);
149
+ } catch {
150
+ const errorMessage = logs.filter((l) => l.stream === "stderr").map((l) => l.message).join(`
151
+ `);
152
+ reject(new Error(errorMessage || `Step process exited with code ${code}`));
153
+ }
154
+ } else {
155
+ reject(new Error(`Step process exited with code ${code}`));
156
+ }
157
+ }
158
+ });
159
+ const input = JSON.stringify({ stepPath: stepFile, dependencies, ctx });
160
+ child.stdin.write(input);
161
+ child.stdin.end();
162
+ });
163
+ }
14
164
 
15
165
  // src/discovery.ts
16
166
  import fs from "node:fs/promises";
@@ -12626,184 +12776,6 @@ async function discoverSteps(root = path.resolve("steps")) {
12626
12776
  }
12627
12777
  return loaded;
12628
12778
  }
12629
-
12630
- // src/validation.ts
12631
- function detectCycles(steps) {
12632
- const visiting = new Set;
12633
- const visited = new Set;
12634
- function dfs(s) {
12635
- if (visited.has(s.id))
12636
- return;
12637
- if (visiting.has(s.id)) {
12638
- throw new Error(`Cycle detected involving step "${s.name}" (id: ${s.id})`);
12639
- }
12640
- visiting.add(s.id);
12641
- for (const dep of Object.values(s.dependencies))
12642
- dfs(dep);
12643
- visiting.delete(s.id);
12644
- visited.add(s.id);
12645
- }
12646
- for (const s of steps)
12647
- dfs(s);
12648
- }
12649
-
12650
- // src/subprocess-executor.ts
12651
- import { spawn } from "node:child_process";
12652
- import { resolve, dirname } from "node:path";
12653
- import { fileURLToPath } from "node:url";
12654
- import { mkdir, readFile, unlink } from "node:fs/promises";
12655
- import { getMicrosecondTimestamp, ensureErrorMessage } from "@cascade-flow/backend-interface";
12656
- function createStreamHandler(streamType, attemptNumber, emitLog) {
12657
- let buffer = "";
12658
- const handler = (chunk) => {
12659
- buffer += chunk.toString();
12660
- const lines = buffer.split(`
12661
- `);
12662
- buffer = lines.pop() || "";
12663
- for (const line of lines) {
12664
- if (!line.trim())
12665
- continue;
12666
- const timestamp = getMicrosecondTimestamp();
12667
- emitLog({
12668
- timestamp,
12669
- stream: streamType,
12670
- message: line,
12671
- attemptNumber
12672
- });
12673
- }
12674
- };
12675
- const getBuffer = () => buffer;
12676
- const flushBuffer = () => {
12677
- if (buffer.trim()) {
12678
- emitLog({
12679
- timestamp: getMicrosecondTimestamp(),
12680
- stream: streamType,
12681
- message: buffer,
12682
- attemptNumber
12683
- });
12684
- buffer = "";
12685
- }
12686
- };
12687
- return { handler, getBuffer, flushBuffer };
12688
- }
12689
- async function executeStepInSubprocess(stepFile, stepId, dependencies, ctx, attemptNumber, outputPath, onLog, options) {
12690
- const executorPath = resolve(dirname(fileURLToPath(import.meta.url)), "step-executor");
12691
- await mkdir(dirname(outputPath), { recursive: true });
12692
- return new Promise((resolve2, reject) => {
12693
- const child = spawn("bun", [executorPath], {
12694
- stdio: ["pipe", "pipe", "pipe"],
12695
- env: {
12696
- ...process.env,
12697
- STEP_OUTPUT_FILE: outputPath
12698
- }
12699
- });
12700
- const logs = [];
12701
- const logWritePromises = [];
12702
- let logError = null;
12703
- const emitLog = (entry) => {
12704
- logs.push(entry);
12705
- if (!onLog)
12706
- return;
12707
- const trackedPromise = Promise.resolve(onLog(entry)).catch((err) => {
12708
- if (!logError) {
12709
- logError = err instanceof Error ? err : new Error(String(err));
12710
- }
12711
- });
12712
- logWritePromises.push(trackedPromise);
12713
- };
12714
- const signal = options?.signal;
12715
- let aborted2 = false;
12716
- let abortReason;
12717
- const abortHandler = signal ? () => {
12718
- if (aborted2)
12719
- return;
12720
- aborted2 = true;
12721
- abortReason = signal?.reason ?? new Error("Step execution aborted");
12722
- try {
12723
- child.kill("SIGKILL");
12724
- } catch {}
12725
- } : null;
12726
- const cleanup = () => {
12727
- if (signal && abortHandler) {
12728
- signal.removeEventListener("abort", abortHandler);
12729
- }
12730
- };
12731
- if (signal) {
12732
- if (signal.aborted) {
12733
- abortHandler?.();
12734
- } else {
12735
- signal.addEventListener("abort", abortHandler);
12736
- }
12737
- }
12738
- const stdoutHandler = createStreamHandler("stdout", attemptNumber, emitLog);
12739
- const stderrHandler = createStreamHandler("stderr", attemptNumber, emitLog);
12740
- child.stdout.on("data", stdoutHandler.handler);
12741
- child.stderr.on("data", stderrHandler.handler);
12742
- child.on("error", (err) => {
12743
- cleanup();
12744
- reject(err);
12745
- });
12746
- child.on("close", async (code, signal2) => {
12747
- cleanup();
12748
- stdoutHandler.flushBuffer();
12749
- stderrHandler.flushBuffer();
12750
- try {
12751
- await Promise.all(logWritePromises);
12752
- } catch {}
12753
- if (logError) {
12754
- reject(logError);
12755
- return;
12756
- }
12757
- if (aborted2) {
12758
- const reason = abortReason instanceof Error ? abortReason : new Error(String(abortReason ?? "Step execution aborted"));
12759
- reject(reason);
12760
- return;
12761
- }
12762
- if (code === 0) {
12763
- try {
12764
- const outputContent = await readFile(outputPath, "utf-8");
12765
- const result = JSON.parse(outputContent);
12766
- try {
12767
- await unlink(outputPath);
12768
- } catch {}
12769
- resolve2({ result, logs });
12770
- } catch (err) {
12771
- reject(new Error(`Failed to read/parse output file ${outputPath}: ${err}`));
12772
- }
12773
- } else if (signal2) {
12774
- const error46 = new Error(`Step process killed by signal: ${signal2}`);
12775
- error46.isCrash = true;
12776
- reject(error46);
12777
- } else {
12778
- const lastStderrLog = logs.filter((l) => l.stream === "stderr").pop();
12779
- if (lastStderrLog) {
12780
- try {
12781
- const errorObj = JSON.parse(lastStderrLog.message);
12782
- const errorMessage = ensureErrorMessage(errorObj.message);
12783
- const error46 = new Error(errorMessage);
12784
- error46.stack = errorObj.stack;
12785
- error46.name = errorObj.name || "Error";
12786
- reject(error46);
12787
- } catch {
12788
- const errorMessage = logs.filter((l) => l.stream === "stderr").map((l) => l.message).join(`
12789
- `);
12790
- reject(new Error(errorMessage || `Step process exited with code ${code}`));
12791
- }
12792
- } else {
12793
- reject(new Error(`Step process exited with code ${code}`));
12794
- }
12795
- }
12796
- });
12797
- const input = JSON.stringify({ stepPath: stepFile, dependencies, ctx });
12798
- child.stdin.write(input);
12799
- child.stdin.end();
12800
- });
12801
- }
12802
-
12803
- // src/index.ts
12804
- import { getMicrosecondTimestamp as getMicrosecondTimestamp2 } from "@cascade-flow/backend-interface";
12805
- import { Skip, isOptional as isOptional2 } from "@cascade-flow/workflow";
12806
-
12807
12779
  // src/versioning.ts
12808
12780
  import { createHash } from "node:crypto";
12809
12781
  import { readFile as readFile2, readdir } from "node:fs/promises";
@@ -12882,332 +12854,68 @@ async function getGitInfo(workflowDir) {
12882
12854
  return;
12883
12855
  }
12884
12856
  }
12857
+ // src/validation.ts
12858
+ function getAllDependents(targetStepId, allSteps) {
12859
+ const dependentsMap = new Map;
12860
+ for (const step of allSteps) {
12861
+ for (const depStep of Object.values(step.dependencies)) {
12862
+ if (!dependentsMap.has(depStep.id)) {
12863
+ dependentsMap.set(depStep.id, []);
12864
+ }
12865
+ dependentsMap.get(depStep.id).push(step.id);
12866
+ }
12867
+ }
12868
+ const allDependents = new Set;
12869
+ const visited = new Set;
12870
+ function dfs(stepId) {
12871
+ if (visited.has(stepId))
12872
+ return;
12873
+ visited.add(stepId);
12874
+ const dependents = dependentsMap.get(stepId) || [];
12875
+ for (const dependent of dependents) {
12876
+ allDependents.add(dependent);
12877
+ dfs(dependent);
12878
+ }
12879
+ }
12880
+ dfs(targetStepId);
12881
+ return allDependents;
12882
+ }
12883
+ async function validateWorkflowVersion(workflowSlug, parentRunId, currentVersionId, backend, log) {
12884
+ const workflowEvents = await backend.loadEvents(workflowSlug, parentRunId, { category: "workflow" });
12885
+ const workflowStartedEvent = workflowEvents.find((e) => e.type === "WorkflowStarted");
12886
+ if (!workflowStartedEvent || workflowStartedEvent.type !== "WorkflowStarted") {
12887
+ throw new Error(`Parent run ${parentRunId} has no WorkflowStarted event`);
12888
+ }
12889
+ const parentVersionId = workflowStartedEvent.versionId;
12890
+ if (parentVersionId !== currentVersionId && log) {
12891
+ const previousVersion = await backend.getWorkflowVersion(workflowSlug, parentVersionId);
12892
+ const currentVersion = await backend.getWorkflowVersion(workflowSlug, currentVersionId);
12893
+ const message = [
12894
+ `ℹ️ Workflow definition changed since parent run`,
12895
+ ` Parent: ${parentVersionId}`,
12896
+ ` Current: ${currentVersionId}`
12897
+ ];
12898
+ if (previousVersion?.git && currentVersion?.git) {
12899
+ message.push(` Git: ${previousVersion.git.commit} → ${currentVersion.git.commit}`);
12900
+ }
12901
+ message.forEach((line) => log(line));
12902
+ }
12903
+ return parentVersionId;
12904
+ }
12885
12905
 
12886
12906
  // src/index.ts
12887
12907
  async function executeStepInProcess(stepFile, stepId, dependencies, ctx, attemptNumber, backend, onLog, options) {
12888
12908
  const outputPath = backend.getStepOutputPath(ctx.workflow.slug, ctx.runId, stepId, attemptNumber);
12889
12909
  return executeStepInSubprocess(stepFile, stepId, dependencies, ctx, attemptNumber, outputPath, onLog, options);
12890
12910
  }
12891
- async function runAll(options) {
12892
- const workflows = await discoverWorkflows();
12893
- if (workflows.length === 0) {
12894
- throw new Error('No workflows found. Please create a "workflows" directory with at least one workflow.');
12895
- }
12896
- const workflow = workflows.find((w) => w.slug === options.workflow);
12897
- if (!workflow) {
12898
- const available = workflows.map((w) => w.slug).join(", ");
12899
- throw new Error(`Workflow "${options.workflow}" not found. Available workflows: ${available}`);
12900
- }
12901
- const steps = await discoverSteps(workflow.stepsDir);
12902
- detectCycles(steps);
12903
- const byId = new Map(steps.map((s) => [s.id, s]));
12904
- const selected = options?.only?.length ? options.only.map((id) => {
12905
- const s = byId.get(id);
12906
- if (!s)
12907
- throw new Error(`Unknown step "${id}"`);
12908
- return s;
12909
- }) : steps;
12910
- const backend = options.backend;
12911
- const workflowSlug = workflow.slug;
12912
- const cache = new Map;
12913
- const skippedSteps = new Set;
12914
- const runId = options?.runId ?? `${getMicrosecondTimestamp2()}`;
12915
- const defaultCtx = {
12916
- runId,
12917
- workflow: {
12918
- slug: workflow.slug,
12919
- name: workflow.name
12920
- },
12921
- input: undefined,
12922
- log: (...args) => console.log("[runner]", ...args),
12923
- ...options?.ctx
12924
- };
12925
- const workflowStartTime = getMicrosecondTimestamp2();
12926
- await backend.initializeRun(workflowSlug, runId);
12927
- const versionId = await calculateWorkflowHash(workflow);
12928
- const git = await getGitInfo(workflow.dir);
12929
- const stepManifest = steps.map((s) => s.id);
12930
- await backend.createWorkflowVersion({
12931
- workflowSlug,
12932
- versionId,
12933
- createdAt: getMicrosecondTimestamp2(),
12934
- stepManifest,
12935
- totalSteps: steps.length,
12936
- git
12937
- });
12938
- const hasInputSchema = workflow.inputSchema !== undefined;
12939
- const hasInput = options.input !== undefined;
12940
- await backend.saveWorkflowStart(workflowSlug, runId, {
12941
- versionId,
12942
- workflowAttemptNumber: 1,
12943
- hasInputSchema,
12944
- hasInput
12945
- });
12946
- let validatedInput = undefined;
12947
- if (workflow.inputSchema) {
12948
- const parseResult = workflow.inputSchema.safeParse(options.input ?? {});
12949
- if (!parseResult.success) {
12950
- const validationErrors = parseResult.error.issues.map((e) => ({
12951
- path: e.path.join("."),
12952
- message: e.message
12953
- }));
12954
- const errorMessage = validationErrors.map((e) => ` ${e.path}: ${e.message}`).join(`
12955
- `);
12956
- const error46 = {
12957
- name: "ValidationError",
12958
- message: `Invalid workflow input:
12959
- ${errorMessage}`
12960
- };
12961
- await backend.saveWorkflowInputValidation(workflowSlug, runId, {
12962
- workflowAttemptNumber: 1,
12963
- hasSchema: true,
12964
- success: false,
12965
- error: error46,
12966
- validationErrors
12967
- });
12968
- const duration3 = getMicrosecondTimestamp2() - workflowStartTime;
12969
- await backend.saveWorkflowFailed(workflowSlug, runId, error46, {
12970
- workflowAttemptNumber: 1,
12971
- duration: duration3,
12972
- completedSteps: 0
12973
- }, "step-failed");
12974
- throw new Error(error46.message);
12975
- }
12976
- validatedInput = parseResult.data;
12977
- } else if (options.input !== undefined) {
12978
- validatedInput = options.input;
12979
- }
12980
- defaultCtx.input = validatedInput;
12981
- if (hasInputSchema || hasInput) {
12982
- await backend.saveWorkflowInputValidation(workflowSlug, runId, {
12983
- workflowAttemptNumber: 1,
12984
- hasSchema: hasInputSchema,
12985
- success: true
12986
- });
12987
- }
12988
- if (options?.resume) {
12989
- defaultCtx.log(`Resuming run ${defaultCtx.runId}...`);
12990
- const workflowEvents = await backend.loadEvents(workflowSlug, defaultCtx.runId, { category: "workflow" });
12991
- const workflowStartedEvent = workflowEvents.find((e) => e.type === "WorkflowStarted");
12992
- if (workflowStartedEvent && workflowStartedEvent.type === "WorkflowStarted") {
12993
- const previousVersionId = workflowStartedEvent.versionId;
12994
- if (previousVersionId !== versionId) {
12995
- const previousVersion = await backend.getWorkflowVersion(workflowSlug, previousVersionId);
12996
- const currentVersion = await backend.getWorkflowVersion(workflowSlug, versionId);
12997
- defaultCtx.log(`⚠️ Workflow definition changed since original run`);
12998
- defaultCtx.log(` Original: ${previousVersionId}`);
12999
- defaultCtx.log(` Current: ${versionId}`);
13000
- if (previousVersion?.git && currentVersion?.git) {
13001
- defaultCtx.log(` Git: ${previousVersion.git.commit} → ${currentVersion.git.commit}`);
13002
- }
13003
- }
13004
- }
13005
- const existingRecords = await backend.loadRun(workflowSlug, defaultCtx.runId);
13006
- let resumedSteps = 0;
13007
- for (const record2 of existingRecords) {
13008
- if (record2.status === "completed" && record2.output !== undefined && record2.output !== null) {
13009
- try {
13010
- const output = JSON.parse(record2.output);
13011
- cache.set(record2.stepId, Promise.resolve(output));
13012
- const step = steps.find((s) => s.id === record2.stepId);
13013
- const displayName = step?.name ?? record2.stepId;
13014
- defaultCtx.log(`✓ ${displayName} (resumed from cache)`);
13015
- resumedSteps++;
13016
- } catch (err) {
13017
- const step = steps.find((s) => s.id === record2.stepId);
13018
- const displayName = step?.name ?? record2.stepId;
13019
- defaultCtx.log(`⚠ Failed to deserialize ${displayName}, will re-run`);
13020
- }
13021
- }
13022
- }
13023
- const pendingSteps = steps.length - resumedSteps;
13024
- await backend.saveWorkflowResumed(workflowSlug, defaultCtx.runId, {
13025
- originalRunId: defaultCtx.runId,
13026
- resumedSteps,
13027
- pendingSteps
13028
- });
13029
- }
13030
- async function execute(step, stack = []) {
13031
- const existing = cache.get(step.id);
13032
- if (existing)
13033
- return existing;
13034
- const p = (async () => {
13035
- const startTime = getMicrosecondTimestamp2();
13036
- await backend.saveStepStart(workflowSlug, defaultCtx.runId, step.id, "local", {
13037
- dependencies: Object.keys(step.dependencies),
13038
- timestamp: startTime,
13039
- attemptNumber: 1
13040
- });
13041
- defaultCtx.log(`→ ${step.name} (waiting deps: ${Object.keys(step.dependencies).join(", ") || "none"})`);
13042
- try {
13043
- const depEntries = Object.entries(step.dependencies);
13044
- const depOutputsPairs = await Promise.all(depEntries.map(async ([alias, dep]) => {
13045
- const output = await execute(dep, [...stack, step.id]);
13046
- const isSkipped = skippedSteps.has(dep.id);
13047
- const isOptionalDep = isOptional2(step.dependencies[alias]);
13048
- return [alias, isSkipped && isOptionalDep ? undefined : output];
13049
- }));
13050
- const depOutputs = Object.fromEntries(depOutputsPairs);
13051
- const skippedRequiredDeps = depEntries.filter(([alias, dep]) => {
13052
- const isSkipped = skippedSteps.has(dep.id);
13053
- const isOptionalDep = isOptional2(step.dependencies[alias]);
13054
- return isSkipped && !isOptionalDep;
13055
- });
13056
- if (skippedRequiredDeps.length > 0) {
13057
- const cascadedFromStep = skippedRequiredDeps[0][1];
13058
- const endTime2 = getMicrosecondTimestamp2();
13059
- await backend.saveStepSkipped(workflowSlug, defaultCtx.runId, step.id, {
13060
- skipType: "cascade",
13061
- reason: `Dependency '${cascadedFromStep.name}' was skipped`,
13062
- duration: endTime2 - startTime,
13063
- attemptNumber: 1,
13064
- cascadedFrom: cascadedFromStep.id
13065
- });
13066
- skippedSteps.add(step.id);
13067
- defaultCtx.log(`⊘ ${step.name} (skipped: dependency '${cascadedFromStep.name}' was skipped)`);
13068
- return {};
13069
- }
13070
- const stepFile = join2(step.dir, "step.ts");
13071
- const maxRetries = step.maxRetries ?? 0;
13072
- let lastError = null;
13073
- for (let attemptNumber = 1;attemptNumber <= maxRetries + 1; attemptNumber++) {
13074
- try {
13075
- const { result, logs } = await executeStepInProcess(stepFile, step.id, depOutputs, defaultCtx, attemptNumber, backend);
13076
- const endTime2 = getMicrosecondTimestamp2();
13077
- await backend.saveStepComplete(workflowSlug, defaultCtx.runId, step.id, result, {
13078
- timestamp: endTime2,
13079
- duration: endTime2 - startTime,
13080
- logs: logs.length > 0 ? logs : undefined,
13081
- attemptNumber,
13082
- output: result
13083
- }, step.exportOutput ?? false);
13084
- defaultCtx.log(`✓ ${step.name}`);
13085
- return result;
13086
- } catch (err) {
13087
- lastError = err instanceof Error ? err : new Error(String(err));
13088
- if (lastError.name === "Skip" || lastError instanceof Skip) {
13089
- const endTime2 = getMicrosecondTimestamp2();
13090
- const skipError = lastError;
13091
- await backend.saveStepSkipped(workflowSlug, defaultCtx.runId, step.id, {
13092
- skipType: "primary",
13093
- reason: skipError.reason || skipError.message.replace("Step skipped: ", ""),
13094
- metadata: skipError.metadata,
13095
- duration: endTime2 - startTime,
13096
- attemptNumber
13097
- });
13098
- skippedSteps.add(step.id);
13099
- defaultCtx.log(`⊘ ${step.name} (skipped: ${skipError.reason || skipError.message})`);
13100
- return {};
13101
- }
13102
- if (attemptNumber <= maxRetries) {
13103
- const error47 = {
13104
- message: lastError.message,
13105
- stack: lastError.stack,
13106
- name: lastError.name
13107
- };
13108
- const retryEvent = {
13109
- category: "step",
13110
- eventId: "",
13111
- timestampUs: getMicrosecondTimestamp2(),
13112
- workflowSlug,
13113
- runId: defaultCtx.runId,
13114
- stepId: step.id,
13115
- type: "StepRetrying",
13116
- attemptNumber,
13117
- nextAttempt: attemptNumber + 1,
13118
- error: error47,
13119
- maxRetries
13120
- };
13121
- await backend.appendEvent(workflowSlug, defaultCtx.runId, retryEvent);
13122
- defaultCtx.log(`⟳ ${step.name} retry ${attemptNumber + 1}/${maxRetries + 1} after error: ${error47.message}`);
13123
- }
13124
- }
13125
- }
13126
- const error46 = {
13127
- message: lastError.message,
13128
- stack: lastError.stack,
13129
- name: lastError.name
13130
- };
13131
- const failureReason = lastError.isCrash ? "worker-crash" : "exhausted-retries";
13132
- const endTime = getMicrosecondTimestamp2();
13133
- await backend.saveStepFailed(workflowSlug, defaultCtx.runId, step.id, error46, {
13134
- duration: endTime - startTime,
13135
- attemptNumber: maxRetries + 1,
13136
- terminal: true,
13137
- failureReason
13138
- });
13139
- defaultCtx.log(`✗ ${step.name} failed after ${maxRetries + 1} attempts: ${error46.message}`);
13140
- throw lastError;
13141
- } catch (err) {
13142
- throw err;
13143
- }
13144
- })();
13145
- cache.set(step.id, p);
13146
- return p;
13147
- }
13148
- try {
13149
- await Promise.all(selected.map((s) => execute(s)));
13150
- const results = {};
13151
- for (const s of steps) {
13152
- if (s.exportOutput && !skippedSteps.has(s.id)) {
13153
- const val = cache.get(s.id);
13154
- if (val)
13155
- results[s.id] = await val;
13156
- }
13157
- }
13158
- const workflowEndTime = getMicrosecondTimestamp2();
13159
- const workflowDuration = workflowEndTime - workflowStartTime;
13160
- await backend.saveWorkflowComplete(workflowSlug, defaultCtx.runId, results, {
13161
- workflowAttemptNumber: 1,
13162
- timestamp: workflowEndTime,
13163
- duration: workflowDuration,
13164
- totalSteps: steps.length
13165
- });
13166
- return results;
13167
- } catch (err) {
13168
- const workflowEndTime = getMicrosecondTimestamp2();
13169
- const workflowDuration = workflowEndTime - workflowStartTime;
13170
- let completedSteps = 0;
13171
- for (const s of steps) {
13172
- const val = cache.get(s.id);
13173
- if (val) {
13174
- try {
13175
- await val;
13176
- completedSteps++;
13177
- } catch {}
13178
- }
13179
- }
13180
- let failedStep;
13181
- if (err instanceof Error && err.message) {
13182
- for (const s of steps) {
13183
- if (err.message.includes(s.name) || err.message.includes(s.id)) {
13184
- failedStep = s.id;
13185
- break;
13186
- }
13187
- }
13188
- }
13189
- const error46 = {
13190
- message: err instanceof Error ? err.message : String(err),
13191
- stack: err instanceof Error ? err.stack : undefined,
13192
- name: err instanceof Error ? err.name : undefined
13193
- };
13194
- const failureReason = err.isCrash ? "worker-crash" : "step-failed";
13195
- await backend.saveWorkflowFailed(workflowSlug, defaultCtx.runId, error46, {
13196
- workflowAttemptNumber: 1,
13197
- duration: workflowDuration,
13198
- completedSteps,
13199
- failedStep
13200
- }, failureReason);
13201
- throw err;
13202
- }
13203
- }
13204
12911
  export {
13205
- runAll,
12912
+ validateWorkflowVersion,
13206
12913
  getGitInfo,
12914
+ getAllDependents,
13207
12915
  executeStepInProcess,
13208
12916
  discoverWorkflows,
13209
12917
  discoverSteps,
13210
12918
  calculateWorkflowHash
13211
12919
  };
13212
12920
 
13213
- //# debugId=0A6F5DFCD4D7954F64756E2164756E21
12921
+ //# debugId=6283553AC8E9AA7C64756E2164756E21