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')
+}