diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index 7bd062ac..11ae1505 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -22,6 +22,7 @@ import { } from './config/settings.js'; import { themeManager } from './ui/themes/theme-manager.js'; import { getStartupWarnings } from './utils/startupWarnings.js'; +import { getUserStartupWarnings } from './utils/userStartupWarnings.js'; import { runNonInteractive } from './nonInteractiveCli.js'; import { loadExtensions, Extension } from './config/extension.js'; import { cleanupCheckpoints } from './utils/cleanup.js'; @@ -165,7 +166,10 @@ export async function main() { } } let input = config.getQuestion(); - const startupWarnings = await getStartupWarnings(); + const startupWarnings = [ + ...(await getStartupWarnings()), + ...(await getUserStartupWarnings(workspaceRoot)), + ]; // Render UI, passing necessary config values. Check that there is no command line question. if (process.stdin.isTTY && input?.length === 0) { diff --git a/packages/cli/src/utils/userStartupWarnings.test.ts b/packages/cli/src/utils/userStartupWarnings.test.ts new file mode 100644 index 00000000..61053029 --- /dev/null +++ b/packages/cli/src/utils/userStartupWarnings.test.ts @@ -0,0 +1,74 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { getUserStartupWarnings } from './userStartupWarnings.js'; +import * as os from 'os'; +import fs from 'fs/promises'; + +vi.mock('os', () => ({ + default: { homedir: vi.fn() }, + homedir: vi.fn(), +})); + +vi.mock('fs/promises', () => ({ + default: { realpath: vi.fn() }, +})); + +describe('getUserStartupWarnings', () => { + const homeDir = '/home/user'; + + beforeEach(() => { + vi.mocked(os.homedir).mockReturnValue(homeDir); + vi.mocked(fs.realpath).mockImplementation(async (path) => path.toString()); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('home directory check', () => { + it('should return a warning when running in home directory', async () => { + vi.mocked(fs.realpath) + .mockResolvedValueOnce(homeDir) + .mockResolvedValueOnce(homeDir); + + const warnings = await getUserStartupWarnings(homeDir); + + expect(warnings).toContainEqual( + expect.stringContaining('home directory'), + ); + }); + + it('should not return a warning when running in a project directory', async () => { + vi.mocked(fs.realpath) + .mockResolvedValueOnce('/some/project/path') + .mockResolvedValueOnce(homeDir); + + const warnings = await getUserStartupWarnings('/some/project/path'); + expect(warnings).not.toContainEqual( + expect.stringContaining('home directory'), + ); + }); + + it('should handle errors when checking directory', async () => { + vi.mocked(fs.realpath) + .mockRejectedValueOnce(new Error('FS error')) + .mockResolvedValueOnce(homeDir); + + const warnings = await getUserStartupWarnings('/error/path'); + expect(warnings).toContainEqual( + expect.stringContaining('Could not verify'), + ); + }); + }); + + // // Example of how to add a new check: + // describe('node version check', () => { + // // Tests for node version check would go here + // // This shows how easy it is to add new test sections + // }); +}); diff --git a/packages/cli/src/utils/userStartupWarnings.ts b/packages/cli/src/utils/userStartupWarnings.ts new file mode 100644 index 00000000..3d76a6e1 --- /dev/null +++ b/packages/cli/src/utils/userStartupWarnings.ts @@ -0,0 +1,45 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import fs from 'fs/promises'; +import * as os from 'os'; + +type WarningCheck = { + id: string; + check: (workspaceRoot: string) => Promise; +}; + +// Individual warning checks +const homeDirectoryCheck: WarningCheck = { + id: 'home-directory', + check: async (workspaceRoot: string) => { + try { + const [workspaceRealPath, homeRealPath] = await Promise.all([ + fs.realpath(workspaceRoot), + fs.realpath(os.homedir()), + ]); + + if (workspaceRealPath === homeRealPath) { + return 'You are running Gemini CLI in your home directory. It is recommended to run in a project-specific directory.'; + } + return null; + } catch (_err: unknown) { + return 'Could not verify the current directory due to a file system error.'; + } + }, +}; + +// All warning checks +const WARNING_CHECKS: readonly WarningCheck[] = [homeDirectoryCheck]; + +export async function getUserStartupWarnings( + workspaceRoot: string, +): Promise { + const results = await Promise.all( + WARNING_CHECKS.map((check) => check.check(workspaceRoot)), + ); + return results.filter((msg) => msg !== null); +}