Skip to content

Commit fa4e74f

Browse files
committed
fix: guard subgraph blueprint root node
1 parent ca84401 commit fa4e74f

2 files changed

Lines changed: 34 additions & 4 deletions

File tree

src/stores/subgraphStore.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,26 @@ describe('useSubgraphStore', () => {
285285
consoleSpy.mockRestore()
286286
})
287287

288+
it('should reject blueprints without a root node', async () => {
289+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
290+
await mockFetch({
291+
'empty-node.json': {
292+
...mockGraph,
293+
nodes: []
294+
}
295+
})
296+
297+
const error = consoleSpy.mock.calls.find(
298+
([message]) => message === 'Failed to load subgraph blueprint'
299+
)?.[1]
300+
expect(error).toBeInstanceOf(TypeError)
301+
expect((error as Error).message).toBe(
302+
"Subgraph blueprint 'empty-node' must contain a root node"
303+
)
304+
expect(store.subgraphBlueprints).toHaveLength(0)
305+
consoleSpy.mockRestore()
306+
})
307+
288308
it('should handle global blueprint with rejected data promise gracefully', async () => {
289309
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
290310
await mockFetch(

src/stores/subgraphStore.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,14 +153,20 @@ export const useSubgraphStore = defineStore('subgraph', () => {
153153
if (!force && this.isLoaded) return await super.load({ force })
154154
const loaded = await super.load({ force })
155155
const st = loaded.activeState
156+
const rootNode = st.nodes[0]
157+
if (!rootNode) {
158+
throw new TypeError(
159+
`Subgraph blueprint '${this.filename}' must contain a root node`
160+
)
161+
}
156162
const sg = (st.definitions?.subgraphs ?? []).find(
157-
(sg) => sg.id == st.nodes[0].type
163+
(sg) => sg.id == rootNode.type
158164
)
159165
if (!sg)
160166
throw new Error(
161167
'Loaded subgraph blueprint does not contain valid subgraph'
162168
)
163-
sg.name = st.nodes[0].title = this.filename
169+
sg.name = rootNode.title = this.filename
164170

165171
// Copy blueprint metadata from workflow extra to subgraph extra
166172
// so it's available when editing via canvas.subgraph.extra
@@ -283,8 +289,12 @@ export const useSubgraphStore = defineStore('subgraph', () => {
283289
overrides: Partial<ComfyNodeDefV1> = {},
284290
name: string = workflow.filename
285291
) {
286-
const subgraphNode = workflow.changeTracker.initialState
287-
.nodes[0] as ComfyNode
292+
const subgraphNode = workflow.changeTracker.initialState.nodes[0]
293+
if (!subgraphNode) {
294+
throw new TypeError(
295+
`Subgraph blueprint '${name}' must contain a root node`
296+
)
297+
}
288298
subgraphNode.inputs ??= []
289299
subgraphNode.outputs ??= []
290300
//NOTE: Types are cast to string. This is only used for input coloring on previews

0 commit comments

Comments
 (0)