From faa82a56798733b99d2aa681279990b566c4ab19 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Sat, 22 Nov 2025 14:53:38 -0800 Subject: [PATCH 1/4] Recreate missing ComfyUI directories during validation --- src/main-process/comfyInstallation.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main-process/comfyInstallation.ts b/src/main-process/comfyInstallation.ts index 3859f59aa..3a14d29ca 100644 --- a/src/main-process/comfyInstallation.ts +++ b/src/main-process/comfyInstallation.ts @@ -1,6 +1,7 @@ import log from 'electron-log/main'; import { rm } from 'node:fs/promises'; +import { ComfyConfigManager } from '../config/comfyConfigManager'; import { ComfyServerConfig } from '../config/comfyServerConfig'; import { ComfySettings, useComfySettings } from '../config/comfySettings'; import { evaluatePathRestrictions } from '../handlers/pathHandlers'; @@ -135,7 +136,8 @@ export class ComfyInstallation { } else { await this.updateBasePathAndVenv(basePath); - validation.basePath = 'OK'; + const hasDirectories = this.ensureBaseDirectoryStructure(basePath); + validation.basePath = hasDirectories ? 'OK' : 'error'; } this.onUpdate?.(validation); @@ -260,4 +262,23 @@ export class ComfyInstallation { } await useDesktopConfig().permanentlyDeleteConfigFile(); } + + /** + * Ensure the base path contains the expected ComfyUI directory structure. + * Creates missing directories; returns false if the structure is still invalid. + */ + private ensureBaseDirectoryStructure(basePath: string): boolean { + try { + ComfyConfigManager.createComfyDirectories(basePath); + } catch (error) { + log.error('Failed to create ComfyUI directories at base path.', error); + return false; + } + + const isValid = ComfyConfigManager.isComfyUIDirectory(basePath); + if (!isValid) { + log.error(`Base path is missing required ComfyUI directories after creation attempt. [${basePath}]`); + } + return isValid; + } } From 4cfa0339cb983dd322539a5e58208db2e2d6324e Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Sat, 22 Nov 2025 15:05:27 -0800 Subject: [PATCH 2/4] Treat missing requirements as upgrade instead of error --- src/virtualEnvironment.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/virtualEnvironment.ts b/src/virtualEnvironment.ts index 8b744a6ac..6ee9f388b 100644 --- a/src/virtualEnvironment.ts +++ b/src/virtualEnvironment.ts @@ -751,9 +751,18 @@ export class VirtualEnvironment implements HasTelemetry, PythonExecutor { return 'package-upgrade'; } - const result = coreOk && managerOk ? 'OK' : 'error'; - log.debug('hasRequirements result:', result); - return result; + if (!coreOk || !managerOk) { + log.info('Requirements are out of date. Treating as package upgrade.', { + coreOk, + managerOk, + upgradeCore, + upgradeManager, + }); + return 'package-upgrade'; + } + + log.debug('hasRequirements result:', 'OK'); + return 'OK'; } /** From 470df5103c02983d5955d4d3ee2e542d6f92d6bd Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Sat, 22 Nov 2025 15:08:45 -0800 Subject: [PATCH 3/4] Revert "Treat missing requirements as upgrade instead of error" This reverts commit 4cfa0339cb983dd322539a5e58208db2e2d6324e. --- src/virtualEnvironment.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/virtualEnvironment.ts b/src/virtualEnvironment.ts index 6ee9f388b..8b744a6ac 100644 --- a/src/virtualEnvironment.ts +++ b/src/virtualEnvironment.ts @@ -751,18 +751,9 @@ export class VirtualEnvironment implements HasTelemetry, PythonExecutor { return 'package-upgrade'; } - if (!coreOk || !managerOk) { - log.info('Requirements are out of date. Treating as package upgrade.', { - coreOk, - managerOk, - upgradeCore, - upgradeManager, - }); - return 'package-upgrade'; - } - - log.debug('hasRequirements result:', 'OK'); - return 'OK'; + const result = coreOk && managerOk ? 'OK' : 'error'; + log.debug('hasRequirements result:', result); + return result; } /** From 9933aa5aa3e9e06cf8b201a2d57f8fca6efbde0f Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Sat, 22 Nov 2025 15:15:54 -0800 Subject: [PATCH 4/4] Add tests for directory self-heal in ComfyInstallation --- .../main-process/comfyInstallation.test.ts | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 tests/unit/main-process/comfyInstallation.test.ts diff --git a/tests/unit/main-process/comfyInstallation.test.ts b/tests/unit/main-process/comfyInstallation.test.ts new file mode 100644 index 000000000..93a54b77f --- /dev/null +++ b/tests/unit/main-process/comfyInstallation.test.ts @@ -0,0 +1,97 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { ComfyInstallation } from '@/main-process/comfyInstallation'; +import type { ITelemetry } from '@/services/telemetry'; + +vi.mock('electron-log/main', () => ({ + default: { + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + verbose: vi.fn(), + }, +})); + +vi.mock('@/virtualEnvironment', () => ({ + VirtualEnvironment: vi.fn(() => ({})), +})); + +const createDirectories = vi.fn(); +const isComfyDirectory = vi.fn(); + +vi.mock('@/config/comfyConfigManager', () => ({ + ComfyConfigManager: { + createComfyDirectories: createDirectories, + isComfyUIDirectory: isComfyDirectory, + }, +})); + +vi.mock('@/store/desktopConfig', () => ({ + useDesktopConfig: vi.fn(() => ({ + get: vi.fn((key: string) => { + if (key === 'selectedDevice') return 'cpu'; + if (key === 'basePath') return '/base/path'; + return undefined; + }), + set: vi.fn(), + })), +})); + +vi.mock('@/config/comfySettings', () => ({ + useComfySettings: vi.fn(() => ({ + get: vi.fn(), + set: vi.fn(), + })), + ComfySettings: { + load: vi.fn(), + }, +})); + +const telemetry: ITelemetry = { + track: vi.fn(), + hasConsent: true, + flush: vi.fn(), + registerHandlers: vi.fn(), + loadGenerationCount: vi.fn(), +}; + +describe('ComfyInstallation.ensureBaseDirectoryStructure', () => { + const basePath = '/base/path'; + let installation: ComfyInstallation; + + beforeEach(() => { + vi.clearAllMocks(); + installation = new ComfyInstallation('installed', basePath, telemetry); + }); + + it('creates and validates directory structure', () => { + isComfyDirectory.mockReturnValue(true); + + const result = (installation as any).ensureBaseDirectoryStructure(basePath); + + expect(result).toBe(true); + expect(createDirectories).toHaveBeenCalledWith(basePath); + expect(isComfyDirectory).toHaveBeenCalledWith(basePath); + }); + + it('returns false when directory creation throws', () => { + createDirectories.mockImplementation(() => { + throw new Error('fail'); + }); + + const result = (installation as any).ensureBaseDirectoryStructure(basePath); + + expect(result).toBe(false); + expect(isComfyDirectory).not.toHaveBeenCalled(); + }); + + it('returns false when directory validation fails', () => { + isComfyDirectory.mockReturnValue(false); + + const result = (installation as any).ensureBaseDirectoryStructure(basePath); + + expect(result).toBe(false); + expect(createDirectories).toHaveBeenCalledWith(basePath); + expect(isComfyDirectory).toHaveBeenCalledWith(basePath); + }); +});