@@ -13,6 +13,7 @@ import { createDiscoverEntriesPlugin } from './discover-entries-esbuild-plugin.j
1313import { createNodeModuleErrorPlugin } from './node-module-esbuild-plugin.js' ;
1414import { createSwcPlugin } from './swc-esbuild-plugin.js' ;
1515import type { WorkflowConfig } from './types.js' ;
16+ import { extractWorkflowGraphs } from './workflows-extractor.js' ;
1617
1718const enhancedResolve = promisify ( enhancedResolveOriginal ) ;
1819
@@ -280,6 +281,7 @@ export abstract class BaseBuilder {
280281 * Steps have full Node.js runtime access and handle side effects, API calls, etc.
281282 *
282283 * @param externalizeNonSteps - If true, only bundles step entry points and externalizes other code
284+ * @returns Build context (for watch mode) and the collected workflow manifest
283285 */
284286 protected async createStepsBundle ( {
285287 inputFiles,
@@ -295,16 +297,17 @@ export abstract class BaseBuilder {
295297 outfile : string ;
296298 format ?: 'cjs' | 'esm' ;
297299 externalizeNonSteps ?: boolean ;
298- } ) : Promise < esbuild . BuildContext | undefined > {
300+ } ) : Promise < {
301+ context : esbuild . BuildContext | undefined ;
302+ manifest : WorkflowManifest ;
303+ } > {
299304 // These need to handle watching for dev to scan for
300305 // new entries and changes to existing ones
301- const { discoveredSteps : stepFiles } = await this . discoverEntries (
302- inputFiles ,
303- dirname ( outfile )
304- ) ;
306+ const { discoveredSteps : stepFiles , discoveredWorkflows : workflowFiles } =
307+ await this . discoverEntries ( inputFiles , dirname ( outfile ) ) ;
305308
306309 // log the step files for debugging
307- await this . writeDebugFile ( outfile , { stepFiles } ) ;
310+ await this . writeDebugFile ( outfile , { stepFiles, workflowFiles } ) ;
308311
309312 const stepsBundleStart = Date . now ( ) ;
310313 const workflowManifest : WorkflowManifest = { } ;
@@ -326,6 +329,7 @@ export abstract class BaseBuilder {
326329
327330 const combinedStepFiles : string [ ] = [
328331 ...stepFiles ,
332+ ...workflowFiles ,
329333 ...( resolvedBuiltInSteps
330334 ? [
331335 resolvedBuiltInSteps ,
@@ -337,6 +341,8 @@ export abstract class BaseBuilder {
337341
338342 // Create a virtual entry that imports all files. All step definitions
339343 // will get registered thanks to the swc transform.
344+ // We also import workflow files so their metadata is collected by the SWC plugin,
345+ // even though they'll be externalized from the final bundle.
340346 const imports = combinedStepFiles
341347 . map ( ( file ) => {
342348 // Normalize both paths to forward slashes before calling relative()
@@ -420,23 +426,14 @@ export abstract class BaseBuilder {
420426 this . logEsbuildMessages ( stepsResult , 'steps bundle creation' ) ;
421427 console . log ( 'Created steps bundle' , `${ Date . now ( ) - stepsBundleStart } ms` ) ;
422428
423- const partialWorkflowManifest = {
424- steps : workflowManifest . steps ,
425- } ;
426- // always write to debug file
427- await this . writeDebugFile (
428- join ( dirname ( outfile ) , 'manifest' ) ,
429- partialWorkflowManifest ,
430- true
431- ) ;
432-
433429 // Create .gitignore in .swc directory
434430 await this . createSwcGitignore ( ) ;
435431
436432 if ( this . config . watch ) {
437- return esbuildCtx ;
433+ return { context : esbuildCtx , manifest : workflowManifest } ;
438434 }
439435 await esbuildCtx . dispose ( ) ;
436+ return { context : undefined , manifest : workflowManifest } ;
440437 }
441438
442439 /**
@@ -556,16 +553,6 @@ export abstract class BaseBuilder {
556553 `${ Date . now ( ) - bundleStartTime } ms`
557554 ) ;
558555
559- const partialWorkflowManifest = {
560- workflows : workflowManifest . workflows ,
561- } ;
562-
563- await this . writeDebugFile (
564- join ( dirname ( outfile ) , 'manifest' ) ,
565- partialWorkflowManifest ,
566- true
567- ) ;
568-
569556 if ( this . config . workflowManifestPath ) {
570557 const resolvedPath = resolve (
571558 process . cwd ( ) ,
@@ -917,4 +904,107 @@ export const OPTIONS = handler;`;
917904 // We're intentionally silently ignoring this error - creating .gitignore isn't critical
918905 }
919906 }
907+
908+ /**
909+ * Creates a manifest JSON file containing step/workflow metadata
910+ * and graph data for visualization.
911+ */
912+ protected async createManifest ( {
913+ workflowBundlePath,
914+ manifestDir,
915+ manifest,
916+ } : {
917+ workflowBundlePath : string ;
918+ manifestDir : string ;
919+ manifest : WorkflowManifest ;
920+ } ) : Promise < void > {
921+ const buildStart = Date . now ( ) ;
922+ console . log ( 'Creating manifest...' ) ;
923+
924+ try {
925+ const workflowGraphs = await extractWorkflowGraphs ( workflowBundlePath ) ;
926+
927+ const steps = this . convertStepsManifest ( manifest . steps ) ;
928+ const workflows = this . convertWorkflowsManifest (
929+ manifest . workflows ,
930+ workflowGraphs
931+ ) ;
932+
933+ const output = { version : '1.0.0' , steps, workflows } ;
934+
935+ await mkdir ( manifestDir , { recursive : true } ) ;
936+ await writeFile (
937+ join ( manifestDir , 'manifest.json' ) ,
938+ JSON . stringify ( output , null , 2 )
939+ ) ;
940+
941+ const stepCount = Object . values ( steps ) . reduce (
942+ ( acc , s ) => acc + Object . keys ( s ) . length ,
943+ 0
944+ ) ;
945+ const workflowCount = Object . values ( workflows ) . reduce (
946+ ( acc , w ) => acc + Object . keys ( w ) . length ,
947+ 0
948+ ) ;
949+
950+ console . log (
951+ `Created manifest with ${ stepCount } step(s) and ${ workflowCount } workflow(s)` ,
952+ `${ Date . now ( ) - buildStart } ms`
953+ ) ;
954+ } catch ( error ) {
955+ console . warn (
956+ 'Failed to create manifest:' ,
957+ error instanceof Error ? error . message : String ( error )
958+ ) ;
959+ }
960+ }
961+
962+ private convertStepsManifest (
963+ steps : WorkflowManifest [ 'steps' ]
964+ ) : Record < string , Record < string , { stepId : string } > > {
965+ const result : Record < string , Record < string , { stepId : string } > > = { } ;
966+ if ( ! steps ) return result ;
967+
968+ for ( const [ filePath , entries ] of Object . entries ( steps ) ) {
969+ result [ filePath ] = { } ;
970+ for ( const [ name , data ] of Object . entries ( entries ) ) {
971+ result [ filePath ] [ name ] = { stepId : data . stepId } ;
972+ }
973+ }
974+ return result ;
975+ }
976+
977+ private convertWorkflowsManifest (
978+ workflows : WorkflowManifest [ 'workflows' ] ,
979+ graphs : Record <
980+ string ,
981+ Record < string , { graph : { nodes : any [ ] ; edges : any [ ] } } >
982+ >
983+ ) : Record <
984+ string ,
985+ Record <
986+ string ,
987+ { workflowId : string ; graph : { nodes : any [ ] ; edges : any [ ] } }
988+ >
989+ > {
990+ const result : Record <
991+ string ,
992+ Record <
993+ string ,
994+ { workflowId : string ; graph : { nodes : any [ ] ; edges : any [ ] } }
995+ >
996+ > = { } ;
997+ if ( ! workflows ) return result ;
998+
999+ for ( const [ filePath , entries ] of Object . entries ( workflows ) ) {
1000+ result [ filePath ] = { } ;
1001+ for ( const [ name , data ] of Object . entries ( entries ) ) {
1002+ result [ filePath ] [ name ] = {
1003+ workflowId : data . workflowId ,
1004+ graph : graphs [ filePath ] ?. [ name ] ?. graph || { nodes : [ ] , edges : [ ] } ,
1005+ } ;
1006+ }
1007+ }
1008+ return result ;
1009+ }
9201010}
0 commit comments