Compare commits

...

7 Commits

Author SHA1 Message Date
Jin Yi
52fe41cc57 test: checking commit functionality 2025-09-08 22:01:01 +09:00
Jin Yi
a7db5bdd64 fix: unused function 2025-09-08 21:59:02 +09:00
Jin Yi
63ab1dcbbe refactor: duplicate state deleted & unused function deleted 2025-09-08 21:51:41 +09:00
Jin Yi
3689e5ccce refactor: conflict detect 2025-09-07 20:39:30 +09:00
Jin Yi
7f7189d1d0 refactor: namaing function modified 2025-09-07 19:14:00 +09:00
Jin Yi
007560d391 refactor: unused system environment deleted 2025-09-07 18:30:29 +09:00
Jin Yi
5ee22981ba feature: detect os bug fix / refactor 2025-09-07 16:45:16 +09:00
10 changed files with 452 additions and 837 deletions

View File

@@ -91,7 +91,7 @@ const dialogStore = useDialogStore()
const progressDialogContent = useManagerProgressDialogStore()
const comfyManagerStore = useComfyManagerStore()
const settingStore = useSettingStore()
const { performConflictDetection } = useConflictDetection()
const { runFullConflictAnalysis } = useConflictDetection()
// State management for restart process
const isRestarting = ref<boolean>(false)
@@ -155,7 +155,7 @@ const handleRestart = async () => {
await useWorkflowService().reloadCurrentWorkflow()
// Run conflict detection after restart completion
await performConflictDetection()
await runFullConflictAnalysis()
} finally {
await settingStore.set(
'Comfy.Toast.DisableReconnectingToast',

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
/**
* Simple compatibility type definitions
* Registry supports exactly these values, null/undefined means compatible with all
*/
// Registry OS
export type RegistryOS = 'Windows' | 'macOS' | 'Linux'
// Registry Accelerator
export type RegistryAccelerator = 'CUDA' | 'ROCm' | 'Metal' | 'CPU'

View File

@@ -18,7 +18,6 @@ export type ConflictType =
| 'comfyui_version' // ComfyUI version mismatch
| 'frontend_version' // Frontend version mismatch
| 'import_failed'
// | 'python_version' // Python version mismatch
| 'os' // Operating system incompatibility
| 'accelerator' // GPU/accelerator incompatibility
| 'banned' // Banned package
@@ -28,7 +27,7 @@ export type ConflictType =
* Node Pack requirements from Registry API
* Extends Node type with additional installation and compatibility metadata
*/
export interface NodePackRequirements extends Node {
export interface NodeRequirements extends Node {
installed_version: string
is_enabled: boolean
is_banned: boolean
@@ -42,23 +41,12 @@ export interface NodePackRequirements extends Node {
*/
export interface SystemEnvironment {
// Version information
comfyui_version: string
frontend_version: string
// python_version: string
comfyui_version?: string
frontend_version?: string
// Platform information
os: string
platform_details: string
architecture: string
os?: string
// GPU/accelerator information
available_accelerators: Node['supported_accelerators']
primary_accelerator: string
gpu_memory_mb?: number
// Runtime information
node_env: 'development' | 'production'
user_agent: string
accelerator?: string
}
/**
@@ -101,7 +89,7 @@ export interface ConflictDetectionSummary {
export interface ConflictDetectionResponse {
success: boolean
error_message?: string
summary: ConflictDetectionSummary
summary?: ConflictDetectionSummary
results: ConflictDetectionResult[]
detected_system_environment?: Partial<SystemEnvironment>
}

114
src/utils/conflictUtils.ts Normal file
View File

@@ -0,0 +1,114 @@
import { groupBy, uniqBy } from 'es-toolkit/compat'
import type {
ConflictDetail,
ConflictDetectionResult,
ConflictDetectionSummary,
ConflictType
} from '@/types/conflictDetectionTypes'
import { normalizePackId } from '@/utils/packUtils'
/**
* Checks for banned package status conflicts.
*/
export function createBannedConflict(
isBanned?: boolean
): ConflictDetail | null {
if (isBanned === true) {
return {
type: 'banned',
current_value: 'installed',
required_value: 'not_banned'
}
}
return null
}
/**
* Checks for pending package status conflicts.
*/
export function createPendingConflict(
isPending?: boolean
): ConflictDetail | null {
if (isPending === true) {
return {
type: 'pending',
current_value: 'installed',
required_value: 'not_pending'
}
}
return null
}
/**
* Groups and deduplicates conflicts by normalized package name.
* Consolidates multiple conflict sources (registry checks, import failures, disabled packages with version suffix)
* into a single UI entry per package.
*
* Example:
* - Input: [{name: "pack@1_0_3", conflicts: [...]}, {name: "pack", conflicts: [...]}]
* - Output: [{name: "pack", conflicts: [...combined unique conflicts...]}]
*
* @param conflicts Array of conflict detection results (may have duplicate packages with version suffixes)
* @returns Array of deduplicated conflict results grouped by normalized package name
*/
export function consolidateConflictsByPackage(
conflicts: ConflictDetectionResult[]
): ConflictDetectionResult[] {
// Group conflicts by normalized package name using es-toolkit
const grouped = groupBy(conflicts, (conflict) =>
normalizePackId(conflict.package_name)
)
// Merge conflicts for each group
return Object.entries(grouped).map(([packageName, packageConflicts]) => {
// Flatten all conflicts from the group
const allConflicts = packageConflicts.flatMap((pc) => pc.conflicts)
// Remove duplicate conflicts using uniqBy
const uniqueConflicts = uniqBy(
allConflicts,
(conflict) =>
`${conflict.type}|${conflict.current_value}|${conflict.required_value}`
)
// Use the first item as base and update with merged data
const baseItem = packageConflicts[0]
return {
...baseItem,
package_name: packageName, // Use normalized name
conflicts: uniqueConflicts,
has_conflict: uniqueConflicts.length > 0,
is_compatible: uniqueConflicts.length === 0
}
})
}
/**
* Generates summary of conflict detection results.
*/
export function generateConflictSummary(
results: ConflictDetectionResult[],
durationMs: number
): ConflictDetectionSummary {
const conflictsByTypeDetails: Record<ConflictType, string[]> = {
comfyui_version: [],
frontend_version: [],
import_failed: [],
os: [],
accelerator: [],
banned: [],
pending: []
}
return {
total_packages: results.length,
compatible_packages: results.filter((r) => r.is_compatible).length,
conflicted_packages: results.filter((r) => r.has_conflict).length,
banned_packages: conflictsByTypeDetails['banned'].length,
pending_packages: conflictsByTypeDetails['pending'].length,
conflicts_by_type_details: conflictsByTypeDetails,
last_check_timestamp: new Date().toISOString(),
check_duration_ms: durationMs
}
}

View File

@@ -0,0 +1,126 @@
import { isEmpty, isNil } from 'es-toolkit/compat'
import type {
RegistryAccelerator,
RegistryOS
} from '@/types/compatibility.types'
import type { ConflictDetail } from '@/types/conflictDetectionTypes'
/**
* Maps system OS string to Registry OS format
* @param systemOS Raw OS string from system stats ('darwin', 'win32', 'linux', etc)
* @returns Registry OS or undefined if unknown
*/
export function getRegistryOS(systemOS?: string): RegistryOS | undefined {
if (!systemOS) return undefined
const lower = systemOS.toLowerCase()
if (lower.includes('win')) return 'Windows'
if (lower.includes('darwin') || lower.includes('mac')) return 'macOS'
if (lower.includes('linux')) return 'Linux'
return undefined
}
/**
* Maps device type to Registry accelerator format
* @param deviceType Raw device type from system stats ('cuda', 'mps', 'rocm', 'cpu', etc)
* @returns Registry accelerator
*/
export function getRegistryAccelerator(
deviceType?: string
): RegistryAccelerator {
if (!deviceType) return 'CPU'
const lower = deviceType.toLowerCase()
if (lower === 'cuda') return 'CUDA'
if (lower === 'mps') return 'Metal'
if (lower === 'rocm') return 'ROCm'
return 'CPU'
}
/**
* Checks OS compatibility
* @param supported Supported OS list from Registry (null/undefined = all OS supported)
* @param current Current system OS
* @returns ConflictDetail if incompatible, null if compatible
*/
export function checkOSCompatibility(
supported?: RegistryOS[] | null,
current?: string
): ConflictDetail | null {
// null/undefined/empty = all OS supported
if (isNil(supported) || isEmpty(supported)) return null
const currentOS = getRegistryOS(current)
if (!currentOS) {
return {
type: 'os',
current_value: 'Unknown',
required_value: supported.join(', ')
}
}
if (!supported.includes(currentOS)) {
return {
type: 'os',
current_value: currentOS,
required_value: supported.join(', ')
}
}
return null
}
/**
* Checks accelerator compatibility
* @param supported Supported accelerators from Registry (null/undefined = all accelerators supported)
* @param current Current device type
* @returns ConflictDetail if incompatible, null if compatible
*/
export function checkAcceleratorCompatibility(
supported?: RegistryAccelerator[] | null,
current?: string
): ConflictDetail | null {
// null/undefined/empty = all accelerator supported
if (isNil(supported) || isEmpty(supported)) return null
const currentAcc = getRegistryAccelerator(current)
if (!supported.includes(currentAcc)) {
return {
type: 'accelerator',
current_value: currentAcc,
required_value: supported.join(', ')
}
}
return null
}
/**
* Normalizes OS values from Registry API
* Handles edge cases like "OS Independent"
* @returns undefined if all OS supported, otherwise filtered valid OS list
*/
export function normalizeOSList(
osValues?: string[] | null
): RegistryOS[] | undefined {
if (isNil(osValues) || isEmpty(osValues)) return undefined
// "OS Independent" means all OS supported
if (osValues.some((os) => os.toLowerCase() === 'os independent')) {
return undefined
}
// Filter to valid Registry OS values only
const validOS: RegistryOS[] = []
osValues.forEach((os) => {
if (os === 'Windows' || os === 'macOS' || os === 'Linux') {
if (!validOS.includes(os)) validOS.push(os)
}
})
return validOS.length > 0 ? validOS : undefined
}

View File

@@ -1,5 +1,7 @@
import { isEmpty, isNil } from 'es-toolkit/compat'
import * as semver from 'semver'
import config from '@/config'
import type {
ConflictDetail,
ConflictType
@@ -37,47 +39,38 @@ export function satisfiesVersion(version: string, range: string): boolean {
* @param supportedVersion Required version range string
* @returns ConflictDetail object if incompatible, null if compatible
*/
export function utilCheckVersionCompatibility(
export function checkVersionCompatibility(
type: ConflictType,
currentVersion: string,
supportedVersion: string
currentVersion?: string,
supportedVersion?: string
): ConflictDetail | null {
// If current version is unknown, assume compatible (no conflict)
if (!currentVersion || currentVersion === 'unknown') {
// Use es-toolkit for null/empty checks
if (isNil(currentVersion) || isEmpty(currentVersion)) {
return null
}
// If no version requirement specified, assume compatible (no conflict)
if (!supportedVersion || supportedVersion.trim() === '') {
// Use es-toolkit for supported version validation
if (isNil(supportedVersion) || isEmpty(supportedVersion?.trim())) {
return null
}
try {
// Clean the current version using semver utilities
const cleanCurrent = cleanVersion(currentVersion)
// Clean and check version compatibility
const cleanCurrent = cleanVersion(currentVersion)
const isCompatible = satisfiesVersion(cleanCurrent, supportedVersion)
// Check version compatibility using semver library
const isCompatible = satisfiesVersion(cleanCurrent, supportedVersion)
if (isCompatible) return null
if (!isCompatible) {
return {
type,
current_value: currentVersion,
required_value: supportedVersion
}
}
return null
} catch (error) {
console.warn(
`[VersionUtil] Failed to parse version requirement: ${supportedVersion}`,
error
)
// On error, assume incompatible to be safe
return {
type,
current_value: currentVersion,
required_value: supportedVersion
}
return {
type,
current_value: currentVersion,
required_value: supportedVersion
}
}
/**
* get frontend version from config.
* @returns frontend version string or undefined
*/
export function getFrontendVersion(): string | undefined {
return config.app_version || import.meta.env.VITE_APP_VERSION || undefined
}

1
test-commit.txt Normal file
View File

@@ -0,0 +1 @@
test commit

View File

@@ -25,7 +25,7 @@ vi.mock('@/services/comfyManagerService')
vi.mock('@/composables/useConflictDetection', () => ({
useConflictDetection: vi.fn(() => ({
conflictedPackages: { value: [] },
performConflictDetection: vi.fn().mockResolvedValue(undefined)
runFullConflictAnalysis: vi.fn().mockResolvedValue(undefined)
}))
}))

View File

@@ -173,8 +173,8 @@ describe.skip('useConflictDetection with Registry Store', () => {
describe('system environment detection', () => {
it('should collect system environment information successfully', async () => {
const { detectSystemEnvironment } = useConflictDetection()
const environment = await detectSystemEnvironment()
const { collectSystemEnvironment } = useConflictDetection()
const environment = await collectSystemEnvironment()
expect(environment.comfyui_version).toBe('0.3.41')
expect(environment.frontend_version).toBe('1.24.0-1')
@@ -190,8 +190,8 @@ describe.skip('useConflictDetection with Registry Store', () => {
)
mockSystemStatsStore.systemStats = null
const { detectSystemEnvironment } = useConflictDetection()
const environment = await detectSystemEnvironment()
const { collectSystemEnvironment } = useConflictDetection()
const environment = await collectSystemEnvironment()
expect(environment.comfyui_version).toBe('unknown')
expect(environment.frontend_version).toBe('1.24.0-1')
@@ -260,8 +260,8 @@ describe.skip('useConflictDetection with Registry Store', () => {
}
)
const { performConflictDetection } = useConflictDetection()
const result = await performConflictDetection()
const { runFullConflictAnalysis } = useConflictDetection()
const result = await runFullConflictAnalysis()
expect(result.success).toBe(true)
expect(result.summary.total_packages).toBeGreaterThanOrEqual(1)
@@ -309,8 +309,8 @@ describe.skip('useConflictDetection with Registry Store', () => {
// Mock Registry Service returning null (no packages found)
mockRegistryService.getPackByVersion.mockResolvedValue(null)
const { performConflictDetection } = useConflictDetection()
const result = await performConflictDetection()
const { runFullConflictAnalysis } = useConflictDetection()
const result = await runFullConflictAnalysis()
expect(result.success).toBe(true)
expect(result.summary.total_packages).toBe(1)
@@ -332,8 +332,8 @@ describe.skip('useConflictDetection with Registry Store', () => {
it('should return empty array when local package information cannot be retrieved', async () => {
mockComfyManagerService.listInstalledPacks.mockResolvedValue(null)
const { performConflictDetection } = useConflictDetection()
const result = await performConflictDetection()
const { runFullConflictAnalysis } = useConflictDetection()
const result = await runFullConflictAnalysis()
expect(result.success).toBe(true)
expect(result.summary.total_packages).toBe(0)
@@ -377,8 +377,8 @@ describe.skip('useConflictDetection with Registry Store', () => {
}
)
const { performConflictDetection } = useConflictDetection()
const result = await performConflictDetection()
const { runFullConflictAnalysis } = useConflictDetection()
const result = await runFullConflictAnalysis()
expect(result.success).toBe(true)
expect(result.summary.conflicted_packages).toBe(0)
@@ -420,8 +420,8 @@ describe.skip('useConflictDetection with Registry Store', () => {
}
)
const { performConflictDetection } = useConflictDetection()
const result = await performConflictDetection()
const { runFullConflictAnalysis } = useConflictDetection()
const result = await runFullConflictAnalysis()
expect(result.success).toBe(true)
expect(result.summary.conflicted_packages).toBe(1)
@@ -472,8 +472,8 @@ describe.skip('useConflictDetection with Registry Store', () => {
}
)
const { performConflictDetection } = useConflictDetection()
const result = await performConflictDetection()
const { runFullConflictAnalysis } = useConflictDetection()
const result = await runFullConflictAnalysis()
expect(result.success).toBe(true)
expect(result.summary.conflicted_packages).toBe(1)
@@ -523,8 +523,8 @@ describe.skip('useConflictDetection with Registry Store', () => {
}
)
const { performConflictDetection } = useConflictDetection()
const result = await performConflictDetection()
const { runFullConflictAnalysis } = useConflictDetection()
const result = await runFullConflictAnalysis()
expect(result.success).toBe(true)
expect(result.summary.banned_packages).toBe(1)
@@ -577,8 +577,8 @@ describe.skip('useConflictDetection with Registry Store', () => {
}
)
const { performConflictDetection } = useConflictDetection()
const result = await performConflictDetection()
const { runFullConflictAnalysis } = useConflictDetection()
const result = await runFullConflictAnalysis()
expect(result.success).toBe(true)
expect(result.summary.banned_packages).toBe(1)
@@ -633,13 +633,13 @@ describe.skip('useConflictDetection with Registry Store', () => {
}
)
const { hasConflicts, performConflictDetection } = useConflictDetection()
const { hasConflicts, runFullConflictAnalysis } = useConflictDetection()
// Initial value should be false
expect(hasConflicts.value).toBe(false)
// Execute conflict detection
await performConflictDetection()
await runFullConflictAnalysis()
await nextTick()
// Should be true when conflicts are detected
@@ -680,10 +680,10 @@ describe.skip('useConflictDetection with Registry Store', () => {
}
)
const { conflictedPackages, performConflictDetection } =
const { conflictedPackages, runFullConflictAnalysis } =
useConflictDetection()
await performConflictDetection()
await runFullConflictAnalysis()
await nextTick()
expect(conflictedPackages.value.length).toBeGreaterThan(0)
@@ -740,10 +740,10 @@ describe.skip('useConflictDetection with Registry Store', () => {
}
)
const { bannedPackages, performConflictDetection } =
const { bannedPackages, runFullConflictAnalysis } =
useConflictDetection()
await performConflictDetection()
await runFullConflictAnalysis()
await nextTick()
expect(bannedPackages.value).toHaveLength(1)
@@ -766,8 +766,8 @@ describe.skip('useConflictDetection with Registry Store', () => {
}
)
const { performConflictDetection } = useConflictDetection()
const result = await performConflictDetection()
const { runFullConflictAnalysis } = useConflictDetection()
const result = await runFullConflictAnalysis()
expect(result.success).toBe(true)
expect(result.detected_system_environment?.comfyui_version).toBe(
@@ -781,8 +781,8 @@ describe.skip('useConflictDetection with Registry Store', () => {
new Error('Service error')
)
const { performConflictDetection } = useConflictDetection()
const result = await performConflictDetection()
const { runFullConflictAnalysis } = useConflictDetection()
const result = await runFullConflictAnalysis()
expect(result.success).toBe(true)
expect(result.summary.total_packages).toBe(0)
@@ -830,8 +830,8 @@ describe.skip('useConflictDetection with Registry Store', () => {
}
)
const { performConflictDetection } = useConflictDetection()
const result = await performConflictDetection()
const { runFullConflictAnalysis } = useConflictDetection()
const result = await runFullConflictAnalysis()
expect(result.success).toBe(true)
expect(result.summary.total_packages).toBeGreaterThanOrEqual(1)
@@ -862,8 +862,8 @@ describe.skip('useConflictDetection with Registry Store', () => {
new Error('Critical error')
)
const { performConflictDetection } = useConflictDetection()
const result = await performConflictDetection()
const { runFullConflictAnalysis } = useConflictDetection()
const result = await runFullConflictAnalysis()
expect(result.success).toBe(true) // Error resilience maintains success
expect(result.summary.total_packages).toBe(0)
@@ -889,8 +889,8 @@ describe.skip('useConflictDetection with Registry Store', () => {
status: 'NodeVersionStatusActive'
})
const { performConflictDetection } = useConflictDetection()
await performConflictDetection()
const { runFullConflictAnalysis } = useConflictDetection()
await runFullConflictAnalysis()
expect(mockAcknowledgment.checkComfyUIVersionChange).toHaveBeenCalledWith(
'0.3.41'
@@ -945,11 +945,11 @@ describe.skip('useConflictDetection with Registry Store', () => {
}
)
const { shouldShowConflictModalAfterUpdate, performConflictDetection } =
const { shouldShowConflictModalAfterUpdate, runFullConflictAnalysis } =
useConflictDetection()
// First run conflict detection to populate conflicts
await performConflictDetection()
await runFullConflictAnalysis()
await nextTick()
// Now check if modal should show after update
@@ -967,10 +967,10 @@ describe.skip('useConflictDetection with Registry Store', () => {
devices: []
}
const { detectSystemEnvironment } = useConflictDetection()
const { collectSystemEnvironment } = useConflictDetection()
// Detect system environment
const environment = await detectSystemEnvironment()
const environment = await collectSystemEnvironment()
expect(environment.comfyui_version).toBe('0.3.41')
})