From 89a89408c6ee082de564d034f6da511d824c090b Mon Sep 17 00:00:00 2001 From: 07akioni <07akioni2@gmail.com> Date: Tue, 15 Sep 2026 20:38:37 +0800 Subject: [PATCH] refactor(desktop): remove unused recovery IPC paths --- ...15-desktop-native-fatal-recovery.i18n.yaml | 4 +-- ...026-09-15-desktop-native-fatal-recovery.md | 2 +- ...-09-15-desktop-native-fatal-recovery.zh.md | 2 +- apps/desktop/README.i18n.yaml | 4 +-- apps/desktop/README.md | 2 +- apps/desktop/README.zh.md | 2 +- apps/desktop/src/ipc.ts | 9 ------ apps/desktop/src/main.ts | 13 +------- apps/desktop/src/preload.ts | 10 ------ apps/desktop/src/project-manager.ts | 9 ++---- apps/desktop/tests/main-startup.spec.ts | 32 ++++++++----------- apps/desktop/tests/plugin-manager.spec.ts | 1 - apps/desktop/tests/plugin-pnpm.spec.ts | 2 +- apps/desktop/tests/project-manager.spec.ts | 14 +++----- 14 files changed, 31 insertions(+), 75 deletions(-) diff --git a/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.i18n.yaml index 2d23c42ebc..1a76ed8846 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.md -2026-09-15-desktop-native-fatal-recovery.md: 6922b65495656a52b449b94e23f24b036a0bf79b -2026-09-15-desktop-native-fatal-recovery.zh.md: 32c35753a6346849b3da26c5492a245fee5ab991 +2026-09-15-desktop-native-fatal-recovery.md: b2a0dfff1a630d1eedaa898e4a4c8144ff7ae776 +2026-09-15-desktop-native-fatal-recovery.zh.md: 0fee729e3be791d10966050d21482c5132b14fe6 diff --git a/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.md b/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.md index 6922b65495..b2a0dfff1a 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.md +++ b/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.md @@ -14,7 +14,7 @@ Electron owns one native fatal dialog per application process. Explicit main-win The first report claims presentation before awaiting the dialog. Later reports remain in logs. The Electron console retains the complete reported diagnostic. The dialog bounds the first diagnostic to its final eight lines and limits the complete detail to 1,200 UTF-16 code units, including truncation notice and reinstall advice, because native dialogs cannot scroll. The dialog offers exit, restart, or disabling third-party bundles followed by a whole-application restart. Disabling writes activation metadata under the existing profile transaction lock after Host shutdown, without requiring runtime initialization or deleting installed files. An explicit recovery-operation failure is presented separately and does not count as another automatic fatal report. -The Web document stays in place. A carrier callback owns startup failure presentation while the shared boot page retains its spinner; ordinary browser boot still renders its own failure report. Only the primary application frame may report a Web boot failure. Desktop has no profile reset, plugin-window recovery controls, or emergency recovery document. A fatal backend failure requires one of the native recovery actions rather than an in-process retry. +The Web document stays in place. A carrier callback owns startup failure presentation while the shared boot page retains its spinner; ordinary browser boot still renders its own failure report. Only the primary application frame may report a Web boot failure. The plugin window exposes package operations only; backend state remains in the main process, and native recovery directly owns disabling all third-party bundles. Desktop has no profile reset, plugin-window recovery controls, or emergency recovery document. A fatal backend failure requires one of the native recovery actions rather than an in-process retry. This supersedes recovery-page and reset behavior in the [immediate-window decision](2026-09-09-desktop-immediate-window-and-direct-start.md), whose immediate visibility and direct Host startup rationale remain active. The [in-place profile decision](2026-09-09-desktop-in-place-profile.md) still owns package transactions and partial changes. diff --git a/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.zh.md b/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.zh.md index 32c35753a6..0fee729e3b 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.zh.md @@ -14,7 +14,7 @@ Electron 在每个应用进程中提供一次原生致命错误对话框。明 首次报告在等待对话框前取得展示权。后续报告保留在日志中。Electron 控制台保留完整的已报告诊断。原生对话框无法滚动,因此仅显示首次诊断末尾八行,并将包含截断提示和重装建议的完整详情限制为 1,200 个 UTF-16 代码单元。对话框提供退出、重启或禁用第三方 bundle 后重启整个应用。禁用操作等待 Host 关闭后,在已有 profile 事务锁内写入启用元数据,不要求运行时初始化,也不删除安装文件。显式恢复操作失败会单独展示,不计为另一次自动致命报告。 -Web 文档保留在原位。宿主回调负责启动失败展示,共享启动页保留加载动画;普通浏览器启动仍显示自身的失败报告。只有主应用框架可以上报 Web 启动失败。Desktop 不提供 profile 重置、插件窗口恢复控件或应急恢复文档。后端致命故障必须通过原生恢复操作处理,不在当前进程中重试。 +Web 文档保留在原位。宿主回调负责启动失败展示,共享启动页保留加载动画;普通浏览器启动仍显示自身的失败报告。只有主应用框架可以上报 Web 启动失败。插件窗口只暴露包操作;后端状态保留在主进程中,原生恢复直接负责禁用全部第三方 bundle。Desktop 不提供 profile 重置、插件窗口恢复控件或应急恢复文档。后端致命故障必须通过原生恢复操作处理,不在当前进程中重试。 这取代了[立即显示窗口决策](2026-09-09-desktop-immediate-window-and-direct-start.zh.md)中的恢复页和重置行为;该决策关于立即可见性和直接启动 Host 的理由仍然有效。[原位 profile 决策](2026-09-09-desktop-in-place-profile.zh.md)仍负责包事务和部分变更。 diff --git a/apps/desktop/README.i18n.yaml b/apps/desktop/README.i18n.yaml index 1b0b2b08d0..f21e458396 100644 --- a/apps/desktop/README.i18n.yaml +++ b/apps/desktop/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write apps/desktop/README.md -README.md: 70493cf0ba7badb72419e68470960a8a8ad33f58 -README.zh.md: c2aba1304571d6095f4a487dc1df795e64391b03 +README.md: 9075e86b0817853b28b28aa1cee45f35904a38d0 +README.zh.md: c25cc17a89ff65a7deedceb78778651b2d4a5c1f diff --git a/apps/desktop/README.md b/apps/desktop/README.md index 70493cf0ba..9075e86b08 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -62,7 +62,7 @@ Fatal main-window creation, main-document loading, preload, renderer, Web initia Native dialog details include at most 1,200 UTF-16 code units and eight diagnostic lines; the complete reported error is written to the Electron console. Host error diagnostics retain only the last 64 Ki characters written to stderr. Earlier output is discarded so a long-running Host does not grow the shell’s diagnostic buffer indefinitely. -Recovery waits for Host shutdown before changing plugin activation. Disabling third-party bundles writes the profile under its transaction lock without loading runtime metadata or deleting files. Invalid profile data or write failures are reported as recovery-operation errors; Desktop does not restart as though disabling succeeded. Desktop has no profile-reset action or emergency HTML document. +Recovery waits for Host shutdown before changing plugin activation. The native recovery action disables third-party bundles by writing the profile under its transaction lock without loading runtime metadata or deleting files. Invalid profile data or write failures are reported as recovery-operation errors; Desktop does not restart as though disabling succeeded. Desktop has no profile-reset action or emergency HTML document. Package transactions hold `$DSH_HOME/profiles/desktop/lock` exclusively through pnpm process exit. Before pnpm runs, the shared module-fallback helper removes only its owned links and preserves pnpm-managed directories; development Host startup restores needed links. Link cleanup preserves target directories. Native builds follow pnpm’s configured build policy; release preparation owns its separate build-time allowlist. diff --git a/apps/desktop/README.zh.md b/apps/desktop/README.zh.md index c2aba13045..c25cc17a89 100644 --- a/apps/desktop/README.zh.md +++ b/apps/desktop/README.zh.md @@ -62,7 +62,7 @@ CLI 与 Desktop 共用已安装依赖清单及 bundle 列表协调逻辑。bundl 原生弹窗详情最多包含 1,200 个 UTF-16 代码单元和八行诊断;完整的已报告错误写入 Electron 控制台。Host 错误诊断仅保留 stderr 输出的最后 64 Ki 个字符。更早的输出会被丢弃,避免长期运行的 Host 使壳的诊断缓冲区无限增长。 -恢复操作等待 Host 关闭后才修改插件启用状态。禁用第三方 bundle 时持有事务锁写入 profile,不加载运行时元数据,也不删除文件。profile 数据无效或写入失败会作为恢复操作错误报告;Desktop 不会假装禁用成功后重启。Desktop 不提供 profile 重置操作或应急 HTML 文档。 +恢复操作等待 Host 关闭后才修改插件启用状态。原生恢复操作禁用第三方 bundle 时持有事务锁写入 profile,不加载运行时元数据,也不删除文件。profile 数据无效或写入失败会作为恢复操作错误报告;Desktop 不会假装禁用成功后重启。Desktop 不提供 profile 重置操作或应急 HTML 文档。 包事务独占 `$DSH_HOME/profiles/desktop/lock`,直到 pnpm 进程退出。pnpm 运行前,共享模块回退辅助函数只删除其拥有的链接,保留 pnpm 管理的目录;开发 Host 在启动时重建所需链接。链接清理保留目标目录。原生构建遵循 pnpm 配置的构建策略;发布准备使用独立的构建期允许列表。 diff --git a/apps/desktop/src/ipc.ts b/apps/desktop/src/ipc.ts index 20c6046b28..a303799d95 100644 --- a/apps/desktop/src/ipc.ts +++ b/apps/desktop/src/ipc.ts @@ -2,7 +2,6 @@ import type { DesktopPluginRecord } from './project-manager.ts' import type { DesktopLocale } from './locale.ts' -import type { DesktopBackendState } from './backend-controller.ts' /** IPC channel names kept private to the desktop application bundle. */ export const DESKTOP_IPC = { @@ -14,9 +13,6 @@ export const DESKTOP_IPC = { pluginsRemove: 'dsh-desktop:plugins-remove', pluginsUpdate: 'dsh-desktop:plugins-update', pluginsToggle: 'dsh-desktop:plugins-toggle', - pluginsDisableAll: 'dsh-desktop:plugins-disable-all', - backendStatus: 'dsh-desktop:backend-status', - backendState: 'dsh-desktop:backend-state', updatesCheck: 'dsh-desktop:updates-check', updatesInstall: 'dsh-desktop:updates-install', updatesState: 'dsh-desktop:updates-state', @@ -40,11 +36,6 @@ export interface DshDesktopApi { remove(name: string): Promise update(name: string, version: string): Promise toggle(name: string, enabled: boolean): Promise - disableAll(): Promise - } - readonly backend: { - status(): Promise - subscribe(listener: (state: DesktopBackendState) => void): () => void } readonly updates: { check(): Promise diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 65fd9a9269..fdcf251f89 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -20,7 +20,7 @@ import { resolveDesktopPaths } from './paths.ts' import { DesktopProjectManager, type DesktopProjectHooks } from './project-manager.ts' import { DesktopHostProcess } from './host-process.ts' import { desktopNodeEnvironment } from './node-environment.ts' -import { DesktopBackendController, type DesktopBackendState } from './backend-controller.ts' +import { DesktopBackendController } from './backend-controller.ts' import { DESKTOP_IPC, type DesktopUpdateState } from './ipc.ts' import { formatDesktopMessage, resolveDesktopLocale } from './locale.ts' import { claimDesktopSingleInstance } from './single-instance.ts' @@ -223,11 +223,6 @@ async function main(): Promise { navigation = next return next.promise } - const publishBackend = (state: DesktopBackendState): void => { - for (const window of BrowserWindow.getAllWindows()) { - window.webContents.send(DESKTOP_IPC.backendState, state) - } - } const backend = new DesktopBackendController((onFailure) => { const hostInspectPort = developmentHostInspectPort(development) const host = new DesktopHostProcess(resources.node, resources.dsh, activeProject, @@ -247,7 +242,6 @@ async function main(): Promise { } }, (state) => { if (state.phase === 'error') reportFatal(new Error(state.message)) - publishBackend(state) }) const publishUpdate = (state: DesktopUpdateState): DesktopUpdateState => { @@ -377,11 +371,6 @@ async function main(): Promise { if (typeof name !== 'string' || typeof enabled !== 'boolean') throw new Error('dsh desktop: invalid plugin activation request') return mutate(event, { type: 'plugin-toggle', name, enabled }) }) - ipcMain.handle(DESKTOP_IPC.pluginsDisableAll, event => mutate(event, { type: 'plugins-disable-all' })) - ipcMain.handle(DESKTOP_IPC.backendStatus, (event) => { - assertDesktopSender(event, ['shell']) - return backend.state - }) ipcMain.handle(DESKTOP_IPC.updatesCheck, async (event) => { assertDesktopSender(event, ['shell']) return updates.check() diff --git a/apps/desktop/src/preload.ts b/apps/desktop/src/preload.ts index 6a1f9f6052..c8e1eb1452 100644 --- a/apps/desktop/src/preload.ts +++ b/apps/desktop/src/preload.ts @@ -3,7 +3,6 @@ import { contextBridge, ipcRenderer } from 'electron' import { DESKTOP_IPC, type DshDesktopApi, type DesktopUpdateState } from './ipc.ts' import { markDocumentPlatform } from './preload-platform.ts' -import type { DesktopBackendState } from './backend-controller.ts' const api: DshDesktopApi = { protocolVersion: 1, @@ -13,17 +12,8 @@ const api: DshDesktopApi = { add: spec => ipcRenderer.invoke(DESKTOP_IPC.pluginsAdd, spec) as Promise, remove: name => ipcRenderer.invoke(DESKTOP_IPC.pluginsRemove, name) as Promise, toggle: (name, enabled) => ipcRenderer.invoke(DESKTOP_IPC.pluginsToggle, name, enabled) as Promise, - disableAll: () => ipcRenderer.invoke(DESKTOP_IPC.pluginsDisableAll) as Promise, update: (name, version) => ipcRenderer.invoke(DESKTOP_IPC.pluginsUpdate, name, version) as Promise, }, - backend: { - status: () => ipcRenderer.invoke(DESKTOP_IPC.backendStatus) as ReturnType, - subscribe(listener) { - const handle = (_event: Electron.IpcRendererEvent, state: DesktopBackendState): void => { listener(state) } - ipcRenderer.on(DESKTOP_IPC.backendState, handle) - return () => { ipcRenderer.off(DESKTOP_IPC.backendState, handle) } - }, - }, updates: { check: () => ipcRenderer.invoke(DESKTOP_IPC.updatesCheck) as Promise, install: () => ipcRenderer.invoke(DESKTOP_IPC.updatesInstall) as Promise, diff --git a/apps/desktop/src/project-manager.ts b/apps/desktop/src/project-manager.ts index 592162ac5a..a1b1de1e37 100644 --- a/apps/desktop/src/project-manager.ts +++ b/apps/desktop/src/project-manager.ts @@ -61,7 +61,6 @@ export type DesktopProjectMutation = | { readonly type: 'plugin-remove'; readonly name: string } | { readonly type: 'plugin-update'; readonly name: string; readonly version: string } | { readonly type: 'plugin-toggle'; readonly name: string; readonly enabled: boolean } - | { readonly type: 'plugins-disable-all' } const PROJECT_NAME = '@deepseek-ai/dsh-desktop-runtime' const DSH_PACKAGE = '@deepseek-ai/dsh' @@ -173,11 +172,7 @@ export class DesktopProjectManager { if (!existsSync(this.paths.profile)) throw new Error('desktop project: active profile is not installed') await hooks.beforeChange() try { - if (mutation.type === 'plugins-disable-all') { - writeProfileBundles(this.paths.profile, readProfileManifest('dsh', this.paths.profile), WEB_PROFILE.bundles) - } else { - await this.applyMutation(this.paths.profile, mutation) - } + await this.applyMutation(this.paths.profile, mutation) } catch (error) { try { await hooks.afterChange() } catch (restartError) { throw new AggregateError([error, restartError], 'Desktop package operation and backend restart failed') @@ -188,7 +183,7 @@ export class DesktopProjectManager { }) } - private async applyMutation(projectDir: string, mutation: Exclude): Promise { + private async applyMutation(projectDir: string, mutation: DesktopProjectMutation): Promise { const location = this.pluginLocation(projectDir) const before = readProfilePlugins(location) const bundles = before.manifest.dsh?.profile?.bundles ?? [] diff --git a/apps/desktop/tests/main-startup.spec.ts b/apps/desktop/tests/main-startup.spec.ts index c0fe424850..57c01a75fc 100644 --- a/apps/desktop/tests/main-startup.spec.ts +++ b/apps/desktop/tests/main-startup.spec.ts @@ -16,13 +16,13 @@ const harness = await vi.hoisted(async () => { const windows: FakeWindow[] = [] let windowFailure: Error | undefined const hosts: FakeHost[] = [] - const handlers = new Map unknown>() + const handlers = new Map unknown>() let pluginsEnabled = false let preparing = deferred() let prepared = deferred() let hostStarted = deferred() let navigated = deferred() - let errorPublished = deferred() + let dialogShown = deferred() let quitCompleted = deferred() class FakeWindow extends EventEmitter { destroyed = false @@ -32,9 +32,7 @@ const harness = await vi.hoisted(async () => { setWindowOpenHandler: vi.fn(), openDevTools: vi.fn(), getURL: () => this.urls.at(-1) ?? '', - send: vi.fn((channel: string, state: { phase?: string }) => { - if (channel === 'dsh-desktop:backend-state' && state.phase === 'error') errorPublished.resolve() - }), + send: vi.fn(), }) readonly show = vi.fn() readonly focus = vi.fn() @@ -96,7 +94,7 @@ const harness = await vi.hoisted(async () => { disableAllPlugins: vi.fn(async () => { pluginsEnabled = false }), get preparing() { return preparing }, get prepared() { return prepared }, get hostStarted() { return hostStarted }, get navigated() { return navigated }, - get errorPublished() { return errorPublished }, get quitCompleted() { return quitCompleted }, + get dialogShown() { return dialogShown }, get quitCompleted() { return quitCompleted }, nextNavigation() { navigated = deferred(); return navigated.promise }, nextHostStart() { hostStarted = deferred(); return hostStarted.promise }, get pluginsEnabled() { return pluginsEnabled }, @@ -107,7 +105,7 @@ const harness = await vi.hoisted(async () => { windowFailure = undefined pluginsEnabled = false preparing = deferred(); prepared = deferred(); hostStarted = deferred() - navigated = deferred(); errorPublished = deferred(); quitCompleted = deferred() + navigated = deferred(); dialogShown = deferred(); quitCompleted = deferred() }, } }) @@ -150,10 +148,10 @@ vi.mock('../src/project-manager.ts', () => ({ vi.mock('../src/host-process.ts', () => ({ DesktopHostProcess: harness.FakeHost })) vi.mock('../src/update-coordinator.ts', () => ({ DesktopUpdateCoordinator: vi.fn() })) -function invoke(channel: string): unknown { +function invoke(channel: string, ...args: unknown[]): unknown { const handler = harness.handlers.get(channel) if (handler === undefined) throw new Error(`missing handler ${channel}`) - return handler({ senderFrame: { url: channel === DESKTOP_IPC.boot ? 'dsh-app://app/' : 'dsh-app://shell/plugin-manager.html' } }) + return handler({ senderFrame: { url: channel === DESKTOP_IPC.boot ? 'dsh-app://app/' : 'dsh-app://shell/plugin-manager.html' } }, ...args) } beforeEach(() => { @@ -161,7 +159,7 @@ beforeEach(() => { vi.clearAllMocks() vi.useFakeTimers() harness.reset() - harness.dialog.showMessageBox.mockImplementation(() => new Promise(() => {})) + harness.dialog.showMessageBox.mockImplementation(() => { harness.dialogShown.resolve(); return new Promise(() => {}) }) vi.spyOn(console, 'error').mockImplementation(() => {}) vi.stubEnv('DSH_DESKTOP_PNPM_ENTRY', 'test-pnpm') vi.stubEnv('DSH_DESKTOP_DSH_DIR', 'test-runtime') @@ -339,11 +337,10 @@ describe('desktop main startup', () => { harness.mutateFailure.mockImplementationOnce(() => { throw new Error('package write failed') }) host.exited.resolve() const nextStarted = harness.nextHostStart() - const failure = expect(invoke(DESKTOP_IPC.pluginsDisableAll)).rejects.toThrow('package write failed') + const failure = expect(invoke(DESKTOP_IPC.pluginsAdd, 'example-plugin')).rejects.toThrow('package write failed') await nextStarted harness.hosts[1]!.ready.resolve() await failure - expect(invoke(DESKTOP_IPC.backendStatus)).toEqual({ phase: 'ready' }) expect(harness.dialog.showMessageBox).not.toHaveBeenCalled() expect(harness.windows[0]!.urls).toEqual(['dsh-app://app/', 'dsh-app://app/']) }) @@ -358,7 +355,7 @@ describe('desktop main startup', () => { await Promise.resolve(invoke(DESKTOP_IPC.boot)) host.exited.resolve() const nextStarted = harness.nextHostStart() - const failure = expect(invoke(DESKTOP_IPC.pluginsDisableAll)).rejects.toThrow('new Host failed') + const failure = expect(invoke(DESKTOP_IPC.pluginsAdd, 'example-plugin')).rejects.toThrow('new Host failed') await nextStarted const replacement = harness.hosts[1]! replacement.exited.resolve() @@ -373,7 +370,7 @@ describe('desktop main startup', () => { await import('../src/main.ts') await harness.preparing.promise harness.prepared.reject(new Error('runtime resources missing')) - await harness.errorPublished.promise + await harness.dialogShown.promise expect(harness.dialog.showMessageBox.mock.calls[0]![0].detail).toContain('runtime resources missing') expect(harness.dialog.showMessageBox.mock.calls[0]![0].buttons).toEqual(['Exit', 'Restart', 'Disable all third-party plugins and restart']) expect(harness.windows[0]!.urls).toEqual(['dsh-app://app/']) @@ -387,7 +384,7 @@ describe('desktop main startup', () => { else window.webContents.emit('render-process-gone', {}, { reason: 'crashed' }) window.webContents.emit('preload-error', {}, 'preload-app.cjs', new Error('secondary failure')) harness.prepared.reject(new Error('backend also failed')) - await harness.errorPublished.promise + await harness.dialogShown.promise expect(harness.dialog.showMessageBox).toHaveBeenCalledOnce() expect(harness.dialog.showMessageBox.mock.calls[0]![0].detail).not.toContain('secondary failure') expect(window.urls).toEqual(['dsh-app://app/']) @@ -462,7 +459,6 @@ describe('desktop main startup', () => { expect(harness.hosts[0]!.start).toHaveBeenCalledTimes(1) expect(harness.windows).toHaveLength(1) expect(window.urls).toEqual(['dsh-app://app/']) - expect(invoke(DESKTOP_IPC.backendStatus)).toEqual({ phase: 'ready' }) }) it('prepares an independent plugin profile for the unpackaged Host', async () => { @@ -488,11 +484,11 @@ describe('desktop main startup', () => { const first = harness.hosts[0]! first.exited.resolve() first.ready.reject(new Error('plugin composition failed')) - await harness.errorPublished.promise + await harness.dialogShown.promise expect(harness.windows[0]!.urls).toEqual(['dsh-app://app/']) expect(harness.dialog.showMessageBox).toHaveBeenCalledOnce() expect(harness.handlers.has('dsh-desktop:backend-retry')).toBe(false) - await expect(invoke(DESKTOP_IPC.pluginsDisableAll)).rejects.toThrow('The application could not start or stopped unexpectedly.') + await expect(invoke(DESKTOP_IPC.pluginsAdd, 'example-plugin')).rejects.toThrow('The application could not start or stopped unexpectedly.') expect(harness.hosts).toHaveLength(1) }) diff --git a/apps/desktop/tests/plugin-manager.spec.ts b/apps/desktop/tests/plugin-manager.spec.ts index 9ddbc3b9a4..e4a86ab397 100644 --- a/apps/desktop/tests/plugin-manager.spec.ts +++ b/apps/desktop/tests/plugin-manager.spec.ts @@ -31,7 +31,6 @@ it.each(['en', 'zh-CN'])('updates through the inline form, including the install const update = vi.fn(async () => {}) Object.defineProperty(dom.window, 'dshDesktop', { value: { locale: async () => locale, - backend: { status: async () => ({ phase: 'ready' }) }, plugins: { list: async () => [{ name: 'example-plugin', version: '1.0.0', enabled: true }], update }, } }) try { diff --git a/apps/desktop/tests/plugin-pnpm.spec.ts b/apps/desktop/tests/plugin-pnpm.spec.ts index 34e08eca4e..9c279e41c4 100644 --- a/apps/desktop/tests/plugin-pnpm.spec.ts +++ b/apps/desktop/tests/plugin-pnpm.spec.ts @@ -108,7 +108,7 @@ it.each(['directory', 'file', 'tarball', 'plain'] as const)('installs a %s sourc await manager.mutate({ type: 'plugin-add', spec }, hooks) expect(manager.listPlugins()).toEqual([{ name: 'local-plugin', version: '1.0.0', enabled: source !== 'plain' }]) expect(existsSync(join(manager.paths.profile, 'node_modules/local-plugin/missing.yml'))).toBe(false) - await manager.mutate({ type: 'plugins-disable-all' }, hooks) + await manager.disableAllPlugins() expect(manager.listPlugins()[0]?.enabled).toBe(false) await manager.mutate({ type: 'plugin-remove', name: 'local-plugin' }, hooks) expect(manager.listPlugins()).toEqual([]) diff --git a/apps/desktop/tests/project-manager.spec.ts b/apps/desktop/tests/project-manager.spec.ts index 9959c2c54b..b2adad9116 100644 --- a/apps/desktop/tests/project-manager.spec.ts +++ b/apps/desktop/tests/project-manager.spec.ts @@ -89,7 +89,7 @@ describe('desktop external plugin profile', () => { const manifest = join(manager.paths.profile, 'node_modules/plugin/package.json') writeFileSync(manifest, '{broken') await expect(manager.applyRelease()).resolves.toBeUndefined() - await manager.mutate({ type: 'plugins-disable-all' }, hooks()) + await manager.disableAllPlugins() await expect(manager.applyRelease()).resolves.toBeUndefined() expect(readFileSync(manifest, 'utf8')).toBe('{broken') }) @@ -100,11 +100,7 @@ describe('desktop external plugin profile', () => { await manager.mutate({ type: 'plugin-add', spec: 'plugin@1.0.0' }, hooks()) const patch = join(manager.paths.profile, 'node_modules/plugin/bundle.yml') unlinkSync(patch) - await manager.mutate({ type: 'plugins-disable-all' }, hooks({ afterChange: async () => { - expect((JSON.parse(readFileSync(join(manager.paths.profile, 'package.json'), 'utf8')) as { - dsh: { profile: { bundles: string[] } } - }).dsh.profile.bundles).not.toContain('plugin') - } })) + await manager.disableAllPlugins() expect((JSON.parse(readFileSync(join(manager.paths.profile, 'package.json'), 'utf8')) as { dsh: { profile: { bundles: string[] } } }).dsh.profile.bundles).not.toContain('plugin') @@ -291,7 +287,7 @@ describe('desktop external plugin profile', () => { const { root, manager } = setup() await manager.applyRelease() await manager.mutate({ type: 'plugin-add', spec: 'plugin@1.0.0' }, hooks()) - await manager.mutate({ type: 'plugins-disable-all' }, hooks()) + await manager.disableAllPlugins() expect(calls(root)).toHaveLength(1) expect(manager.listPlugins()).toEqual([{ name: 'plugin', version: '1.0.0', enabled: false }]) await manager.mutate({ type: 'plugin-update', name: 'plugin', version: '1.1.0' }, hooks()) @@ -339,7 +335,7 @@ describe('desktop external plugin profile', () => { const next = new DesktopProjectManager(manager.paths, { ...manager.runtime, dsh }) await expect(next.applyRelease()).resolves.toBeUndefined() expect(next.dshVersion()).toBe('2.0.0') - await next.mutate({ type: 'plugins-disable-all' }, hooks()) + await next.disableAllPlugins() expect(next.dshVersion()).toBe('2.0.0') expect(next.listPlugins()).toEqual([{ name: 'plugin', version: '1.0.0', enabled: false }]) }) @@ -375,7 +371,7 @@ describe('desktop external plugin profile', () => { expect(existsSync(manager.paths.lock)).toBe(false) writeFileSync(join(manager.paths.profile, 'desktop-packages-pending'), '') await manager.applyRelease() - await manager.mutate({ type: 'plugins-disable-all' }, hooks({ afterChange: async () => { starts++ } })) + await manager.mutate({ type: 'plugin-toggle', name: 'plugin', enabled: false }, hooks({ afterChange: async () => { starts++ } })) expect(starts).toBe(2) await manager.mutate({ type: 'plugin-remove', name: 'plugin' }, hooks()) expect(manager.listPlugins()).toEqual([])