From c4538ccb74061c6d88ddbaa5861f91088577eb72 Mon Sep 17 00:00:00 2001 From: Chen <99816898+donteatfriedrice@users.noreply.github.com> Date: Wed, 15 Apr 2026 16:44:59 +0800 Subject: [PATCH] fix(prereq): accept zero-padded CalVer + align Setup card icons to first line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prereq checker used semver.valid() directly, which rejects zero-padded CalVer like yt-dlp's 2026.03.17 (semver disallows leading zeros in any numeric segment). Detection landed on "Could not parse detected version" even when the regex matched the output. Normalize by stripping leading zeros from numeric segments before handing to semver — ordering is preserved and existing semver-shaped versions are unaffected. Also in the Setup card, switch the row from items-center to items-start and wrap status + action icons in fixed-height spans so they sit at the first-line center instead of being centered against (label + hint), which visually drifted downward whenever a prereq had a hint below the label (e.g. error cases). Discovered while onboarding @graydawnc/connector-youtube — yt-dlp hit both issues at once. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../renderer/components/PackageSetupCard.tsx | 8 +++-- .../core/src/connectors/prerequisites.test.ts | 36 +++++++++++++++++++ packages/core/src/connectors/prerequisites.ts | 13 +++++-- 3 files changed, 52 insertions(+), 5 deletions(-) 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') +}