@@ -88,6 +88,135 @@ export function addWorkflow(file: WorkflowFile, name: string): WorkflowFile {
8888 } ;
8989}
9090
91+ // Collect all workflow names that are referenced by any node in the file.
92+ // References come from two sources:
93+ // 1. SwitchBranch.thenWorkflowName — a `then:` redirect to a named workflow
94+ // 2. TaskNode with config.kind === 'run-workflow' — an explicit run call
95+ //
96+ // The returned set contains workflow *names* (not IDs).
97+ export function referencedWorkflowNames ( file : WorkflowFile ) : Set < string > {
98+ const names = new Set < string > ( ) ;
99+
100+ function walkGraph ( graph : FlowGraph ) : void {
101+ for ( const node of Object . values ( graph . nodes ) ) {
102+ if ( node . type === 'task' ) {
103+ if ( node . config . kind === 'run-workflow' ) {
104+ names . add ( node . config . name ) ;
105+ }
106+ } else if ( node . type === 'switch' ) {
107+ for ( const branch of node . branches ) {
108+ if ( branch . thenWorkflowName ) names . add ( branch . thenWorkflowName ) ;
109+ walkGraph ( branch . graph ) ;
110+ }
111+ } else if ( node . type === 'fork' ) {
112+ for ( const branch of node . branches ) {
113+ walkGraph ( branch . graph ) ;
114+ }
115+ } else if ( node . type === 'try' ) {
116+ walkGraph ( node . tryGraph ) ;
117+ if ( node . catchGraph ) walkGraph ( node . catchGraph ) ;
118+ } else if ( node . type === 'loop' ) {
119+ walkGraph ( node . bodyGraph ) ;
120+ }
121+ }
122+ }
123+
124+ for ( const workflow of Object . values ( file . workflows ) ) {
125+ walkGraph ( workflow . root ) ;
126+ }
127+
128+ return names ;
129+ }
130+
131+ // Update every reference to a workflow name throughout the entire file.
132+ // Called after renaming a workflow so that SwitchBranch `then:` redirects and
133+ // run-workflow task configs stay consistent with the new name.
134+ export function renameWorkflowReferences (
135+ file : WorkflowFile ,
136+ oldName : string ,
137+ newName : string ,
138+ ) : WorkflowFile {
139+ function updateNode ( node : Node ) : Node {
140+ if ( node . type === 'task' ) {
141+ if ( node . config . kind === 'run-workflow' && node . config . name === oldName ) {
142+ return { ...node , config : { ...node . config , name : newName } } ;
143+ }
144+ return node ;
145+ }
146+ if ( node . type === 'switch' ) {
147+ const newBranches = node . branches . map ( ( branch ) => {
148+ const updatedGraph = updateGraph ( branch . graph ) ;
149+ const updatedName =
150+ branch . thenWorkflowName === oldName
151+ ? newName
152+ : branch . thenWorkflowName ;
153+ if (
154+ updatedGraph === branch . graph &&
155+ updatedName === branch . thenWorkflowName
156+ )
157+ return branch ;
158+ return {
159+ ...branch ,
160+ graph : updatedGraph ,
161+ thenWorkflowName : updatedName ,
162+ } ;
163+ } ) ;
164+ if ( newBranches . every ( ( b , i ) => b === node . branches [ i ] ) ) return node ;
165+ return { ...node , branches : newBranches } ;
166+ }
167+ if ( node . type === 'fork' ) {
168+ const newBranches = node . branches . map ( ( branch ) => {
169+ const updatedGraph = updateGraph ( branch . graph ) ;
170+ return updatedGraph === branch . graph
171+ ? branch
172+ : { ...branch , graph : updatedGraph } ;
173+ } ) ;
174+ if ( newBranches . every ( ( b , i ) => b === node . branches [ i ] ) ) return node ;
175+ return { ...node , branches : newBranches } ;
176+ }
177+ if ( node . type === 'try' ) {
178+ const newTryGraph = updateGraph ( node . tryGraph ) ;
179+ const newCatchGraph = node . catchGraph
180+ ? updateGraph ( node . catchGraph )
181+ : undefined ;
182+ if ( newTryGraph === node . tryGraph && newCatchGraph === node . catchGraph )
183+ return node ;
184+ return { ...node , tryGraph : newTryGraph , catchGraph : newCatchGraph } ;
185+ }
186+ if ( node . type === 'loop' ) {
187+ const newBodyGraph = updateGraph ( node . bodyGraph ) ;
188+ return newBodyGraph === node . bodyGraph
189+ ? node
190+ : { ...node , bodyGraph : newBodyGraph } ;
191+ }
192+ return node ;
193+ }
194+
195+ function updateGraph ( graph : FlowGraph ) : FlowGraph {
196+ const newNodes : Record < string , Node > = { } ;
197+ let changed = false ;
198+ for ( const [ id , node ] of Object . entries ( graph . nodes ) ) {
199+ const updated = updateNode ( node ) ;
200+ if ( updated !== node ) changed = true ;
201+ newNodes [ id ] = updated ;
202+ }
203+ return changed ? { ...graph , nodes : newNodes } : graph ;
204+ }
205+
206+ const newWorkflows : WorkflowFile [ 'workflows' ] = { } ;
207+ let changed = false ;
208+ for ( const [ id , wf ] of Object . entries ( file . workflows ) ) {
209+ const newRoot = updateGraph ( wf . root ) ;
210+ if ( newRoot === wf . root ) {
211+ newWorkflows [ id ] = wf ;
212+ } else {
213+ changed = true ;
214+ newWorkflows [ id ] = { ...wf , root : newRoot } ;
215+ }
216+ }
217+ return changed ? { ...file , workflows : newWorkflows } : file ;
218+ }
219+
91220export function removeWorkflow ( file : WorkflowFile , id : string ) : WorkflowFile {
92221 if ( file . order . length <= 1 ) {
93222 throw new Error ( 'Cannot remove the last workflow from a file' ) ;
0 commit comments