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(),
|
isGitHubRepository: vi.fn(),
|
||||||
getGitRepoRoot: vi.fn(),
|
getGitRepoRoot: vi.fn(),
|
||||||
getLatestGitHubRelease: vi.fn(),
|
getLatestGitHubRelease: vi.fn(),
|
||||||
|
getGitHubRepoInfo: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('setupGithubCommand', async () => {
|
describe('setupGithubCommand', async () => {
|
||||||
|
@ -30,7 +31,9 @@ describe('setupGithubCommand', async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns a tool action to download github workflows and handles paths', 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';
|
const fakeReleaseVersion = 'v1.2.3';
|
||||||
|
|
||||||
vi.mocked(gitUtils.isGitHubRepository).mockReturnValueOnce(true);
|
vi.mocked(gitUtils.isGitHubRepository).mockReturnValueOnce(true);
|
||||||
|
@ -38,6 +41,10 @@ describe('setupGithubCommand', async () => {
|
||||||
vi.mocked(gitUtils.getLatestGitHubRelease).mockResolvedValueOnce(
|
vi.mocked(gitUtils.getLatestGitHubRelease).mockResolvedValueOnce(
|
||||||
fakeReleaseVersion,
|
fakeReleaseVersion,
|
||||||
);
|
);
|
||||||
|
vi.mocked(gitUtils.getGitHubRepoInfo).mockReturnValue({
|
||||||
|
owner: fakeRepoOwner,
|
||||||
|
repo: fakeRepoName,
|
||||||
|
});
|
||||||
|
|
||||||
const result = (await setupGithubCommand.action?.(
|
const result = (await setupGithubCommand.action?.(
|
||||||
{} as CommandContext,
|
{} as CommandContext,
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
getGitRepoRoot,
|
getGitRepoRoot,
|
||||||
getLatestGitHubRelease,
|
getLatestGitHubRelease,
|
||||||
isGitHubRepository,
|
isGitHubRepository,
|
||||||
|
getGitHubRepoInfo,
|
||||||
} from '../../utils/gitUtils.js';
|
} from '../../utils/gitUtils.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -18,6 +19,27 @@ import {
|
||||||
SlashCommand,
|
SlashCommand,
|
||||||
SlashCommandActionReturn,
|
SlashCommandActionReturn,
|
||||||
} from './types.js';
|
} 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 = {
|
export const setupGithubCommand: SlashCommand = {
|
||||||
name: 'setup-github',
|
name: 'setup-github',
|
||||||
|
@ -71,11 +93,14 @@ export const setupGithubCommand: SlashCommand = {
|
||||||
commands.push(curlCommand);
|
commands.push(curlCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const readmeUrl = `https://github.com/google-github-actions/run-gemini-cli/blob/${releaseTag}/README.md#quick-start`;
|
||||||
|
|
||||||
commands.push(
|
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."`,
|
`echo "Successfully downloaded ${workflows.length} workflows. Follow the steps in ${readmeUrl} (skipping the /setup-github step) to complete setup."`,
|
||||||
`open https://github.com/google-github-actions/run-gemini-cli/blob/${releaseTag}/README.md#quick-start`,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
commands.push(...getOpenUrlsCommands(readmeUrl));
|
||||||
|
|
||||||
const command = `(${commands.join(' && ')})`;
|
const command = `(${commands.join(' && ')})`;
|
||||||
return {
|
return {
|
||||||
type: 'tool',
|
type: 'tool',
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
isAtCommand,
|
isAtCommand,
|
||||||
isSlashCommand,
|
isSlashCommand,
|
||||||
copyToClipboard,
|
copyToClipboard,
|
||||||
|
getUrlOpenCommand,
|
||||||
} from './commandUtils.js';
|
} from './commandUtils.js';
|
||||||
|
|
||||||
// Mock child_process
|
// 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -80,3 +80,27 @@ export const copyToClipboard = async (text: string): Promise<void> => {
|
||||||
throw new Error(`Unsupported platform: ${process.platform}`);
|
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,
|
isGitHubRepository,
|
||||||
getGitRepoRoot,
|
getGitRepoRoot,
|
||||||
getLatestGitHubRelease,
|
getLatestGitHubRelease,
|
||||||
|
getGitHubRepoInfo,
|
||||||
} from './gitUtils.js';
|
} from './gitUtils.js';
|
||||||
|
|
||||||
vi.mock('child_process');
|
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 () => {
|
describe('getGitRepoRoot', async () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.resetAllMocks();
|
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