Pass whole extensions rather than just context files (#10910)

Co-authored-by: Jake Macdonald <jakemac@google.com>
This commit is contained in:
Zack Birkenbuel
2025-10-20 16:15:23 -07:00
committed by GitHub
parent 995ae717cc
commit cc7e1472f9
35 changed files with 487 additions and 1193 deletions
@@ -22,6 +22,7 @@ import * as path from 'node:path';
import * as tar from 'tar';
import * as archiver from 'archiver';
import type { GeminiCLIExtension } from '@google/gemini-cli-core';
import { ExtensionEnablementManager } from './extensionEnablement.js';
const mockPlatform = vi.hoisted(() => vi.fn());
const mockArch = vi.hoisted(() => vi.fn());
@@ -149,7 +150,10 @@ describe('git extension helpers', () => {
},
contextFiles: [],
};
const result = await checkForExtensionUpdate(extension);
const result = await checkForExtensionUpdate(
extension,
new ExtensionEnablementManager(),
);
expect(result).toBe(ExtensionUpdateState.NOT_UPDATABLE);
});
@@ -166,7 +170,10 @@ describe('git extension helpers', () => {
contextFiles: [],
};
mockGit.getRemotes.mockResolvedValue([]);
const result = await checkForExtensionUpdate(extension);
const result = await checkForExtensionUpdate(
extension,
new ExtensionEnablementManager(),
);
expect(result).toBe(ExtensionUpdateState.ERROR);
});
@@ -188,7 +195,10 @@ describe('git extension helpers', () => {
mockGit.listRemote.mockResolvedValue('remote-hash\tHEAD');
mockGit.revparse.mockResolvedValue('local-hash');
const result = await checkForExtensionUpdate(extension);
const result = await checkForExtensionUpdate(
extension,
new ExtensionEnablementManager(),
);
expect(result).toBe(ExtensionUpdateState.UPDATE_AVAILABLE);
});
@@ -210,7 +220,10 @@ describe('git extension helpers', () => {
mockGit.listRemote.mockResolvedValue('same-hash\tHEAD');
mockGit.revparse.mockResolvedValue('same-hash');
const result = await checkForExtensionUpdate(extension);
const result = await checkForExtensionUpdate(
extension,
new ExtensionEnablementManager(),
);
expect(result).toBe(ExtensionUpdateState.UP_TO_DATE);
});
@@ -228,7 +241,10 @@ describe('git extension helpers', () => {
};
mockGit.getRemotes.mockRejectedValue(new Error('git error'));
const result = await checkForExtensionUpdate(extension);
const result = await checkForExtensionUpdate(
extension,
new ExtensionEnablementManager(),
);
expect(result).toBe(ExtensionUpdateState.ERROR);
});
});
@@ -20,6 +20,7 @@ import { EXTENSIONS_CONFIG_FILENAME, loadExtension } from '../extension.js';
import * as tar from 'tar';
import extract from 'extract-zip';
import { fetchJson, getGitHubToken } from './github_fetch.js';
import { type ExtensionEnablementManager } from './extensionEnablement.js';
/**
* Clones a Git repository to a specified local path.
@@ -152,6 +153,7 @@ export async function fetchReleaseFromGithub(
export async function checkForExtensionUpdate(
extension: GeminiCLIExtension,
extensionEnablementManager: ExtensionEnablementManager,
cwd: string = process.cwd(),
): Promise<ExtensionUpdateState> {
const installMetadata = extension.installMetadata;
@@ -159,6 +161,7 @@ export async function checkForExtensionUpdate(
const newExtension = loadExtension({
extensionDir: installMetadata.source,
workspaceDir: cwd,
extensionEnablementManager,
});
if (!newExtension) {
debugLogger.error(
@@ -11,7 +11,6 @@ import * as path from 'node:path';
import {
EXTENSIONS_CONFIG_FILENAME,
INSTALL_METADATA_FILENAME,
annotateActiveExtensions,
loadExtension,
} from '../extension.js';
import { checkForAllExtensionUpdates, updateExtension } from './update.js';
@@ -128,18 +127,15 @@ describe('update tests', () => {
);
});
mockGit.getRemotes.mockResolvedValue([{ name: 'origin' }]);
const extension = annotateActiveExtensions(
[
loadExtension({
extensionDir: targetExtDir,
workspaceDir: tempWorkspaceDir,
})!,
],
process.cwd(),
new ExtensionEnablementManager(),
)[0];
const extensionEnablementManager = new ExtensionEnablementManager();
const extension = loadExtension({
extensionDir: targetExtDir,
workspaceDir: tempWorkspaceDir,
extensionEnablementManager,
})!;
const updateInfo = await updateExtension(
extension,
extensionEnablementManager,
tempHomeDir,
async (_) => true,
ExtensionUpdateState.UPDATE_AVAILABLE,
@@ -185,18 +181,15 @@ describe('update tests', () => {
mockGit.getRemotes.mockResolvedValue([{ name: 'origin' }]);
const dispatch = vi.fn();
const extension = annotateActiveExtensions(
[
loadExtension({
extensionDir,
workspaceDir: tempWorkspaceDir,
})!,
],
process.cwd(),
new ExtensionEnablementManager(),
)[0];
const extensionEnablementManager = new ExtensionEnablementManager();
const extension = loadExtension({
extensionDir,
workspaceDir: tempWorkspaceDir,
extensionEnablementManager,
})!;
await updateExtension(
extension,
extensionEnablementManager,
tempHomeDir,
async (_) => true,
ExtensionUpdateState.UPDATE_AVAILABLE,
@@ -235,19 +228,16 @@ describe('update tests', () => {
mockGit.getRemotes.mockResolvedValue([{ name: 'origin' }]);
const dispatch = vi.fn();
const extension = annotateActiveExtensions(
[
loadExtension({
extensionDir,
workspaceDir: tempWorkspaceDir,
})!,
],
process.cwd(),
new ExtensionEnablementManager(),
)[0];
const extensionEnablementManager = new ExtensionEnablementManager();
const extension = loadExtension({
extensionDir,
workspaceDir: tempWorkspaceDir,
extensionEnablementManager,
})!;
await expect(
updateExtension(
extension,
extensionEnablementManager,
tempHomeDir,
async (_) => true,
ExtensionUpdateState.UPDATE_AVAILABLE,
@@ -283,16 +273,12 @@ describe('update tests', () => {
type: 'git',
},
});
const extension = annotateActiveExtensions(
[
loadExtension({
extensionDir,
workspaceDir: tempWorkspaceDir,
})!,
],
process.cwd(),
new ExtensionEnablementManager(),
)[0];
const extensionEnablementManager = new ExtensionEnablementManager();
const extension = loadExtension({
extensionDir,
workspaceDir: tempWorkspaceDir,
extensionEnablementManager,
})!;
mockGit.getRemotes.mockResolvedValue([
{ name: 'origin', refs: { fetch: 'https://some.git/repo' } },
@@ -303,6 +289,7 @@ describe('update tests', () => {
const dispatch = vi.fn();
await checkForAllExtensionUpdates(
[extension],
extensionEnablementManager,
dispatch,
tempWorkspaceDir,
);
@@ -325,16 +312,12 @@ describe('update tests', () => {
type: 'git',
},
});
const extension = annotateActiveExtensions(
[
loadExtension({
extensionDir,
workspaceDir: tempWorkspaceDir,
})!,
],
process.cwd(),
new ExtensionEnablementManager(),
)[0];
const extensionEnablementManager = new ExtensionEnablementManager();
const extension = loadExtension({
extensionDir,
workspaceDir: tempWorkspaceDir,
extensionEnablementManager,
})!;
mockGit.getRemotes.mockResolvedValue([
{ name: 'origin', refs: { fetch: 'https://some.git/repo' } },
@@ -345,6 +328,7 @@ describe('update tests', () => {
const dispatch = vi.fn();
await checkForAllExtensionUpdates(
[extension],
extensionEnablementManager,
dispatch,
tempWorkspaceDir,
);
@@ -371,19 +355,16 @@ describe('update tests', () => {
version: '1.0.0',
installMetadata: { source: sourceExtensionDir, type: 'local' },
});
const extension = annotateActiveExtensions(
[
loadExtension({
extensionDir: installedExtensionDir,
workspaceDir: tempWorkspaceDir,
})!,
],
process.cwd(),
new ExtensionEnablementManager(),
)[0];
const extensionEnablementManager = new ExtensionEnablementManager();
const extension = loadExtension({
extensionDir: installedExtensionDir,
workspaceDir: tempWorkspaceDir,
extensionEnablementManager,
})!;
const dispatch = vi.fn();
await checkForAllExtensionUpdates(
[extension],
extensionEnablementManager,
dispatch,
tempWorkspaceDir,
);
@@ -410,19 +391,16 @@ describe('update tests', () => {
version: '1.0.0',
installMetadata: { source: sourceExtensionDir, type: 'local' },
});
const extension = annotateActiveExtensions(
[
loadExtension({
extensionDir: installedExtensionDir,
workspaceDir: tempWorkspaceDir,
})!,
],
process.cwd(),
new ExtensionEnablementManager(),
)[0];
const extensionEnablementManager = new ExtensionEnablementManager();
const extension = loadExtension({
extensionDir: installedExtensionDir,
workspaceDir: tempWorkspaceDir,
extensionEnablementManager,
})!;
const dispatch = vi.fn();
await checkForAllExtensionUpdates(
[extension],
extensionEnablementManager,
dispatch,
tempWorkspaceDir,
);
@@ -445,22 +423,19 @@ describe('update tests', () => {
type: 'git',
},
});
const extension = annotateActiveExtensions(
[
loadExtension({
extensionDir,
workspaceDir: tempWorkspaceDir,
})!,
],
process.cwd(),
new ExtensionEnablementManager(),
)[0];
const extensionEnablementManager = new ExtensionEnablementManager();
const extension = loadExtension({
extensionDir,
workspaceDir: tempWorkspaceDir,
extensionEnablementManager,
})!;
mockGit.getRemotes.mockRejectedValue(new Error('Git error'));
const dispatch = vi.fn();
await checkForAllExtensionUpdates(
[extension],
extensionEnablementManager,
dispatch,
tempWorkspaceDir,
);
+13 -5
View File
@@ -21,6 +21,7 @@ import { checkForExtensionUpdate } from './github.js';
import { debugLogger, type GeminiCLIExtension } from '@google/gemini-cli-core';
import * as fs from 'node:fs';
import { getErrorMessage } from '../../utils/errors.js';
import { type ExtensionEnablementManager } from './extensionEnablement.js';
export interface ExtensionUpdateInfo {
name: string;
@@ -30,6 +31,7 @@ export interface ExtensionUpdateInfo {
export async function updateExtension(
extension: GeminiCLIExtension,
extensionEnablementManager: ExtensionEnablementManager,
cwd: string = process.cwd(),
requestConsent: (consent: string) => Promise<boolean>,
currentState: ExtensionUpdateState,
@@ -67,6 +69,7 @@ export async function updateExtension(
const previousExtensionConfig = await loadExtensionConfig({
extensionDir: extension.path,
workspaceDir: cwd,
extensionEnablementManager,
});
await installOrUpdateExtension(
installMetadata,
@@ -79,6 +82,7 @@ export async function updateExtension(
const updatedExtension = loadExtension({
extensionDir: updatedExtensionStorage.getExtensionDir(),
workspaceDir: cwd,
extensionEnablementManager,
});
if (!updatedExtension) {
dispatchExtensionStateUpdate({
@@ -120,6 +124,7 @@ export async function updateAllUpdatableExtensions(
requestConsent: (consent: string) => Promise<boolean>,
extensions: GeminiCLIExtension[],
extensionsState: Map<string, ExtensionUpdateStatus>,
extensionEnablementManager: ExtensionEnablementManager,
dispatch: (action: ExtensionUpdateAction) => void,
): Promise<ExtensionUpdateInfo[]> {
return (
@@ -133,6 +138,7 @@ export async function updateAllUpdatableExtensions(
.map((extension) =>
updateExtension(
extension,
extensionEnablementManager,
cwd,
requestConsent,
extensionsState.get(extension.name)!.status,
@@ -150,6 +156,7 @@ export interface ExtensionUpdateCheckResult {
export async function checkForAllExtensionUpdates(
extensions: GeminiCLIExtension[],
extensionEnablementManager: ExtensionEnablementManager,
dispatch: (action: ExtensionUpdateAction) => void,
cwd: string = process.cwd(),
): Promise<void> {
@@ -174,11 +181,12 @@ export async function checkForAllExtensionUpdates(
},
});
promises.push(
checkForExtensionUpdate(extension, cwd).then((state) =>
dispatch({
type: 'SET_STATE',
payload: { name: extension.name, state },
}),
checkForExtensionUpdate(extension, extensionEnablementManager, cwd).then(
(state) =>
dispatch({
type: 'SET_STATE',
payload: { name: extension.name, state },
}),
),
);
}
@@ -4,6 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import type { ExtensionEnablementManager } from './extensionEnablement.js';
export interface VariableDefinition {
type: 'string';
description: string;
@@ -18,6 +20,7 @@ export interface VariableSchema {
export interface LoadExtensionContext {
extensionDir: string;
workspaceDir: string;
extensionEnablementManager: ExtensionEnablementManager;
}
const PATH_SEPARATOR_DEFINITION = {