Skip to content

Commit df2fda6

Browse files
christian-byrneDrJKLclaude
authored
[refactor] Replace manual semantic version utilities/functions with semver package (#5653)
## Summary - Replace custom `compareVersions()` with `semver.compare()` - Replace custom `isSemVer()` with `semver.valid()` - Remove deprecated version comparison functions from `formatUtil.ts` - Update all version comparison logic across components and stores - Fix tests to use semver mocking instead of formatUtil mocking ## Benefits - **Industry standard**: Uses well-maintained, battle-tested `semver` package - **Better reliability**: Handles edge cases more robustly than custom implementation - **Consistent behavior**: All version comparisons now use the same underlying logic - **Type safety**: Better TypeScript support with proper semver types Fixes #4787 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5653-refactor-Replace-manual-semantic-version-utilities-functions-with-semver-package-2736d73d365081fb8498ee11cbcc10e2) by [Unito](https://www.unito.io) --------- Co-authored-by: DrJKL <[email protected]> Co-authored-by: Claude <[email protected]>
1 parent 4f5bbe0 commit df2fda6

File tree

12 files changed

+118
-152
lines changed

12 files changed

+118
-152
lines changed

src/components/dialog/content/MissingCoreNodesMessage.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@
4343

4444
<script setup lang="ts">
4545
import Message from 'primevue/message'
46+
import { compare } from 'semver'
4647
import { computed } from 'vue'
4748
4849
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
4950
import { useSystemStatsStore } from '@/stores/systemStatsStore'
50-
import { compareVersions } from '@/utils/formatUtil'
5151
5252
const props = defineProps<{
5353
missingCoreNodes: Record<string, LGraphNode[]>
@@ -68,7 +68,7 @@ const currentComfyUIVersion = computed<string | null>(() => {
6868
const sortedMissingCoreNodes = computed(() => {
6969
return Object.entries(props.missingCoreNodes).sort(([a], [b]) => {
7070
// Sort by version in descending order (newest first)
71-
return compareVersions(b, a) // Reversed for descending order
71+
return compare(b, a) // Reversed for descending order
7272
})
7373
})
7474

src/composables/nodePack/usePackUpdateStatus.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import { compare, valid } from 'semver'
12
import { computed } from 'vue'
23

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

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

1818
const isNightlyPack = computed(
19-
() => !!installedVersion.value && !isSemVer(installedVersion.value)
19+
() => !!installedVersion.value && !valid(installedVersion.value)
2020
)
2121

2222
const isUpdateAvailable = computed(() => {
2323
if (!isInstalled.value || isNightlyPack.value || !latestVersion.value) {
2424
return false
2525
}
26-
return compareVersions(latestVersion.value, installedVersion.value) > 0
26+
return compare(latestVersion.value, installedVersion.value) > 0
2727
})
2828

2929
return {

src/composables/nodePack/useUpdateAvailableNodes.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import { compare, valid } from 'semver'
12
import { computed, onMounted } from 'vue'
23

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

88
/**
@@ -25,13 +25,13 @@ export const useUpdateAvailableNodes = () => {
2525
)
2626
const latestVersion = pack.latest_version?.version
2727

28-
const isNightlyPack = !!installedVersion && !isSemVer(installedVersion)
28+
const isNightlyPack = !!installedVersion && !valid(installedVersion)
2929

3030
if (isNightlyPack || !latestVersion) {
3131
return false
3232
}
3333

34-
return compareVersions(latestVersion, installedVersion) > 0
34+
return compare(latestVersion, installedVersion) > 0
3535
}
3636

3737
// Same filtering logic as ManagerDialogContent.vue

src/platform/settings/settingStore.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import _ from 'es-toolkit/compat'
22
import { defineStore } from 'pinia'
3+
import { compare, valid } from 'semver'
34
import { ref } from 'vue'
45

56
import type { SettingParams } from '@/platform/settings/types'
67
import type { Settings } from '@/schemas/apiSchema'
78
import { api } from '@/scripts/api'
89
import { app } from '@/scripts/app'
910
import type { TreeNode } from '@/types/treeExplorerTypes'
10-
import { compareVersions, isSemVer } from '@/utils/formatUtil'
1111

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

133133
if (installedVersion) {
134134
const sortedVersions = Object.keys(defaultsByInstallVersion).sort(
135-
(a, b) => compareVersions(b, a)
135+
(a, b) => compare(b, a)
136136
)
137137

138138
for (const version of sortedVersions) {
139139
// Ensure the version is in a valid format before comparing
140-
if (!isSemVer(version)) {
140+
if (!valid(version)) {
141141
continue
142142
}
143143

144-
if (compareVersions(installedVersion, version) >= 0) {
145-
const versionedDefault = defaultsByInstallVersion[version]
146-
return typeof versionedDefault === 'function'
147-
? versionedDefault()
148-
: versionedDefault
144+
if (compare(installedVersion, version) >= 0) {
145+
const versionedDefault =
146+
defaultsByInstallVersion[
147+
version as keyof typeof defaultsByInstallVersion
148+
]
149+
if (versionedDefault !== undefined) {
150+
return typeof versionedDefault === 'function'
151+
? versionedDefault()
152+
: versionedDefault
153+
}
149154
}
150155
}
151156
}

src/platform/updates/common/releaseStore.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { until } from '@vueuse/core'
22
import { defineStore } from 'pinia'
3+
import { compare } from 'semver'
34
import { computed, ref } from 'vue'
45

56
import { useSettingStore } from '@/platform/settings/settingStore'
67
import { useSystemStatsStore } from '@/stores/systemStatsStore'
78
import { isElectron } from '@/utils/envUtil'
8-
import { compareVersions, stringToLocale } from '@/utils/formatUtil'
9+
import { stringToLocale } from '@/utils/formatUtil'
910

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

@@ -56,16 +57,19 @@ export const useReleaseStore = defineStore('release', () => {
5657
const isNewVersionAvailable = computed(
5758
() =>
5859
!!recentRelease.value &&
59-
compareVersions(
60+
compare(
6061
recentRelease.value.version,
61-
currentComfyUIVersion.value
62+
currentComfyUIVersion.value || '0.0.0'
6263
) > 0
6364
)
6465

6566
const isLatestVersion = computed(
6667
() =>
6768
!!recentRelease.value &&
68-
!compareVersions(recentRelease.value.version, currentComfyUIVersion.value)
69+
compare(
70+
recentRelease.value.version,
71+
currentComfyUIVersion.value || '0.0.0'
72+
) === 0
6973
)
7074

7175
const hasMediumOrHighAttention = computed(() =>

src/platform/updates/common/versionCompatibilityStore.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { until, useStorage } from '@vueuse/core'
22
import { defineStore } from 'pinia'
3-
import * as semver from 'semver'
3+
import { gt, valid } from 'semver'
44
import { computed } from 'vue'
55

66
import config from '@/config'
@@ -26,13 +26,13 @@ export const useVersionCompatibilityStore = defineStore(
2626
if (
2727
!frontendVersion.value ||
2828
!requiredFrontendVersion.value ||
29-
!semver.valid(frontendVersion.value) ||
30-
!semver.valid(requiredFrontendVersion.value)
29+
!valid(frontendVersion.value) ||
30+
!valid(requiredFrontendVersion.value)
3131
) {
3232
return false
3333
}
3434
// Returns true if required version is greater than frontend version
35-
return semver.gt(requiredFrontendVersion.value, frontendVersion.value)
35+
return gt(requiredFrontendVersion.value, frontendVersion.value)
3636
})
3737

3838
const isFrontendNewer = computed(() => {

src/utils/formatUtil.ts

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -364,39 +364,6 @@ export const downloadUrlToHfRepoUrl = (url: string): string => {
364364
}
365365
}
366366

367-
export const isSemVer = (
368-
version: string
369-
): version is `${number}.${number}.${number}` => {
370-
const regex = /^\d+\.\d+\.\d+$/
371-
return regex.test(version)
372-
}
373-
374-
const normalizeVersion = (version: string) =>
375-
version
376-
.split(/[+.-]/)
377-
.map(Number)
378-
.filter((part) => !Number.isNaN(part))
379-
380-
export function compareVersions(
381-
versionA: string | undefined,
382-
versionB: string | undefined
383-
): number {
384-
versionA ??= '0.0.0'
385-
versionB ??= '0.0.0'
386-
387-
const aParts = normalizeVersion(versionA)
388-
const bParts = normalizeVersion(versionB)
389-
390-
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
391-
const aPart = aParts[i] ?? 0
392-
const bPart = bParts[i] ?? 0
393-
if (aPart < bPart) return -1
394-
if (aPart > bPart) return 1
395-
}
396-
397-
return 0
398-
}
399-
400367
/**
401368
* Converts Metronome's integer amount back to a formatted currency string.
402369
* For USD, converts from cents to dollars.

src/utils/versionUtil.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as semver from 'semver'
1+
import { clean, satisfies } from 'semver'
22

33
import type {
44
ConflictDetail,
@@ -11,7 +11,7 @@ import type {
1111
* @returns Cleaned version string or original if cleaning fails
1212
*/
1313
export function cleanVersion(version: string): string {
14-
return semver.clean(version) || version
14+
return clean(version) || version
1515
}
1616

1717
/**
@@ -23,7 +23,7 @@ export function cleanVersion(version: string): string {
2323
export function satisfiesVersion(version: string, range: string): boolean {
2424
try {
2525
const cleanedVersion = cleanVersion(version)
26-
return semver.satisfies(cleanedVersion, range)
26+
return satisfies(cleanedVersion, range)
2727
} catch {
2828
return false
2929
}

src/workbench/extensions/manager/components/manager/PackVersionBadge.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@
4343

4444
<script setup lang="ts">
4545
import Popover from 'primevue/popover'
46+
import { valid as validSemver } from 'semver'
4647
import { computed, ref, watch } from 'vue'
4748
4849
import { usePackUpdateStatus } from '@/composables/nodePack/usePackUpdateStatus'
4950
import type { components } from '@/types/comfyRegistryTypes'
50-
import { isSemVer } from '@/utils/formatUtil'
5151
import PackVersionSelectorPopover from '@/workbench/extensions/manager/components/manager/PackVersionSelectorPopover.vue'
5252
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
5353
@@ -81,7 +81,9 @@ const installedVersion = computed(() => {
8181
'nightly'
8282
8383
// If Git hash, truncate to 7 characters
84-
return isSemVer(version) ? version : version.slice(0, TRUNCATED_HASH_LENGTH)
84+
return validSemver(version)
85+
? version
86+
: version.slice(0, TRUNCATED_HASH_LENGTH)
8587
})
8688
8789
const toggleVersionSelector = (event: Event) => {

src/workbench/extensions/manager/components/manager/PackVersionSelectorPopover.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ import { whenever } from '@vueuse/core'
8484
import Button from 'primevue/button'
8585
import Listbox from 'primevue/listbox'
8686
import ProgressSpinner from 'primevue/progressspinner'
87+
import { valid as validSemver } from 'semver'
8788
import { computed, onMounted, ref } from 'vue'
8889
import { useI18n } from 'vue-i18n'
8990
@@ -94,7 +95,6 @@ import { useConflictDetection } from '@/composables/useConflictDetection'
9495
import { useComfyRegistryService } from '@/services/comfyRegistryService'
9596
import type { components } from '@/types/comfyRegistryTypes'
9697
import { getJoinedConflictMessages } from '@/utils/conflictMessageUtil'
97-
import { isSemVer } from '@/utils/formatUtil'
9898
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
9999
import type { components as ManagerComponents } from '@/workbench/extensions/manager/types/generatedManagerTypes'
100100
@@ -142,7 +142,7 @@ onMounted(() => {
142142
getInitialSelectedVersion() ?? SelectedVersionValues.LATEST
143143
selectedVersion.value =
144144
// Use NIGHTLY when version is a Git hash
145-
isSemVer(initialVersion) ? initialVersion : SelectedVersionValues.NIGHTLY
145+
validSemver(initialVersion) ? initialVersion : SelectedVersionValues.NIGHTLY
146146
})
147147
148148
const getInitialSelectedVersion = () => {

0 commit comments

Comments
 (0)