diff --git a/packages/app/src/renderer/components/PackageSetupCard.tsx b/packages/app/src/renderer/components/PackageSetupCard.tsx index 2868f9b..1e932d3 100644 --- a/packages/app/src/renderer/components/PackageSetupCard.tsx +++ b/packages/app/src/renderer/components/PackageSetupCard.tsx @@ -126,8 +126,10 @@ function StepRow({ packageId, step, onChanged }: { packageId: string; step: Setu } return ( -
- +
+ + +
@@ -140,7 +142,7 @@ function StepRow({ packageId, step, onChanged }: { packageId: string; step: Setu
{step.hint}
)}
-
{renderAction()}
+
{renderAction()}
{install?.kind === 'browser-extension' && install.manual && ( { expect(steps[0].status).toBe('missing') }) + it('accepts zero-padded CalVer (e.g. yt-dlp 2026.03.17) by stripping leading zeros', async () => { + const exec = { run: vi.fn().mockResolvedValue({ exitCode: 0, stdout: '2026.03.17\n', stderr: '' }) } + const checker = new PrerequisiteChecker(exec as any) + const pkg = mkPkg('p1', [ + { + id: 'yt-dlp', + name: 'yt-dlp', + kind: 'cli', + detect: { type: 'exec', command: 'yt-dlp', args: ['--version'], versionRegex: '(\\d{4}\\.\\d{2}\\.\\d{2})' }, + minVersion: '2024.01.01', + install: { kind: 'cli', command: { darwin: 'brew install yt-dlp' } }, + }, + ]) + const steps = await checker.check(pkg) + expect(steps[0].status).toBe('ok') + expect(steps[0].detectedVersion).toBe('2026.03.17') + }) + + it('correctly compares zero-padded CalVer across years/months', async () => { + const exec = { run: vi.fn().mockResolvedValue({ exitCode: 0, stdout: '2024.02.05\n', stderr: '' }) } + const checker = new PrerequisiteChecker(exec as any) + const pkg = mkPkg('p1', [ + { + id: 'yt-dlp', + name: 'yt-dlp', + kind: 'cli', + detect: { type: 'exec', command: 'yt-dlp', args: ['--version'], versionRegex: '(\\d{4}\\.\\d{2}\\.\\d{2})' }, + minVersion: '2024.03.01', + install: { kind: 'cli', command: { darwin: 'brew install yt-dlp' } }, + }, + ]) + const steps = await checker.check(pkg) + expect(steps[0].status).toBe('outdated') + expect(steps[0].detectedVersion).toBe('2024.02.05') + }) + it('marks outdated when version is below minVersion', async () => { const exec = { run: vi.fn().mockResolvedValue({ exitCode: 0, stdout: 'v0.2.1\n', stderr: '' }) } const checker = new PrerequisiteChecker(exec as any) diff --git a/packages/core/src/connectors/prerequisites.ts b/packages/core/src/connectors/prerequisites.ts index a37495b..4e3ebe1 100644 --- a/packages/core/src/connectors/prerequisites.ts +++ b/packages/core/src/connectors/prerequisites.ts @@ -89,10 +89,15 @@ export class PrerequisiteChecker { return baseStep(p, 'error', { hint: 'Could not parse version' }) } const detectedVersion = vm[1] - if (!valid(detectedVersion)) { + // Normalize for semver: strip leading zeros from numeric segments so + // zero-padded CalVer (yt-dlp, Ubuntu, Postgres) like `2026.03.17` + // validates as `2026.3.17`. Ordering is preserved. + const normalizedDetected = stripLeadingZeros(detectedVersion) + const normalizedMin = stripLeadingZeros(p.minVersion) + if (!valid(normalizedDetected)) { return baseStep(p, 'error', { hint: 'Could not parse detected version' }) } - if (gte(detectedVersion, p.minVersion)) { + if (gte(normalizedDetected, normalizedMin)) { return baseStep(p, 'ok', { detectedVersion }) } return baseStep(p, 'outdated', { @@ -104,3 +109,7 @@ export class PrerequisiteChecker { return result.exitCode === 0 ? baseStep(p, 'ok') : baseStep(p, 'missing') } } + +function stripLeadingZeros(version: string): string { + return version.replace(/(^|\.)0+(\d)/g, '$1$2') +}