fix(updater): separate Windows 7 offline update path

This commit is contained in:
zipg authored and GitHub committed 2026-07-27 16:44:47 +08:00
1 parent 0b7a886cfd
commit 0aced0012c
29 files changed
+1851 -24

No files matched your search

+20
View File
@@ -14,6 +14,7 @@ const mountedApps: App[] = [];
interface DialogState {
open: boolean;
portableMode: boolean;
manualUpdateOnly: boolean;
isDownloadingUpdate: boolean;
downloadProgress: number;
updateDownloaded: boolean;
@@ -30,6 +31,7 @@ async function mountDialog(activeTaskCount: number, initialState: Partial<Dialog
const state = reactive<DialogState>({
open: true,
portableMode: false,
manualUpdateOnly: false,
isDownloadingUpdate: false,
downloadProgress: 0,
updateDownloaded: false,
@@ -67,6 +69,7 @@ async function mountDialog(activeTaskCount: number, initialState: Partial<Dialog
latest_version: "0.5.61",
update_available: true,
portable_mode: state.portableMode,
manual_update_only: state.manualUpdateOnly,
release_name: "DBX v0.5.61",
release_url: "https://github.com/t8y2/dbx/releases/tag/v0.5.61",
release_notes: "",
@@ -136,6 +139,23 @@ describe("UpdateDialog active task guard", () => {
expect(downloadButton()?.disabled).toBe(false);
});
it("routes Windows 7 builds to the dedicated installer", async () => {
await mountDialog(0, { manualUpdateOnly: true });
expect(document.body.textContent).toContain("WebView2 109 offline installer");
expect(downloadButton()).toBeUndefined();
expect(buttonWithText("Open Release")).toBeDefined();
});
it("prevents Windows 7 portable builds from installing the regular x64 portable update", async () => {
await mountDialog(0, { portableMode: true, manualUpdateOnly: true });
expect(document.body.textContent).toContain("WebView2 109 offline installer");
expect(document.body.textContent).not.toContain("signed portable ZIP");
expect(downloadButton()).toBeUndefined();
expect(buttonWithText("Open Release")).toBeDefined();
});
it("retains the downloaded update and enables installation only after tasks finish", async () => {
await mountDialog(1, { updateDownloaded: true, downloadProgress: 100 });
@@ -106,9 +106,12 @@ watch(
<code class="bg-muted px-1 py-0.5 rounded text-[11px]">docker compose pull && docker compose up -d</code>
{{ t("updates.toUpdate") }}
</p>
<p v-if="isDesktop && updateInfo?.update_available && updateInfo.portable_mode" class="text-xs text-muted-foreground">
<p v-if="isDesktop && updateInfo?.update_available && updateInfo.portable_mode && !updateInfo.manual_update_only" class="text-xs text-muted-foreground">
{{ t("updates.portableAutomaticUpdate") }}
</p>
<p v-if="isDesktop && updateInfo?.update_available && updateInfo.manual_update_only" class="text-xs text-muted-foreground">
{{ t("updates.windows7ManualUpdate") }}
</p>
<div v-if="canDownloadAndInstallUpdate(updateInfo, isDesktop) && activeTaskCount > 0" role="alert" class="flex items-start gap-2 rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-xs text-amber-700 dark:text-amber-300">
<AlertTriangle class="mt-0.5 h-4 w-4 shrink-0" />
<span>{{ t("updates.activeTasksBlockUpdate", { count: activeTaskCount }) }}</span>
@@ -19,7 +19,7 @@ export function shouldOpenUpdateDialog(options: { silent?: boolean }) {
}
export function canDownloadAndInstallUpdate(info: api.UpdateInfo | null, isDesktop: boolean) {
return isDesktop && info?.update_available === true;
return isDesktop && info?.update_available === true && info.manual_update_only !== true;
}
export function normalizeUpdateDownloadSource(value: unknown): SettingsUpdateDownloadSource {
+1
View File
@@ -71,6 +71,7 @@ export default {
downloadAndInstall: "Download & Install",
activeTasksBlockUpdate: "{count} task(s) are still running. Wait for them to finish before updating DBX.",
portableAutomaticUpdate: "DBX will download the signed portable ZIP, replace only DBX.exe after exit, and restart automatically. portable.dbx and data will be kept.",
windows7ManualUpdate: "Windows 7 requires the dedicated WebView2 109 offline installer. Open the release page and download the Windows 7 package to update.",
downloading: "Downloading {progress}%",
downloadFailed: "Update download failed: {error}",
installing: "Installing update...",
+1
View File
@@ -73,6 +73,7 @@ export default withEnglishFallback({
downloadAndInstall: "Descargar e instalar",
activeTasksBlockUpdate: "Hay {count} tarea(s) en ejecución. Espera a que terminen antes de actualizar DBX.",
portableAutomaticUpdate: "DBX descargará el ZIP portable firmado, reemplazará solo DBX.exe después de salir y se reiniciará automáticamente. portable.dbx y data se conservarán.",
windows7ManualUpdate: "Windows 7 requiere el instalador sin conexión dedicado de WebView2 109. Abre la página de lanzamiento y descarga el paquete para Windows 7.",
downloading: "Descargando {progress}%",
downloadFailed: "Error al descargar la actualización: {error}",
installing: "Instalando actualización...",
+1
View File
@@ -72,6 +72,7 @@ export default withEnglishFallback({
downloadAndInstall: "Scarica e Installa",
activeTasksBlockUpdate: "Ci sono {count} attività in esecuzione. Attendi che terminino prima di aggiornare DBX.",
portableAutomaticUpdate: "DBX scaricherà lo ZIP portatile firmato, sostituirà solo DBX.exe dopo l'uscita e si riavvierà automaticamente. portable.dbx e i dati verranno mantenuti.",
windows7ManualUpdate: "Windows 7 richiede il programma di installazione offline dedicato WebView2 109. Apri la pagina della release e scarica il pacchetto per Windows 7.",
downloading: "Download in corso {progress}%",
downloadFailed: "Download dell'aggiornamento non riuscito: {error}",
installing: "Installazione dell'aggiornamento...",
+1
View File
@@ -73,6 +73,7 @@ export default withEnglishFallback({
downloadAndInstall: "ダウンロード & インストール",
activeTasksBlockUpdate: "{count} 件のタスクが実行中です。完了してから DBX を更新してください。",
portableAutomaticUpdate: "DBX は署名済みのポータブル ZIP をダウンロードし、終了後に DBX.exe のみを置き換えて自動的に再起動します。portable.dbx とデータは保持されます。",
windows7ManualUpdate: "Windows 7 では専用の WebView2 109 オフラインインストーラーが必要です。リリースページから Windows 7 用パッケージをダウンロードして更新してください。",
downloading: "ダウンロード中 {progress}%",
downloadFailed: "アップデートのダウンロードに失敗しました: {error}",
installing: "アップデートをインストール中...",
+1
View File
@@ -73,6 +73,7 @@ export default withEnglishFallback({
downloadAndInstall: "Baixar e Instalar",
activeTasksBlockUpdate: "Há {count} tarefa(s) em execução. Aguarde a conclusão antes de atualizar o DBX.",
portableAutomaticUpdate: "O DBX baixará o ZIP portátil assinado, substituirá apenas o DBX.exe após sair e reiniciará automaticamente. O portable.dbx e os dados serão mantidos.",
windows7ManualUpdate: "O Windows 7 requer o instalador offline dedicado do WebView2 109. Abra a página da versão e baixe o pacote para Windows 7.",
downloading: "Baixando {progress}%",
downloadFailed: "Falha ao baixar a atualização: {error}",
installing: "Instalando atualização...",
+1
View File
@@ -73,6 +73,7 @@ export default withEnglishFallback({
downloadAndInstall: "下载并安装",
activeTasksBlockUpdate: "有 {count} 个任务正在执行,请等待任务完成后再更新 DBX。",
portableAutomaticUpdate: "DBX 将下载已签名的便携版 ZIP,退出后仅替换 DBX.exe 并自动重启。portable.dbx 和 data 会保留。",
windows7ManualUpdate: "Windows 7 需要使用专用的 WebView2 109 离线安装包更新。请打开下载页并下载 Windows 7 安装包。",
downloading: "下载中 {progress}%",
downloadFailed: "更新下载失败:{error}",
installing: "正在安装更新...",
+1
View File
@@ -73,6 +73,7 @@ export default withEnglishFallback({
downloadAndInstall: "下載並安裝",
activeTasksBlockUpdate: "有 {count} 個任務正在執行,請等待任務完成後再更新 DBX。",
portableAutomaticUpdate: "DBX 將下載已簽署的可攜版 ZIP,結束後僅替換 DBX.exe 並自動重新啟動。portable.dbx 和 data 會保留。",
windows7ManualUpdate: "Windows 7 需要使用專用的 WebView2 109 離線安裝套件更新。請開啟下載頁並下載 Windows 7 安裝套件。",
downloading: "下載中 {progress}%",
downloadFailed: "更新下載失敗:{error}",
installing: "正在安裝更新...",
@@ -0,0 +1,65 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
import { shouldAbortWindowsWebView2RuntimeFallback } from "@/lib/app/windowsWebView2RuntimePolicy";
const template = readFileSync(resolve(process.cwd(), "src-tauri/windows/nsis/installer.nsi"), "utf8");
describe("Windows offline installer template", () => {
it.each([
{
name: "continues after installer failure when a compatible Runtime remains",
input: { installerExitCode: 1, runtimeDetected: true, runtimeMeetsMinimum: true },
expected: false,
},
{
name: "aborts after installer failure when the Runtime is missing",
input: { installerExitCode: 1, runtimeDetected: false, runtimeMeetsMinimum: false },
expected: true,
},
{
name: "aborts after installer failure when the Runtime is below the minimum",
input: { installerExitCode: 1, runtimeDetected: true, runtimeMeetsMinimum: false },
expected: true,
},
{
name: "continues after a successful installer even before the registry refresh",
input: { installerExitCode: 0, runtimeDetected: false, runtimeMeetsMinimum: false },
expected: false,
},
])("$name", ({ input, expected }) => {
expect(shouldAbortWindowsWebView2RuntimeFallback(input)).toBe(expected);
});
it("binds the tested fallback inputs to the NSIS Runtime recheck contract", () => {
expect(template).toContain(`!macro ShouldAbortWebView2OfflineInstall INSTALL_RESULT RUNTIME_VERSION MINIMUM_COMPARISON RESULT
StrCpy \${RESULT} 0
\${If} \${INSTALL_RESULT} <> 0
\${If} \${RUNTIME_VERSION} == ""
StrCpy \${RESULT} 1
\${ElseIf} \${MINIMUM_COMPARISON} = 1
StrCpy \${RESULT} 1
\${EndIf}
\${EndIf}
!macroend`);
expect(template).toContain(`!insertmacro ReadWebView2RuntimeVersion $4
\${If} $4 != ""
!if "\${MINIMUMWEBVIEW2VERSION}" != ""
\${VersionCompare} "\${MINIMUMWEBVIEW2VERSION}" "$4" $R0
!endif
\${EndIf}
\${EndIf}
!insertmacro ShouldAbortWebView2OfflineInstall $1 $4 $R0 $R1
\${If} $R1 = 1
Abort "$(webview2AbortError)"
\${EndIf}`);
});
it("uses the same registry detection before and after Runtime installation", () => {
const runtimeChecks = template.match(/!insertmacro ReadWebView2RuntimeVersion \$4/g) ?? [];
expect(runtimeChecks).toHaveLength(2);
expect(template).toContain('ReadRegStr ${RESULT} HKLM "SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\${WEBVIEW2APPGUID}" "pv"');
expect(template).toContain('ReadRegStr ${RESULT} HKCU "SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\${WEBVIEW2APPGUID}" "pv"');
});
});
@@ -0,0 +1,9 @@
export interface WindowsWebView2RuntimeFallbackInput {
installerExitCode: number;
runtimeDetected: boolean;
runtimeMeetsMinimum: boolean;
}
export function shouldAbortWindowsWebView2RuntimeFallback(input: WindowsWebView2RuntimeFallbackInput): boolean {
return input.installerExitCode !== 0 && (!input.runtimeDetected || !input.runtimeMeetsMinimum);
}
+1
View File
@@ -1492,6 +1492,7 @@ export interface UpdateInfo {
latest_version: string;
update_available: boolean;
portable_mode: boolean;
manual_update_only: boolean;
release_name: string;
release_url: string;
release_notes: string;