From 94bcad2d1a7c843be5a75aba1140d997f7fcc0bd Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Fri, 10 Apr 2026 23:06:37 -0400 Subject: [PATCH] fix(update): align no-update dependency injection --- src/commands/update-command.ts | 42 +++---------------- src/utils/update-checker.ts | 2 +- .../update-command-current-install.test.ts | 25 +++++++---- 3 files changed, 25 insertions(+), 44 deletions(-) diff --git a/src/commands/update-command.ts b/src/commands/update-command.ts index fd1c2ad4..a7354102 100644 --- a/src/commands/update-command.ts +++ b/src/commands/update-command.ts @@ -15,7 +15,7 @@ import { type CurrentInstall, type InstalledPackageState, } from '../utils/package-manager-detector'; -import { compareVersionsWithPrerelease } from '../utils/update-checker'; +import { compareVersionsWithPrerelease, type UpdateResult } from '../utils/update-checker'; import { getVersion } from '../utils/version'; /** @@ -28,29 +28,6 @@ export interface UpdateOptions { type TargetTag = 'latest' | 'dev'; -type UpdateCheckResult = - | { - status: 'update_available'; - current?: string; - latest?: string; - message?: string; - reason?: string; - } - | { - status: 'check_failed'; - message?: string; - latest?: string; - current?: string; - reason?: string; - } - | { - status: 'no_update'; - reason?: string; - latest?: string; - current?: string; - message?: string; - }; - export interface UpdateCommandDeps { initUI: typeof initUI; getVersion: typeof getVersion; @@ -64,7 +41,7 @@ export interface UpdateCommandDeps { interactive: boolean, channel: 'npm' | 'direct', targetTag: TargetTag - ) => Promise; + ) => Promise; spawn: typeof spawn; } @@ -73,14 +50,9 @@ async function loadCheckForUpdates( interactive: boolean, channel: 'npm' | 'direct', targetTag: TargetTag -): Promise { +): Promise { const { checkForUpdates } = await import('../utils/update-checker'); - return checkForUpdates( - currentVersion, - interactive, - channel, - targetTag - ) as Promise; + return checkForUpdates(currentVersion, interactive, channel, targetTag); } const defaultDeps: UpdateCommandDeps = { @@ -150,7 +122,7 @@ export async function handleUpdateCommand( } if (updateResult.status === 'no_update') { - handleNoUpdate(updateResult.reason); + handleNoUpdate(updateResult.reason, currentVersion); return; } @@ -215,9 +187,7 @@ function handleCheckFailed( /** * Handle no update available */ -function handleNoUpdate(reason: string | undefined): void { - const version = getVersion(); - +function handleNoUpdate(reason: string | undefined, version: string): void { let message = `You are already on the latest version (${version})`; switch (reason) { diff --git a/src/utils/update-checker.ts b/src/utils/update-checker.ts index 019e0c6e..002a17a5 100644 --- a/src/utils/update-checker.ts +++ b/src/utils/update-checker.ts @@ -21,7 +21,7 @@ interface UpdateCache { dismissed_version: string | null; } -interface UpdateResult { +export interface UpdateResult { status: 'update_available' | 'no_update' | 'check_failed'; reason?: string; latest?: string; diff --git a/tests/unit/commands/update-command-current-install.test.ts b/tests/unit/commands/update-command-current-install.test.ts index d65d6a51..ef540deb 100644 --- a/tests/unit/commands/update-command-current-install.test.ts +++ b/tests/unit/commands/update-command-current-install.test.ts @@ -1,5 +1,6 @@ import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; import { handleUpdateCommand, type UpdateCommandDeps } from '../../../src/commands/update-command'; +import type { UpdateResult } from '../../../src/utils/update-checker'; let logLines: string[] = []; let spawnCalls: Array<{ command: string; args: string[]; env?: NodeJS.ProcessEnv }> = []; @@ -18,13 +19,8 @@ type Scenario = { afterState: InstalledState; }; -type UpdateCheckResult = - | { status: 'update_available'; current: string; latest: string } - | { status: 'no_update' } - | { status: 'check_failed'; message: string }; - let scenario: Scenario; -let updateCheckResult: UpdateCheckResult; +let updateCheckResult: UpdateResult; let currentInstallOverride: ReturnType; let stateReads = 0; @@ -39,7 +35,7 @@ function installDescriptor() { }; } -function createDeps(): UpdateCommandDeps { +function createDeps(overrides: Partial = {}): UpdateCommandDeps { return { initUI: async () => {}, getVersion: () => '7.67.0-dev.5', @@ -95,6 +91,7 @@ function createDeps(): UpdateCommandDeps { }, }; }) as typeof UpdateCommandDeps.prototype.spawn, + ...overrides, }; } @@ -196,6 +193,20 @@ describe('update-command current install handling', () => { expect(exitCodes).toContain(0); }); + it('uses the injected version in the no-update message', async () => { + updateCheckResult = { status: 'no_update' }; + + await handleUpdateCommand( + {}, + createDeps({ + getVersion: () => '9.9.9-test.1', + }) + ); + + expect(logLines.join('\n')).toContain('latest version (9.9.9-test.1)'); + expect(exitCodes).toContain(0); + }); + it('accepts a newer installed version when the dist-tag moves during update', async () => { scenario = { beforeState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 },