mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-28 22:14:52 -07:00
Better parsing of github extension source uris (#8736)
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
|||||||
checkForExtensionUpdate,
|
checkForExtensionUpdate,
|
||||||
cloneFromGit,
|
cloneFromGit,
|
||||||
findReleaseAsset,
|
findReleaseAsset,
|
||||||
|
parseGitHubRepoForReleases,
|
||||||
} from './github.js';
|
} from './github.js';
|
||||||
import { simpleGit, type SimpleGit } from 'simple-git';
|
import { simpleGit, type SimpleGit } from 'simple-git';
|
||||||
import { ExtensionUpdateState } from '../../ui/state/extensions.js';
|
import { ExtensionUpdateState } from '../../ui/state/extensions.js';
|
||||||
@@ -231,4 +232,55 @@ describe('git extension helpers', () => {
|
|||||||
expect(result).toBeUndefined();
|
expect(result).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('parseGitHubRepoForReleases', () => {
|
||||||
|
it('should parse owner and repo from a full GitHub URL', () => {
|
||||||
|
const source = 'https://github.com/owner/repo.git';
|
||||||
|
const { owner, repo } = parseGitHubRepoForReleases(source);
|
||||||
|
expect(owner).toBe('owner');
|
||||||
|
expect(repo).toBe('repo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse owner and repo from a full GitHub UR without .git', () => {
|
||||||
|
const source = 'https://github.com/owner/repo';
|
||||||
|
const { owner, repo } = parseGitHubRepoForReleases(source);
|
||||||
|
expect(owner).toBe('owner');
|
||||||
|
expect(repo).toBe('repo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail on a GitHub SSH URL', () => {
|
||||||
|
const source = 'git@github.com:owner/repo.git';
|
||||||
|
expect(() => parseGitHubRepoForReleases(source)).toThrow(
|
||||||
|
'GitHub release-based extensions are not supported for SSH. You must use an HTTPS URI with a personal access token to download releases from private repositories. You can set your personal access token in the GITHUB_TOKEN environment variable and install the extension via SSH.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse owner and repo from a shorthand string', () => {
|
||||||
|
const source = 'owner/repo';
|
||||||
|
const { owner, repo } = parseGitHubRepoForReleases(source);
|
||||||
|
expect(owner).toBe('owner');
|
||||||
|
expect(repo).toBe('repo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle .git suffix in repo name', () => {
|
||||||
|
const source = 'owner/repo.git';
|
||||||
|
const { owner, repo } = parseGitHubRepoForReleases(source);
|
||||||
|
expect(owner).toBe('owner');
|
||||||
|
expect(repo).toBe('repo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for invalid source format', () => {
|
||||||
|
const source = 'invalid-format';
|
||||||
|
expect(() => parseGitHubRepoForReleases(source)).toThrow(
|
||||||
|
'Invalid GitHub repository source: invalid-format. Expected "owner/repo" or a github repo uri.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for source with too many parts', () => {
|
||||||
|
const source = 'https://github.com/owner/repo/extra';
|
||||||
|
expect(() => parseGitHubRepoForReleases(source)).toThrow(
|
||||||
|
'Invalid GitHub repository source: https://github.com/owner/repo/extra. Expected "owner/repo" or a github repo uri.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -74,20 +74,28 @@ export async function cloneFromGit(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseGitHubRepo(source: string): { owner: string; repo: string } {
|
export function parseGitHubRepoForReleases(source: string): {
|
||||||
// The source should be "owner/repo" or a full GitHub URL.
|
owner: string;
|
||||||
const parts = source.split('/');
|
repo: string;
|
||||||
if (!source.includes('://') && parts.length !== 2) {
|
} {
|
||||||
|
// Default to a github repo path, so `source` can be just an org/repo
|
||||||
|
const parsedUrl = URL.parse(source, 'https://github.com');
|
||||||
|
// The pathname should be "/owner/repo".
|
||||||
|
const parts = parsedUrl?.pathname.substring(1).split('/');
|
||||||
|
if (parts?.length !== 2) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid GitHub repository source: ${source}. Expected "owner/repo".`,
|
`Invalid GitHub repository source: ${source}. Expected "owner/repo" or a github repo uri.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const owner = parts.at(-2);
|
const owner = parts[0];
|
||||||
const repo = parts.at(-1)?.replace('.git', '');
|
const repo = parts[1].replace('.git', '');
|
||||||
|
|
||||||
if (!owner || !repo) {
|
if (owner.startsWith('git@github.com')) {
|
||||||
throw new Error(`Invalid GitHub repository source: ${source}`);
|
throw new Error(
|
||||||
|
`GitHub release-based extensions are not supported for SSH. You must use an HTTPS URI with a personal access token to download releases from private repositories. You can set your personal access token in the GITHUB_TOKEN environment variable and install the extension via SSH.`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { owner, repo };
|
return { owner, repo };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +163,7 @@ export async function checkForExtensionUpdate(
|
|||||||
if (!source) {
|
if (!source) {
|
||||||
return ExtensionUpdateState.ERROR;
|
return ExtensionUpdateState.ERROR;
|
||||||
}
|
}
|
||||||
const { owner, repo } = parseGitHubRepo(source);
|
const { owner, repo } = parseGitHubRepoForReleases(source);
|
||||||
|
|
||||||
const releaseData = await fetchFromGithub(
|
const releaseData = await fetchFromGithub(
|
||||||
owner,
|
owner,
|
||||||
@@ -180,7 +188,7 @@ export async function downloadFromGitHubRelease(
|
|||||||
destination: string,
|
destination: string,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const { source, ref } = installMetadata;
|
const { source, ref } = installMetadata;
|
||||||
const { owner, repo } = parseGitHubRepo(source);
|
const { owner, repo } = parseGitHubRepoForReleases(source);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const releaseData = await fetchFromGithub(owner, repo, ref);
|
const releaseData = await fetchFromGithub(owner, repo, ref);
|
||||||
|
|||||||
Reference in New Issue
Block a user