From f8144a3bff8caf8f027492418ca5c9a693829bec Mon Sep 17 00:00:00 2001 From: CRT Date: Wed, 30 Sep 2026 23:23:43 +0700 Subject: [PATCH] Windows installer: an install made under the app's old name is older than any setup of the renamed app, whatever its version number (upgrade in place, data kept) The renamed app may start its numbering again. Told apart by DisplayName in the uninstall key (the old app wrote its old name there; this template writes the new one). Also exempt from the silent-downgrade abort. Compiled with and without a legacy name and run on the test variant. Co-Authored-By: Claude Opus 5.5 --- docs/ARCHITECTURE.md | 4 ++- packages/waybetter/windows/installer.nsi | 33 ++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index ad384d4..1eb1031 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -492,7 +492,9 @@ The npm package adds the app-side tooling: install the updater has moved on. `/FORCE` reinstalls the same version (repair), `/NORUN` does not open the app, `/UPDATE` is the updater's path and always installs. An app renamed after its first release keeps its registry keys by defining `WAYBETTER_LEGACY_NAME` in its installer hooks - (`bundle.windows.nsis.installerHooks`). + (`bundle.windows.nsis.installerHooks`). An install made under the old name (its `DisplayName` is + still that name) counts as older than any setup of the renamed app, whatever its version number: + the renamed app may start its numbering again and still upgrades such an install in place. ## Consuming it diff --git a/packages/waybetter/windows/installer.nsi b/packages/waybetter/windows/installer.nsi index 8330de1..86df64c 100644 --- a/packages/waybetter/windows/installer.nsi +++ b/packages/waybetter/windows/installer.nsi @@ -7,7 +7,9 @@ ; - the same or a newer version is installed: installs nothing, opens the installed app (its ; single-instance guard shows the running window) and exits 0. So an old setup left in ; Downloads can never replace, restart or downgrade an install the updater has moved on; -; - an older version is installed: upgrades in place (app data untouched), then opens the app. +; - an older version is installed: upgrades in place (app data untouched), then opens the app; +; - an install made under the app's old name (see WAYBETTER_LEGACY_NAME below) counts as older, +; whatever its version number says: it is upgraded in place, data kept. ; Flags: `/NORUN` never opens the app, `/FORCE` reinstalls the same version (repair; it still never ; downgrades), `/UPDATE` is the updater's path (tauri-plugin-updater: `/UPDATE /R /ARGS ...`), ; which always installs and is otherwise Tauri's. `/NS` (no shortcuts) and `/D=` as in Tauri. @@ -17,6 +19,9 @@ ; (`bundle.windows.nsis.installerHooks`), which are included before anything here reads it: ; !define WAYBETTER_LEGACY_NAME "Old Product Name" ; !define WAYBETTER_LEGACY_MANUFACTURER "Old Publisher" ; optional, defaults to the old name +; The renamed app may start its version numbers again (a 1.0.0 after an old 1.0.2): an install +; whose DisplayName is still the old name was written by the old app, and every setup of the +; renamed app is newer than it. Unicode true ManifestDPIAware true ; wayBetter: never show a wizard. @@ -111,6 +116,7 @@ Var ForceMode Var NoShortcutMode Var WixMode Var OldMainBinaryName +Var LegacyInstall Name "${PRODUCTNAME}" BrandingText "${COPYRIGHT}" @@ -330,10 +336,24 @@ Function .onInit ${EndIf} FunctionEnd +; wayBetter: $LegacyInstall = 1 when the install recorded in ${UNINSTKEY} was made by the app under +; its old name (its DisplayName is still ${LEGACYNAME}; this template writes ${PRODUCTNAME}), else 0. +; Such an install is older than this setup whatever its version number says. +!macro WaybetterLegacyInstall + StrCpy $LegacyInstall 0 + !ifdef LEGACYNAME + ReadRegStr $R6 SHCTX "${UNINSTKEY}" "DisplayName" + ${If} $R6 == "${LEGACYNAME}" + StrCpy $LegacyInstall 1 + ${EndIf} + !endif +!macroend + ; wayBetter: when the app is installed in $INSTDIR at this setup's version or a newer one, open it ; and quit with exit code 0: nothing is copied, no running app is closed, nothing is downgraded. ; Returns (and the install goes ahead) when nothing is installed, when the registry names an install -; whose exe is gone, when the installed version is older, or on /FORCE with the same version. +; whose exe is gone, when the installed version is older, when the install was made under the app's +; old name, or on /FORCE with the same version. Function OpenInstalledIfCurrent ; $R1 the installed exe's name, $R2 the installed version, $R3 this setup compared to it ReadRegStr $R1 SHCTX "${UNINSTKEY}" "MainBinaryName" @@ -344,6 +364,12 @@ Function OpenInstalledIfCurrent Return ${EndIf} + ; Installed under the old name: older, whatever its number (upgrade in place). + !insertmacro WaybetterLegacyInstall + ${If} $LegacyInstall = 1 + Return + ${EndIf} + ReadRegStr $R2 SHCTX "${UNINSTKEY}" "DisplayVersion" ${If} $R2 == "" ; No version in the registry: the exe's own file version, a.b.c.d -> a.b.c @@ -389,7 +415,10 @@ Section EarlyChecks ; If downgrading (wayBetter: there is no reinstall page to compare the versions, so here) StrCpy $R0 0 ReadRegStr $R2 SHCTX "${UNINSTKEY}" "DisplayVersion" + ; (An install made under the app's old name is never a downgrade target: it is older.) + !insertmacro WaybetterLegacyInstall ${If} $R2 != "" + ${AndIf} $LegacyInstall <> 1 nsis_tauri_utils::SemverCompare "${VERSION}" $R2 Pop $R0 ${EndIf}