refactor: replace runtime isElectron() with build-time isDesktop constant (#8710)

## Summary

Replace all runtime `isElectron()` function calls with the build-time
`isDesktop` constant from `@/platform/distribution/types`, enabling
dead-code elimination in non-desktop builds.

## Changes

- **What**: Migrate 30 files from runtime `isElectron()` detection
(checking `window.electronAPI`) to the compile-time `isDesktop` constant
(driven by `__DISTRIBUTION__` Vite define). Remove `isElectron` from
`envUtil.ts`. Update `isNativeWindow()` to use `isDesktop`. Guard
`electronAPI()` calls behind `isDesktop` checks in stores. Update 7 test
files to use `vi.hoisted` + getter mock pattern for per-test `isDesktop`
toggling. Add `DISTRIBUTION=desktop` to `dev:electron` script.

## Review Focus

- The `electronDownloadStore.ts` now guards the top-level
`electronAPI()` call behind `isDesktop` to prevent crashes on
non-desktop builds.
- Test mocking pattern uses `vi.hoisted` with a getter to allow per-test
toggling of the `isDesktop` value.
- Pre-existing issues not addressed: `as ElectronAPI` cast in
`envUtil.ts`, `:class="[]"` in `BaseViewTemplate.vue`,
`@ts-expect-error` in `ModelLibrarySidebarTab.vue`.
- This subsumes PR #8627 and renders PR #6122 and PR #7374 obsolete.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8710-refactor-replace-runtime-isElectron-with-build-time-isDesktop-constant-3006d73d365081c08037f0e61c2f6c77)
by [Unito](https://www.unito.io)
This commit is contained in:
Christian Byrne
2026-02-07 19:47:05 -08:00
committed by GitHub
parent b7fef1c744
commit 6c8473e4e4
30 changed files with 165 additions and 142 deletions

View File

@@ -1,5 +1,3 @@
import { isElectron } from '@/utils/envUtil'
/**
* Distribution types and compile-time constants for managing
* multi-distribution builds (Desktop, Localhost, Cloud)
@@ -15,10 +13,8 @@ declare global {
/** Current distribution - replaced at compile time */
const DISTRIBUTION: Distribution = __DISTRIBUTION__
/** Distribution type checks */
export const isDesktop = DISTRIBUTION === 'desktop' || isElectron() // TODO: replace with build var
export const isDesktop = DISTRIBUTION === 'desktop'
export const isCloud = DISTRIBUTION === 'cloud'
// export const isLocalhost = DISTRIBUTION === 'localhost' || (!isDesktop && !isCloud)
/**
* Whether this is a nightly build (from main branch).

View File

@@ -6,11 +6,11 @@ import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useBillingContext } from '@/composables/billing/useBillingContext'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
import { isCloud } from '@/platform/distribution/types'
import { isCloud, isDesktop } from '@/platform/distribution/types'
import type { SettingTreeNode } from '@/platform/settings/settingStore'
import { useSettingStore } from '@/platform/settings/settingStore'
import type { SettingParams } from '@/platform/settings/types'
import { isElectron } from '@/utils/envUtil'
import { normalizeI18nKey } from '@/utils/formatUtil'
import { buildTree } from '@/utils/treeUtil'
@@ -226,7 +226,7 @@ export function useSettingUI(
...(shouldShowWorkspacePanel.value ? [workspacePanel] : []),
keybindingPanel,
extensionPanel,
...(isElectron() ? [serverConfigPanel] : []),
...(isDesktop ? [serverConfigPanel] : []),
...(shouldShowPlanCreditsPanel.value && subscriptionPanel
? [subscriptionPanel]
: []),
@@ -283,7 +283,7 @@ export function useSettingUI(
translateCategory(keybindingPanel.node),
translateCategory(extensionPanel.node),
translateCategory(aboutPanel.node),
...(isElectron() ? [translateCategory(serverConfigPanel.node)] : [])
...(isDesktop ? [translateCategory(serverConfigPanel.node)] : [])
]
}),
// Custom node settings (only shown if custom nodes have registered settings)
@@ -332,7 +332,7 @@ export function useSettingUI(
keybindingPanel.node,
extensionPanel.node,
aboutPanel.node,
...(isElectron() ? [serverConfigPanel.node] : [])
...(isDesktop ? [serverConfigPanel.node] : [])
].map(translateCategory)
}
])

View File

@@ -9,7 +9,6 @@ import { useSettingStore } from '@/platform/settings/settingStore'
import { useReleaseStore } from '@/platform/updates/common/releaseStore'
import { useReleaseService } from '@/platform/updates/common/releaseService'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import { isElectron } from '@/utils/envUtil'
import { createTestingPinia } from '@pinia/testing'
import type { SystemStats } from '@/types'
@@ -19,11 +18,14 @@ vi.mock('semver', () => ({
valid: vi.fn(() => '1.0.0')
}))
vi.mock('@/utils/envUtil', () => ({
isElectron: vi.fn(() => true)
}))
const mockData = vi.hoisted(() => ({ isDesktop: true }))
vi.mock('@/platform/distribution/types', () => ({ isCloud: false }))
vi.mock('@/platform/distribution/types', () => ({
get isDesktop() {
return mockData.isDesktop
},
isCloud: false
}))
vi.mock('@/platform/updates/common/releaseService', () => {
const getReleases = vi.fn()
@@ -675,10 +677,10 @@ describe('useReleaseStore', () => {
})
})
describe('isElectron environment checks', () => {
describe('when running in Electron (desktop)', () => {
describe('isDesktop environment checks', () => {
describe('when running on desktop', () => {
beforeEach(() => {
vi.mocked(isElectron).mockReturnValue(true)
mockData.isDesktop = true
})
it('should show toast when conditions are met', () => {
@@ -709,9 +711,9 @@ describe('useReleaseStore', () => {
})
})
describe('when NOT running in Electron (web)', () => {
describe('when NOT running on desktop (web)', () => {
beforeEach(() => {
vi.mocked(isElectron).mockReturnValue(false)
mockData.isDesktop = false
})
it('should NOT show toast even when all other conditions are met', () => {

View File

@@ -3,10 +3,9 @@ import { defineStore } from 'pinia'
import { compare, valid } from 'semver'
import { computed, ref } from 'vue'
import { isCloud } from '@/platform/distribution/types'
import { isCloud, isDesktop } from '@/platform/distribution/types'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import { isElectron } from '@/utils/envUtil'
import { stringToLocale } from '@/utils/formatUtil'
import { useReleaseService } from './releaseService'
@@ -95,7 +94,7 @@ export const useReleaseStore = defineStore('release', () => {
// Show toast if needed
const shouldShowToast = computed(() => {
// Only show on desktop version
if (!isElectron() || isCloud) {
if (!isDesktop || isCloud) {
return false
}
@@ -127,7 +126,7 @@ export const useReleaseStore = defineStore('release', () => {
// Show red-dot indicator
const shouldShowRedDot = computed(() => {
// Only show on desktop version
if (!isElectron() || isCloud) {
if (!isDesktop || isCloud) {
return false
}
@@ -172,7 +171,7 @@ export const useReleaseStore = defineStore('release', () => {
})
const shouldShowPopup = computed(() => {
if (!isElectron() && !isCloud) {
if (!isDesktop && !isCloud) {
return false
}

View File

@@ -5,9 +5,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { ReleaseNote } from '../common/releaseService'
import ReleaseNotificationToast from './ReleaseNotificationToast.vue'
interface TestWindow extends Window {
electronAPI?: Record<string, unknown>
}
const mockData = vi.hoisted(() => ({ isDesktop: false }))
const { commandExecuteMock } = vi.hoisted(() => ({
commandExecuteMock: vi.fn()
@@ -17,6 +15,14 @@ const { toastErrorHandlerMock } = vi.hoisted(() => ({
toastErrorHandlerMock: vi.fn()
}))
vi.mock('@/platform/distribution/types', () => ({
isCloud: false,
isNightly: false,
get isDesktop() {
return mockData.isDesktop
}
}))
// Mock dependencies
vi.mock('vue-i18n', () => ({
useI18n: vi.fn(() => ({
@@ -105,6 +111,7 @@ describe('ReleaseNotificationToast', () => {
beforeEach(() => {
vi.clearAllMocks()
mockData.isDesktop = false
// Reset store state
mockReleaseStore.recentRelease = null
mockReleaseStore.shouldShowToast = true // Force show for testing
@@ -183,7 +190,8 @@ describe('ReleaseNotificationToast', () => {
)
})
it('executes desktop updater flow when running in Electron', async () => {
it('executes desktop updater flow when running on desktop', async () => {
mockData.isDesktop = true
mockReleaseStore.recentRelease = {
version: '1.2.3',
content: '# Test Release'
@@ -196,7 +204,6 @@ describe('ReleaseNotificationToast', () => {
value: mockWindowOpen,
writable: true
})
;(window as TestWindow).electronAPI = {}
wrapper = mountComponent()
await wrapper.vm.handleUpdate()
@@ -206,11 +213,10 @@ describe('ReleaseNotificationToast', () => {
)
expect(mockWindowOpen).not.toHaveBeenCalled()
expect(toastErrorHandlerMock).not.toHaveBeenCalled()
delete (window as TestWindow).electronAPI
})
it('shows an error toast if the desktop updater flow fails in Electron', async () => {
it('shows an error toast if the desktop updater flow fails on desktop', async () => {
mockData.isDesktop = true
mockReleaseStore.recentRelease = {
version: '1.2.3',
content: '# Test Release'
@@ -224,15 +230,12 @@ describe('ReleaseNotificationToast', () => {
value: mockWindowOpen,
writable: true
})
;(window as TestWindow).electronAPI = {}
wrapper = mountComponent()
await wrapper.vm.handleUpdate()
expect(toastErrorHandlerMock).toHaveBeenCalledWith(error)
expect(mockWindowOpen).not.toHaveBeenCalled()
delete (window as TestWindow).electronAPI
})
it('calls handleShowChangelog when learn more link is clicked', async () => {

View File

@@ -72,7 +72,7 @@ import { useI18n } from 'vue-i18n'
import { useErrorHandling } from '@/composables/useErrorHandling'
import { useExternalLink } from '@/composables/useExternalLink'
import { useCommandStore } from '@/stores/commandStore'
import { isElectron } from '@/utils/envUtil'
import { isDesktop } from '@/platform/distribution/types'
import { formatVersionAnchor } from '@/utils/formatUtil'
import { renderMarkdownToHtml } from '@/utils/markdownRendererUtil'
@@ -177,7 +177,7 @@ const handleLearnMore = () => {
}
const handleUpdate = async () => {
if (isElectron()) {
if (isDesktop) {
try {
await useCommandStore().execute('Comfy-Desktop.CheckForUpdates')
dismissToast()