Add ExtensionLoader interface, use that on Config object (#12116)

This commit is contained in:
Jacob MacDonald
2025-10-28 09:04:30 -07:00
committed by GitHub
parent 25f27509c0
commit 1b302deeff
35 changed files with 619 additions and 505 deletions
@@ -170,7 +170,7 @@ describe('git extension helpers', () => {
workspaceDir: tempWorkspaceDir,
requestConsent: mockRequestConsent,
requestSetting: mockPromptForSettings,
loadedSettings: loadSettings(tempWorkspaceDir),
settings: loadSettings(tempWorkspaceDir).merged,
});
});
+5 -3
View File
@@ -157,14 +157,16 @@ export async function checkForExtensionUpdate(
): Promise<ExtensionUpdateState> {
const installMetadata = extension.installMetadata;
if (installMetadata?.type === 'local') {
const newExtension = extensionManager.loadExtension(installMetadata.source);
if (!newExtension) {
const latestConfig = extensionManager.loadExtensionConfig(
installMetadata.source,
);
if (!latestConfig) {
debugLogger.error(
`Failed to check for update for local extension "${extension.name}". Could not load extension from source path: ${installMetadata.source}`,
);
return ExtensionUpdateState.ERROR;
}
if (newExtension.version !== extension.version) {
if (latestConfig.version !== extension.version) {
return ExtensionUpdateState.UPDATE_AVAILABLE;
}
return ExtensionUpdateState.UP_TO_DATE;
@@ -109,7 +109,7 @@ describe('update tests', () => {
workspaceDir: tempWorkspaceDir,
requestConsent: mockRequestConsent,
requestSetting: mockPromptForSettings,
loadedSettings: loadSettings(tempWorkspaceDir),
settings: loadSettings(tempWorkspaceDir).merged,
});
});
@@ -145,7 +145,9 @@ describe('update tests', () => {
);
});
mockGit.getRemotes.mockResolvedValue([{ name: 'origin' }]);
const extension = extensionManager.loadExtension(targetExtDir)!;
const extension = extensionManager
.loadExtensions()
.find((e) => e.name === extensionName)!;
const updateInfo = await updateExtension(
extension,
extensionManager,
@@ -170,7 +172,7 @@ describe('update tests', () => {
it('should call setExtensionUpdateState with UPDATING and then UPDATED_NEEDS_RESTART on success', async () => {
const extensionName = 'test-extension';
const extensionDir = createExtension({
createExtension({
extensionsDir: userExtensionsDir,
name: extensionName,
version: '1.0.0',
@@ -192,7 +194,10 @@ describe('update tests', () => {
mockGit.getRemotes.mockResolvedValue([{ name: 'origin' }]);
const dispatch = vi.fn();
const extension = extensionManager.loadExtension(extensionDir)!;
const extension = extensionManager
.loadExtensions()
.find((e) => e.name === extensionName)!;
await updateExtension(
extension,
extensionManager,
@@ -218,7 +223,7 @@ describe('update tests', () => {
it('should call setExtensionUpdateState with ERROR on failure', async () => {
const extensionName = 'test-extension';
const extensionDir = createExtension({
createExtension({
extensionsDir: userExtensionsDir,
name: extensionName,
version: '1.0.0',
@@ -232,7 +237,9 @@ describe('update tests', () => {
mockGit.getRemotes.mockResolvedValue([{ name: 'origin' }]);
const dispatch = vi.fn();
const extension = extensionManager.loadExtension(extensionDir)!;
const extension = extensionManager
.loadExtensions()
.find((e) => e.name === extensionName)!;
await expect(
updateExtension(
extension,
@@ -261,7 +268,7 @@ describe('update tests', () => {
describe('checkForAllExtensionUpdates', () => {
it('should return UpdateAvailable for a git extension with updates', async () => {
const extensionDir = createExtension({
createExtension({
extensionsDir: userExtensionsDir,
name: 'test-extension',
version: '1.0.0',
@@ -270,7 +277,6 @@ describe('update tests', () => {
type: 'git',
},
});
const extension = extensionManager.loadExtension(extensionDir)!;
mockGit.getRemotes.mockResolvedValue([
{ name: 'origin', refs: { fetch: 'https://some.git/repo' } },
@@ -280,7 +286,7 @@ describe('update tests', () => {
const dispatch = vi.fn();
await checkForAllExtensionUpdates(
[extension],
extensionManager.loadExtensions(),
extensionManager,
dispatch,
);
@@ -294,7 +300,7 @@ describe('update tests', () => {
});
it('should return UpToDate for a git extension with no updates', async () => {
const extensionDir = createExtension({
createExtension({
extensionsDir: userExtensionsDir,
name: 'test-extension',
version: '1.0.0',
@@ -303,7 +309,6 @@ describe('update tests', () => {
type: 'git',
},
});
const extension = extensionManager.loadExtension(extensionDir)!;
mockGit.getRemotes.mockResolvedValue([
{ name: 'origin', refs: { fetch: 'https://some.git/repo' } },
@@ -313,7 +318,7 @@ describe('update tests', () => {
const dispatch = vi.fn();
await checkForAllExtensionUpdates(
[extension],
extensionManager.loadExtensions(),
extensionManager,
dispatch,
);
@@ -334,16 +339,15 @@ describe('update tests', () => {
version: '1.0.0',
});
const installedExtensionDir = createExtension({
createExtension({
extensionsDir: userExtensionsDir,
name: 'local-extension',
version: '1.0.0',
installMetadata: { source: sourceExtensionDir, type: 'local' },
});
const extension = extensionManager.loadExtension(installedExtensionDir)!;
const dispatch = vi.fn();
await checkForAllExtensionUpdates(
[extension],
extensionManager.loadExtensions(),
extensionManager,
dispatch,
);
@@ -360,20 +364,19 @@ describe('update tests', () => {
const localExtensionSourcePath = path.join(tempHomeDir, 'local-source');
const sourceExtensionDir = createExtension({
extensionsDir: localExtensionSourcePath,
name: 'my-local-ext',
name: 'local-extension',
version: '1.1.0',
});
const installedExtensionDir = createExtension({
createExtension({
extensionsDir: userExtensionsDir,
name: 'local-extension',
version: '1.0.0',
installMetadata: { source: sourceExtensionDir, type: 'local' },
});
const extension = extensionManager.loadExtension(installedExtensionDir)!;
const dispatch = vi.fn();
await checkForAllExtensionUpdates(
[extension],
extensionManager.loadExtensions(),
extensionManager,
dispatch,
);
@@ -387,7 +390,7 @@ describe('update tests', () => {
});
it('should return Error when git check fails', async () => {
const extensionDir = createExtension({
createExtension({
extensionsDir: userExtensionsDir,
name: 'error-extension',
version: '1.0.0',
@@ -396,13 +399,12 @@ describe('update tests', () => {
type: 'git',
},
});
const extension = extensionManager.loadExtension(extensionDir)!;
mockGit.getRemotes.mockRejectedValue(new Error('Git error'));
const dispatch = vi.fn();
await checkForAllExtensionUpdates(
[extension],
extensionManager.loadExtensions(),
extensionManager,
dispatch,
);
+10 -10
View File
@@ -61,20 +61,20 @@ export async function updateExtension(
const previousExtensionConfig = await extensionManager.loadExtensionConfig(
extension.path,
);
await extensionManager.installOrUpdateExtension(
installMetadata,
previousExtensionConfig,
);
const updatedExtensionStorage = new ExtensionStorage(extension.name);
const updatedExtension = extensionManager.loadExtension(
updatedExtensionStorage.getExtensionDir(),
);
if (!updatedExtension) {
let updatedExtension: GeminiCLIExtension;
try {
updatedExtension = await extensionManager.installOrUpdateExtension(
installMetadata,
previousExtensionConfig,
);
} catch (e) {
dispatchExtensionStateUpdate({
type: 'SET_STATE',
payload: { name: extension.name, state: ExtensionUpdateState.ERROR },
});
throw new Error('Updated extension not found after installation.');
throw new Error(
`Updated extension not found after installation, got error:\n${e}`,
);
}
const updatedVersion = updatedExtension.version;
dispatchExtensionStateUpdate({