[refactor] centralize hardcoded URLs into organized constants

- Create src/constants/urls.ts with centralized URL constants (COMFY_URLS, GITHUB_REPOS, MODEL_SOURCES, DEVELOPER_TOOLS)
- Move runtime domain config to src/config/comfyDomain.ts to allow forkers to customize via env var
- Rename uvMirrors.ts to mirrors.ts for better naming consistency
- Add platform and locale-aware desktop guide URL generation (matching PR #4471)
- Update 10 components to use centralized URL constants
- Add comprehensive unit and e2e tests for URL constants validation

This refactoring improves maintainability by centralizing 150+ hardcoded URLs found across 50+ files into a single organized structure.
This commit is contained in:
bymyself
2025-07-18 13:28:30 -07:00
parent 282f9ce27a
commit 5faf9e0105
13 changed files with 288 additions and 42 deletions

View File

@@ -38,23 +38,14 @@ import { useI18n } from 'vue-i18n'
import FileDownload from '@/components/common/FileDownload.vue'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import { MODEL_SOURCES } from '@/constants/urls'
import { useSettingStore } from '@/stores/settingStore'
import { isElectron } from '@/utils/envUtil'
// TODO: Read this from server internal API rather than hardcoding here
// as some installations may wish to use custom sources
const allowedSources = [
'https://civitai.com/',
'https://huggingface.co/',
'http://localhost:' // Included for testing usage only
]
/** @todo Read this from server internal API rather than hardcoding here */
const allowedSources = MODEL_SOURCES.allowedDomains
const allowedSuffixes = ['.safetensors', '.sft']
// Models that fail above conditions but are still allowed
const whiteListedUrls = new Set([
'https://huggingface.co/stabilityai/stable-zero123/resolve/main/stable_zero123.ckpt',
'https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_depth_sd14v1.pth?download=true',
'https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth'
])
const whiteListedUrls = new Set(MODEL_SOURCES.whitelistedUrls)
interface ModelInfo {
name: string

View File

@@ -123,6 +123,7 @@ import Button from 'primevue/button'
import { type CSSProperties, computed, nextTick, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { COMFY_URLS, GITHUB_REPOS, getDesktopGuideUrl } from '@/constants/urls'
import { type ReleaseNote } from '@/services/releaseService'
import { useCommandStore } from '@/stores/commandStore'
import { useReleaseStore } from '@/stores/releaseStore'
@@ -143,11 +144,10 @@ interface MenuItem {
// Constants
const EXTERNAL_LINKS = {
DOCS: 'https://docs.comfy.org/',
DISCORD: 'https://www.comfy.org/discord',
GITHUB: 'https://github.com/comfyanonymous/ComfyUI',
DESKTOP_GUIDE: 'https://comfyorg.notion.site/',
UPDATE_GUIDE: 'https://docs.comfy.org/installation/update_comfyui'
DOCS: COMFY_URLS.docs.base,
DISCORD: COMFY_URLS.community.discord,
GITHUB: GITHUB_REPOS.comfyui,
UPDATE_GUIDE: COMFY_URLS.docs.installation.update
} as const
const TIME_UNITS = {
@@ -199,7 +199,7 @@ const menuItems = computed<MenuItem[]>(() => {
type: 'item',
label: t('helpCenter.desktopUserGuide'),
action: () => {
openExternalLink(EXTERNAL_LINKS.DESKTOP_GUIDE)
openExternalLink(getDesktopGuideUrl(locale.value))
emit('close')
}
},
@@ -451,8 +451,8 @@ const onUpdate = (_: ReleaseNote): void => {
const getChangelogUrl = (): string => {
const isChineseLocale = locale.value === 'zh'
return isChineseLocale
? 'https://docs.comfy.org/zh-CN/changelog'
: 'https://docs.comfy.org/changelog'
? COMFY_URLS.docs.changelog.zh
: COMFY_URLS.docs.changelog.en
}
// Lifecycle

View File

@@ -48,6 +48,7 @@
import { computed, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { COMFY_URLS } from '@/constants/urls'
import type { ReleaseNote } from '@/services/releaseService'
import { useReleaseStore } from '@/stores/releaseStore'
import { formatVersionAnchor } from '@/utils/formatUtil'
@@ -72,8 +73,8 @@ const shouldShow = computed(
const changelogUrl = computed(() => {
const isChineseLocale = locale.value === 'zh'
const baseUrl = isChineseLocale
? 'https://docs.comfy.org/zh-CN/changelog'
: 'https://docs.comfy.org/changelog'
? COMFY_URLS.docs.changelog.zh
: COMFY_URLS.docs.changelog.en
if (latestRelease.value?.version) {
const versionAnchor = formatVersionAnchor(latestRelease.value.version)
@@ -120,7 +121,7 @@ const handleLearnMore = () => {
}
const handleUpdate = () => {
window.open('https://docs.comfy.org/installation/update_comfyui', '_blank')
window.open(COMFY_URLS.docs.installation.update, '_blank')
dismissToast()
}

View File

@@ -68,6 +68,7 @@ import { marked } from 'marked'
import { computed, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { COMFY_URLS } from '@/constants/urls'
import type { ReleaseNote } from '@/services/releaseService'
import { useReleaseStore } from '@/stores/releaseStore'
import { formatVersionAnchor } from '@/utils/formatUtil'
@@ -92,8 +93,8 @@ const shouldShow = computed(
const changelogUrl = computed(() => {
const isChineseLocale = locale.value === 'zh'
const baseUrl = isChineseLocale
? 'https://docs.comfy.org/zh-CN/changelog'
: 'https://docs.comfy.org/changelog'
? COMFY_URLS.docs.changelog.zh
: COMFY_URLS.docs.changelog.en
if (latestRelease.value?.version) {
const versionAnchor = formatVersionAnchor(latestRelease.value.version)

View File

@@ -43,7 +43,7 @@ import Panel from 'primevue/panel'
import { ModelRef, computed, onMounted, ref } from 'vue'
import MirrorItem from '@/components/install/mirror/MirrorItem.vue'
import { PYPI_MIRROR, PYTHON_MIRROR, UVMirror } from '@/constants/uvMirrors'
import { PYPI_MIRROR, PYTHON_MIRROR, UVMirror } from '@/constants/mirrors'
import { t } from '@/i18n'
import { isInChina } from '@/utils/networkUtil'
import { ValidationState, mergeValidationStates } from '@/utils/validationUtil'

View File

@@ -23,7 +23,7 @@
import { computed, onMounted, ref, watch } from 'vue'
import UrlInput from '@/components/common/UrlInput.vue'
import { UVMirror } from '@/constants/uvMirrors'
import { UVMirror } from '@/constants/mirrors'
import { normalizeI18nKey } from '@/utils/formatUtil'
import { checkMirrorReachable } from '@/utils/networkUtil'
import { ValidationState } from '@/utils/validationUtil'

View File

@@ -11,6 +11,7 @@ import {
DEFAULT_DARK_COLOR_PALETTE,
DEFAULT_LIGHT_COLOR_PALETTE
} from '@/constants/coreColorPalettes'
import { COMFY_URLS, GITHUB_REPOS } from '@/constants/urls'
import { t } from '@/i18n'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
@@ -543,10 +544,7 @@ export function useCoreCommands(): ComfyCommand[] {
menubarLabel: 'ComfyUI Issues',
versionAdded: '1.5.5',
function: () => {
window.open(
'https://github.com/comfyanonymous/ComfyUI/issues',
'_blank'
)
window.open(GITHUB_REPOS.comfyuiIssues, '_blank')
}
},
{
@@ -556,7 +554,7 @@ export function useCoreCommands(): ComfyCommand[] {
menubarLabel: 'ComfyUI Docs',
versionAdded: '1.5.5',
function: () => {
window.open('https://docs.comfy.org/', '_blank')
window.open(COMFY_URLS.docs.base, '_blank')
}
},
{
@@ -566,7 +564,7 @@ export function useCoreCommands(): ComfyCommand[] {
menubarLabel: 'Comfy-Org Discord',
versionAdded: '1.5.5',
function: () => {
window.open('https://www.comfy.org/discord', '_blank')
window.open(COMFY_URLS.community.discord, '_blank')
}
},
{
@@ -646,7 +644,7 @@ export function useCoreCommands(): ComfyCommand[] {
menubarLabel: 'ComfyUI Forum',
versionAdded: '1.8.2',
function: () => {
window.open('https://forum.comfy.org/', '_blank')
window.open(COMFY_URLS.community.forum.base, '_blank')
}
},
{

14
src/config/comfyDomain.ts Normal file
View File

@@ -0,0 +1,14 @@
/**
* Base domain configuration and core website URLs
* Forkers can change the base domain to use their own
*/
export const COMFY_BASE_DOMAIN =
process.env.VITE_COMFY_BASE_DOMAIN || 'comfy.org'
const WEBSITE_BASE_URL = `https://www.${COMFY_BASE_DOMAIN}`
export const COMFY_WEBSITE_URLS = {
base: WEBSITE_BASE_URL,
termsOfService: `${WEBSITE_BASE_URL}/terms-of-service`,
privacy: `${WEBSITE_BASE_URL}/privacy`
}

95
src/constants/urls.ts Normal file
View File

@@ -0,0 +1,95 @@
/**
* URL constants for ComfyUI frontend
* Centralized location for all URL references
*/
import { COMFY_BASE_DOMAIN } from '@/config/comfyDomain'
const DOCS_BASE_URL = `https://docs.${COMFY_BASE_DOMAIN}`
const FORUM_BASE_URL = `https://forum.${COMFY_BASE_DOMAIN}`
const WEBSITE_BASE_URL = `https://www.${COMFY_BASE_DOMAIN}`
export const COMFY_URLS = {
website: {
base: WEBSITE_BASE_URL,
termsOfService: `${WEBSITE_BASE_URL}/terms-of-service`,
privacy: `${WEBSITE_BASE_URL}/privacy`
},
docs: {
base: DOCS_BASE_URL,
changelog: {
en: `${DOCS_BASE_URL}/changelog`,
zh: `${DOCS_BASE_URL}/zh-CN/changelog`
},
installation: {
update: `${DOCS_BASE_URL}/installation/update_comfyui`
},
tutorials: {
apiNodes: {
overview: `${DOCS_BASE_URL}/tutorials/api-nodes/overview`,
faq: `${DOCS_BASE_URL}/tutorials/api-nodes/faq`,
pricing: `${DOCS_BASE_URL}/tutorials/api-nodes/pricing`,
apiKeyLogin: `${DOCS_BASE_URL}/interface/user#logging-in-with-an-api-key`,
nonWhitelistedLogin: `${DOCS_BASE_URL}/tutorials/api-nodes/overview#log-in-with-api-key-on-non-whitelisted-websites`
}
},
getLocalized: (path: string, locale: string) => {
return locale === 'zh' || locale === 'zh-CN'
? `${DOCS_BASE_URL}/zh-CN/${path}`
: `${DOCS_BASE_URL}/${path}`
}
},
community: {
discord: `${WEBSITE_BASE_URL}/discord`,
forum: {
base: `${FORUM_BASE_URL}/`,
v1Feedback: `${FORUM_BASE_URL}/c/v1-feedback/`
}
}
}
export const GITHUB_REPOS = {
comfyui: 'https://github.com/comfyanonymous/ComfyUI',
comfyuiIssues: 'https://github.com/comfyanonymous/ComfyUI/issues',
frontend: 'https://github.com/Comfy-Org/ComfyUI_frontend',
electron: 'https://github.com/Comfy-Org/electron',
desktopPlatforms:
'https://github.com/Comfy-Org/desktop#currently-supported-platforms'
}
export const MODEL_SOURCES = {
repos: {
civitai: 'https://civitai.com/',
huggingface: 'https://huggingface.co/'
},
whitelistedUrls: [
'https://huggingface.co/stabilityai/stable-zero123/resolve/main/stable_zero123.ckpt',
'https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_depth_sd14v1.pth?download=true',
'https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth'
],
allowedDomains: [
'https://civitai.com/',
'https://huggingface.co/',
'http://localhost:' // TODO: Remove in production
]
}
export const DEVELOPER_TOOLS = {
git: 'https://git-scm.com/downloads/',
vcRedist: 'https://aka.ms/vs/17/release/vc_redist.x64.exe',
uv: 'https://docs.astral.sh/uv/getting-started/installation/'
}
// Platform and locale-aware desktop guide URL generator
export const getDesktopGuideUrl = (locale: string): string => {
const isChineseLocale = locale === 'zh'
const isMacOS =
typeof navigator !== 'undefined' &&
navigator.platform.toUpperCase().indexOf('MAC') >= 0
const platform = isMacOS ? 'macos' : 'windows'
const baseUrl = isChineseLocale
? `https://docs.${COMFY_BASE_DOMAIN}/zh-CN/installation/desktop`
: `https://docs.${COMFY_BASE_DOMAIN}/installation/desktop`
return `${baseUrl}/${platform}`
}

View File

@@ -1,7 +1,8 @@
import log from 'loglevel'
import { PYTHON_MIRROR } from '@/constants/uvMirrors'
import { t } from '@/i18n'
import { PYTHON_MIRROR } from '@/constants/mirrors'
import { getDesktopGuideUrl } from '@/constants/urls'
import { i18n, t } from '@/i18n'
import { app } from '@/scripts/app'
import { useDialogService } from '@/services/dialogService'
import { useToastStore } from '@/stores/toastStore'
@@ -159,7 +160,7 @@ import { checkMirrorReachable } from '@/utils/networkUtil'
label: 'Desktop User Guide',
icon: 'pi pi-book',
function() {
window.open('https://comfyorg.notion.site/', '_blank')
window.open(getDesktopGuideUrl(i18n.global.locale.value), '_blank')
}
},
{

View File

@@ -1,6 +1,7 @@
import { defineStore } from 'pinia'
import { computed } from 'vue'
import { COMFY_URLS, GITHUB_REPOS } from '@/constants/urls'
import { AboutPageBadge } from '@/types/comfy'
import { electronAPI, isElectron } from '@/utils/envUtil'
@@ -24,20 +25,20 @@ export const useAboutPanelStore = defineStore('aboutPanel', () => {
? 'v' + electronAPI().getComfyUIVersion()
: coreVersion.value
}`,
url: 'https://github.com/comfyanonymous/ComfyUI',
url: GITHUB_REPOS.comfyui,
icon: 'pi pi-github'
},
{
label: `ComfyUI_frontend v${frontendVersion}`,
url: 'https://github.com/Comfy-Org/ComfyUI_frontend',
url: GITHUB_REPOS.frontend,
icon: 'pi pi-github'
},
{
label: 'Discord',
url: 'https://www.comfy.org/discord',
url: COMFY_URLS.community.discord,
icon: 'pi pi-discord'
},
{ label: 'ComfyOrg', url: 'https://www.comfy.org/', icon: 'pi pi-globe' }
{ label: 'ComfyOrg', url: COMFY_URLS.website.base, icon: 'pi pi-globe' }
])
const allBadges = computed<AboutPageBadge[]>(() => [

View File

@@ -0,0 +1,144 @@
import { describe, expect, it } from 'vitest'
import { COMFY_BASE_DOMAIN, COMFY_WEBSITE_URLS } from '@/config/comfyDomain'
import { PYPI_MIRROR, PYTHON_MIRROR } from '@/constants/mirrors'
import {
COMFY_URLS,
DEVELOPER_TOOLS,
GITHUB_REPOS,
MODEL_SOURCES,
getDesktopGuideUrl
} from '@/constants/urls'
describe('URL Constants', () => {
describe('URL Format Validation', () => {
it('should have valid HTTPS URLs throughout', () => {
const httpsPattern =
/^https:\/\/[a-z0-9]+([-.][a-z0-9]+)*\.[a-z]{2,}(:[0-9]{1,5})?(\/.*)?$/i
// Test COMFY_URLS
expect(COMFY_URLS.website.base).toMatch(httpsPattern)
expect(COMFY_URLS.docs.base).toMatch(httpsPattern)
expect(COMFY_URLS.community.discord).toMatch(httpsPattern)
// Test GITHUB_REPOS
Object.values(GITHUB_REPOS).forEach((url) => {
expect(url).toMatch(httpsPattern)
})
// Test MODEL_SOURCES
Object.values(MODEL_SOURCES.repos).forEach((url) => {
expect(url).toMatch(httpsPattern)
})
// Test DEVELOPER_TOOLS
Object.values(DEVELOPER_TOOLS).forEach((url) => {
expect(url).toMatch(httpsPattern)
})
})
it('should have proper GitHub URL format', () => {
const githubPattern = /^https:\/\/github\.com\/[\w-]+\/[\w-]+(\/[\w-]+)?$/
expect(GITHUB_REPOS.comfyui).toMatch(githubPattern)
expect(GITHUB_REPOS.comfyuiIssues).toMatch(githubPattern)
expect(GITHUB_REPOS.frontend).toMatch(githubPattern)
})
})
describe('Mirror Configuration', () => {
it('should have valid mirror URLs', () => {
const urlPattern =
/^https?:\/\/[a-z0-9]+([-\.]{1}[a-z0-9]+)*\.[a-z]{2,}(:[0-9]{1,5})?(\/.*)?$/i
expect(PYTHON_MIRROR.mirror).toMatch(urlPattern)
expect(PYTHON_MIRROR.fallbackMirror).toMatch(urlPattern)
expect(PYPI_MIRROR.mirror).toMatch(urlPattern)
expect(PYPI_MIRROR.fallbackMirror).toMatch(urlPattern)
})
})
describe('Domain Configuration', () => {
it('should have valid domain format', () => {
const domainPattern = /^[a-z0-9]+([-.][a-z0-9]+)*\.[a-z]{2,}$/i
expect(COMFY_BASE_DOMAIN).toMatch(domainPattern)
})
it('should construct proper website URLs from base domain', () => {
const expectedBase = `https://www.${COMFY_BASE_DOMAIN}`
expect(COMFY_WEBSITE_URLS.base).toBe(expectedBase)
expect(COMFY_WEBSITE_URLS.termsOfService).toBe(
`${expectedBase}/terms-of-service`
)
expect(COMFY_WEBSITE_URLS.privacy).toBe(`${expectedBase}/privacy`)
})
})
describe('Localization', () => {
it('should handle valid language codes in getLocalized', () => {
const validLanguageCodes = ['en', 'zh', 'ja', 'ko', 'es', 'fr', 'de']
validLanguageCodes.forEach((lang) => {
const result = COMFY_URLS.docs.getLocalized('test-path', lang)
expect(result).toMatch(/^\/docs\/(([a-z]{2}-[A-Z]{2}\/)?test-path)$/)
})
})
it('should properly format localized paths', () => {
expect(COMFY_URLS.docs.getLocalized('test-path', 'en')).toBe(
'/docs/test-path'
)
expect(COMFY_URLS.docs.getLocalized('test-path', 'zh')).toBe(
'/docs/zh-CN/test-path'
)
expect(COMFY_URLS.docs.getLocalized('/', 'en')).toBe('/docs/')
expect(COMFY_URLS.docs.getLocalized('/', 'zh')).toBe('/docs/zh-CN/')
})
it('should generate platform and locale-aware desktop guide URLs', () => {
// Test English locale
const enUrl = getDesktopGuideUrl('en')
expect(enUrl).toMatch(
/^https:\/\/docs\.comfy\.org\/installation\/desktop\/(windows|macos)$/
)
// Test Chinese locale
const zhUrl = getDesktopGuideUrl('zh')
expect(zhUrl).toMatch(
/^https:\/\/docs\.comfy\.org\/zh-CN\/installation\/desktop\/(windows|macos)$/
)
// Test other locales default to English
const frUrl = getDesktopGuideUrl('fr')
expect(frUrl).toMatch(
/^https:\/\/docs\.comfy\.org\/installation\/desktop\/(windows|macos)$/
)
})
})
describe('Security', () => {
it('should only use secure HTTPS for external URLs', () => {
const allUrls = [
...Object.values(GITHUB_REPOS),
...Object.values(MODEL_SOURCES.repos),
...Object.values(DEVELOPER_TOOLS),
COMFY_URLS.website.base,
COMFY_URLS.docs.base,
COMFY_URLS.community.discord
]
allUrls.forEach((url) => {
expect(url.startsWith('https://')).toBe(true)
expect(url.startsWith('http://')).toBe(false)
})
})
it('should have valid domain allowlist for model sources', () => {
const domainPattern = /^[a-z0-9]+([-.][a-z0-9]+)*\.[a-z]{2,}$/i
MODEL_SOURCES.allowedDomains.forEach((domain) => {
expect(domain).toMatch(domainPattern)
})
})
})
})