fix(update): align no-update dependency injection

This commit is contained in:
Tam Nhu Tran
2026-04-10 23:06:37 -04:00
parent a4496ff1d9
commit 94bcad2d1a
3 changed files with 25 additions and 44 deletions
+6 -36
View File
@@ -15,7 +15,7 @@ import {
type CurrentInstall, type CurrentInstall,
type InstalledPackageState, type InstalledPackageState,
} from '../utils/package-manager-detector'; } from '../utils/package-manager-detector';
import { compareVersionsWithPrerelease } from '../utils/update-checker'; import { compareVersionsWithPrerelease, type UpdateResult } from '../utils/update-checker';
import { getVersion } from '../utils/version'; import { getVersion } from '../utils/version';
/** /**
@@ -28,29 +28,6 @@ export interface UpdateOptions {
type TargetTag = 'latest' | 'dev'; 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 { export interface UpdateCommandDeps {
initUI: typeof initUI; initUI: typeof initUI;
getVersion: typeof getVersion; getVersion: typeof getVersion;
@@ -64,7 +41,7 @@ export interface UpdateCommandDeps {
interactive: boolean, interactive: boolean,
channel: 'npm' | 'direct', channel: 'npm' | 'direct',
targetTag: TargetTag targetTag: TargetTag
) => Promise<UpdateCheckResult>; ) => Promise<UpdateResult>;
spawn: typeof spawn; spawn: typeof spawn;
} }
@@ -73,14 +50,9 @@ async function loadCheckForUpdates(
interactive: boolean, interactive: boolean,
channel: 'npm' | 'direct', channel: 'npm' | 'direct',
targetTag: TargetTag targetTag: TargetTag
): Promise<UpdateCheckResult> { ): Promise<UpdateResult> {
const { checkForUpdates } = await import('../utils/update-checker'); const { checkForUpdates } = await import('../utils/update-checker');
return checkForUpdates( return checkForUpdates(currentVersion, interactive, channel, targetTag);
currentVersion,
interactive,
channel,
targetTag
) as Promise<UpdateCheckResult>;
} }
const defaultDeps: UpdateCommandDeps = { const defaultDeps: UpdateCommandDeps = {
@@ -150,7 +122,7 @@ export async function handleUpdateCommand(
} }
if (updateResult.status === 'no_update') { if (updateResult.status === 'no_update') {
handleNoUpdate(updateResult.reason); handleNoUpdate(updateResult.reason, currentVersion);
return; return;
} }
@@ -215,9 +187,7 @@ function handleCheckFailed(
/** /**
* Handle no update available * Handle no update available
*/ */
function handleNoUpdate(reason: string | undefined): void { function handleNoUpdate(reason: string | undefined, version: string): void {
const version = getVersion();
let message = `You are already on the latest version (${version})`; let message = `You are already on the latest version (${version})`;
switch (reason) { switch (reason) {
+1 -1
View File
@@ -21,7 +21,7 @@ interface UpdateCache {
dismissed_version: string | null; dismissed_version: string | null;
} }
interface UpdateResult { export interface UpdateResult {
status: 'update_available' | 'no_update' | 'check_failed'; status: 'update_available' | 'no_update' | 'check_failed';
reason?: string; reason?: string;
latest?: string; latest?: string;
@@ -1,5 +1,6 @@
import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
import { handleUpdateCommand, type UpdateCommandDeps } from '../../../src/commands/update-command'; import { handleUpdateCommand, type UpdateCommandDeps } from '../../../src/commands/update-command';
import type { UpdateResult } from '../../../src/utils/update-checker';
let logLines: string[] = []; let logLines: string[] = [];
let spawnCalls: Array<{ command: string; args: string[]; env?: NodeJS.ProcessEnv }> = []; let spawnCalls: Array<{ command: string; args: string[]; env?: NodeJS.ProcessEnv }> = [];
@@ -18,13 +19,8 @@ type Scenario = {
afterState: InstalledState; afterState: InstalledState;
}; };
type UpdateCheckResult =
| { status: 'update_available'; current: string; latest: string }
| { status: 'no_update' }
| { status: 'check_failed'; message: string };
let scenario: Scenario; let scenario: Scenario;
let updateCheckResult: UpdateCheckResult; let updateCheckResult: UpdateResult;
let currentInstallOverride: ReturnType<typeof installDescriptor>; let currentInstallOverride: ReturnType<typeof installDescriptor>;
let stateReads = 0; let stateReads = 0;
@@ -39,7 +35,7 @@ function installDescriptor() {
}; };
} }
function createDeps(): UpdateCommandDeps { function createDeps(overrides: Partial<UpdateCommandDeps> = {}): UpdateCommandDeps {
return { return {
initUI: async () => {}, initUI: async () => {},
getVersion: () => '7.67.0-dev.5', getVersion: () => '7.67.0-dev.5',
@@ -95,6 +91,7 @@ function createDeps(): UpdateCommandDeps {
}, },
}; };
}) as typeof UpdateCommandDeps.prototype.spawn, }) as typeof UpdateCommandDeps.prototype.spawn,
...overrides,
}; };
} }
@@ -196,6 +193,20 @@ describe('update-command current install handling', () => {
expect(exitCodes).toContain(0); 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 () => { it('accepts a newer installed version when the dist-tag moves during update', async () => {
scenario = { scenario = {
beforeState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 }, beforeState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 },