feat(release): ReleasePlanner — proposed beta targets from the current version
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>feat/v1-foundation
parent
39ccf0d539
commit
05c98a7b9e
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pure semver arithmetic for the Release page: given the current version, what beta-target versions
|
||||||
|
* may the operator cut? The dashboard computes the target X.Y.Z; the host bridge computes -betaN.
|
||||||
|
*/
|
||||||
|
class ReleasePlanner
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Proposed target versions (bare X.Y.Z) for the current version. A beta also offers continuing
|
||||||
|
* its own base (another beta of the same target).
|
||||||
|
*
|
||||||
|
* @return array{patch:string, minor:string, major:string, continueBeta?:string}
|
||||||
|
*/
|
||||||
|
public function proposedTargets(string $current): array
|
||||||
|
{
|
||||||
|
$current = ltrim($current, 'vV');
|
||||||
|
$base = preg_replace('/-.*$/', '', $current);
|
||||||
|
[$maj, $min, $pat] = array_map('intval', array_pad(explode('.', (string) $base), 3, '0'));
|
||||||
|
|
||||||
|
$targets = [
|
||||||
|
'patch' => "{$maj}.{$min}.".($pat + 1),
|
||||||
|
'minor' => "{$maj}.".($min + 1).'.0',
|
||||||
|
'major' => ($maj + 1).'.0.0',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (str_contains($current, '-beta')) {
|
||||||
|
$targets['continueBeta'] = "{$maj}.{$min}.{$pat}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $targets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The flat list of allowed target versions — the server-side allow-list for a posted target.
|
||||||
|
*
|
||||||
|
* @return array<int,string>
|
||||||
|
*/
|
||||||
|
public function allowedTargets(string $current): array
|
||||||
|
{
|
||||||
|
return array_values($this->proposedTargets($current));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Unit;
|
||||||
|
|
||||||
|
use App\Services\ReleasePlanner;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class ReleasePlannerTest extends TestCase
|
||||||
|
{
|
||||||
|
private ReleasePlanner $planner;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->planner = new ReleasePlanner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_stable_version_offers_patch_minor_major_without_continue_beta(): void
|
||||||
|
{
|
||||||
|
$t = $this->planner->proposedTargets('0.9.58');
|
||||||
|
|
||||||
|
$this->assertSame('0.9.59', $t['patch']);
|
||||||
|
$this->assertSame('0.10.0', $t['minor']);
|
||||||
|
$this->assertSame('1.0.0', $t['major']);
|
||||||
|
$this->assertArrayNotHasKey('continueBeta', $t);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_beta_version_also_offers_continue_beta_of_its_base(): void
|
||||||
|
{
|
||||||
|
$t = $this->planner->proposedTargets('0.10.0-beta3');
|
||||||
|
|
||||||
|
$this->assertSame('0.10.0', $t['continueBeta']);
|
||||||
|
$this->assertSame('0.10.1', $t['patch']);
|
||||||
|
$this->assertSame('0.11.0', $t['minor']);
|
||||||
|
$this->assertSame('1.0.0', $t['major']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_strips_a_leading_v(): void
|
||||||
|
{
|
||||||
|
$this->assertSame('0.9.59', $this->planner->proposedTargets('v0.9.58')['patch']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_allowed_targets_are_the_proposed_values(): void
|
||||||
|
{
|
||||||
|
$allowed = $this->planner->allowedTargets('0.10.0-beta1');
|
||||||
|
|
||||||
|
$this->assertContains('0.10.0', $allowed); // continueBeta
|
||||||
|
$this->assertContains('0.11.0', $allowed); // minor
|
||||||
|
$this->assertNotContains('0.9.0', $allowed);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue