Skip to content

Commit 5ed555c

Browse files
Backport desktop update issue fixes to 1.30 (#6759)
Backport of these two PRs: #6733 #6750 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6759-Backport-desktop-update-issue-2b16d73d36508182b91cdbb123418783) by [Unito](https://www.unito.io)
1 parent 300c492 commit 5ed555c

File tree

10 files changed

+287
-28
lines changed

10 files changed

+287
-28
lines changed

apps/desktop-ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191
"build-storybook": "storybook build -o dist/storybook"
9292
},
9393
"dependencies": {
94-
"@comfyorg/comfyui-electron-types": "0.4.73-0",
94+
"@comfyorg/comfyui-electron-types": "catalog:",
9595
"@comfyorg/shared-frontend-utils": "workspace:*",
9696
"@primevue/core": "catalog:",
9797
"@primevue/themes": "catalog:",

apps/desktop-ui/src/components/install/InstallLocationPicker.vue

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,19 +115,18 @@ import Button from 'primevue/button'
115115
import Divider from 'primevue/divider'
116116
import InputText from 'primevue/inputtext'
117117
import Message from 'primevue/message'
118-
import { type ModelRef, computed, onMounted, ref } from 'vue'
118+
import { computed, onMounted, ref } from 'vue'
119+
import type { ModelRef } from 'vue'
119120
import { useI18n } from 'vue-i18n'
120121
121-
import MigrationPicker from '@/components/install/MigrationPicker.vue'
122-
import MirrorItem from '@/components/install/mirror/MirrorItem.vue'
123-
import {
124-
PYPI_MIRROR,
125-
PYTHON_MIRROR,
126-
type UVMirror
127-
} from '@/constants/uvMirrors'
122+
import { PYPI_MIRROR, PYTHON_MIRROR } from '@/constants/uvMirrors'
123+
import type { UVMirror } from '@/constants/uvMirrors'
128124
import { electronAPI } from '@/utils/envUtil'
129125
import { ValidationState } from '@/utils/validationUtil'
130126
127+
import MigrationPicker from './MigrationPicker.vue'
128+
import MirrorItem from './mirror/MirrorItem.vue'
129+
131130
const { t } = useI18n()
132131
133132
const installPath = defineModel<string>('installPath', { required: true })
@@ -229,6 +228,10 @@ const validatePath = async (path: string | undefined) => {
229228
}
230229
if (validation.parentMissing) errors.push(t('install.parentMissing'))
231230
if (validation.isOneDrive) errors.push(t('install.isOneDrive'))
231+
if (validation.isInsideAppInstallDir)
232+
errors.push(t('install.insideAppInstallDir'))
233+
if (validation.isInsideUpdaterCache)
234+
errors.push(t('install.insideUpdaterCache'))
232235
233236
if (validation.error)
234237
errors.push(`${t('install.unhandledError')}: ${validation.error}`)

apps/desktop-ui/src/constants/desktopMaintenanceTasks.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ export const DESKTOP_MAINTENANCE_TASKS: Readonly<MaintenanceTask>[] = [
1616
execute: async () => await electron.setBasePath(),
1717
name: 'Base path',
1818
shortDescription: 'Change the application base path.',
19-
errorDescription: 'Unable to open the base path. Please select a new one.',
19+
errorDescription:
20+
'The current base path is invalid or unsafe. Please select a new location.',
2021
description:
2122
'The base path is the default location where ComfyUI stores data. It is the location for the python environment, and may also contain models, custom nodes, and other extensions.',
2223
isInstallationFix: true,

apps/desktop-ui/src/stores/maintenanceTaskStore.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
8585
const electron = electronAPI()
8686

8787
// Reactive state
88+
const lastUpdate = ref<InstallValidation | null>(null)
8889
const isRefreshing = ref(false)
8990
const isRunningTerminalCommand = computed(() =>
9091
tasks.value
@@ -97,6 +98,13 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
9798
.some((task) => getRunner(task)?.executing)
9899
)
99100

101+
const unsafeBasePath = computed(
102+
() => lastUpdate.value?.unsafeBasePath === true
103+
)
104+
const unsafeBasePathReason = computed(
105+
() => lastUpdate.value?.unsafeBasePathReason
106+
)
107+
100108
// Task list
101109
const tasks = ref(DESKTOP_MAINTENANCE_TASKS)
102110

@@ -123,6 +131,7 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
123131
* @param validationUpdate Update details passed in by electron
124132
*/
125133
const processUpdate = (validationUpdate: InstallValidation) => {
134+
lastUpdate.value = validationUpdate
126135
const update = validationUpdate as IndexedUpdate
127136
isRefreshing.value = true
128137

@@ -155,14 +164,20 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
155164
}
156165

157166
const execute = async (task: MaintenanceTask) => {
158-
return getRunner(task).execute(task)
167+
const success = await getRunner(task).execute(task)
168+
if (success && task.isInstallationFix) {
169+
await refreshDesktopTasks()
170+
}
171+
return success
159172
}
160173

161174
return {
162175
tasks,
163176
isRefreshing,
164177
isRunningTerminalCommand,
165178
isRunningInstallationFix,
179+
unsafeBasePath,
180+
unsafeBasePathReason,
166181
execute,
167182
getRunner,
168183
processUpdate,
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// eslint-disable-next-line storybook/no-renderer-packages
2+
import type { Meta, StoryObj } from '@storybook/vue3'
3+
import { defineAsyncComponent } from 'vue'
4+
5+
type UnsafeReason = 'appInstallDir' | 'updaterCache' | 'oneDrive' | null
6+
type ValidationIssueState = 'OK' | 'warning' | 'error' | 'skipped'
7+
8+
type ValidationState = {
9+
inProgress: boolean
10+
installState: string
11+
basePath?: ValidationIssueState
12+
unsafeBasePath: boolean
13+
unsafeBasePathReason: UnsafeReason
14+
venvDirectory?: ValidationIssueState
15+
pythonInterpreter?: ValidationIssueState
16+
pythonPackages?: ValidationIssueState
17+
uv?: ValidationIssueState
18+
git?: ValidationIssueState
19+
vcRedist?: ValidationIssueState
20+
upgradePackages?: ValidationIssueState
21+
}
22+
23+
const validationState: ValidationState = {
24+
inProgress: false,
25+
installState: 'installed',
26+
basePath: 'OK',
27+
unsafeBasePath: false,
28+
unsafeBasePathReason: null,
29+
venvDirectory: 'OK',
30+
pythonInterpreter: 'OK',
31+
pythonPackages: 'OK',
32+
uv: 'OK',
33+
git: 'OK',
34+
vcRedist: 'OK',
35+
upgradePackages: 'OK'
36+
}
37+
38+
const createMockElectronAPI = () => {
39+
const logListeners: Array<(message: string) => void> = []
40+
41+
const getValidationUpdate = () => ({
42+
...validationState
43+
})
44+
45+
return {
46+
getPlatform: () => 'darwin',
47+
changeTheme: (_theme: unknown) => {},
48+
onLogMessage: (listener: (message: string) => void) => {
49+
logListeners.push(listener)
50+
},
51+
showContextMenu: (_options: unknown) => {},
52+
Events: {
53+
trackEvent: (_eventName: string, _data?: unknown) => {}
54+
},
55+
Validation: {
56+
onUpdate: (_callback: (update: unknown) => void) => {},
57+
async getStatus() {
58+
return getValidationUpdate()
59+
},
60+
async validateInstallation(callback: (update: unknown) => void) {
61+
callback(getValidationUpdate())
62+
},
63+
async complete() {
64+
// Only allow completion when the base path is safe
65+
return !validationState.unsafeBasePath
66+
},
67+
dispose: () => {}
68+
},
69+
setBasePath: () => Promise.resolve(true),
70+
reinstall: () => Promise.resolve(),
71+
uv: {
72+
installRequirements: () => Promise.resolve(),
73+
clearCache: () => Promise.resolve(),
74+
resetVenv: () => Promise.resolve()
75+
}
76+
}
77+
}
78+
79+
const ensureElectronAPI = () => {
80+
const globalWindow = window as unknown as { electronAPI?: unknown }
81+
if (!globalWindow.electronAPI) {
82+
globalWindow.electronAPI = createMockElectronAPI()
83+
}
84+
85+
return globalWindow.electronAPI
86+
}
87+
88+
const MaintenanceView = defineAsyncComponent(async () => {
89+
ensureElectronAPI()
90+
const module = await import('./MaintenanceView.vue')
91+
return module.default
92+
})
93+
94+
const meta: Meta<typeof MaintenanceView> = {
95+
title: 'Desktop/Views/MaintenanceView',
96+
component: MaintenanceView,
97+
parameters: {
98+
layout: 'fullscreen',
99+
backgrounds: {
100+
default: 'dark',
101+
values: [
102+
{ name: 'dark', value: '#0a0a0a' },
103+
{ name: 'neutral-900', value: '#171717' },
104+
{ name: 'neutral-950', value: '#0a0a0a' }
105+
]
106+
}
107+
}
108+
}
109+
110+
export default meta
111+
type Story = StoryObj<typeof meta>
112+
113+
export const Default: Story = {
114+
name: 'All tasks OK',
115+
render: () => ({
116+
components: { MaintenanceView },
117+
setup() {
118+
validationState.inProgress = false
119+
validationState.installState = 'installed'
120+
validationState.basePath = 'OK'
121+
validationState.unsafeBasePath = false
122+
validationState.unsafeBasePathReason = null
123+
validationState.venvDirectory = 'OK'
124+
validationState.pythonInterpreter = 'OK'
125+
validationState.pythonPackages = 'OK'
126+
validationState.uv = 'OK'
127+
validationState.git = 'OK'
128+
validationState.vcRedist = 'OK'
129+
validationState.upgradePackages = 'OK'
130+
ensureElectronAPI()
131+
return {}
132+
},
133+
template: '<MaintenanceView />'
134+
})
135+
}
136+
137+
export const UnsafeBasePathOneDrive: Story = {
138+
name: 'Unsafe base path (OneDrive)',
139+
render: () => ({
140+
components: { MaintenanceView },
141+
setup() {
142+
validationState.inProgress = false
143+
validationState.installState = 'installed'
144+
validationState.basePath = 'error'
145+
validationState.unsafeBasePath = true
146+
validationState.unsafeBasePathReason = 'oneDrive'
147+
validationState.venvDirectory = 'OK'
148+
validationState.pythonInterpreter = 'OK'
149+
validationState.pythonPackages = 'OK'
150+
validationState.uv = 'OK'
151+
validationState.git = 'OK'
152+
validationState.vcRedist = 'OK'
153+
validationState.upgradePackages = 'OK'
154+
ensureElectronAPI()
155+
return {}
156+
},
157+
template: '<MaintenanceView />'
158+
})
159+
}

apps/desktop-ui/src/views/MaintenanceView.vue

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,28 @@
4747
</div>
4848
</div>
4949

50+
<!-- Unsafe migration warning -->
51+
<div v-if="taskStore.unsafeBasePath" class="my-4">
52+
<p class="flex items-start gap-3 text-neutral-300">
53+
<Tag
54+
icon="pi pi-exclamation-triangle"
55+
severity="warn"
56+
:value="t('icon.exclamation-triangle')"
57+
/>
58+
<span>
59+
<strong class="block mb-1">
60+
{{ t('maintenance.unsafeMigration.title') }}
61+
</strong>
62+
<span class="block mb-1">
63+
{{ unsafeReasonText }}
64+
</span>
65+
<span class="block text-sm text-neutral-400">
66+
{{ t('maintenance.unsafeMigration.action') }}
67+
</span>
68+
</span>
69+
</p>
70+
</div>
71+
5072
<!-- Tasks -->
5173
<TaskListPanel
5274
class="border-neutral-700 border-solid border-x-0 border-y"
@@ -89,10 +111,10 @@
89111
import { PrimeIcons } from '@primevue/core/api'
90112
import Button from 'primevue/button'
91113
import SelectButton from 'primevue/selectbutton'
114+
import Tag from 'primevue/tag'
92115
import Toast from 'primevue/toast'
93116
import { useToast } from 'primevue/usetoast'
94-
import { computed, onMounted, onUnmounted, ref } from 'vue'
95-
import { watch } from 'vue'
117+
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
96118
97119
import RefreshButton from '@/components/common/RefreshButton.vue'
98120
import StatusTag from '@/components/maintenance/StatusTag.vue'
@@ -139,6 +161,27 @@ const filterOptions = ref([
139161
/** Filter binding; can be set to show all tasks, or only errors. */
140162
const filter = ref<MaintenanceFilter>(filterOptions.value[0])
141163
164+
const unsafeReasonText = computed(() => {
165+
const reason = taskStore.unsafeBasePathReason
166+
if (!reason) {
167+
return t('maintenance.unsafeMigration.generic')
168+
}
169+
170+
if (reason === 'appInstallDir') {
171+
return t('maintenance.unsafeMigration.appInstallDir')
172+
}
173+
174+
if (reason === 'updaterCache') {
175+
return t('maintenance.unsafeMigration.updaterCache')
176+
}
177+
178+
if (reason === 'oneDrive') {
179+
return t('maintenance.unsafeMigration.oneDrive')
180+
}
181+
182+
return t('maintenance.unsafeMigration.generic')
183+
})
184+
142185
/** If valid, leave the validation window. */
143186
const completeValidation = async () => {
144187
const isValid = await electron.Validation.complete()

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@
120120
"dependencies": {
121121
"@alloc/quick-lru": "catalog:",
122122
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
123-
"@comfyorg/comfyui-electron-types": "0.4.73-0",
123+
"@comfyorg/comfyui-electron-types": "catalog:",
124124
"@comfyorg/design-system": "workspace:*",
125125
"@comfyorg/registry-types": "workspace:*",
126126
"@comfyorg/tailwind-utils": "workspace:*",

0 commit comments

Comments
 (0)