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>
This commit is contained in:
bymyself
2025-08-06 17:06:31 -07:00
committed by Christian Byrne
parent 9da2a5b6de
commit b92369493a
8 changed files with 104 additions and 39 deletions

View File

@@ -44,11 +44,12 @@
<script setup lang="ts">
import { whenever } from '@vueuse/core'
import Message from 'primevue/message'
import * as semver from 'semver'
import { rcompare } from 'semver'
import { computed, ref } from 'vue'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import { coerceVersion } from '@/utils/versionUtil'
const props = defineProps<{
missingCoreNodes: Record<string, LGraphNode[]>
@@ -78,10 +79,10 @@ whenever(
const sortedMissingCoreNodes = computed(() => {
return Object.entries(props.missingCoreNodes).sort(([a], [b]) => {
// Sort by version in descending order (newest first)
const versionA = semver.coerce(a)
const versionB = semver.coerce(b)
const versionA = coerceVersion(a, '')
const versionB = coerceVersion(b, '')
if (!versionA || !versionB) return 0
return semver.rcompare(versionA, versionB)
return rcompare(versionA, versionB)
})
})

View File

@@ -37,7 +37,6 @@
<script setup lang="ts">
import Popover from 'primevue/popover'
import * as semver from 'semver'
import { computed, ref, watch } from 'vue'
import PackVersionSelectorPopover from '@/components/dialog/content/manager/PackVersionSelectorPopover.vue'
@@ -45,6 +44,7 @@ import { usePackUpdateStatus } from '@/composables/nodePack/usePackUpdateStatus'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import { SelectedVersion } from '@/types/comfyManagerTypes'
import { components } from '@/types/comfyRegistryTypes'
import { isValidSemver } from '@/utils/versionUtil'
const TRUNCATED_HASH_LENGTH = 7
@@ -70,8 +70,7 @@ const installedVersion = computed(() => {
nodePack.latest_version?.version ??
SelectedVersion.NIGHTLY
// If Git hash, truncate to 7 characters
return semver.valid(version)
return isValidSemver(version)
? version
: version.slice(0, TRUNCATED_HASH_LENGTH)
})

View File

@@ -62,7 +62,6 @@ import { whenever } from '@vueuse/core'
import Button from 'primevue/button'
import Listbox from 'primevue/listbox'
import ProgressSpinner from 'primevue/progressspinner'
import * as semver from 'semver'
import { onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
@@ -76,6 +75,7 @@ import {
SelectedVersion
} from '@/types/comfyManagerTypes'
import { components } from '@/types/comfyRegistryTypes'
import { isValidSemver } from '@/utils/versionUtil'
const { nodePack } = defineProps<{
nodePack: components['schemas']['Node']
@@ -95,9 +95,9 @@ const isQueueing = ref(false)
const selectedVersion = ref<string>(SelectedVersion.LATEST)
onMounted(() => {
const initialVersion = getInitialSelectedVersion() ?? SelectedVersion.LATEST
selectedVersion.value =
// Use NIGHTLY when version is a Git hash
semver.valid(initialVersion) ? initialVersion : SelectedVersion.NIGHTLY
selectedVersion.value = isValidSemver(initialVersion)
? initialVersion
: SelectedVersion.NIGHTLY
})
const getInitialSelectedVersion = () => {

View File

@@ -1,8 +1,9 @@
import * as semver from 'semver'
import { gt } from 'semver'
import { computed } from 'vue'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import type { components } from '@/types/comfyRegistryTypes'
import { coerceVersion, isNightlyVersion } from '@/utils/versionUtil'
export const usePackUpdateStatus = (
nodePack: components['schemas']['Node']
@@ -16,17 +17,25 @@ export const usePackUpdateStatus = (
const latestVersion = computed(() => nodePack.latest_version?.version)
const isNightlyPack = computed(
() => !!installedVersion.value && !semver.valid(installedVersion.value)
() => !!installedVersion.value && isNightlyVersion(installedVersion.value)
)
const coercedInstalledVersion = computed(() =>
installedVersion.value ? coerceVersion(installedVersion.value) : null
)
const coercedLatestVersion = computed(() =>
latestVersion.value ? coerceVersion(latestVersion.value) : null
)
const isUpdateAvailable = computed(() => {
if (!isInstalled.value || isNightlyPack.value || !latestVersion.value) {
return false
}
const installedSemver = semver.coerce(installedVersion.value)
const latestSemver = semver.coerce(latestVersion.value)
if (!installedSemver || !latestSemver) return false
return semver.gt(latestSemver, installedSemver)
if (!coercedInstalledVersion.value || !coercedLatestVersion.value) {
return false
}
return gt(coercedLatestVersion.value, coercedInstalledVersion.value)
})
return {

View File

@@ -1,5 +1,5 @@
import { defineStore } from 'pinia'
import * as semver from 'semver'
import { eq, gt } from 'semver'
import { computed, ref } from 'vue'
import { type ReleaseNote, useReleaseService } from '@/services/releaseService'
@@ -7,6 +7,7 @@ import { useSettingStore } from '@/stores/settingStore'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import { isElectron } from '@/utils/envUtil'
import { stringToLocale } from '@/utils/formatUtil'
import { coerceVersion } from '@/utils/versionUtil'
// Store for managing release notes
export const useReleaseStore = defineStore('release', () => {
@@ -20,11 +21,18 @@ export const useReleaseStore = defineStore('release', () => {
const systemStatsStore = useSystemStatsStore()
const settingStore = useSettingStore()
// Current ComfyUI version
const currentComfyUIVersion = computed(
() => systemStatsStore?.systemStats?.system?.comfyui_version ?? ''
)
const coercedCurrentVersion = computed(() =>
coerceVersion(currentComfyUIVersion.value)
)
const coercedRecentReleaseVersion = computed(() =>
recentRelease.value ? coerceVersion(recentRelease.value.version) : '0.0.0'
)
// Release data from settings
const locale = computed(() => settingStore.get('Comfy.Locale'))
const releaseVersion = computed(() =>
@@ -55,19 +63,13 @@ export const useReleaseStore = defineStore('release', () => {
const isNewVersionAvailable = computed(
() =>
!!recentRelease.value &&
semver.gt(
semver.coerce(recentRelease.value.version) || '0.0.0',
semver.coerce(currentComfyUIVersion.value) || '0.0.0'
)
gt(coercedRecentReleaseVersion.value, coercedCurrentVersion.value)
)
const isLatestVersion = computed(
() =>
!!recentRelease.value &&
semver.eq(
semver.coerce(recentRelease.value.version) || '0.0.0',
semver.coerce(currentComfyUIVersion.value) || '0.0.0'
)
eq(coercedRecentReleaseVersion.value, coercedCurrentVersion.value)
)
const hasMediumOrHighAttention = computed(() =>

View File

@@ -1,6 +1,6 @@
import _ from 'lodash'
import { defineStore } from 'pinia'
import * as semver from 'semver'
import { gte, rcompare, valid } from 'semver'
import { ref } from 'vue'
import type { Settings } from '@/schemas/apiSchema'
@@ -8,6 +8,7 @@ import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import type { SettingParams } from '@/types/settingTypes'
import type { TreeNode } from '@/types/treeExplorerTypes'
import { coerceVersion } from '@/utils/versionUtil'
export const getSettingInfo = (setting: SettingParams) => {
const parts = setting.category || setting.id.split('.')
@@ -133,24 +134,24 @@ export const useSettingStore = defineStore('setting', () => {
if (installedVersion) {
const sortedVersions = Object.keys(defaultsByInstallVersion).sort(
(a, b) => {
const versionA = semver.coerce(a)
const versionB = semver.coerce(b)
const versionA = coerceVersion(a, '')
const versionB = coerceVersion(b, '')
if (!versionA || !versionB) return 0
return semver.rcompare(versionA, versionB)
return rcompare(versionA, versionB)
}
)
for (const version of sortedVersions) {
if (!semver.valid(version)) {
if (!valid(version)) {
continue
}
const installedSemver = semver.coerce(installedVersion)
const targetSemver = semver.coerce(version)
const installedSemver = coerceVersion(installedVersion, '')
const targetSemver = coerceVersion(version, '')
if (
installedSemver &&
targetSemver &&
semver.gte(installedSemver, targetSemver)
gte(installedSemver, targetSemver)
) {
const versionedDefault =
defaultsByInstallVersion[

View File

@@ -1,10 +1,11 @@
import { useStorage } from '@vueuse/core'
import { defineStore } from 'pinia'
import * as semver from 'semver'
import { gt } from 'semver'
import { computed } from 'vue'
import config from '@/config'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import { isValidSemver } from '@/utils/versionUtil'
const DISMISSAL_DURATION_MS = 7 * 24 * 60 * 60 * 1000 // 7 days
@@ -26,13 +27,13 @@ export const useVersionCompatibilityStore = defineStore(
if (
!frontendVersion.value ||
!requiredFrontendVersion.value ||
!semver.valid(frontendVersion.value) ||
!semver.valid(requiredFrontendVersion.value)
!isValidSemver(frontendVersion.value) ||
!isValidSemver(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(() => {

52
src/utils/versionUtil.ts Normal file
View File

@@ -0,0 +1,52 @@
import { coerce, valid } from 'semver'
/**
* Validates if a string is a valid semantic version.
* @param version The version string to validate
* @returns true if the version is a valid semver, false otherwise
*/
export function isValidSemver(version: string): boolean {
return !!valid(version)
}
/**
* Checks if a version string is a Git hash (not a semantic version).
* Git hashes are typically 7-40 characters of hexadecimal.
* @param version The version string to check
* @returns true if the version appears to be a Git hash, false otherwise
*/
export function isGitHash(version: string): boolean {
// Check if it's NOT a valid semver and matches git hash pattern
if (isValidSemver(version)) {
return false
}
// Git hash pattern: 7-40 hexadecimal characters
const gitHashPattern = /^[0-9a-f]{7,40}$/i
return gitHashPattern.test(version)
}
/**
* Coerces a version string to a valid semver version with a fallback.
* @param version The version string to coerce
* @param fallback The fallback version if coercion fails (default: '0.0.0')
* @returns A valid semver version string
*/
export function coerceVersion(
version: string | undefined,
fallback = '0.0.0'
): string {
if (!version) return fallback
const coerced = coerce(version)
return coerced ? coerced.version : fallback
}
/**
* Determines if a version string represents a nightly/development version.
* This includes Git hashes and any non-semver version strings.
* @param version The version string to check
* @returns true if the version is a nightly/development version
*/
export function isNightlyVersion(version: string): boolean {
return !isValidSemver(version)
}