From 8ad3dc5c4fbc5d90708ae802c9e1b1055eb40624 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 15 Aug 2026 16:31:38 +0200 Subject: [PATCH] fix(android): raise the versionCode floor so 0.5.x can install over v0.5.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v0.5.2 shipped Android versionCode 5002, from an earlier `minor*1000` scheme. The `minor*100` formula that replaced it yields only 1502 for that same version, and 1503 for 0.5.3 — lower than what is already installed, so Android refuses the update as a downgrade. Every 0.5.x release built from this script was un-installable for anyone already on v0.5.2. This is the exact failure the block was written to prevent; its floor simply went stale. The floor tracked "codes below 1000 are already in the field", which was true when written, but a 5002 build has shipped since — and the highest code this formula has *produced* is not the same as the highest code in the field. Widen the multipliers and raise the floor past 5002: code = 10000 + major*1000000 + minor*1000 + patch 0.0.14 -> 10014 0.5.2 -> 15002 0.6.0 -> 16000 0.1.0 -> 11000 0.5.3 -> 15003 1.0.0 -> 1010000 Still strictly monotonic across the upgrade sequence. The guard test gains a case pinning 0.5.3 above the 5002 in the field, so the floor is expressed as "clears what shipped" rather than a literal that can silently go stale again. --- scripts/set-version.sh | 14 +++++++++++--- scripts/set-version.test.ts | 36 ++++++++++++++++++++++++------------ 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/scripts/set-version.sh b/scripts/set-version.sh index 0fc40807..3f76843d 100755 --- a/scripts/set-version.sh +++ b/scripts/set-version.sh @@ -81,8 +81,16 @@ fi # builds shipped versionCode 1000 (from a 0.1.0 config), so a plain 15 is a # *downgrade* and Android refuses the update. # -# code = 1000 + major*10000 + minor*100 + patch -# e.g. 0.0.14 -> 1014, 0.0.15 -> 1015, 0.1.0 -> 1100, 1.0.0 -> 11000. +# The floor has to clear the highest code actually in the field, which is not the +# same as the highest this formula has produced. v0.5.2 shipped versionCode +# **5002** under an earlier `minor*1000` scheme; the `minor*100` formula that +# replaced it yields only 1502 for that same version, and 1503 for 0.5.3 — so +# every 0.5.x release built from it was an un-installable downgrade for anyone +# already on v0.5.2, which is exactly the failure this block exists to prevent. +# The multipliers are widened and the floor raised past 5002 accordingly. +# +# code = 10000 + major*1000000 + minor*1000 + patch +# e.g. 0.0.14 -> 10014, 0.1.0 -> 11000, 0.5.3 -> 15003, 1.0.0 -> 1010000. PROPS="src-tauri/gen/android/app/tauri.properties" if [ -f "$PROPS" ]; then # Strip any -rc1/+build suffix first: it is not numeric, and feeding it to @@ -93,7 +101,7 @@ if [ -f "$PROPS" ]; then MIN=$(echo "$CORE" | cut -d. -f2) PAT=$(echo "$CORE" | cut -d. -f3) : "${MAJ:=0}" "${MIN:=0}" "${PAT:=0}" - CODE=$(( 1000 + MAJ*10000 + MIN*100 + PAT )) + CODE=$(( 10000 + MAJ*1000000 + MIN*1000 + PAT )) echo " versionCode=$CODE (from $CORE)" if grep -q '^tauri.android.versionCode=' "$PROPS"; then sed -i "s/^tauri.android.versionCode=.*/tauri.android.versionCode=$CODE/" "$PROPS" diff --git a/scripts/set-version.test.ts b/scripts/set-version.test.ts index 5c1f5b66..241979f6 100644 --- a/scripts/set-version.test.ts +++ b/scripts/set-version.test.ts @@ -106,21 +106,33 @@ describe("set-version.sh", () => { }); describe("Android versionCode", () => { - // Codes below 1000 are already in the field; a newer release must never - // produce a smaller number than an older one. - it("clears the 1000 floor shipped by earlier builds", () => { + // A newer release must never produce a smaller number than an older one, or + // Android refuses the update. The floor tracks the highest code actually in + // the field, which is NOT the same as the highest this formula has produced: + // v0.5.2 shipped versionCode 5002 from an earlier `minor*1000` scheme, while + // the `minor*100` formula that replaced it yields only 1502 for that same + // version — so every 0.5.x release built from it was an un-installable + // downgrade for anyone already on v0.5.2. The floor is raised to clear it. + it("clears the highest code shipped by earlier builds", () => { run("0.0.1"); - expect(versionCode()).toBeGreaterThan(1000); + // v0.5.2 shipped 5002; anything at or below that cannot install over it. + expect(versionCode()).toBeGreaterThan(5002); }); - it("uses 1000 + major*10000 + minor*100 + patch", () => { + it("keeps 0.5.3 installable over the 5002 that shipped as v0.5.2", () => { + run("0.5.3"); + expect(versionCode()).toBeGreaterThan(5002); + }); + + it("uses 10000 + major*1000000 + minor*1000 + patch", () => { const cases: Array<[string, number]> = [ - ["0.0.14", 1014], - ["0.0.15", 1015], - ["0.1.0", 1100], - ["0.4.8", 1408], - ["0.5.0", 1500], - ["1.0.0", 11000], + ["0.0.14", 10014], + ["0.0.15", 10015], + ["0.1.0", 11000], + ["0.4.8", 14008], + ["0.5.0", 15000], + ["0.5.3", 15003], + ["1.0.0", 1010000], ]; for (const [version, code] of cases) { seed(tmp); @@ -145,7 +157,7 @@ describe("set-version.sh", () => { // stripped before the arithmetic. it("derives the code from the numeric core of a prerelease", () => { run("0.6.0-rc1"); - expect(versionCode()).toBe(1600); + expect(versionCode()).toBe(16000); expect(JSON.parse(read("package.json")).version).toBe("0.6.0-rc1"); }); });