diff --git a/src/platform/workflow/management/stores/comfyWorkflow.ts b/src/platform/workflow/management/stores/comfyWorkflow.ts index ee539edb6..d44dd6c4d 100644 --- a/src/platform/workflow/management/stores/comfyWorkflow.ts +++ b/src/platform/workflow/management/stores/comfyWorkflow.ts @@ -106,8 +106,13 @@ export class ComfyWorkflow extends UserFile { await super.load({ force }) if (!force && this.isLoaded) return this as this & LoadedComfyWorkflow - if (!this.originalContent) { - throw new Error('[ASSERT] Workflow content should be loaded') + if (this.originalContent == null) { + throw new Error( + `[ASSERT] Workflow content should be loaded for '${this.path}'` + ) + } + if (this.originalContent.trim().length === 0) { + throw new Error(`Workflow content is empty for '${this.path}'`) } const initialState = JSON.parse(this.originalContent) diff --git a/src/scripts/api.ts b/src/scripts/api.ts index 557d36b8e..e34695a27 100644 --- a/src/scripts/api.ts +++ b/src/scripts/api.ts @@ -1181,9 +1181,16 @@ export class ComfyApi extends EventTarget { async getGlobalSubgraphData(id: string): Promise { const resp = await api.fetchApi('/global_subgraphs/' + id) - if (resp.status !== 200) return '' + if (resp.status !== 200) { + throw new Error( + `Failed to fetch global subgraph '${id}': ${resp.status} ${resp.statusText}` + ) + } const subgraph: GlobalSubgraphData = await resp.json() - return subgraph?.data ?? '' + if (!subgraph?.data) { + throw new Error(`Global subgraph '${id}' returned empty data`) + } + return subgraph.data as string } async getGlobalSubgraphs(): Promise> { const resp = await api.fetchApi('/global_subgraphs') diff --git a/src/stores/subgraphStore.test.ts b/src/stores/subgraphStore.test.ts index 5cdc1a8fc..f2bef04c6 100644 --- a/src/stores/subgraphStore.test.ts +++ b/src/stores/subgraphStore.test.ts @@ -225,6 +225,68 @@ describe('useSubgraphStore', () => { }) }) + it('should handle global blueprint with empty data gracefully', async () => { + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + await mockFetch( + {}, + { + broken_blueprint: { + name: 'Broken Blueprint', + info: { node_pack: 'test_pack' }, + data: '' + } + } + ) + expect(consoleSpy).toHaveBeenCalledWith( + 'Failed to load subgraph blueprint', + expect.any(Error) + ) + expect(store.subgraphBlueprints).toHaveLength(0) + consoleSpy.mockRestore() + }) + + it('should handle global blueprint with rejected data promise gracefully', async () => { + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + await mockFetch( + {}, + { + failing_blueprint: { + name: 'Failing Blueprint', + info: { node_pack: 'test_pack' }, + data: Promise.reject(new Error('Network error')) as unknown as string + } + } + ) + expect(consoleSpy).toHaveBeenCalledWith( + 'Failed to load subgraph blueprint', + expect.any(Error) + ) + expect(store.subgraphBlueprints).toHaveLength(0) + consoleSpy.mockRestore() + }) + + it('should load valid global blueprints even when others fail', async () => { + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + await mockFetch( + {}, + { + broken: { + name: 'Broken', + info: { node_pack: 'test_pack' }, + data: '' + }, + valid: { + name: 'Valid Blueprint', + info: { node_pack: 'test_pack' }, + data: JSON.stringify(mockGraph) + } + } + ) + expect(consoleSpy).toHaveBeenCalled() + expect(store.subgraphBlueprints).toHaveLength(1) + consoleSpy.mockRestore() + }) + describe('search_aliases support', () => { it('should include search_aliases from workflow extra', async () => { const mockGraphWithAliases = { diff --git a/src/stores/subgraphStore.ts b/src/stores/subgraphStore.ts index 752ed957f..78fac3d13 100644 --- a/src/stores/subgraphStore.ts +++ b/src/stores/subgraphStore.ts @@ -198,13 +198,19 @@ export const useSubgraphStore = defineStore('subgraph', () => { } async function loadInstalledBlueprints() { async function loadGlobalBlueprint([k, v]: [string, GlobalSubgraphData]) { + const data = await v.data + if (typeof data !== 'string' || data.trim().length === 0) { + throw new Error( + `Global blueprint '${v.name}' (${k}) returned empty content` + ) + } const path = SubgraphBlueprint.basePath + v.name + '.json' const blueprint = new SubgraphBlueprint({ path, modified: Date.now(), size: -1 }) - blueprint.originalContent = blueprint.content = await v.data + blueprint.originalContent = blueprint.content = data blueprint.filename = v.name useWorkflowStore().attachWorkflow(blueprint) const loaded = await blueprint.load() @@ -238,16 +244,19 @@ export const useSubgraphStore = defineStore('subgraph', () => { return false return true }) - await Promise.allSettled(filteredEntries.map(loadGlobalBlueprint)) + return Promise.allSettled(filteredEntries.map(loadGlobalBlueprint)) } const userSubs = ( await api.listUserDataFullInfo(SubgraphBlueprint.basePath) ).filter((f) => f.path.endsWith('.json')) - const settled = await Promise.allSettled([ - ...userSubs.map(loadBlueprint), - loadInstalledBlueprints() + const [globalResult, ...userResults] = await Promise.allSettled([ + loadInstalledBlueprints(), + ...userSubs.map(loadBlueprint) ]) + const globalResults = + globalResult.status === 'fulfilled' ? globalResult.value : [] + const settled = [...globalResults, ...userResults] const errors = settled.filter((i) => 'reason' in i).map((i) => i.reason) errors.forEach((e) => console.error('Failed to load subgraph blueprint', e))