fix(updater): separate Windows 7 offline update path
This commit is contained in:
29 files changed
+1851
-24
No files matched your search
@@ -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 {
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -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: "アップデートをインストール中...",
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -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: "正在安装更新...",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user