From 30ac3f8b8d97044a7e08aa924237ccdcb2ee822e Mon Sep 17 00:00:00 2001 From: Aaron Bushnell Date: Fri, 26 Jun 2026 21:07:09 -0400 Subject: [PATCH 1/2] Fix false "Update available" when installed version is newer than marketplace latest The update check treated "not exactly equal to the marketplace's latest" as "an update is available", rather than "the installed version is older than the latest". This surfaced an "Update available" badge whenever a local install was ahead of the marketplace's indexed version (e.g. in the window between a release landing on Packagist and the marketplace indexing it). - Addon::isLatestVersion() now uses `>=` instead of strict equality, so an install equal to or newer than the marketplace latest is up to date. - The per-product updater badge derives "up to date" from the changelog's release types (no `upgrade` releases) instead of a string comparison, matching the server's classification and the count badge. Co-Authored-By: Claude Opus 4.8 (1M context) --- resources/js/components/updater/Updater.vue | 2 +- src/Addons/Addon.php | 2 +- tests/Addons/AddonTest.php | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/resources/js/components/updater/Updater.vue b/resources/js/components/updater/Updater.vue index e78019ee218..aecffc16691 100644 --- a/resources/js/components/updater/Updater.vue +++ b/resources/js/components/updater/Updater.vue @@ -92,7 +92,7 @@ export default { }, onLatestVersion() { - return this.currentVersion && this.currentVersion == this.latestVersion; + return this.currentVersion && !this.changelog.some((release) => release.type === 'upgrade'); }, securityUpdateAvailable() { diff --git a/src/Addons/Addon.php b/src/Addons/Addon.php index 64544ea7865..cefc03abaeb 100644 --- a/src/Addons/Addon.php +++ b/src/Addons/Addon.php @@ -419,7 +419,7 @@ public function isLatestVersion() $version = $versionParser->normalize($this->version); $latestVersion = $versionParser->normalize($this->latestVersion()); - return version_compare($version, $latestVersion, '='); + return version_compare($version, $latestVersion, '>='); } public function license() diff --git a/tests/Addons/AddonTest.php b/tests/Addons/AddonTest.php index 5ef50a9be99..795b5a3fb7d 100644 --- a/tests/Addons/AddonTest.php +++ b/tests/Addons/AddonTest.php @@ -342,6 +342,11 @@ public static function isLatestVersionProvider() ['2.0.0', '2.0.0', true], ['1.0', '1.0.0', true], ['1.0', '1.0.1', false], + + // Installed version is newer than the marketplace's indexed latest version + ['1.0.2', '1.0.1', true], + ['2.0.0', '1.0.0', true], + ['7.12.1', '7.12.0', true], ]; } From 6a9b79ada3e84f54f733fd0f613545d7ae609333 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 7 Jul 2026 12:06:13 -0400 Subject: [PATCH 2/2] Make updater badge state server-authoritative The "Up to date" and "Security update available" header badges were derived from the current changelog page. Paginating past page 1 (which holds the newest, upgrade-typed releases) left only older releases, so both badges dropped incorrectly. Return onLatestVersion and securityUpdateAvailable from the changelog endpoint, computed across the full release set, and bind them directly. Co-Authored-By: Claude Opus 4.8 --- resources/js/components/updater/Updater.vue | 23 ++++--------------- .../CP/Updater/UpdateProductController.php | 2 ++ 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/resources/js/components/updater/Updater.vue b/resources/js/components/updater/Updater.vue index aecffc16691..97ad89a1b9c 100644 --- a/resources/js/components/updater/Updater.vue +++ b/resources/js/components/updater/Updater.vue @@ -74,7 +74,8 @@ export default { gettingChangelog: true, changelog: [], currentVersion: null, - latestRelease: null, + onLatestVersion: false, + securityUpdateAvailable: false, showingUnlicensedReleases: false, page: 1, perPage: 10, @@ -91,16 +92,6 @@ export default { return !this.gettingChangelog; }, - onLatestVersion() { - return this.currentVersion && !this.changelog.some((release) => release.type === 'upgrade'); - }, - - securityUpdateAvailable() { - return this.currentVersion && this.changelog - .filter((release) => release.type === 'upgrade') - .some((release) => release.security); - }, - licensedReleases() { return this.changelog.filter((release) => release.licensed); }, @@ -113,10 +104,6 @@ export default { return this.unlicensedReleases.length > 0; }, - latestVersion() { - return this.latestRelease && this.latestRelease.version; - }, - link() { return ( __('Learn more about :link', { @@ -145,11 +132,9 @@ export default { this.gettingChangelog = false; this.changelog = response.data.changelog; this.currentVersion = response.data.currentVersion; + this.onLatestVersion = response.data.onLatestVersion; + this.securityUpdateAvailable = response.data.securityUpdateAvailable; this.meta = response.data.meta; - - if (this.page === 1 && response.data.changelog.length > 0) { - this.latestRelease = response.data.changelog[0]; - } }); }, diff --git a/src/Http/Controllers/CP/Updater/UpdateProductController.php b/src/Http/Controllers/CP/Updater/UpdateProductController.php index f96b62a4689..8b8fca135fc 100644 --- a/src/Http/Controllers/CP/Updater/UpdateProductController.php +++ b/src/Http/Controllers/CP/Updater/UpdateProductController.php @@ -85,6 +85,8 @@ public function changelog(Request $request, $marketplaceProductSlug) return [ 'changelog' => $paginated['data'], 'currentVersion' => $changelog->currentVersion(), + 'onLatestVersion' => $changelog->availableUpdatesCount() === 0, + 'securityUpdateAvailable' => $changelog->hasSecurityUpdate(), 'meta' => $paginated['meta'], ]; }