Add support for auto-updating git extensions (#8511)

This commit is contained in:
Jacob MacDonald
2025-09-18 14:49:47 -07:00
committed by GitHub
parent e94ce7e2fd
commit 22b7d86574
20 changed files with 1314 additions and 529 deletions

View File

@@ -14,7 +14,7 @@ import {
import { simpleGit, type SimpleGit } from 'simple-git';
import { ExtensionUpdateState } from '../../ui/state/extensions.js';
import type * as os from 'node:os';
import type { ExtensionInstallMetadata } from '@google/gemini-cli-core';
import type { GeminiCLIExtension } from '@google/gemini-cli-core';
const mockPlatform = vi.hoisted(() => vi.fn());
const mockArch = vi.hoisted(() => vi.fn());
@@ -122,28 +122,54 @@ describe('git extension helpers', () => {
});
it('should return NOT_UPDATABLE for non-git extensions', async () => {
const installMetadata: ExtensionInstallMetadata = {
type: 'local',
source: '',
const extension: GeminiCLIExtension = {
name: 'test',
path: '/ext',
version: '1.0.0',
isActive: true,
installMetadata: {
type: 'local',
source: '',
},
};
const result = await checkForExtensionUpdate(installMetadata);
let result: ExtensionUpdateState | undefined = undefined;
await checkForExtensionUpdate(
extension,
(newState) => (result = newState),
);
expect(result).toBe(ExtensionUpdateState.NOT_UPDATABLE);
});
it('should return ERROR if no remotes found', async () => {
const installMetadata: ExtensionInstallMetadata = {
type: 'git',
source: '',
const extension: GeminiCLIExtension = {
name: 'test',
path: '/ext',
version: '1.0.0',
isActive: true,
installMetadata: {
type: 'git',
source: '',
},
};
mockGit.getRemotes.mockResolvedValue([]);
const result = await checkForExtensionUpdate(installMetadata);
let result: ExtensionUpdateState | undefined = undefined;
await checkForExtensionUpdate(
extension,
(newState) => (result = newState),
);
expect(result).toBe(ExtensionUpdateState.ERROR);
});
it('should return UPDATE_AVAILABLE when remote hash is different', async () => {
const installMetadata: ExtensionInstallMetadata = {
type: 'git',
source: '/ext',
const extension: GeminiCLIExtension = {
name: 'test',
path: '/ext',
version: '1.0.0',
isActive: true,
installMetadata: {
type: 'git',
source: 'my/ext',
},
};
mockGit.getRemotes.mockResolvedValue([
{ name: 'origin', refs: { fetch: 'http://my-repo.com' } },
@@ -151,14 +177,24 @@ describe('git extension helpers', () => {
mockGit.listRemote.mockResolvedValue('remote-hash\tHEAD');
mockGit.revparse.mockResolvedValue('local-hash');
const result = await checkForExtensionUpdate(installMetadata);
let result: ExtensionUpdateState | undefined = undefined;
await checkForExtensionUpdate(
extension,
(newState) => (result = newState),
);
expect(result).toBe(ExtensionUpdateState.UPDATE_AVAILABLE);
});
it('should return UP_TO_DATE when remote and local hashes are the same', async () => {
const installMetadata: ExtensionInstallMetadata = {
type: 'git',
source: '/ext',
const extension: GeminiCLIExtension = {
name: 'test',
path: '/ext',
version: '1.0.0',
isActive: true,
installMetadata: {
type: 'git',
source: 'my/ext',
},
};
mockGit.getRemotes.mockResolvedValue([
{ name: 'origin', refs: { fetch: 'http://my-repo.com' } },
@@ -166,17 +202,32 @@ describe('git extension helpers', () => {
mockGit.listRemote.mockResolvedValue('same-hash\tHEAD');
mockGit.revparse.mockResolvedValue('same-hash');
const result = await checkForExtensionUpdate(installMetadata);
let result: ExtensionUpdateState | undefined = undefined;
await checkForExtensionUpdate(
extension,
(newState) => (result = newState),
);
expect(result).toBe(ExtensionUpdateState.UP_TO_DATE);
});
it('should return ERROR on git error', async () => {
const installMetadata: ExtensionInstallMetadata = {
type: 'git',
source: '/ext',
const extension: GeminiCLIExtension = {
name: 'test',
path: '/ext',
version: '1.0.0',
isActive: true,
installMetadata: {
type: 'git',
source: 'my/ext',
},
};
mockGit.getRemotes.mockRejectedValue(new Error('git error'));
const result = await checkForExtensionUpdate(installMetadata);
let result: ExtensionUpdateState | undefined = undefined;
await checkForExtensionUpdate(
extension,
(newState) => (result = newState),
);
expect(result).toBe(ExtensionUpdateState.ERROR);
});
});