feat: open repo secrets page in addition to README (#5684)
This commit is contained in:
parent
6ae75c9f32
commit
8d848dca4a
|
@ -18,6 +18,7 @@ vi.mock('../../utils/gitUtils.js', () => ({
|
|||
isGitHubRepository: vi.fn(),
|
||||
getGitRepoRoot: vi.fn(),
|
||||
getLatestGitHubRelease: vi.fn(),
|
||||
getGitHubRepoInfo: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('setupGithubCommand', async () => {
|
||||
|
@ -30,7 +31,9 @@ describe('setupGithubCommand', async () => {
|
|||
});
|
||||
|
||||
it('returns a tool action to download github workflows and handles paths', async () => {
|
||||
const fakeRepoRoot = '/github.com/fake/repo/root';
|
||||
const fakeRepoOwner = 'fake';
|
||||
const fakeRepoName = 'repo';
|
||||
const fakeRepoRoot = `/github.com/${fakeRepoOwner}/${fakeRepoName}/root`;
|
||||
const fakeReleaseVersion = 'v1.2.3';
|
||||
|
||||
vi.mocked(gitUtils.isGitHubRepository).mockReturnValueOnce(true);
|
||||
|
@ -38,6 +41,10 @@ describe('setupGithubCommand', async () => {
|
|||
vi.mocked(gitUtils.getLatestGitHubRelease).mockResolvedValueOnce(
|
||||
fakeReleaseVersion,
|
||||
);
|
||||
vi.mocked(gitUtils.getGitHubRepoInfo).mockReturnValue({
|
||||
owner: fakeRepoOwner,
|
||||
repo: fakeRepoName,
|
||||
});
|
||||
|
||||
const result = (await setupGithubCommand.action?.(
|
||||
{} as CommandContext,
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
getGitRepoRoot,
|
||||
getLatestGitHubRelease,
|
||||
isGitHubRepository,
|
||||
getGitHubRepoInfo,
|
||||
} from '../../utils/gitUtils.js';
|
||||
|
||||
import {
|
||||
|
@ -18,6 +19,27 @@ import {
|
|||
SlashCommand,
|
||||
SlashCommandActionReturn,
|
||||
} from './types.js';
|
||||
import { getUrlOpenCommand } from '../../ui/utils/commandUtils.js';
|
||||
|
||||
// Generate OS-specific commands to open the GitHub pages needed for setup.
|
||||
function getOpenUrlsCommands(readmeUrl: string): string[] {
|
||||
// Determine the OS-specific command to open URLs, ex: 'open', 'xdg-open', etc
|
||||
const openCmd = getUrlOpenCommand();
|
||||
|
||||
// Build a list of URLs to open
|
||||
const urlsToOpen = [readmeUrl];
|
||||
|
||||
const repoInfo = getGitHubRepoInfo();
|
||||
if (repoInfo) {
|
||||
urlsToOpen.push(
|
||||
`https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/secrets/actions`,
|
||||
);
|
||||
}
|
||||
|
||||
// Create and join the individual commands
|
||||
const commands = urlsToOpen.map((url) => `${openCmd} "${url}"`);
|
||||
return commands;
|
||||
}
|
||||
|
||||
export const setupGithubCommand: SlashCommand = {
|
||||
name: 'setup-github',
|
||||
|
@ -71,11 +93,14 @@ export const setupGithubCommand: SlashCommand = {
|
|||
commands.push(curlCommand);
|
||||
}
|
||||
|
||||
const readmeUrl = `https://github.com/google-github-actions/run-gemini-cli/blob/${releaseTag}/README.md#quick-start`;
|
||||
|
||||
commands.push(
|
||||
`echo "Successfully downloaded ${workflows.length} workflows. Follow the steps in https://github.com/google-github-actions/run-gemini-cli/blob/${releaseTag}/README.md#quick-start (skipping the /setup-github step) to complete setup."`,
|
||||
`open https://github.com/google-github-actions/run-gemini-cli/blob/${releaseTag}/README.md#quick-start`,
|
||||
`echo "Successfully downloaded ${workflows.length} workflows. Follow the steps in ${readmeUrl} (skipping the /setup-github step) to complete setup."`,
|
||||
);
|
||||
|
||||
commands.push(...getOpenUrlsCommands(readmeUrl));
|
||||
|
||||
const command = `(${commands.join(' && ')})`;
|
||||
return {
|
||||
type: 'tool',
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
isAtCommand,
|
||||
isSlashCommand,
|
||||
copyToClipboard,
|
||||
getUrlOpenCommand,
|
||||
} from './commandUtils.js';
|
||||
|
||||
// Mock child_process
|
||||
|
@ -342,4 +343,42 @@ describe('commandUtils', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUrlOpenCommand', () => {
|
||||
describe('on macOS (darwin)', () => {
|
||||
beforeEach(() => {
|
||||
mockProcess.platform = 'darwin';
|
||||
});
|
||||
it('should return open', () => {
|
||||
expect(getUrlOpenCommand()).toBe('open');
|
||||
});
|
||||
});
|
||||
|
||||
describe('on Windows (win32)', () => {
|
||||
beforeEach(() => {
|
||||
mockProcess.platform = 'win32';
|
||||
});
|
||||
it('should return start', () => {
|
||||
expect(getUrlOpenCommand()).toBe('start');
|
||||
});
|
||||
});
|
||||
|
||||
describe('on Linux (linux)', () => {
|
||||
beforeEach(() => {
|
||||
mockProcess.platform = 'linux';
|
||||
});
|
||||
it('should return xdg-open', () => {
|
||||
expect(getUrlOpenCommand()).toBe('xdg-open');
|
||||
});
|
||||
});
|
||||
|
||||
describe('on unmatched OS', () => {
|
||||
beforeEach(() => {
|
||||
mockProcess.platform = 'unmatched';
|
||||
});
|
||||
it('should return xdg-open', () => {
|
||||
expect(getUrlOpenCommand()).toBe('xdg-open');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -27,7 +27,7 @@ export const isAtCommand = (query: string): boolean =>
|
|||
*/
|
||||
export const isSlashCommand = (query: string): boolean => query.startsWith('/');
|
||||
|
||||
//Copies a string snippet to the clipboard for different platforms
|
||||
// Copies a string snippet to the clipboard for different platforms
|
||||
export const copyToClipboard = async (text: string): Promise<void> => {
|
||||
const run = (cmd: string, args: string[]) =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
|
@ -80,3 +80,27 @@ export const copyToClipboard = async (text: string): Promise<void> => {
|
|||
throw new Error(`Unsupported platform: ${process.platform}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const getUrlOpenCommand = (): string => {
|
||||
// --- Determine the OS-specific command to open URLs ---
|
||||
let openCmd: string;
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
openCmd = 'open';
|
||||
break;
|
||||
case 'win32':
|
||||
openCmd = 'start';
|
||||
break;
|
||||
case 'linux':
|
||||
openCmd = 'xdg-open';
|
||||
break;
|
||||
default:
|
||||
// Default to xdg-open, which appears to be supported for the less popular operating systems.
|
||||
openCmd = 'xdg-open';
|
||||
console.warn(
|
||||
`Unknown platform: ${process.platform}. Attempting to open URLs with: ${openCmd}.`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
return openCmd;
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
isGitHubRepository,
|
||||
getGitRepoRoot,
|
||||
getLatestGitHubRelease,
|
||||
getGitHubRepoInfo,
|
||||
} from './gitUtils.js';
|
||||
|
||||
vi.mock('child_process');
|
||||
|
@ -44,6 +45,39 @@ describe('isGitHubRepository', async () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getGitHubRepoInfo', async () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('throws an error if github repo info cannot be determined', async () => {
|
||||
vi.mocked(child_process.execSync).mockImplementation((): string => {
|
||||
throw new Error('oops');
|
||||
});
|
||||
expect(() => {
|
||||
getGitHubRepoInfo();
|
||||
}).toThrowError(/oops/);
|
||||
});
|
||||
|
||||
it('throws an error if owner/repo could not be determined', async () => {
|
||||
vi.mocked(child_process.execSync).mockReturnValueOnce('');
|
||||
expect(() => {
|
||||
getGitHubRepoInfo();
|
||||
}).toThrowError(/Owner & repo could not be extracted from remote URL/);
|
||||
});
|
||||
|
||||
it('returns the owner and repo', async () => {
|
||||
vi.mocked(child_process.execSync).mockReturnValueOnce(
|
||||
'https://github.com/owner/repo.git ',
|
||||
);
|
||||
expect(getGitHubRepoInfo()).toEqual({ owner: 'owner', repo: 'repo' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGitRepoRoot', async () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
|
|
|
@ -91,3 +91,28 @@ export const getLatestGitHubRelease = async (
|
|||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* getGitHubRepoInfo returns the owner and repository for a GitHub repo.
|
||||
* @returns the owner and repository of the github repo.
|
||||
* @throws error if the exec command fails.
|
||||
*/
|
||||
export function getGitHubRepoInfo(): { owner: string; repo: string } {
|
||||
const remoteUrl = execSync('git remote get-url origin', {
|
||||
encoding: 'utf-8',
|
||||
}).trim();
|
||||
|
||||
// Matches either https://github.com/owner/repo.git or git@github.com:owner/repo.git
|
||||
const match = remoteUrl.match(
|
||||
/(?:https?:\/\/|git@)github\.com(?::|\/)([^/]+)\/([^/]+?)(?:\.git)?$/,
|
||||
);
|
||||
|
||||
// If the regex fails match, throw an error.
|
||||
if (!match || !match[1] || !match[2]) {
|
||||
throw new Error(
|
||||
`Owner & repo could not be extracted from remote URL: ${remoteUrl}`,
|
||||
);
|
||||
}
|
||||
|
||||
return { owner: match[1], repo: match[2] };
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue