Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/components/dialog/content/MissingCoreNodesMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@
<script setup lang="ts">
import { whenever } from '@vueuse/core'
import Message from 'primevue/message'
import { rcompare } from 'semver'
import { computed, ref } from 'vue'

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

const props = defineProps<{
missingCoreNodes: Record<string, LGraphNode[]>
Expand Down Expand Up @@ -78,7 +79,10 @@ whenever(
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
const versionA = coerceVersion(a, '')
const versionB = coerceVersion(b, '')
if (!versionA || !versionB) return 0
return rcompare(versionA, versionB)
})
})

Expand Down
7 changes: 4 additions & 3 deletions src/components/dialog/content/manager/PackVersionBadge.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import { usePackUpdateStatus } from '@/composables/nodePack/usePackUpdateStatus'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import { SelectedVersion } from '@/types/comfyManagerTypes'
import { components } from '@/types/comfyRegistryTypes'
import { isSemVer } from '@/utils/formatUtil'
import { isValidSemver } from '@/utils/versionUtil'

const TRUNCATED_HASH_LENGTH = 7

Expand All @@ -70,8 +70,9 @@ const installedVersion = computed(() => {
nodePack.latest_version?.version ??
SelectedVersion.NIGHTLY

// If Git hash, truncate to 7 characters
return isSemVer(version) ? version : version.slice(0, TRUNCATED_HASH_LENGTH)
return isValidSemver(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 @@ -75,7 +75,7 @@ import {
SelectedVersion
} from '@/types/comfyManagerTypes'
import { components } from '@/types/comfyRegistryTypes'
import { isSemVer } from '@/utils/formatUtil'
import { isValidSemver } from '@/utils/versionUtil'

const { nodePack } = defineProps<{
nodePack: components['schemas']['Node']
Expand All @@ -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
isSemVer(initialVersion) ? initialVersion : SelectedVersion.NIGHTLY
selectedVersion.value = isValidSemver(initialVersion)
? initialVersion
: SelectedVersion.NIGHTLY
})

const getInitialSelectedVersion = () => {
Expand Down
18 changes: 15 additions & 3 deletions src/composables/nodePack/usePackUpdateStatus.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { gt } from 'semver'
import { computed } from 'vue'

import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import type { components } from '@/types/comfyRegistryTypes'
import { compareVersions, isSemVer } from '@/utils/formatUtil'
import { coerceVersion, isNightlyVersion } from '@/utils/versionUtil'

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

const isNightlyPack = computed(
() => !!installedVersion.value && !isSemVer(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
}
return compareVersions(latestVersion.value, installedVersion.value) > 0
if (!coercedInstalledVersion.value || !coercedLatestVersion.value) {
return false
}
return gt(coercedLatestVersion.value, coercedInstalledVersion.value)
})

return {
Expand Down
20 changes: 13 additions & 7 deletions src/stores/releaseStore.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { defineStore } from 'pinia'
import { eq, gt } from 'semver'
import { computed, ref } from 'vue'

import { type ReleaseNote, useReleaseService } from '@/services/releaseService'
import { useSettingStore } from '@/stores/settingStore'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import { isElectron } from '@/utils/envUtil'
import { compareVersions, stringToLocale } from '@/utils/formatUtil'
import { stringToLocale } from '@/utils/formatUtil'
import { coerceVersion } from '@/utils/versionUtil'

// Store for managing release notes
export const useReleaseStore = defineStore('release', () => {
Expand All @@ -19,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(() =>
Expand Down Expand Up @@ -54,16 +63,13 @@ export const useReleaseStore = defineStore('release', () => {
const isNewVersionAvailable = computed(
() =>
!!recentRelease.value &&
compareVersions(
recentRelease.value.version,
currentComfyUIVersion.value
) > 0
gt(coercedRecentReleaseVersion.value, coercedCurrentVersion.value)
)

const isLatestVersion = computed(
() =>
!!recentRelease.value &&
!compareVersions(recentRelease.value.version, currentComfyUIVersion.value)
eq(coercedRecentReleaseVersion.value, coercedCurrentVersion.value)
)

const hasMediumOrHighAttention = computed(() =>
Expand Down
26 changes: 20 additions & 6 deletions src/stores/settingStore.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import _ from 'lodash'
import { defineStore } from 'pinia'
import { gte, rcompare, valid } from 'semver'
import { ref } from 'vue'

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

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

if (installedVersion) {
const sortedVersions = Object.keys(defaultsByInstallVersion).sort(
(a, b) => compareVersions(b, a)
(a, b) => {
const versionA = coerceVersion(a, '')
const versionB = coerceVersion(b, '')
if (!versionA || !versionB) return 0
return rcompare(versionA, versionB)
}
)

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]
const installedSemver = coerceVersion(installedVersion, '')
const targetSemver = coerceVersion(version, '')
if (
installedSemver &&
targetSemver &&
gte(installedSemver, targetSemver)
) {
const versionedDefault =
defaultsByInstallVersion[
version as `${number}.${number}.${number}`
]
return typeof versionedDefault === 'function'
? versionedDefault()
: versionedDefault
Expand Down
9 changes: 5 additions & 4 deletions src/stores/versionCompatibilityStore.ts
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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(() => {
Expand Down
33 changes: 0 additions & 33 deletions src/utils/formatUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,39 +390,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 a currency amount to Metronome's integer representation.
* For USD, converts to cents (multiplied by 100).
Expand Down
52 changes: 52 additions & 0 deletions src/utils/versionUtil.ts
Original file line number Diff line number Diff line change
@@ -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)
}
Loading