From d0b87b16dcb9eb9e9303c8edd748ba8570e81e90 Mon Sep 17 00:00:00 2001 From: Chaim Date: Mon, 6 Apr 2026 15:05:15 +0000 Subject: [PATCH] fix: use semver comparison for extension version upgrades MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the system used simple string inequality to detect available updates, causing downgrades to appear as upgrades (e.g. 2.5.1 → 2.5.0). Now both backend (SQL) and frontend (JS) compare version tuples properly. Also fixes latest_version in DB on registry sync startup. Co-Authored-By: Claude Opus 4.6 (1M context) --- app_server.py | 14 ++++++++++++-- dashboard/src/App.jsx | 3 ++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app_server.py b/app_server.py index c1f9316..fd088cc 100644 --- a/app_server.py +++ b/app_server.py @@ -156,6 +156,15 @@ def sync_extension_registry(): (name,), fetch=None) deactivated.append(name) + # Fix latest_version: set to the highest version from extension_versions + query("""UPDATE extensions e SET latest_version = sub.max_ver, updated_at = NOW() + FROM (SELECT extension_id, version as max_ver FROM extension_versions ev1 + WHERE NOT EXISTS (SELECT 1 FROM extension_versions ev2 + WHERE ev2.extension_id = ev1.extension_id + AND string_to_array(ev2.version, '.')::int[] > string_to_array(ev1.version, '.')::int[]) + ) sub WHERE e.id = sub.extension_id AND (e.latest_version IS NULL OR e.latest_version != sub.max_ver)""", + fetch=None) + # Reload MODULES from DB _load_modules_from_db() @@ -363,8 +372,9 @@ def build_and_publish(module, version): SELECT id, %s, %s, %s FROM extensions WHERE name = %s ON CONFLICT (extension_id, version) DO NOTHING""", (version, f'{module}-{version}.zip', tag, module)) - cur.execute("UPDATE extensions SET latest_version = %s, updated_at = NOW() WHERE name = %s", - (version, module)) + cur.execute("""UPDATE extensions SET latest_version = %s, updated_at = NOW() + WHERE name = %s AND (latest_version IS NULL OR string_to_array(latest_version, '.')::int[] < string_to_array(%s, '.')::int[])""", + (version, module, version)) conn.commit() cur.close() conn.close() diff --git a/dashboard/src/App.jsx b/dashboard/src/App.jsx index 7ecaf52..27b0ec5 100644 --- a/dashboard/src/App.jsx +++ b/dashboard/src/App.jsx @@ -456,7 +456,8 @@ function CustomerDetail({ customerId, onDelete }) { {sorted.map(e => { const installed = e.installed_version - const hasUpdate = installed && e.latest_version && installed !== e.latest_version + const semverGt = (a, b) => { const pa = a.split('.').map(Number), pb = b.split('.').map(Number); for (let i = 0; i < Math.max(pa.length, pb.length); i++) { if ((pa[i]||0) > (pb[i]||0)) return true; if ((pa[i]||0) < (pb[i]||0)) return false; } return false } + const hasUpdate = installed && e.latest_version && semverGt(e.latest_version, installed) const missingDeps = (deps[e.name] || []).filter(d => !installedNames.has(d)) const blocked = !installed && missingDeps.length > 0 return (