fix(updates): auto-continue client upgrade on update-all
This commit is contained in:
10 files changed
+200
-61
No files matched your search
+20
-11
@@ -33,6 +33,7 @@ import type { PluginUpdateBlock } from "@/composables/useComponentUpdates";
|
||||
import { COMPONENT_UPDATES_CHANGED_EVENT, notifyComponentPluginsUpdated, notifyComponentUpdatesChanged } from "@/lib/updates/componentUpdateEvents";
|
||||
import { driverStoreUpdateBadgeCount, showMcpUpdateBadge, showToolbarUpdateAction } from "@/lib/updates/updateBadges";
|
||||
import {
|
||||
continuePreparedAppUpdate,
|
||||
hasPendingComponentUpdatesAfterAppRestart,
|
||||
markPendingComponentUpdatesAfterAppUpdate,
|
||||
resolveUpdateAllAction,
|
||||
@@ -40,6 +41,7 @@ import {
|
||||
runPendingComponentUpdatePlan,
|
||||
shouldCloseUpdateCenterAfterComponentUpdate,
|
||||
takePendingComponentUpdatesAfterAppRestart,
|
||||
updateBlockerLabels,
|
||||
type PendingComponentUpdatePlan,
|
||||
} from "@/lib/updates/componentUpdateOrchestration";
|
||||
import { isUpdatePreviewMockEnabled } from "@/lib/updates/updatePreviewMock";
|
||||
@@ -1289,7 +1291,9 @@ function reportComponentUpdateResult(result: Awaited<ReturnType<typeof component
|
||||
if (result.failed.length === 0) syncToolbarComponentUpdateState();
|
||||
if (result.plugins > 0) notifyComponentPluginsUpdated();
|
||||
if (updatedComponents.length) toast(t("updates.componentsAutoUpdated", { components: updatedComponents.join(t("updates.componentListSeparator")) }));
|
||||
if (result.skippedDrivers > 0) toast(t("updates.componentsAutoUpdateSkipped"), 6000);
|
||||
if (result.blockedDrivers.length) {
|
||||
toast(t("driverStore.driverUpdateBlocked", { labels: updateBlockerLabels(result.blockedDrivers).join(", ") }), 8000);
|
||||
} else if (result.skippedDrivers > 0) toast(t("updates.componentsAutoUpdateSkipped"), 6000);
|
||||
const otherFailureCount = result.failed.length - result.blockedPlugins.length;
|
||||
const failureMessages = [result.blockedPlugins.map(pluginUpdateBlockMessage).join("\n"), otherFailureCount > 0 ? t("updates.componentsAutoUpdateFailed", { count: otherFailureCount }) : ""].filter(Boolean);
|
||||
if (failureMessages.length) toast(failureMessages.join("\n"), 8000);
|
||||
@@ -1342,6 +1346,19 @@ function availableComponentUpdateCategories(): ComponentUpdateCategory[] {
|
||||
return categories;
|
||||
}
|
||||
|
||||
function continueAppUpdateWithComponents(categories: ComponentUpdateCategory[]) {
|
||||
return continuePreparedAppUpdate({
|
||||
hasComponentUpdates: categories.length > 0,
|
||||
restartOnly: updateReady.value,
|
||||
rememberComponentUpdates: () => rememberComponentUpdatesForRestartedApp({ kind: "manual", categories }),
|
||||
installComponents: async () => {
|
||||
reportComponentUpdateResult(await componentUpdates.installCategories(categories));
|
||||
},
|
||||
installDownloadedUpdate,
|
||||
restartApp,
|
||||
});
|
||||
}
|
||||
|
||||
async function updateAllAvailable() {
|
||||
if (updatingAllUpdates.value) return;
|
||||
updatingAllUpdates.value = true;
|
||||
@@ -1359,17 +1376,9 @@ async function updateAllAvailable() {
|
||||
reportComponentUpdateResult(await componentUpdates.installCategories(categories));
|
||||
return;
|
||||
}
|
||||
if (action === "defer-components") {
|
||||
const remembered = await rememberComponentUpdatesForRestartedApp({ kind: "manual", categories });
|
||||
if (remembered) {
|
||||
toast(t("settings.updateRestartHint"), 6000);
|
||||
return;
|
||||
}
|
||||
reportComponentUpdateResult(await componentUpdates.installCategories(categories));
|
||||
return;
|
||||
}
|
||||
if (action === "install-app") return continueAppUpdateWithComponents(categories);
|
||||
if (action === "download-app") await downloadUpdateInBackground();
|
||||
if (updateDownloaded.value || updateReady.value) await rememberComponentUpdatesForRestartedApp({ kind: "manual", categories });
|
||||
if (updateDownloaded.value || updateReady.value) await continueAppUpdateWithComponents(categories);
|
||||
} finally {
|
||||
updatingAllUpdates.value = false;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import { runAgentOfflineExportAction } from "@/lib/driverStore/agentOfflineExpor
|
||||
import { DRIVER_CATEGORIES, getCategoryForAgentDriver, assertAgentDriverCategoriesComplete } from "@/lib/connection/driver-category-definitions";
|
||||
import { hasAnyUpdatableDriverMatching, countInstalledDrivers, countAvailableDrivers, partitionDriversByInstallStatus, upgradeAllDriverTypes, upgradeAllMatchesFullUpdateSet, type DriverInstallStatusFilter } from "@/lib/connection/driverListFilter";
|
||||
import { notifyComponentUpdatesChanged } from "@/lib/updates/componentUpdateEvents";
|
||||
import { updateBlockerLabels } from "@/lib/updates/componentUpdateOrchestration";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { toast } = useToast();
|
||||
@@ -53,6 +54,10 @@ function backendError(e: unknown): string {
|
||||
return translateBackendError(t, message);
|
||||
}
|
||||
|
||||
function driverUpdateBlockedMessage(blockers: Awaited<ReturnType<typeof api.checkAgentUpdateBlockers>>): string {
|
||||
return updateBlockerLabels(blockers).join(", ");
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
updateNotificationsEnabled?: boolean;
|
||||
@@ -504,7 +509,7 @@ async function runDriverInstall(dbType: string) {
|
||||
}
|
||||
const blockers = await api.checkAgentUpdateBlockers([dbType]);
|
||||
if (blockers.length > 0) {
|
||||
toast(t("driverStore.driverUpdateBlocked", { labels: blockers.map((blocker) => blocker.label).join(", ") }));
|
||||
toast(t("driverStore.driverUpdateBlocked", { labels: driverUpdateBlockedMessage(blockers) }));
|
||||
return;
|
||||
}
|
||||
const operationId = uuid();
|
||||
@@ -548,7 +553,7 @@ async function upgradeAll() {
|
||||
if (updatableDbTypes.length === 0) return;
|
||||
const blockers = await api.checkAgentUpdateBlockers(updatableDbTypes);
|
||||
if (blockers.length > 0) {
|
||||
toast(t("driverStore.driverUpdateBlocked", { labels: blockers.map((blocker) => blocker.label).join(", ") }));
|
||||
toast(t("driverStore.driverUpdateBlocked", { labels: driverUpdateBlockedMessage(blockers) }));
|
||||
return;
|
||||
}
|
||||
// Do not expose Cancel until the backend operation has registered its token.
|
||||
@@ -623,7 +628,7 @@ async function uninstallDriver(dbType: string) {
|
||||
}
|
||||
const blockers = await api.checkAgentUpdateBlockers([dbType]);
|
||||
if (blockers.length > 0) {
|
||||
toast(t("driverStore.driverUpdateBlocked", { labels: blockers.map((blocker) => blocker.label).join(", ") }));
|
||||
toast(t("driverStore.driverUpdateBlocked", { labels: driverUpdateBlockedMessage(blockers) }));
|
||||
return;
|
||||
}
|
||||
await api.uninstallAgent(dbType);
|
||||
@@ -791,7 +796,7 @@ async function importDriverFile(driver: AgentDriverInfo) {
|
||||
}
|
||||
const blockers = await api.checkAgentUpdateBlockers([dbType]);
|
||||
if (blockers.length > 0) {
|
||||
toast(t("driverStore.driverUpdateBlocked", { labels: blockers.map((blocker) => blocker.label).join(", ") }));
|
||||
toast(t("driverStore.driverUpdateBlocked", { labels: driverUpdateBlockedMessage(blockers) }));
|
||||
return;
|
||||
}
|
||||
const label = driverLabel(dbType);
|
||||
|
||||
@@ -131,7 +131,7 @@ describe("useComponentUpdates", () => {
|
||||
expect(mocks.installJdbcPlugin).toHaveBeenCalledOnce();
|
||||
expect(mocks.installMcpServer).toHaveBeenCalledOnce();
|
||||
expect(mocks.installMarketplacePlugin).toHaveBeenCalledWith({ repositoryId: "official", pluginId: "example", version: "1.1.0" });
|
||||
expect(result).toEqual({ drivers: 1, jdbc: true, mcp: true, plugins: 1, skippedDrivers: 0, failed: [], blockedPlugins: [] });
|
||||
expect(result).toEqual({ drivers: 1, jdbc: true, mcp: true, plugins: 1, skippedDrivers: 0, blockedDrivers: [], failed: [], blockedPlugins: [] });
|
||||
});
|
||||
|
||||
it("shares one update operation between automatic and manual callers", async () => {
|
||||
@@ -172,7 +172,7 @@ describe("useComponentUpdates", () => {
|
||||
expect(mocks.installJdbcPlugin).not.toHaveBeenCalled();
|
||||
expect(mocks.installMcpServer).not.toHaveBeenCalled();
|
||||
expect(mocks.installMarketplacePlugin).not.toHaveBeenCalled();
|
||||
expect(result).toEqual({ drivers: 0, jdbc: false, mcp: false, plugins: 0, skippedDrivers: 0, failed: [], blockedPlugins: [] });
|
||||
expect(result).toEqual({ drivers: 0, jdbc: false, mcp: false, plugins: 0, skippedDrivers: 0, blockedDrivers: [], failed: [], blockedPlugins: [] });
|
||||
});
|
||||
|
||||
it("installs every manually selected category when no DBX update exists and automatic updates are disabled", async () => {
|
||||
@@ -190,7 +190,7 @@ describe("useComponentUpdates", () => {
|
||||
expect(mocks.installJdbcPlugin).toHaveBeenCalledOnce();
|
||||
expect(mocks.installMcpServer).toHaveBeenCalledOnce();
|
||||
expect(mocks.installMarketplacePlugin).toHaveBeenCalledOnce();
|
||||
expect(result).toEqual({ drivers: 1, jdbc: true, mcp: true, plugins: 1, skippedDrivers: 0, failed: [], blockedPlugins: [] });
|
||||
expect(result).toEqual({ drivers: 1, jdbc: true, mcp: true, plugins: 1, skippedDrivers: 0, blockedDrivers: [], failed: [], blockedPlugins: [] });
|
||||
});
|
||||
|
||||
it("resumes a persisted manual update-all plan after restart while automatic updates are disabled", async () => {
|
||||
@@ -213,7 +213,7 @@ describe("useComponentUpdates", () => {
|
||||
expect(mocks.installJdbcPlugin).toHaveBeenCalledOnce();
|
||||
expect(mocks.installMcpServer).toHaveBeenCalledOnce();
|
||||
expect(mocks.installMarketplacePlugin).toHaveBeenCalledOnce();
|
||||
expect(result).toEqual({ drivers: 1, jdbc: true, mcp: true, plugins: 1, skippedDrivers: 0, failed: [], blockedPlugins: [] });
|
||||
expect(result).toEqual({ drivers: 1, jdbc: true, mcp: true, plugins: 1, skippedDrivers: 0, blockedDrivers: [], failed: [], blockedPlugins: [] });
|
||||
});
|
||||
|
||||
it("keeps the toolbar update action hidden while a persisted restart plan is still installing", async () => {
|
||||
@@ -247,7 +247,7 @@ describe("useComponentUpdates", () => {
|
||||
});
|
||||
|
||||
it("skips blocked agent updates while allowing other component updates", async () => {
|
||||
mocks.checkAgentUpdateBlockers.mockResolvedValue([{ db_type: "mysql", label: "MySQL" }]);
|
||||
mocks.checkAgentUpdateBlockers.mockResolvedValue([{ db_type: "mysql", label: "MySQL", connections: ["生产 MySQL", "报表 MySQL"] }]);
|
||||
const updates = useComponentUpdates({ isDesktop: true });
|
||||
|
||||
const result = await updates.autoUpdateEnabledComponents();
|
||||
@@ -257,6 +257,7 @@ describe("useComponentUpdates", () => {
|
||||
expect(mocks.installMcpServer).toHaveBeenCalledOnce();
|
||||
expect(mocks.installMarketplacePlugin).toHaveBeenCalledOnce();
|
||||
expect(result.skippedDrivers).toBe(1);
|
||||
expect(result.blockedDrivers).toEqual([{ db_type: "mysql", label: "MySQL", connections: ["生产 MySQL", "报表 MySQL"] }]);
|
||||
});
|
||||
|
||||
it("does not install from stale state when update detection fails", async () => {
|
||||
@@ -302,7 +303,7 @@ describe("useComponentUpdates", () => {
|
||||
const result = await updates.installCategory("jdbc");
|
||||
|
||||
expect(mocks.installJdbcPlugin).toHaveBeenCalledOnce();
|
||||
expect(result).toEqual({ drivers: 0, jdbc: true, mcp: false, plugins: 0, skippedDrivers: 0, failed: [], blockedPlugins: [] });
|
||||
expect(result).toEqual({ drivers: 0, jdbc: true, mcp: false, plugins: 0, skippedDrivers: 0, blockedDrivers: [], failed: [], blockedPlugins: [] });
|
||||
});
|
||||
|
||||
it("reports the blocking connections when a K8S plugin update cannot start", async () => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { mcpUpdateAvailability } from "@/lib/mcp/mcpUpdateStatus";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { isUpdatePreviewMockEnabled, previewDriverUpdates, previewJdbcUpdate, previewMcpUpdate, previewPluginUpdates } from "@/lib/updates/updatePreviewMock";
|
||||
import type { ComponentUpdateCategory } from "@/lib/updates/componentUpdateOrchestration";
|
||||
import type { AgentDriverInfo, McpServerStatus } from "@/lib/backend/tauri";
|
||||
import type { AgentDriverInfo, AgentUpdateBlocker, McpServerStatus } from "@/lib/backend/tauri";
|
||||
import type { JdbcPluginStatus } from "@/types/database";
|
||||
|
||||
export type { ComponentUpdateCategory } from "@/lib/updates/componentUpdateOrchestration";
|
||||
@@ -19,6 +19,7 @@ export interface ComponentUpdateResult {
|
||||
mcp: boolean;
|
||||
plugins: number;
|
||||
skippedDrivers: number;
|
||||
blockedDrivers: AgentUpdateBlocker[];
|
||||
failed: string[];
|
||||
blockedPlugins: PluginUpdateBlock[];
|
||||
}
|
||||
@@ -28,7 +29,7 @@ interface ComponentUpdateRefreshOptions {
|
||||
}
|
||||
|
||||
function emptyResult(): ComponentUpdateResult {
|
||||
return { drivers: 0, jdbc: false, mcp: false, plugins: 0, skippedDrivers: 0, failed: [], blockedPlugins: [] };
|
||||
return { drivers: 0, jdbc: false, mcp: false, plugins: 0, skippedDrivers: 0, blockedDrivers: [], failed: [], blockedPlugins: [] };
|
||||
}
|
||||
|
||||
function pluginUpdateBlock(pluginName: string, message: string): PluginUpdateBlock | null {
|
||||
@@ -139,6 +140,7 @@ export function useComponentUpdates(options: { isDesktop: boolean }) {
|
||||
const blockers = await api.checkAgentUpdateBlockers(updatable);
|
||||
if (blockers.length) {
|
||||
result.skippedDrivers = blockers.length;
|
||||
result.blockedDrivers = blockers;
|
||||
return;
|
||||
}
|
||||
const upgraded = await api.upgradeAllAgents();
|
||||
|
||||
@@ -204,6 +204,7 @@ export interface UpgradeAllAgentDriversResult {
|
||||
export interface AgentUpdateBlocker {
|
||||
db_type: string;
|
||||
label: string;
|
||||
connections: string[];
|
||||
}
|
||||
|
||||
export type AgentOfflineArtifactKind = "jar" | "native";
|
||||
|
||||
+70
-3
@@ -3,6 +3,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
clearPendingComponentUpdatesAfterAppUpdate,
|
||||
continuePreparedAppUpdate,
|
||||
hasPendingComponentUpdatesAfterAppRestart,
|
||||
markPendingComponentUpdatesAfterAppUpdate,
|
||||
resolveUpdateAllAction,
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
runPendingComponentUpdatePlan,
|
||||
shouldCloseUpdateCenterAfterComponentUpdate,
|
||||
takePendingComponentUpdatesAfterAppRestart,
|
||||
updateBlockerLabels,
|
||||
} from "@/lib/updates/componentUpdateOrchestration";
|
||||
|
||||
describe("component update orchestration", () => {
|
||||
@@ -17,6 +19,16 @@ describe("component update orchestration", () => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
it("lists blocked connection names and falls back to the driver label", () => {
|
||||
expect(
|
||||
updateBlockerLabels([
|
||||
{ label: "MySQL", connections: ["生产 MySQL", "报表 MySQL"] },
|
||||
{ label: "Oracle", connections: [] },
|
||||
{ label: "DuckDB", connections: ["生产 MySQL"] },
|
||||
]),
|
||||
).toEqual(["生产 MySQL", "报表 MySQL", "Oracle"]);
|
||||
});
|
||||
|
||||
it("keeps pending component updates across a failed or ordinary startup", () => {
|
||||
expect(markPendingComponentUpdatesAfterAppUpdate("0.6.16", "0.6.17")).toBe(true);
|
||||
|
||||
@@ -162,19 +174,74 @@ describe("component update orchestration", () => {
|
||||
expect(takePendingComponentUpdatesAfterAppRestart("0.6.17")?.plan).toEqual({ kind: "auto" });
|
||||
});
|
||||
|
||||
it("downloads DBX before deferring component updates", () => {
|
||||
it("downloads DBX before installing it with component updates", () => {
|
||||
expect(resolveUpdateAllAction({ hasAppUpdate: true, appUpdateCanInstall: true, appUpdatePrepared: false, hasComponentUpdates: true })).toBe("download-app");
|
||||
expect(resolveUpdateAllAction({ hasAppUpdate: true, appUpdateCanInstall: true, appUpdatePrepared: true, hasComponentUpdates: true })).toBe("defer-components");
|
||||
expect(resolveUpdateAllAction({ hasAppUpdate: true, appUpdateCanInstall: true, appUpdatePrepared: true, hasComponentUpdates: true })).toBe("install-app");
|
||||
});
|
||||
|
||||
it("persists the selected component categories when the DBX package is already prepared", () => {
|
||||
const action = resolveUpdateAllAction({ hasAppUpdate: true, appUpdateCanInstall: true, appUpdatePrepared: true, hasComponentUpdates: true });
|
||||
expect(action).toBe("defer-components");
|
||||
expect(action).toBe("install-app");
|
||||
|
||||
expect(markPendingComponentUpdatesAfterAppUpdate("0.6.16", "0.6.17", { kind: "manual", categories: ["drivers", "mcp"] })).toBe(true);
|
||||
expect(takePendingComponentUpdatesAfterAppRestart("0.6.17")?.plan).toEqual({ kind: "manual", categories: ["drivers", "mcp"] });
|
||||
});
|
||||
|
||||
it("continues from update all into the prepared app install", async () => {
|
||||
const rememberComponentUpdates = vi.fn(async () => true);
|
||||
const installComponents = vi.fn(async () => {});
|
||||
const installDownloadedUpdate = vi.fn(async () => {});
|
||||
const restartApp = vi.fn(async () => {});
|
||||
|
||||
await continuePreparedAppUpdate({
|
||||
hasComponentUpdates: true,
|
||||
restartOnly: false,
|
||||
rememberComponentUpdates,
|
||||
installComponents,
|
||||
installDownloadedUpdate,
|
||||
restartApp,
|
||||
});
|
||||
|
||||
expect(rememberComponentUpdates).toHaveBeenCalledOnce();
|
||||
expect(installDownloadedUpdate).toHaveBeenCalledOnce();
|
||||
expect(restartApp).not.toHaveBeenCalled();
|
||||
expect(installComponents).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("continues from update all into restart when the app update is already staged", async () => {
|
||||
const restartApp = vi.fn(async () => {});
|
||||
|
||||
await continuePreparedAppUpdate({
|
||||
hasComponentUpdates: true,
|
||||
restartOnly: true,
|
||||
rememberComponentUpdates: async () => true,
|
||||
installComponents: async () => {},
|
||||
installDownloadedUpdate: async () => {},
|
||||
restartApp,
|
||||
});
|
||||
|
||||
expect(restartApp).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("falls back to component updates without restarting when the restart plan cannot be persisted", async () => {
|
||||
const installComponents = vi.fn(async () => {});
|
||||
const installDownloadedUpdate = vi.fn(async () => {});
|
||||
const restartApp = vi.fn(async () => {});
|
||||
|
||||
await continuePreparedAppUpdate({
|
||||
hasComponentUpdates: true,
|
||||
restartOnly: false,
|
||||
rememberComponentUpdates: async () => false,
|
||||
installComponents,
|
||||
installDownloadedUpdate,
|
||||
restartApp,
|
||||
});
|
||||
|
||||
expect(installComponents).toHaveBeenCalledOnce();
|
||||
expect(installDownloadedUpdate).not.toHaveBeenCalled();
|
||||
expect(restartApp).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates components immediately when no DBX update exists or the package cannot be installed", () => {
|
||||
expect(resolveUpdateAllAction({ hasAppUpdate: false, appUpdateCanInstall: true, appUpdatePrepared: false, hasComponentUpdates: true })).toBe("update-components");
|
||||
expect(resolveUpdateAllAction({ hasAppUpdate: true, appUpdateCanInstall: false, appUpdatePrepared: false, hasComponentUpdates: true })).toBe("update-components");
|
||||
|
||||
@@ -14,7 +14,11 @@ export interface PendingComponentUpdates {
|
||||
plan: PendingComponentUpdatePlan;
|
||||
}
|
||||
|
||||
export type UpdateAllAction = "none" | "download-app" | "defer-components" | "update-components";
|
||||
export type UpdateAllAction = "none" | "download-app" | "install-app" | "update-components";
|
||||
|
||||
export function updateBlockerLabels(blockers: Array<{ label: string; connections?: string[] }>): string[] {
|
||||
return [...new Set(blockers.flatMap((blocker) => (blocker.connections?.length ? blocker.connections : [blocker.label])))];
|
||||
}
|
||||
|
||||
function normalizeVersion(version: string): string {
|
||||
return version.trim().replace(/^[vV]/, "");
|
||||
@@ -112,7 +116,16 @@ export function takePendingComponentUpdatesAfterAppRestart(currentVersion: strin
|
||||
|
||||
export function resolveUpdateAllAction(options: { hasAppUpdate: boolean; appUpdateCanInstall: boolean; appUpdatePrepared: boolean; hasComponentUpdates: boolean }): UpdateAllAction {
|
||||
if (!options.hasAppUpdate || !options.appUpdateCanInstall) return options.hasComponentUpdates ? "update-components" : "none";
|
||||
return options.appUpdatePrepared ? "defer-components" : "download-app";
|
||||
return options.appUpdatePrepared ? "install-app" : "download-app";
|
||||
}
|
||||
|
||||
export async function continuePreparedAppUpdate(options: { hasComponentUpdates: boolean; restartOnly: boolean; rememberComponentUpdates: () => Promise<boolean>; installComponents: () => Promise<void>; installDownloadedUpdate: () => Promise<void>; restartApp: () => Promise<void> }): Promise<void> {
|
||||
if (options.hasComponentUpdates && !(await options.rememberComponentUpdates())) {
|
||||
await options.installComponents();
|
||||
return;
|
||||
}
|
||||
if (options.restartOnly) await options.restartApp();
|
||||
else await options.installDownloadedUpdate();
|
||||
}
|
||||
|
||||
export function shouldCloseUpdateCenterAfterComponentUpdate(options: { failedCount: number; skippedDriverCount: number; hasAppUpdate: boolean; remainingComponentUpdateCount: number }): boolean {
|
||||
|
||||
Reference in new issue
Block a user