mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-25 04:24:51 -07:00
fix(update): replace update-notifier with latest-version (#11989)
This commit is contained in:
@@ -13,9 +13,9 @@ vi.mock('../../utils/package.js', () => ({
|
||||
getPackageJson,
|
||||
}));
|
||||
|
||||
const updateNotifier = vi.hoisted(() => vi.fn());
|
||||
vi.mock('update-notifier', () => ({
|
||||
default: updateNotifier,
|
||||
const latestVersion = vi.hoisted(() => vi.fn());
|
||||
vi.mock('latest-version', () => ({
|
||||
default: latestVersion,
|
||||
}));
|
||||
|
||||
describe('checkForUpdates', () => {
|
||||
@@ -46,7 +46,7 @@ describe('checkForUpdates', () => {
|
||||
const result = await checkForUpdates(mockSettings);
|
||||
expect(result).toBeNull();
|
||||
expect(getPackageJson).not.toHaveBeenCalled();
|
||||
expect(updateNotifier).not.toHaveBeenCalled();
|
||||
expect(latestVersion).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return null when running from source (DEV=true)', async () => {
|
||||
@@ -55,15 +55,11 @@ describe('checkForUpdates', () => {
|
||||
name: 'test-package',
|
||||
version: '1.0.0',
|
||||
});
|
||||
updateNotifier.mockReturnValue({
|
||||
fetchInfo: vi
|
||||
.fn()
|
||||
.mockResolvedValue({ current: '1.0.0', latest: '1.1.0' }),
|
||||
});
|
||||
latestVersion.mockResolvedValue('1.1.0');
|
||||
const result = await checkForUpdates(mockSettings);
|
||||
expect(result).toBeNull();
|
||||
expect(getPackageJson).not.toHaveBeenCalled();
|
||||
expect(updateNotifier).not.toHaveBeenCalled();
|
||||
expect(latestVersion).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return null if package.json is missing', async () => {
|
||||
@@ -77,9 +73,7 @@ describe('checkForUpdates', () => {
|
||||
name: 'test-package',
|
||||
version: '1.0.0',
|
||||
});
|
||||
updateNotifier.mockReturnValue({
|
||||
fetchInfo: vi.fn().mockResolvedValue(null),
|
||||
});
|
||||
latestVersion.mockResolvedValue('1.0.0');
|
||||
const result = await checkForUpdates(mockSettings);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
@@ -89,15 +83,13 @@ describe('checkForUpdates', () => {
|
||||
name: 'test-package',
|
||||
version: '1.0.0',
|
||||
});
|
||||
updateNotifier.mockReturnValue({
|
||||
fetchInfo: vi
|
||||
.fn()
|
||||
.mockResolvedValue({ current: '1.0.0', latest: '1.1.0' }),
|
||||
});
|
||||
latestVersion.mockResolvedValue('1.1.0');
|
||||
|
||||
const result = await checkForUpdates(mockSettings);
|
||||
expect(result?.message).toContain('1.0.0 → 1.1.0');
|
||||
expect(result?.update).toEqual({ current: '1.0.0', latest: '1.1.0' });
|
||||
expect(result?.update.current).toEqual('1.0.0');
|
||||
expect(result?.update.latest).toEqual('1.1.0');
|
||||
expect(result?.update.name).toEqual('test-package');
|
||||
});
|
||||
|
||||
it('should return null if the latest version is the same as the current version', async () => {
|
||||
@@ -105,11 +97,7 @@ describe('checkForUpdates', () => {
|
||||
name: 'test-package',
|
||||
version: '1.0.0',
|
||||
});
|
||||
updateNotifier.mockReturnValue({
|
||||
fetchInfo: vi
|
||||
.fn()
|
||||
.mockResolvedValue({ current: '1.0.0', latest: '1.0.0' }),
|
||||
});
|
||||
latestVersion.mockResolvedValue('1.0.0');
|
||||
const result = await checkForUpdates(mockSettings);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
@@ -119,23 +107,17 @@ describe('checkForUpdates', () => {
|
||||
name: 'test-package',
|
||||
version: '1.1.0',
|
||||
});
|
||||
updateNotifier.mockReturnValue({
|
||||
fetchInfo: vi
|
||||
.fn()
|
||||
.mockResolvedValue({ current: '1.1.0', latest: '1.0.0' }),
|
||||
});
|
||||
latestVersion.mockResolvedValue('1.0.0');
|
||||
const result = await checkForUpdates(mockSettings);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null if fetchInfo rejects', async () => {
|
||||
it('should return null if latestVersion rejects', async () => {
|
||||
getPackageJson.mockResolvedValue({
|
||||
name: 'test-package',
|
||||
version: '1.0.0',
|
||||
});
|
||||
updateNotifier.mockReturnValue({
|
||||
fetchInfo: vi.fn().mockRejectedValue(new Error('Timeout')),
|
||||
});
|
||||
latestVersion.mockRejectedValue(new Error('Timeout'));
|
||||
|
||||
const result = await checkForUpdates(mockSettings);
|
||||
expect(result).toBeNull();
|
||||
@@ -154,26 +136,13 @@ describe('checkForUpdates', () => {
|
||||
version: '1.2.3-nightly.1',
|
||||
});
|
||||
|
||||
const fetchInfoMock = vi.fn().mockImplementation(({ distTag }) => {
|
||||
if (distTag === 'nightly') {
|
||||
return Promise.resolve({
|
||||
latest: '1.2.3-nightly.2',
|
||||
current: '1.2.3-nightly.1',
|
||||
});
|
||||
latestVersion.mockImplementation(async (name, options) => {
|
||||
if (options?.version === 'nightly') {
|
||||
return '1.2.3-nightly.2';
|
||||
}
|
||||
if (distTag === 'latest') {
|
||||
return Promise.resolve({
|
||||
latest: '1.2.3',
|
||||
current: '1.2.3-nightly.1',
|
||||
});
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
return '1.2.3';
|
||||
});
|
||||
|
||||
updateNotifier.mockImplementation(({ pkg, distTag }) => ({
|
||||
fetchInfo: () => fetchInfoMock({ pkg, distTag }),
|
||||
}));
|
||||
|
||||
const result = await checkForUpdates(mockSettings);
|
||||
expect(result?.message).toContain('1.2.3-nightly.1 → 1.2.3-nightly.2');
|
||||
expect(result?.update.latest).toBe('1.2.3-nightly.2');
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { UpdateInfo } from 'update-notifier';
|
||||
import updateNotifier from 'update-notifier';
|
||||
import latestVersion from 'latest-version';
|
||||
import semver from 'semver';
|
||||
import { getPackageJson } from '../../utils/package.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
@@ -13,32 +12,35 @@ import { debugLogger } from '@google/gemini-cli-core';
|
||||
|
||||
export const FETCH_TIMEOUT_MS = 2000;
|
||||
|
||||
// Replicating the bits of UpdateInfo we need from update-notifier
|
||||
export interface UpdateInfo {
|
||||
latest: string;
|
||||
current: string;
|
||||
name: string;
|
||||
type?: semver.ReleaseType;
|
||||
}
|
||||
|
||||
export interface UpdateObject {
|
||||
message: string;
|
||||
update: UpdateInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* From a nightly and stable update, determines which is the "best" one to offer.
|
||||
* From a nightly and stable version, determines which is the "best" one to offer.
|
||||
* The rule is to always prefer nightly if the base versions are the same.
|
||||
*/
|
||||
function getBestAvailableUpdate(
|
||||
nightly?: UpdateInfo,
|
||||
stable?: UpdateInfo,
|
||||
): UpdateInfo | null {
|
||||
nightly?: string,
|
||||
stable?: string,
|
||||
): string | null {
|
||||
if (!nightly) return stable || null;
|
||||
if (!stable) return nightly || null;
|
||||
|
||||
const nightlyVer = nightly.latest;
|
||||
const stableVer = stable.latest;
|
||||
|
||||
if (
|
||||
semver.coerce(stableVer)?.version === semver.coerce(nightlyVer)?.version
|
||||
) {
|
||||
if (semver.coerce(stable)?.version === semver.coerce(nightly)?.version) {
|
||||
return nightly;
|
||||
}
|
||||
|
||||
return semver.gt(stableVer, nightlyVer) ? stable : nightly;
|
||||
return semver.gt(stable, nightly) ? stable : nightly;
|
||||
}
|
||||
|
||||
export async function checkForUpdates(
|
||||
@@ -59,43 +61,42 @@ export async function checkForUpdates(
|
||||
|
||||
const { name, version: currentVersion } = packageJson;
|
||||
const isNightly = currentVersion.includes('nightly');
|
||||
const createNotifier = (distTag: 'latest' | 'nightly') =>
|
||||
updateNotifier({
|
||||
pkg: {
|
||||
name,
|
||||
version: currentVersion,
|
||||
},
|
||||
updateCheckInterval: 0,
|
||||
shouldNotifyInNpmScript: true,
|
||||
distTag,
|
||||
});
|
||||
|
||||
if (isNightly) {
|
||||
const [nightlyUpdateInfo, latestUpdateInfo] = await Promise.all([
|
||||
createNotifier('nightly').fetchInfo(),
|
||||
createNotifier('latest').fetchInfo(),
|
||||
const [nightlyUpdate, latestUpdate] = await Promise.all([
|
||||
latestVersion(name, { version: 'nightly' }),
|
||||
latestVersion(name),
|
||||
]);
|
||||
|
||||
const bestUpdate = getBestAvailableUpdate(
|
||||
nightlyUpdateInfo,
|
||||
latestUpdateInfo,
|
||||
);
|
||||
const bestUpdate = getBestAvailableUpdate(nightlyUpdate, latestUpdate);
|
||||
|
||||
if (bestUpdate && semver.gt(bestUpdate.latest, currentVersion)) {
|
||||
const message = `A new version of Gemini CLI is available! ${currentVersion} → ${bestUpdate.latest}`;
|
||||
if (bestUpdate && semver.gt(bestUpdate, currentVersion)) {
|
||||
const message = `A new version of Gemini CLI is available! ${currentVersion} → ${bestUpdate}`;
|
||||
const type = semver.diff(bestUpdate, currentVersion) || undefined;
|
||||
return {
|
||||
message,
|
||||
update: { ...bestUpdate, current: currentVersion },
|
||||
update: {
|
||||
latest: bestUpdate,
|
||||
current: currentVersion,
|
||||
name,
|
||||
type,
|
||||
},
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const updateInfo = await createNotifier('latest').fetchInfo();
|
||||
const latestUpdate = await latestVersion(name);
|
||||
|
||||
if (updateInfo && semver.gt(updateInfo.latest, currentVersion)) {
|
||||
const message = `Gemini CLI update available! ${currentVersion} → ${updateInfo.latest}`;
|
||||
if (latestUpdate && semver.gt(latestUpdate, currentVersion)) {
|
||||
const message = `Gemini CLI update available! ${currentVersion} → ${latestUpdate}`;
|
||||
const type = semver.diff(latestUpdate, currentVersion) || undefined;
|
||||
return {
|
||||
message,
|
||||
update: { ...updateInfo, current: currentVersion },
|
||||
update: {
|
||||
latest: latestUpdate,
|
||||
current: currentVersion,
|
||||
name,
|
||||
type,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user