Add extensionRegistryURI setting to change where the registry is read from (#20463)

This commit is contained in:
kevinjwang1
2026-03-10 13:22:03 -07:00
committed by GitHub
parent 5caa192cfc
commit 5b8ad9cd65
10 changed files with 134 additions and 16 deletions
@@ -4,7 +4,12 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { fetchWithTimeout } from '@google/gemini-cli-core';
import * as fs from 'node:fs/promises';
import {
fetchWithTimeout,
resolveToRealPath,
isPrivateIp,
} from '@google/gemini-cli-core';
import { AsyncFzf } from 'fzf';
export interface RegistryExtension {
@@ -29,12 +34,19 @@ export interface RegistryExtension {
}
export class ExtensionRegistryClient {
private static readonly REGISTRY_URL =
static readonly DEFAULT_REGISTRY_URL =
'https://geminicli.com/extensions.json';
private static readonly FETCH_TIMEOUT_MS = 10000; // 10 seconds
private static fetchPromise: Promise<RegistryExtension[]> | null = null;
private readonly registryURI: string;
constructor(registryURI?: string) {
this.registryURI =
registryURI || ExtensionRegistryClient.DEFAULT_REGISTRY_URL;
}
/** @internal */
static resetCache() {
ExtensionRegistryClient.fetchPromise = null;
@@ -97,18 +109,34 @@ export class ExtensionRegistryClient {
return ExtensionRegistryClient.fetchPromise;
}
const uri = this.registryURI;
ExtensionRegistryClient.fetchPromise = (async () => {
try {
const response = await fetchWithTimeout(
ExtensionRegistryClient.REGISTRY_URL,
ExtensionRegistryClient.FETCH_TIMEOUT_MS,
);
if (!response.ok) {
throw new Error(`Failed to fetch extensions: ${response.statusText}`);
}
if (uri.startsWith('http')) {
if (isPrivateIp(uri)) {
throw new Error(
'Private IP addresses are not allowed for the extension registry.',
);
}
const response = await fetchWithTimeout(
uri,
ExtensionRegistryClient.FETCH_TIMEOUT_MS,
);
if (!response.ok) {
throw new Error(
`Failed to fetch extensions: ${response.statusText}`,
);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return (await response.json()) as RegistryExtension[];
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return (await response.json()) as RegistryExtension[];
} else {
// Handle local file path
const filePath = resolveToRealPath(uri);
const content = await fs.readFile(filePath, 'utf-8');
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return JSON.parse(content) as RegistryExtension[];
}
} catch (error) {
ExtensionRegistryClient.fetchPromise = null;
throw error;