Skip to content

Commit 88c86f0

Browse files
refactor: address PR review comments for semver migration
- Use named imports instead of wildcard imports for semver - Add memoized computed properties for coerced versions - Implement explicit Git hash detection function - Standardize coercion fallback pattern with helper functions - Create versionUtil.ts with reusable version validation functions 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 6716b8d commit 88c86f0

File tree

8 files changed

+104
-39
lines changed

8 files changed

+104
-39
lines changed

src/components/dialog/content/MissingCoreNodesMessage.vue

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,12 @@
4444
<script setup lang="ts">
4545
import { whenever } from '@vueuse/core'
4646
import Message from 'primevue/message'
47-
import * as semver from 'semver'
47+
import { rcompare } from 'semver'
4848
import { computed, ref } from 'vue'
4949
5050
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
5151
import { useSystemStatsStore } from '@/stores/systemStatsStore'
52+
import { coerceVersion } from '@/utils/versionUtil'
5253
5354
const props = defineProps<{
5455
missingCoreNodes: Record<string, LGraphNode[]>
@@ -78,10 +79,10 @@ whenever(
7879
const sortedMissingCoreNodes = computed(() => {
7980
return Object.entries(props.missingCoreNodes).sort(([a], [b]) => {
8081
// Sort by version in descending order (newest first)
81-
const versionA = semver.coerce(a)
82-
const versionB = semver.coerce(b)
82+
const versionA = coerceVersion(a, '')
83+
const versionB = coerceVersion(b, '')
8384
if (!versionA || !versionB) return 0
84-
return semver.rcompare(versionA, versionB)
85+
return rcompare(versionA, versionB)
8586
})
8687
})
8788

src/components/dialog/content/manager/PackVersionBadge.vue

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@
3737

3838
<script setup lang="ts">
3939
import Popover from 'primevue/popover'
40-
import * as semver from 'semver'
4140
import { computed, ref, watch } from 'vue'
4241
4342
import PackVersionSelectorPopover from '@/components/dialog/content/manager/PackVersionSelectorPopover.vue'
4443
import { usePackUpdateStatus } from '@/composables/nodePack/usePackUpdateStatus'
4544
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
4645
import { SelectedVersion } from '@/types/comfyManagerTypes'
4746
import { components } from '@/types/comfyRegistryTypes'
47+
import { isValidSemver } from '@/utils/versionUtil'
4848
4949
const TRUNCATED_HASH_LENGTH = 7
5050
@@ -70,8 +70,7 @@ const installedVersion = computed(() => {
7070
nodePack.latest_version?.version ??
7171
SelectedVersion.NIGHTLY
7272
73-
// If Git hash, truncate to 7 characters
74-
return semver.valid(version)
73+
return isValidSemver(version)
7574
? version
7675
: version.slice(0, TRUNCATED_HASH_LENGTH)
7776
})

src/components/dialog/content/manager/PackVersionSelectorPopover.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ import { whenever } from '@vueuse/core'
6262
import Button from 'primevue/button'
6363
import Listbox from 'primevue/listbox'
6464
import ProgressSpinner from 'primevue/progressspinner'
65-
import * as semver from 'semver'
6665
import { onMounted, ref } from 'vue'
6766
import { useI18n } from 'vue-i18n'
6867
@@ -76,6 +75,7 @@ import {
7675
SelectedVersion
7776
} from '@/types/comfyManagerTypes'
7877
import { components } from '@/types/comfyRegistryTypes'
78+
import { isValidSemver } from '@/utils/versionUtil'
7979
8080
const { nodePack } = defineProps<{
8181
nodePack: components['schemas']['Node']
@@ -95,9 +95,9 @@ const isQueueing = ref(false)
9595
const selectedVersion = ref<string>(SelectedVersion.LATEST)
9696
onMounted(() => {
9797
const initialVersion = getInitialSelectedVersion() ?? SelectedVersion.LATEST
98-
selectedVersion.value =
99-
// Use NIGHTLY when version is a Git hash
100-
semver.valid(initialVersion) ? initialVersion : SelectedVersion.NIGHTLY
98+
selectedVersion.value = isValidSemver(initialVersion)
99+
? initialVersion
100+
: SelectedVersion.NIGHTLY
101101
})
102102
103103
const getInitialSelectedVersion = () => {

src/composables/nodePack/usePackUpdateStatus.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import * as semver from 'semver'
1+
import { gt } from 'semver'
22
import { computed } from 'vue'
33

44
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
55
import type { components } from '@/types/comfyRegistryTypes'
6+
import { coerceVersion, isNightlyVersion } from '@/utils/versionUtil'
67

78
export const usePackUpdateStatus = (
89
nodePack: components['schemas']['Node']
@@ -16,17 +17,25 @@ export const usePackUpdateStatus = (
1617
const latestVersion = computed(() => nodePack.latest_version?.version)
1718

1819
const isNightlyPack = computed(
19-
() => !!installedVersion.value && !semver.valid(installedVersion.value)
20+
() => !!installedVersion.value && isNightlyVersion(installedVersion.value)
21+
)
22+
23+
const coercedInstalledVersion = computed(() =>
24+
installedVersion.value ? coerceVersion(installedVersion.value) : null
25+
)
26+
27+
const coercedLatestVersion = computed(() =>
28+
latestVersion.value ? coerceVersion(latestVersion.value) : null
2029
)
2130

2231
const isUpdateAvailable = computed(() => {
2332
if (!isInstalled.value || isNightlyPack.value || !latestVersion.value) {
2433
return false
2534
}
26-
const installedSemver = semver.coerce(installedVersion.value)
27-
const latestSemver = semver.coerce(latestVersion.value)
28-
if (!installedSemver || !latestSemver) return false
29-
return semver.gt(latestSemver, installedSemver)
35+
if (!coercedInstalledVersion.value || !coercedLatestVersion.value) {
36+
return false
37+
}
38+
return gt(coercedLatestVersion.value, coercedInstalledVersion.value)
3039
})
3140

3241
return {

src/stores/releaseStore.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { defineStore } from 'pinia'
2-
import * as semver from 'semver'
2+
import { eq, gt } from 'semver'
33
import { computed, ref } from 'vue'
44

55
import { type ReleaseNote, useReleaseService } from '@/services/releaseService'
66
import { useSettingStore } from '@/stores/settingStore'
77
import { useSystemStatsStore } from '@/stores/systemStatsStore'
88
import { isElectron } from '@/utils/envUtil'
99
import { stringToLocale } from '@/utils/formatUtil'
10+
import { coerceVersion } from '@/utils/versionUtil'
1011

1112
// Store for managing release notes
1213
export const useReleaseStore = defineStore('release', () => {
@@ -20,11 +21,18 @@ export const useReleaseStore = defineStore('release', () => {
2021
const systemStatsStore = useSystemStatsStore()
2122
const settingStore = useSettingStore()
2223

23-
// Current ComfyUI version
2424
const currentComfyUIVersion = computed(
2525
() => systemStatsStore?.systemStats?.system?.comfyui_version ?? ''
2626
)
2727

28+
const coercedCurrentVersion = computed(() =>
29+
coerceVersion(currentComfyUIVersion.value)
30+
)
31+
32+
const coercedRecentReleaseVersion = computed(() =>
33+
recentRelease.value ? coerceVersion(recentRelease.value.version) : '0.0.0'
34+
)
35+
2836
// Release data from settings
2937
const locale = computed(() => settingStore.get('Comfy.Locale'))
3038
const releaseVersion = computed(() =>
@@ -55,19 +63,13 @@ export const useReleaseStore = defineStore('release', () => {
5563
const isNewVersionAvailable = computed(
5664
() =>
5765
!!recentRelease.value &&
58-
semver.gt(
59-
semver.coerce(recentRelease.value.version) || '0.0.0',
60-
semver.coerce(currentComfyUIVersion.value) || '0.0.0'
61-
)
66+
gt(coercedRecentReleaseVersion.value, coercedCurrentVersion.value)
6267
)
6368

6469
const isLatestVersion = computed(
6570
() =>
6671
!!recentRelease.value &&
67-
semver.eq(
68-
semver.coerce(recentRelease.value.version) || '0.0.0',
69-
semver.coerce(currentComfyUIVersion.value) || '0.0.0'
70-
)
72+
eq(coercedRecentReleaseVersion.value, coercedCurrentVersion.value)
7173
)
7274

7375
const hasMediumOrHighAttention = computed(() =>

src/stores/settingStore.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import _ from 'lodash'
22
import { defineStore } from 'pinia'
3-
import * as semver from 'semver'
3+
import { gte, rcompare, valid } from 'semver'
44
import { ref } from 'vue'
55

66
import type { Settings } from '@/schemas/apiSchema'
77
import { api } from '@/scripts/api'
88
import { app } from '@/scripts/app'
99
import type { SettingParams } from '@/types/settingTypes'
1010
import type { TreeNode } from '@/types/treeExplorerTypes'
11+
import { coerceVersion } from '@/utils/versionUtil'
1112

1213
export const getSettingInfo = (setting: SettingParams) => {
1314
const parts = setting.category || setting.id.split('.')
@@ -133,24 +134,24 @@ export const useSettingStore = defineStore('setting', () => {
133134
if (installedVersion) {
134135
const sortedVersions = Object.keys(defaultsByInstallVersion).sort(
135136
(a, b) => {
136-
const versionA = semver.coerce(a)
137-
const versionB = semver.coerce(b)
137+
const versionA = coerceVersion(a, '')
138+
const versionB = coerceVersion(b, '')
138139
if (!versionA || !versionB) return 0
139-
return semver.rcompare(versionA, versionB)
140+
return rcompare(versionA, versionB)
140141
}
141142
)
142143

143144
for (const version of sortedVersions) {
144-
if (!semver.valid(version)) {
145+
if (!valid(version)) {
145146
continue
146147
}
147148

148-
const installedSemver = semver.coerce(installedVersion)
149-
const targetSemver = semver.coerce(version)
149+
const installedSemver = coerceVersion(installedVersion, '')
150+
const targetSemver = coerceVersion(version, '')
150151
if (
151152
installedSemver &&
152153
targetSemver &&
153-
semver.gte(installedSemver, targetSemver)
154+
gte(installedSemver, targetSemver)
154155
) {
155156
const versionedDefault =
156157
defaultsByInstallVersion[

src/stores/versionCompatibilityStore.ts

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

66
import config from '@/config'
77
import { useSystemStatsStore } from '@/stores/systemStatsStore'
8+
import { isValidSemver } from '@/utils/versionUtil'
89

910
const DISMISSAL_DURATION_MS = 7 * 24 * 60 * 60 * 1000 // 7 days
1011

@@ -26,13 +27,13 @@ export const useVersionCompatibilityStore = defineStore(
2627
if (
2728
!frontendVersion.value ||
2829
!requiredFrontendVersion.value ||
29-
!semver.valid(frontendVersion.value) ||
30-
!semver.valid(requiredFrontendVersion.value)
30+
!isValidSemver(frontendVersion.value) ||
31+
!isValidSemver(requiredFrontendVersion.value)
3132
) {
3233
return false
3334
}
3435
// Returns true if required version is greater than frontend version
35-
return semver.gt(requiredFrontendVersion.value, frontendVersion.value)
36+
return gt(requiredFrontendVersion.value, frontendVersion.value)
3637
})
3738

3839
const isFrontendNewer = computed(() => {

src/utils/versionUtil.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { coerce, valid } from 'semver'
2+
3+
/**
4+
* Validates if a string is a valid semantic version.
5+
* @param version The version string to validate
6+
* @returns true if the version is a valid semver, false otherwise
7+
*/
8+
export function isValidSemver(version: string): boolean {
9+
return !!valid(version)
10+
}
11+
12+
/**
13+
* Checks if a version string is a Git hash (not a semantic version).
14+
* Git hashes are typically 7-40 characters of hexadecimal.
15+
* @param version The version string to check
16+
* @returns true if the version appears to be a Git hash, false otherwise
17+
*/
18+
export function isGitHash(version: string): boolean {
19+
// Check if it's NOT a valid semver and matches git hash pattern
20+
if (isValidSemver(version)) {
21+
return false
22+
}
23+
24+
// Git hash pattern: 7-40 hexadecimal characters
25+
const gitHashPattern = /^[0-9a-f]{7,40}$/i
26+
return gitHashPattern.test(version)
27+
}
28+
29+
/**
30+
* Coerces a version string to a valid semver version with a fallback.
31+
* @param version The version string to coerce
32+
* @param fallback The fallback version if coercion fails (default: '0.0.0')
33+
* @returns A valid semver version string
34+
*/
35+
export function coerceVersion(
36+
version: string | undefined,
37+
fallback = '0.0.0'
38+
): string {
39+
if (!version) return fallback
40+
const coerced = coerce(version)
41+
return coerced ? coerced.version : fallback
42+
}
43+
44+
/**
45+
* Determines if a version string represents a nightly/development version.
46+
* This includes Git hashes and any non-semver version strings.
47+
* @param version The version string to check
48+
* @returns true if the version is a nightly/development version
49+
*/
50+
export function isNightlyVersion(version: string): boolean {
51+
return !isValidSemver(version)
52+
}

0 commit comments

Comments
 (0)