diff options
Diffstat (limited to 'packages/cli/src/ui/commands')
| -rw-r--r-- | packages/cli/src/ui/commands/setupGithubCommand.test.ts | 59 | ||||
| -rw-r--r-- | packages/cli/src/ui/commands/setupGithubCommand.ts | 78 |
2 files changed, 85 insertions, 52 deletions
diff --git a/packages/cli/src/ui/commands/setupGithubCommand.test.ts b/packages/cli/src/ui/commands/setupGithubCommand.test.ts index ae6378c7..6417c60a 100644 --- a/packages/cli/src/ui/commands/setupGithubCommand.test.ts +++ b/packages/cli/src/ui/commands/setupGithubCommand.test.ts @@ -5,13 +5,22 @@ */ import { vi, describe, expect, it, afterEach, beforeEach } from 'vitest'; -import * as child_process from 'child_process'; +import * as gitUtils from '../../utils/gitUtils.js'; import { setupGithubCommand } from './setupGithubCommand.js'; import { CommandContext, ToolActionReturn } from './types.js'; vi.mock('child_process'); -describe('setupGithubCommand', () => { +// Mock fetch globally +global.fetch = vi.fn(); + +vi.mock('../../utils/gitUtils.js', () => ({ + isGitHubRepository: vi.fn(), + getGitRepoRoot: vi.fn(), + getLatestGitHubRelease: vi.fn(), +})); + +describe('setupGithubCommand', async () => { beforeEach(() => { vi.resetAllMocks(); }); @@ -20,49 +29,35 @@ describe('setupGithubCommand', () => { vi.restoreAllMocks(); }); - it('returns a tool action to download github workflows and handles paths', () => { + it('returns a tool action to download github workflows and handles paths', async () => { const fakeRepoRoot = '/github.com/fake/repo/root'; - vi.mocked(child_process.execSync).mockReturnValue(fakeRepoRoot); + const fakeReleaseVersion = 'v1.2.3'; - const result = setupGithubCommand.action?.( + vi.mocked(gitUtils.isGitHubRepository).mockReturnValueOnce(true); + vi.mocked(gitUtils.getGitRepoRoot).mockReturnValueOnce(fakeRepoRoot); + vi.mocked(gitUtils.getLatestGitHubRelease).mockResolvedValueOnce( + fakeReleaseVersion, + ); + + const result = (await setupGithubCommand.action?.( {} as CommandContext, '', - ) as ToolActionReturn; - - expect(result.type).toBe('tool'); - expect(result.toolName).toBe('run_shell_command'); - expect(child_process.execSync).toHaveBeenCalledWith( - 'git rev-parse --show-toplevel', - { - encoding: 'utf-8', - }, - ); - expect(child_process.execSync).toHaveBeenCalledWith('git remote -v', { - encoding: 'utf-8', - }); + )) as ToolActionReturn; const { command } = result.toolArgs; const expectedSubstrings = [ + `set -eEuo pipefail`, `mkdir -p "${fakeRepoRoot}/.github/workflows"`, - `curl -fsSL -o "${fakeRepoRoot}/.github/workflows/gemini-cli.yml"`, - `curl -fsSL -o "${fakeRepoRoot}/.github/workflows/gemini-issue-automated-triage.yml"`, - `curl -fsSL -o "${fakeRepoRoot}/.github/workflows/gemini-issue-scheduled-triage.yml"`, - `curl -fsSL -o "${fakeRepoRoot}/.github/workflows/gemini-pr-review.yml"`, - 'https://raw.githubusercontent.com/google-github-actions/run-gemini-cli/refs/tags/v0/examples/workflows/', + `curl --fail --location --output "/github.com/fake/repo/root/.github/workflows/gemini-cli.yml" --show-error --silent`, + `curl --fail --location --output "/github.com/fake/repo/root/.github/workflows/gemini-issue-automated-triage.yml" --show-error --silent`, + `curl --fail --location --output "/github.com/fake/repo/root/.github/workflows/gemini-issue-scheduled-triage.yml" --show-error --silent`, + `curl --fail --location --output "/github.com/fake/repo/root/.github/workflows/gemini-pr-review.yml" --show-error --silent`, + `https://raw.githubusercontent.com/google-github-actions/run-gemini-cli/refs/tags/`, ]; for (const substring of expectedSubstrings) { expect(command).toContain(substring); } }); - - it('throws an error if git root cannot be determined', () => { - vi.mocked(child_process.execSync).mockReturnValue(''); - expect(() => { - setupGithubCommand.action?.({} as CommandContext, ''); - }).toThrow( - 'Unable to determine the GitHub repository. /setup-github must be run from a git repository.', - ); - }); }); diff --git a/packages/cli/src/ui/commands/setupGithubCommand.ts b/packages/cli/src/ui/commands/setupGithubCommand.ts index 047e11eb..1b5b3277 100644 --- a/packages/cli/src/ui/commands/setupGithubCommand.ts +++ b/packages/cli/src/ui/commands/setupGithubCommand.ts @@ -5,8 +5,13 @@ */ import path from 'path'; -import { execSync } from 'child_process'; -import { isGitHubRepository } from '../../utils/gitUtils.js'; + +import { CommandContext } from '../../ui/commands/types.js'; +import { + getGitRepoRoot, + getLatestGitHubRelease, + isGitHubRepository, +} from '../../utils/gitUtils.js'; import { CommandKind, @@ -18,26 +23,29 @@ export const setupGithubCommand: SlashCommand = { name: 'setup-github', description: 'Set up GitHub Actions', kind: CommandKind.BUILT_IN, - action: (): SlashCommandActionReturn => { + action: async ( + context: CommandContext, + ): Promise<SlashCommandActionReturn> => { if (!isGitHubRepository()) { throw new Error( 'Unable to determine the GitHub repository. /setup-github must be run from a git repository.', ); } - let gitRootRepo: string; + // Find the root directory of the repo + let gitRepoRoot: string; try { - gitRootRepo = execSync('git rev-parse --show-toplevel', { - encoding: 'utf-8', - }).trim(); - } catch { + gitRepoRoot = getGitRepoRoot(); + } catch (_error) { + console.debug(`Failed to get git repo root:`, _error); throw new Error( 'Unable to determine the GitHub repository. /setup-github must be run from a git repository.', ); } - const version = 'v0'; - const workflowBaseUrl = `https://raw.githubusercontent.com/google-github-actions/run-gemini-cli/refs/tags/${version}/examples/workflows/`; + // Get the latest release tag from GitHub + const proxy = context?.services?.config?.getProxy(); + const releaseTag = await getLatestGitHubRelease(proxy); const workflows = [ 'gemini-cli/gemini-cli.yml', @@ -46,16 +54,29 @@ export const setupGithubCommand: SlashCommand = { 'pr-review/gemini-pr-review.yml', ]; - const command = [ - 'set -e', - `mkdir -p "${gitRootRepo}/.github/workflows"`, - ...workflows.map((workflow) => { - const fileName = path.basename(workflow); - return `curl -fsSL -o "${gitRootRepo}/.github/workflows/${fileName}" "${workflowBaseUrl}/${workflow}"`; - }), - 'echo "Workflows downloaded successfully. Follow steps in https://github.com/google-github-actions/run-gemini-cli/blob/v0/README.md#quick-start (skipping the /setup-github step) to complete setup."', - 'open https://github.com/google-github-actions/run-gemini-cli/blob/v0/README.md#quick-start', - ].join(' && '); + const commands = []; + + // Ensure fast exit + commands.push(`set -eEuo pipefail`); + + // Make the directory if it doesn't exist + commands.push(`mkdir -p "${gitRepoRoot}/.github/workflows"`); + + for (const workflow of workflows) { + const fileName = path.basename(workflow); + const curlCommand = buildCurlCommand( + `https://raw.githubusercontent.com/google-github-actions/run-gemini-cli/refs/tags/${releaseTag}/examples/workflows/${workflow}`, + [`--output "${gitRepoRoot}/.github/workflows/${fileName}"`], + ); + commands.push(curlCommand); + } + + 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`, + ); + + const command = `(${commands.join(' && ')})`; return { type: 'tool', toolName: 'run_shell_command', @@ -67,3 +88,20 @@ export const setupGithubCommand: SlashCommand = { }; }, }; + +// buildCurlCommand is a helper for constructing a consistent curl command. +function buildCurlCommand(u: string, additionalArgs?: string[]): string { + const args = []; + args.push('--fail'); + args.push('--location'); + args.push('--show-error'); + args.push('--silent'); + + for (const val of additionalArgs || []) { + args.push(val); + } + + args.sort(); + + return `curl ${args.join(' ')} "${u}"`; +} |
