feat(release): collapse to a single stable channel

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat/v1-foundation
boban 2026-07-03 19:58:09 +02:00
parent 176b132da9
commit 4e4023f12d
3 changed files with 31 additions and 13 deletions

View File

@ -2,7 +2,6 @@
namespace App\Services; namespace App\Services;
use App\Models\Setting;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
@ -16,8 +15,6 @@ use Illuminate\Support\Facades\Http;
*/ */
class ReleaseChecker class ReleaseChecker
{ {
private const CHANNELS = ['stable', 'beta'];
/** Cache the resolved tag long enough that the scheduled refresh keeps it continuously warm. */ /** Cache the resolved tag long enough that the scheduled refresh keeps it continuously warm. */
private const TTL_MINUTES = 60; private const TTL_MINUTES = 60;
@ -38,9 +35,7 @@ class ReleaseChecker
public function channel(): string public function channel(): string
{ {
$channel = (string) (Setting::get('release_channel', config('clusev.channel')) ?? 'stable'); return 'stable';
return in_array($channel, self::CHANNELS, true) ? $channel : 'stable';
} }
/** Last resolved latest tag (no network). Null when unknown. */ /** Last resolved latest tag (no network). Null when unknown. */
@ -191,9 +186,7 @@ class ReleaseChecker
*/ */
public function newestVersion(array $tagNames, string $channel): ?string public function newestVersion(array $tagNames, string $channel): ?string
{ {
$pattern = $channel === 'beta' $pattern = '/^v?\d+\.\d+\.\d+$/';
? '/^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?$/'
: '/^v?\d+\.\d+\.\d+$/';
$versions = []; $versions = [];
foreach (array_unique($tagNames) as $tag) { foreach (array_unique($tagNames) as $tag) {

View File

@ -0,0 +1,26 @@
<?php
namespace Tests\Feature;
use App\Models\Setting;
use App\Services\ReleaseChecker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ReleaseCheckerChannelTest extends TestCase
{
use RefreshDatabase;
public function test_channel_is_always_stable_even_if_a_beta_setting_lingers(): void
{
Setting::put('release_channel', 'beta'); // a stale setting from before the drop
$this->assertSame('stable', app(ReleaseChecker::class)->channel());
}
public function test_newest_version_ignores_rc_and_beta_prereleases(): void
{
$checker = app(ReleaseChecker::class);
$tags = ['v0.11.0', 'v0.12.0-rc1', 'v0.12.0-beta2', 'v0.10.0'];
$this->assertSame('0.11.0', $checker->newestVersion($tags, 'stable'));
}
}

View File

@ -110,7 +110,7 @@ class VersionUpdateCheckTest extends TestCase
->assertSet('updateState', 'current'); ->assertSet('updateState', 'current');
} }
public function test_stable_channel_ignores_prereleases_but_beta_sees_them(): void public function test_stable_channel_ignores_prereleases(): void
{ {
config()->set('clusev.version', '0.9.6'); config()->set('clusev.version', '0.9.6');
Http::fake([self::TAGS_URL => Http::response([ Http::fake([self::TAGS_URL => Http::response([
@ -121,13 +121,12 @@ class VersionUpdateCheckTest extends TestCase
Setting::put('release_channel', 'stable'); Setting::put('release_channel', 'stable');
Livewire::test(Index::class)->call('checkUpdates')->assertSet('updateState', 'current'); Livewire::test(Index::class)->call('checkUpdates')->assertSet('updateState', 'current');
// beta: the prerelease is newer → update available // a stale beta setting from before the channel drop is ignored → still current
Cache::flush(); Cache::flush();
Setting::put('release_channel', 'beta'); Setting::put('release_channel', 'beta');
Livewire::test(Index::class) Livewire::test(Index::class)
->call('checkUpdates') ->call('checkUpdates')
->assertSet('updateState', 'update') ->assertSet('updateState', 'current');
->assertSee('1.0.0-beta1');
} }
public function test_degrades_gracefully_when_the_remote_api_is_unreachable(): void public function test_degrades_gracefully_when_the_remote_api_is_unreachable(): void