Skip to content
4 changes: 2 additions & 2 deletions src/components/dialog/content/MissingCoreNodesMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@

<script setup lang="ts">
import Message from 'primevue/message'
import { compare } from 'semver'
import { computed } from 'vue'

import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import { compareVersions } from '@/utils/formatUtil'

const props = defineProps<{
missingCoreNodes: Record<string, LGraphNode[]>
Expand All @@ -68,7 +68,7 @@ const currentComfyUIVersion = computed<string | null>(() => {
const sortedMissingCoreNodes = computed(() => {
return Object.entries(props.missingCoreNodes).sort(([a], [b]) => {
// Sort by version in descending order (newest first)
return compareVersions(b, a) // Reversed for descending order
return compare(b, a) // Reversed for descending order
})
})

Expand Down
6 changes: 3 additions & 3 deletions src/composables/nodePack/usePackUpdateStatus.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { compare, valid } from 'semver'
import { computed } from 'vue'

import type { components } from '@/types/comfyRegistryTypes'
import { compareVersions, isSemVer } from '@/utils/formatUtil'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'

export const usePackUpdateStatus = (
Expand All @@ -16,14 +16,14 @@ export const usePackUpdateStatus = (
const latestVersion = computed(() => nodePack.latest_version?.version)

const isNightlyPack = computed(
() => !!installedVersion.value && !isSemVer(installedVersion.value)
() => !!installedVersion.value && !valid(installedVersion.value)
)

const isUpdateAvailable = computed(() => {
if (!isInstalled.value || isNightlyPack.value || !latestVersion.value) {
return false
}
return compareVersions(latestVersion.value, installedVersion.value) > 0
return compare(latestVersion.value, installedVersion.value) > 0
})

return {
Expand Down
6 changes: 3 additions & 3 deletions src/composables/nodePack/useUpdateAvailableNodes.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { compare, valid } from 'semver'
import { computed, onMounted } from 'vue'

import { useInstalledPacks } from '@/composables/nodePack/useInstalledPacks'
import type { components } from '@/types/comfyRegistryTypes'
import { compareVersions, isSemVer } from '@/utils/formatUtil'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'

/**
Expand All @@ -25,13 +25,13 @@ export const useUpdateAvailableNodes = () => {
)
const latestVersion = pack.latest_version?.version

const isNightlyPack = !!installedVersion && !isSemVer(installedVersion)
const isNightlyPack = !!installedVersion && !valid(installedVersion)

if (isNightlyPack || !latestVersion) {
return false
}

return compareVersions(latestVersion, installedVersion) > 0
return compare(latestVersion, installedVersion) > 0
}

// Same filtering logic as ManagerDialogContent.vue
Expand Down
21 changes: 13 additions & 8 deletions src/platform/settings/settingStore.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import _ from 'es-toolkit/compat'
import { defineStore } from 'pinia'
import { compare, valid } from 'semver'
import { ref } from 'vue'

import type { SettingParams } from '@/platform/settings/types'
import type { Settings } from '@/schemas/apiSchema'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import type { TreeNode } from '@/types/treeExplorerTypes'
import { compareVersions, isSemVer } from '@/utils/formatUtil'

export const getSettingInfo = (setting: SettingParams) => {
const parts = setting.category || setting.id.split('.')
Expand Down Expand Up @@ -132,20 +132,25 @@ export const useSettingStore = defineStore('setting', () => {

if (installedVersion) {
const sortedVersions = Object.keys(defaultsByInstallVersion).sort(
(a, b) => compareVersions(b, a)
(a, b) => compare(b, a)
)

for (const version of sortedVersions) {
// Ensure the version is in a valid format before comparing
if (!isSemVer(version)) {
if (!valid(version)) {
continue
}

if (compareVersions(installedVersion, version) >= 0) {
const versionedDefault = defaultsByInstallVersion[version]
return typeof versionedDefault === 'function'
? versionedDefault()
: versionedDefault
if (compare(installedVersion, version) >= 0) {
const versionedDefault =
defaultsByInstallVersion[
version as keyof typeof defaultsByInstallVersion
]
if (versionedDefault !== undefined) {
return typeof versionedDefault === 'function'
? versionedDefault()
: versionedDefault
}
}
}
}
Expand Down
12 changes: 8 additions & 4 deletions src/platform/updates/common/releaseStore.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { until } from '@vueuse/core'
import { defineStore } from 'pinia'
import { compare } from 'semver'
import { computed, ref } from 'vue'

import { useSettingStore } from '@/platform/settings/settingStore'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import { isElectron } from '@/utils/envUtil'
import { compareVersions, stringToLocale } from '@/utils/formatUtil'
import { stringToLocale } from '@/utils/formatUtil'

import { type ReleaseNote, useReleaseService } from './releaseService'

Expand Down Expand Up @@ -56,16 +57,19 @@ export const useReleaseStore = defineStore('release', () => {
const isNewVersionAvailable = computed(
() =>
!!recentRelease.value &&
compareVersions(
compare(
recentRelease.value.version,
currentComfyUIVersion.value
currentComfyUIVersion.value || '0.0.0'
) > 0
)

const isLatestVersion = computed(
() =>
!!recentRelease.value &&
!compareVersions(recentRelease.value.version, currentComfyUIVersion.value)
compare(
recentRelease.value.version,
currentComfyUIVersion.value || '0.0.0'
) === 0
)

const hasMediumOrHighAttention = computed(() =>
Expand Down
8 changes: 4 additions & 4 deletions src/platform/updates/common/versionCompatibilityStore.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { until, useStorage } from '@vueuse/core'
import { defineStore } from 'pinia'
import * as semver from 'semver'
import { gt, valid } from 'semver'
import { computed } from 'vue'

import config from '@/config'
Expand All @@ -26,13 +26,13 @@ export const useVersionCompatibilityStore = defineStore(
if (
!frontendVersion.value ||
!requiredFrontendVersion.value ||
!semver.valid(frontendVersion.value) ||
!semver.valid(requiredFrontendVersion.value)
!valid(frontendVersion.value) ||
!valid(requiredFrontendVersion.value)
) {
return false
}
// Returns true if required version is greater than frontend version
return semver.gt(requiredFrontendVersion.value, frontendVersion.value)
return gt(requiredFrontendVersion.value, frontendVersion.value)
})

const isFrontendNewer = computed(() => {
Expand Down
33 changes: 0 additions & 33 deletions src/utils/formatUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,39 +364,6 @@ export const downloadUrlToHfRepoUrl = (url: string): string => {
}
}

export const isSemVer = (
version: string
): version is `${number}.${number}.${number}` => {
const regex = /^\d+\.\d+\.\d+$/
return regex.test(version)
}

const normalizeVersion = (version: string) =>
version
.split(/[+.-]/)
.map(Number)
.filter((part) => !Number.isNaN(part))

export function compareVersions(
versionA: string | undefined,
versionB: string | undefined
): number {
versionA ??= '0.0.0'
versionB ??= '0.0.0'

const aParts = normalizeVersion(versionA)
const bParts = normalizeVersion(versionB)

for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
const aPart = aParts[i] ?? 0
const bPart = bParts[i] ?? 0
if (aPart < bPart) return -1
if (aPart > bPart) return 1
}

return 0
}

/**
* Converts Metronome's integer amount back to a formatted currency string.
* For USD, converts from cents to dollars.
Expand Down
6 changes: 3 additions & 3 deletions src/utils/versionUtil.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as semver from 'semver'
import { clean, satisfies } from 'semver'

import type {
ConflictDetail,
Expand All @@ -11,7 +11,7 @@ import type {
* @returns Cleaned version string or original if cleaning fails
*/
export function cleanVersion(version: string): string {
return semver.clean(version) || version
return clean(version) || version
}

/**
Expand All @@ -23,7 +23,7 @@ export function cleanVersion(version: string): string {
export function satisfiesVersion(version: string, range: string): boolean {
try {
const cleanedVersion = cleanVersion(version)
return semver.satisfies(cleanedVersion, range)
return satisfies(cleanedVersion, range)
} catch {
return false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@

<script setup lang="ts">
import Popover from 'primevue/popover'
import { valid as validSemver } from 'semver'
import { computed, ref, watch } from 'vue'

import { usePackUpdateStatus } from '@/composables/nodePack/usePackUpdateStatus'
import type { components } from '@/types/comfyRegistryTypes'
import { isSemVer } from '@/utils/formatUtil'
import PackVersionSelectorPopover from '@/workbench/extensions/manager/components/manager/PackVersionSelectorPopover.vue'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'

Expand Down Expand Up @@ -81,7 +81,9 @@ const installedVersion = computed(() => {
'nightly'

// If Git hash, truncate to 7 characters
return isSemVer(version) ? version : version.slice(0, TRUNCATED_HASH_LENGTH)
return validSemver(version)
? version
: version.slice(0, TRUNCATED_HASH_LENGTH)
})

const toggleVersionSelector = (event: Event) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ import { whenever } from '@vueuse/core'
import Button from 'primevue/button'
import Listbox from 'primevue/listbox'
import ProgressSpinner from 'primevue/progressspinner'
import { valid as validSemver } from 'semver'
import { computed, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'

Expand All @@ -94,7 +95,6 @@ import { useConflictDetection } from '@/composables/useConflictDetection'
import { useComfyRegistryService } from '@/services/comfyRegistryService'
import type { components } from '@/types/comfyRegistryTypes'
import { getJoinedConflictMessages } from '@/utils/conflictMessageUtil'
import { isSemVer } from '@/utils/formatUtil'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
import type { components as ManagerComponents } from '@/workbench/extensions/manager/types/generatedManagerTypes'

Expand Down Expand Up @@ -142,7 +142,7 @@ onMounted(() => {
getInitialSelectedVersion() ?? SelectedVersionValues.LATEST
selectedVersion.value =
// Use NIGHTLY when version is a Git hash
isSemVer(initialVersion) ? initialVersion : SelectedVersionValues.NIGHTLY
validSemver(initialVersion) ? initialVersion : SelectedVersionValues.NIGHTLY
})

const getInitialSelectedVersion = () => {
Expand Down
43 changes: 21 additions & 22 deletions tests-ui/tests/composables/useUpdateAvailableNodes.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { compare, valid } from 'semver'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick, ref } from 'vue'

import { useInstalledPacks } from '@/composables/nodePack/useInstalledPacks'
import { useUpdateAvailableNodes } from '@/composables/nodePack/useUpdateAvailableNodes'
// Import mocked utils
import { compareVersions, isSemVer } from '@/utils/formatUtil'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'

// Mock Vue's onMounted to execute immediately for testing
Expand All @@ -25,16 +24,16 @@ vi.mock('@/workbench/extensions/manager/stores/comfyManagerStore', () => ({
useComfyManagerStore: vi.fn()
}))

vi.mock('@/utils/formatUtil', () => ({
compareVersions: vi.fn(),
isSemVer: vi.fn()
vi.mock('semver', () => ({
compare: vi.fn(),
valid: vi.fn()
}))

const mockUseInstalledPacks = vi.mocked(useInstalledPacks)
const mockUseComfyManagerStore = vi.mocked(useComfyManagerStore)

const mockCompareVersions = vi.mocked(compareVersions)
const mockIsSemVer = vi.mocked(isSemVer)
const mockSemverCompare = vi.mocked(compare)
const mockSemverValid = vi.mocked(valid)

describe('useUpdateAvailableNodes', () => {
const mockInstalledPacks = [
Expand Down Expand Up @@ -86,19 +85,19 @@ describe('useUpdateAvailableNodes', () => {
}
})

mockIsSemVer.mockImplementation(
(version: string): version is `${number}.${number}.${number}` => {
return !version.includes('nightly')
}
)
mockSemverValid.mockImplementation((version) => {
return version &&
typeof version === 'string' &&
!version.includes('nightly')
? version
: null
})

mockCompareVersions.mockImplementation(
(latest: string | undefined, installed: string | undefined) => {
if (latest === '2.0.0' && installed === '1.0.0') return 1 // outdated
if (latest === '1.0.0' && installed === '1.0.0') return 0 // up to date
return 0
}
)
mockSemverCompare.mockImplementation((latest, installed) => {
if (latest === '2.0.0' && installed === '1.0.0') return 1 // outdated
if (latest === '1.0.0' && installed === '1.0.0') return 0 // up to date
return 0
})

mockUseComfyManagerStore.mockReturnValue({
isPackInstalled: mockIsPackInstalled,
Expand Down Expand Up @@ -322,10 +321,10 @@ describe('useUpdateAvailableNodes', () => {
// Access the computed to trigger the logic
expect(updateAvailableNodePacks.value).toBeDefined()

expect(mockCompareVersions).toHaveBeenCalledWith('2.0.0', '1.0.0')
expect(mockSemverCompare).toHaveBeenCalledWith('2.0.0', '1.0.0')
})

it('calls isSemVer to check nightly versions', () => {
it('calls semver.valid to check nightly versions', () => {
mockUseInstalledPacks.mockReturnValue({
installedPacks: ref([mockInstalledPacks[2]]), // pack-3: nightly
isLoading: ref(false),
Expand All @@ -338,7 +337,7 @@ describe('useUpdateAvailableNodes', () => {
// Access the computed to trigger the logic
expect(updateAvailableNodePacks.value).toBeDefined()

expect(mockIsSemVer).toHaveBeenCalledWith('nightly-abc123')
expect(mockSemverValid).toHaveBeenCalledWith('nightly-abc123')
})

it('calls isPackInstalled for each pack', () => {
Expand Down
Loading