Reinstate support for updating locally-installed extensions (#8833)

This commit is contained in:
christine betts
2025-09-21 23:44:58 -04:00
committed by GitHub
parent 8fdb61aabf
commit d9828e2571
6 changed files with 111 additions and 37 deletions
@@ -128,7 +128,7 @@ describe('git extension helpers', () => {
version: '1.0.0',
isActive: true,
installMetadata: {
type: 'local',
type: 'link',
source: '',
},
};
@@ -16,6 +16,7 @@ import * as https from 'node:https';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { execSync } from 'node:child_process';
import { loadExtension } from '../extension.js';
function getGitHubToken(): string | undefined {
return process.env['GITHUB_TOKEN'];
@@ -115,9 +116,29 @@ async function fetchFromGithub(
export async function checkForExtensionUpdate(
extension: GeminiCLIExtension,
setExtensionUpdateState: (updateState: ExtensionUpdateState) => void,
cwd: string = process.cwd(),
): Promise<void> {
setExtensionUpdateState(ExtensionUpdateState.CHECKING_FOR_UPDATES);
const installMetadata = extension.installMetadata;
if (installMetadata?.type === 'local') {
const newExtension = loadExtension({
extensionDir: installMetadata.source,
workspaceDir: cwd,
});
if (!newExtension) {
console.error(
`Failed to check for update for local extension "${extension.name}". Could not load extension from source path: ${installMetadata.source}`,
);
setExtensionUpdateState(ExtensionUpdateState.ERROR);
return;
}
if (newExtension.config.version !== extension.version) {
setExtensionUpdateState(ExtensionUpdateState.UPDATE_AVAILABLE);
return;
}
setExtensionUpdateState(ExtensionUpdateState.UP_TO_DATE);
return;
}
if (
!installMetadata ||
(installMetadata.type !== 'git' &&
@@ -341,17 +341,24 @@ describe('update tests', () => {
expect(result).toBe(ExtensionUpdateState.UP_TO_DATE);
});
it('should return NotUpdatable for a non-git extension', async () => {
const extensionDir = createExtension({
it('should return UpToDate for a local extension with no updates', async () => {
const localExtensionSourcePath = path.join(tempHomeDir, 'local-source');
const sourceExtensionDir = createExtension({
extensionsDir: localExtensionSourcePath,
name: 'my-local-ext',
version: '1.0.0',
});
const installedExtensionDir = createExtension({
extensionsDir: userExtensionsDir,
name: 'local-extension',
version: '1.0.0',
installMetadata: { source: '/local/path', type: 'local' },
installMetadata: { source: sourceExtensionDir, type: 'local' },
});
const extension = annotateActiveExtensions(
[
loadExtension({
extensionDir,
extensionDir: installedExtensionDir,
workspaceDir: tempWorkspaceDir,
})!,
],
@@ -369,9 +376,51 @@ describe('update tests', () => {
extensionState = newState;
}
},
tempWorkspaceDir,
);
const result = results.get('local-extension');
expect(result).toBe(ExtensionUpdateState.NOT_UPDATABLE);
expect(result).toBe(ExtensionUpdateState.UP_TO_DATE);
});
it('should return UpdateAvailable for a local extension with updates', async () => {
const localExtensionSourcePath = path.join(tempHomeDir, 'local-source');
const sourceExtensionDir = createExtension({
extensionsDir: localExtensionSourcePath,
name: 'my-local-ext',
version: '1.1.0',
});
const installedExtensionDir = createExtension({
extensionsDir: userExtensionsDir,
name: 'local-extension',
version: '1.0.0',
installMetadata: { source: sourceExtensionDir, type: 'local' },
});
const extension = annotateActiveExtensions(
[
loadExtension({
extensionDir: installedExtensionDir,
workspaceDir: tempWorkspaceDir,
})!,
],
[],
process.cwd(),
)[0];
let extensionState = new Map();
const results = await checkForAllExtensionUpdates(
[extension],
extensionState,
(newState) => {
if (typeof newState === 'function') {
newState(extensionState);
} else {
extensionState = newState;
}
},
tempWorkspaceDir,
);
const result = results.get('local-extension');
expect(result).toBe(ExtensionUpdateState.UPDATE_AVAILABLE);
});
it('should return Error when git check fails', async () => {
+12 -7
View File
@@ -128,6 +128,7 @@ export async function checkForAllExtensionUpdates(
setExtensionsUpdateState: Dispatch<
SetStateAction<Map<string, ExtensionUpdateState>>
>,
cwd: string = process.cwd(),
): Promise<Map<string, ExtensionUpdateState>> {
for (const extension of extensions) {
const initialState = extensionsUpdateState.get(extension.name);
@@ -143,13 +144,17 @@ export async function checkForAllExtensionUpdates(
});
continue;
}
await checkForExtensionUpdate(extension, (updatedState) => {
setExtensionsUpdateState((prev) => {
extensionsUpdateState = new Map(prev);
extensionsUpdateState.set(extension.name, updatedState);
return extensionsUpdateState;
});
});
await checkForExtensionUpdate(
extension,
(updatedState) => {
setExtensionsUpdateState((prev) => {
extensionsUpdateState = new Map(prev);
extensionsUpdateState.set(extension.name, updatedState);
return extensionsUpdateState;
});
},
cwd,
);
}
}
return extensionsUpdateState;