Skip to content

Commit 33237b2

Browse files
committed
refactor: move graph extraction to TypeScript post-bundle
- Add new TypeScript-based graph-extractor.ts for post-bundle CFG extraction - Update builders to use post-bundle extraction instead of SWC graph mode - Supports accurate CFG with loops, parallels, and conditionals - Works with imported step functions from external packages - No changes to Rust SWC plugin (graph mode still exists but unused)
1 parent 9a583fd commit 33237b2

File tree

19 files changed

+946
-2191
lines changed

19 files changed

+946
-2191
lines changed

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/builders/src/apply-swc-transform.ts

Lines changed: 2 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -20,51 +20,12 @@ export type WorkflowManifest = {
2020
};
2121
};
2222

23-
export type GraphManifest = {
24-
version: string;
25-
workflows: {
26-
[workflowName: string]: {
27-
workflowId: string;
28-
workflowName: string;
29-
filePath: string;
30-
nodes: Array<{
31-
id: string;
32-
type: string;
33-
position: { x: number; y: number };
34-
data: {
35-
label: string;
36-
nodeKind: string;
37-
stepId?: string;
38-
line: number;
39-
};
40-
}>;
41-
edges: Array<{
42-
id: string;
43-
source: string;
44-
target: string;
45-
type: string;
46-
}>;
47-
};
48-
};
49-
debugInfo?: {
50-
manifestPresent: boolean;
51-
manifestStepFiles: number;
52-
importsResolved: number;
53-
importsWithKind: number;
54-
importDetails: Array<{
55-
localName: string;
56-
source: string;
57-
importedName: string;
58-
kind: string | null;
59-
lookupCandidates: string[];
60-
}>;
61-
};
62-
};
23+
// Graph manifest types moved to graph-extractor.ts (post-bundle extraction)
6324

6425
export async function applySwcTransform(
6526
filename: string,
6627
source: string,
67-
mode: 'workflow' | 'step' | 'client' | 'graph' | false,
28+
mode: 'workflow' | 'step' | 'client' | false,
6829
jscConfig?: {
6930
paths?: Record<string, string[]>;
7031
// this must be absolute path
@@ -74,7 +35,6 @@ export async function applySwcTransform(
7435
): Promise<{
7536
code: string;
7637
workflowManifest: WorkflowManifest;
77-
graphManifest?: GraphManifest;
7838
}> {
7939
// Determine if this is a TypeScript file
8040
const isTypeScript = filename.endsWith('.ts') || filename.endsWith('.tsx');
@@ -120,17 +80,8 @@ export async function applySwcTransform(
12080
workflows: metadata.workflows,
12181
} as WorkflowManifest;
12282

123-
// Extract graph manifest from separate comment
124-
const graphCommentMatch = result.code.match(
125-
/\/\*\*__workflow_graph({.*?})\*\//s
126-
);
127-
const graphManifest = graphCommentMatch?.[1]
128-
? (JSON.parse(graphCommentMatch[1]) as GraphManifest)
129-
: undefined;
130-
13183
return {
13284
code: result.code,
13385
workflowManifest: parsedWorkflows || {},
134-
graphManifest,
13586
};
13687
}

packages/builders/src/base-builder.ts

Lines changed: 23 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import enhancedResolveOriginal from 'enhanced-resolve';
77
import * as esbuild from 'esbuild';
88
import { findUp } from 'find-up';
99
import { glob } from 'tinyglobby';
10-
import type { GraphManifest, WorkflowManifest } from './apply-swc-transform.js';
10+
import type { WorkflowManifest } from './apply-swc-transform.js';
1111
import { createDiscoverEntriesPlugin } from './discover-entries-esbuild-plugin.js';
1212
import { createNodeModuleErrorPlugin } from './node-module-esbuild-plugin.js';
1313
import { createSwcPlugin } from './swc-esbuild-plugin.js';
@@ -849,100 +849,41 @@ export const OPTIONS = handler;`;
849849
}
850850

851851
/**
852-
* Creates a graph manifest JSON file by running the SWC plugin in 'graph' mode.
852+
* Creates a graph manifest JSON file by extracting from the bundled workflow file.
853853
* The manifest contains React Flow-compatible graph data for visualizing workflows.
854854
*/
855855
protected async createGraphManifest({
856-
inputFiles,
856+
workflowBundlePath,
857857
outfile,
858-
tsBaseUrl,
859-
tsPaths,
860-
workflowManifest,
861858
}: {
862-
inputFiles: string[];
859+
workflowBundlePath: string;
863860
outfile: string;
864-
tsBaseUrl?: string;
865-
tsPaths?: Record<string, string[]>;
866-
workflowManifest?: WorkflowManifest;
867861
}): Promise<void> {
868862
const graphBuildStart = Date.now();
869863
console.log('Creating workflow graph manifest...');
870864

871-
const { discoveredWorkflows: workflowFiles } = await this.discoverEntries(
872-
inputFiles,
873-
dirname(outfile)
874-
);
875-
876-
if (workflowFiles.length === 0) {
877-
console.log('No workflow files found, skipping graph generation');
878-
return;
879-
}
880-
881-
// Use provided manifest or fall back to last built manifest
882-
const manifest = workflowManifest || this.lastWorkflowManifest;
883-
884-
// Import applySwcTransform dynamically
885-
const { applySwcTransform } = await import('./apply-swc-transform.js');
886-
887-
// Aggregate all graph data from all workflow files
888-
const combinedGraphManifest: GraphManifest = {
889-
version: '1.0.0',
890-
workflows: {},
891-
};
892-
893-
for (const workflowFile of workflowFiles) {
894-
try {
895-
const source = await readFile(workflowFile, 'utf-8');
896-
const normalizedWorkingDir = this.config.workingDir.replace(/\\/g, '/');
897-
const normalizedFile = workflowFile.replace(/\\/g, '/');
898-
let relativePath = relative(
899-
normalizedWorkingDir,
900-
normalizedFile
901-
).replace(/\\/g, '/');
902-
if (!relativePath.startsWith('.')) {
903-
relativePath = `./${relativePath}`;
904-
}
865+
try {
866+
// Import the graph extractor
867+
const { extractGraphFromBundle } = await import('./graph-extractor.js');
905868

906-
const { graphManifest } = await applySwcTransform(
907-
relativePath,
908-
source,
909-
'graph',
910-
{
911-
paths: tsPaths,
912-
baseUrl: tsBaseUrl,
913-
},
914-
manifest
915-
);
869+
// Extract graph from the bundled workflow file
870+
const graphManifest = await extractGraphFromBundle(workflowBundlePath);
916871

917-
if (graphManifest && graphManifest.workflows) {
918-
// Merge the workflows from this file into the combined manifest
919-
Object.assign(
920-
combinedGraphManifest.workflows,
921-
graphManifest.workflows
922-
);
872+
// Write the graph manifest
873+
await this.ensureDirectory(outfile);
874+
await writeFile(outfile, JSON.stringify(graphManifest, null, 2));
923875

924-
// Preserve debug info from the first workflow file (for debugging)
925-
if (graphManifest.debugInfo && !combinedGraphManifest.debugInfo) {
926-
combinedGraphManifest.debugInfo = graphManifest.debugInfo;
927-
}
928-
}
929-
} catch (error) {
930-
console.warn(
931-
`Failed to extract graph from ${workflowFile}:`,
932-
error instanceof Error ? error.message : String(error)
933-
);
934-
}
876+
console.log(
877+
`Created graph manifest with ${
878+
Object.keys(graphManifest.workflows).length
879+
} workflow(s)`,
880+
`${Date.now() - graphBuildStart}ms`
881+
);
882+
} catch (error) {
883+
console.warn(
884+
'Failed to extract graph from bundle:',
885+
error instanceof Error ? error.message : String(error)
886+
);
935887
}
936-
937-
// Write the combined graph manifest
938-
await this.ensureDirectory(outfile);
939-
await writeFile(outfile, JSON.stringify(combinedGraphManifest, null, 2));
940-
941-
console.log(
942-
`Created graph manifest with ${
943-
Object.keys(combinedGraphManifest.workflows).length
944-
} workflow(s)`,
945-
`${Date.now() - graphBuildStart}ms`
946-
);
947888
}
948889
}

0 commit comments

Comments
 (0)