chore: Extract '.gemini' to GEMINI_DIR constant (#10540)

Co-authored-by: Richie Foreman <richie.foreman@gmail.com>
This commit is contained in:
Dongin Kim(Terry)
2025-10-14 02:31:39 +09:00
committed by GitHub
parent 7beaa368a9
commit 518caae62e
36 changed files with 181 additions and 157 deletions

View File

@@ -11,8 +11,8 @@ import type { OAuthCredentials } from '../mcp/token-storage/types.js';
import * as path from 'node:path';
import * as os from 'node:os';
import { promises as fs } from 'node:fs';
import { GEMINI_DIR } from '../utils/paths.js';
const GEMINI_DIR = '.gemini';
const KEYCHAIN_SERVICE_NAME = 'gemini-cli-oauth';
const MAIN_ACCOUNT_KEY = 'main-account';

View File

@@ -25,6 +25,7 @@ import { AuthType } from '../core/contentGenerator.js';
import type { Config } from '../config/config.js';
import readline from 'node:readline';
import { FORCE_ENCRYPTED_FILE_ENV_VAR } from '../mcp/token-storage/index.js';
import { GEMINI_DIR } from '../utils/paths.js';
vi.mock('os', async (importOriginal) => {
const os = await importOriginal<typeof import('os')>();
@@ -182,7 +183,7 @@ describe('oauth2', () => {
// Verify Google Account was cached
const googleAccountPath = path.join(
tempHomeDir,
'.gemini',
GEMINI_DIR,
'google_accounts.json',
);
expect(fs.existsSync(googleAccountPath)).toBe(true);
@@ -290,7 +291,11 @@ describe('oauth2', () => {
it('should attempt to load cached credentials first', async () => {
const cachedCreds = { refresh_token: 'cached-token' };
const credsPath = path.join(tempHomeDir, '.gemini', 'oauth_creds.json');
const credsPath = path.join(
tempHomeDir,
GEMINI_DIR,
'oauth_creds.json',
);
await fs.promises.mkdir(path.dirname(credsPath), { recursive: true });
await fs.promises.writeFile(credsPath, JSON.stringify(cachedCreds));
@@ -328,7 +333,11 @@ describe('oauth2', () => {
await getOauthClient(AuthType.CLOUD_SHELL, mockConfig);
const credsPath = path.join(tempHomeDir, '.gemini', 'oauth_creds.json');
const credsPath = path.join(
tempHomeDir,
GEMINI_DIR,
'oauth_creds.json',
);
expect(fs.existsSync(credsPath)).toBe(false);
});
@@ -355,7 +364,7 @@ describe('oauth2', () => {
const defaultCreds = { refresh_token: 'default-cached-token' };
const defaultCredsPath = path.join(
tempHomeDir,
'.gemini',
GEMINI_DIR,
'oauth_creds.json',
);
await fs.promises.mkdir(path.dirname(defaultCredsPath), {
@@ -463,7 +472,7 @@ describe('oauth2', () => {
// Verify Google Account was cached
const googleAccountPath = path.join(
tempHomeDir,
'.gemini',
GEMINI_DIR,
'google_accounts.json',
);
const cachedContent = fs.readFileSync(googleAccountPath, 'utf-8');
@@ -493,7 +502,11 @@ describe('oauth2', () => {
// Make it fall through to cached credentials path
const cachedCreds = { refresh_token: 'cached-token' };
const credsPath = path.join(tempHomeDir, '.gemini', 'oauth_creds.json');
const credsPath = path.join(
tempHomeDir,
GEMINI_DIR,
'oauth_creds.json',
);
await fs.promises.mkdir(path.dirname(credsPath), { recursive: true });
await fs.promises.writeFile(credsPath, JSON.stringify(cachedCreds));
@@ -524,7 +537,11 @@ describe('oauth2', () => {
// Make it fall through to cached credentials path
const cachedCreds = { refresh_token: 'cached-token' };
const credsPath = path.join(tempHomeDir, '.gemini', 'oauth_creds.json');
const credsPath = path.join(
tempHomeDir,
GEMINI_DIR,
'oauth_creds.json',
);
await fs.promises.mkdir(path.dirname(credsPath), { recursive: true });
await fs.promises.writeFile(credsPath, JSON.stringify(cachedCreds));
@@ -916,13 +933,17 @@ describe('oauth2', () => {
describe('clearCachedCredentialFile', () => {
it('should clear cached credentials and Google account', async () => {
const cachedCreds = { refresh_token: 'test-token' };
const credsPath = path.join(tempHomeDir, '.gemini', 'oauth_creds.json');
const credsPath = path.join(
tempHomeDir,
GEMINI_DIR,
'oauth_creds.json',
);
await fs.promises.mkdir(path.dirname(credsPath), { recursive: true });
await fs.promises.writeFile(credsPath, JSON.stringify(cachedCreds));
const googleAccountPath = path.join(
tempHomeDir,
'.gemini',
GEMINI_DIR,
'google_accounts.json',
);
const accountData = { active: 'test@example.com', old: [] };
@@ -965,7 +986,11 @@ describe('oauth2', () => {
);
// Pre-populate credentials to make getOauthClient resolve quickly
const credsPath = path.join(tempHomeDir, '.gemini', 'oauth_creds.json');
const credsPath = path.join(
tempHomeDir,
GEMINI_DIR,
'oauth_creds.json',
);
await fs.promises.mkdir(path.dirname(credsPath), { recursive: true });
await fs.promises.writeFile(
credsPath,
@@ -1104,7 +1129,7 @@ describe('oauth2', () => {
expect(
OAuthCredentialStorage.saveCredentials as Mock,
).toHaveBeenCalledWith(mockTokens);
const credsPath = path.join(tempHomeDir, '.gemini', 'oauth_creds.json');
const credsPath = path.join(tempHomeDir, GEMINI_DIR, 'oauth_creds.json');
expect(fs.existsSync(credsPath)).toBe(false);
});
@@ -1120,7 +1145,7 @@ describe('oauth2', () => {
// Create a dummy unencrypted credential file.
// If the logic is correct, this file should be ignored.
const unencryptedCreds = { refresh_token: 'unencrypted-token' };
const credsPath = path.join(tempHomeDir, '.gemini', 'oauth_creds.json');
const credsPath = path.join(tempHomeDir, GEMINI_DIR, 'oauth_creds.json');
await fs.promises.mkdir(path.dirname(credsPath), { recursive: true });
await fs.promises.writeFile(credsPath, JSON.stringify(unencryptedCreds));
@@ -1150,7 +1175,7 @@ describe('oauth2', () => {
);
// Create a dummy unencrypted credential file. It should not be deleted.
const credsPath = path.join(tempHomeDir, '.gemini', 'oauth_creds.json');
const credsPath = path.join(tempHomeDir, GEMINI_DIR, 'oauth_creds.json');
await fs.promises.mkdir(path.dirname(credsPath), { recursive: true });
await fs.promises.writeFile(credsPath, '{}');

View File

@@ -81,7 +81,7 @@ vi.mock('../tools/memoryTool', () => ({
setGeminiMdFilename: vi.fn(),
getCurrentGeminiMdFilename: vi.fn(() => 'GEMINI.md'), // Mock the original filename
DEFAULT_CONTEXT_FILENAME: 'GEMINI.md',
GEMINI_CONFIG_DIR: '.gemini',
GEMINI_DIR: '.gemini',
}));
vi.mock('../core/contentGenerator.js');

View File

@@ -17,10 +17,11 @@ vi.mock('fs', async (importOriginal) => {
});
import { Storage } from './storage.js';
import { GEMINI_DIR } from '../utils/paths.js';
describe('Storage getGlobalSettingsPath', () => {
it('returns path to ~/.gemini/settings.json', () => {
const expected = path.join(os.homedir(), '.gemini', 'settings.json');
const expected = path.join(os.homedir(), GEMINI_DIR, 'settings.json');
expect(Storage.getGlobalSettingsPath()).toBe(expected);
});
});
@@ -30,31 +31,31 @@ describe('Storage additional helpers', () => {
const storage = new Storage(projectRoot);
it('getWorkspaceSettingsPath returns project/.gemini/settings.json', () => {
const expected = path.join(projectRoot, '.gemini', 'settings.json');
const expected = path.join(projectRoot, GEMINI_DIR, 'settings.json');
expect(storage.getWorkspaceSettingsPath()).toBe(expected);
});
it('getUserCommandsDir returns ~/.gemini/commands', () => {
const expected = path.join(os.homedir(), '.gemini', 'commands');
const expected = path.join(os.homedir(), GEMINI_DIR, 'commands');
expect(Storage.getUserCommandsDir()).toBe(expected);
});
it('getProjectCommandsDir returns project/.gemini/commands', () => {
const expected = path.join(projectRoot, '.gemini', 'commands');
const expected = path.join(projectRoot, GEMINI_DIR, 'commands');
expect(storage.getProjectCommandsDir()).toBe(expected);
});
it('getMcpOAuthTokensPath returns ~/.gemini/mcp-oauth-tokens.json', () => {
const expected = path.join(
os.homedir(),
'.gemini',
GEMINI_DIR,
'mcp-oauth-tokens.json',
);
expect(Storage.getMcpOAuthTokensPath()).toBe(expected);
});
it('getGlobalBinDir returns ~/.gemini/tmp/bin', () => {
const expected = path.join(os.homedir(), '.gemini', 'tmp', 'bin');
const expected = path.join(os.homedir(), GEMINI_DIR, 'tmp', 'bin');
expect(Storage.getGlobalBinDir()).toBe(expected);
});
});

View File

@@ -8,8 +8,8 @@ import * as path from 'node:path';
import * as os from 'node:os';
import * as crypto from 'node:crypto';
import * as fs from 'node:fs';
import { GEMINI_DIR } from '../utils/paths.js';
export const GEMINI_DIR = '.gemini';
export const GOOGLE_ACCOUNTS_FILENAME = 'google_accounts.json';
export const OAUTH_FILE = 'oauth_creds.json';
const TMP_DIR_NAME = 'tmp';
@@ -25,7 +25,7 @@ export class Storage {
static getGlobalGeminiDir(): string {
const homeDir = os.homedir();
if (!homeDir) {
return path.join(os.tmpdir(), '.gemini');
return path.join(os.tmpdir(), GEMINI_DIR);
}
return path.join(homeDir, GEMINI_DIR);
}

View File

@@ -27,20 +27,15 @@ import type { Content } from '@google/genai';
import crypto from 'node:crypto';
import os from 'node:os';
import { GEMINI_DIR } from '../utils/paths.js';
const GEMINI_DIR_NAME = '.gemini';
const TMP_DIR_NAME = 'tmp';
const LOG_FILE_NAME = 'logs.json';
const CHECKPOINT_FILE_NAME = 'checkpoint.json';
const projectDir = process.cwd();
const hash = crypto.createHash('sha256').update(projectDir).digest('hex');
const TEST_GEMINI_DIR = path.join(
os.homedir(),
GEMINI_DIR_NAME,
TMP_DIR_NAME,
hash,
);
const TEST_GEMINI_DIR = path.join(os.homedir(), GEMINI_DIR, TMP_DIR_NAME, hash);
const TEST_LOG_FILE_PATH = path.join(TEST_GEMINI_DIR, LOG_FILE_NAME);
const TEST_CHECKPOINT_FILE_PATH = path.join(

View File

@@ -10,9 +10,9 @@ import { isGitRepository } from '../utils/gitUtils.js';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { GEMINI_CONFIG_DIR } from '../tools/memoryTool.js';
import type { Config } from '../config/config.js';
import { CodebaseInvestigatorAgent } from '../agents/codebase-investigator.js';
import { GEMINI_DIR } from '../utils/paths.js';
// Mock tool names if they are dynamically generated or complex
vi.mock('../tools/ls', () => ({ LSTool: { Name: 'list_directory' } }));
@@ -223,9 +223,7 @@ describe('Core System Prompt (prompts.ts)', () => {
});
it('should read from default path when GEMINI_SYSTEM_MD is "true"', () => {
const defaultPath = path.resolve(
path.join(GEMINI_CONFIG_DIR, 'system.md'),
);
const defaultPath = path.resolve(path.join(GEMINI_DIR, 'system.md'));
vi.stubEnv('GEMINI_SYSTEM_MD', 'true');
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue('custom system prompt');
@@ -236,9 +234,7 @@ describe('Core System Prompt (prompts.ts)', () => {
});
it('should read from default path when GEMINI_SYSTEM_MD is "1"', () => {
const defaultPath = path.resolve(
path.join(GEMINI_CONFIG_DIR, 'system.md'),
);
const defaultPath = path.resolve(path.join(GEMINI_DIR, 'system.md'));
vi.stubEnv('GEMINI_SYSTEM_MD', '1');
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue('custom system prompt');
@@ -291,9 +287,7 @@ describe('Core System Prompt (prompts.ts)', () => {
});
it('should write to default path when GEMINI_WRITE_SYSTEM_MD is "true"', () => {
const defaultPath = path.resolve(
path.join(GEMINI_CONFIG_DIR, 'system.md'),
);
const defaultPath = path.resolve(path.join(GEMINI_DIR, 'system.md'));
vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', 'true');
getCoreSystemPrompt(mockConfig);
expect(fs.writeFileSync).toHaveBeenCalledWith(
@@ -303,9 +297,7 @@ describe('Core System Prompt (prompts.ts)', () => {
});
it('should write to default path when GEMINI_WRITE_SYSTEM_MD is "1"', () => {
const defaultPath = path.resolve(
path.join(GEMINI_CONFIG_DIR, 'system.md'),
);
const defaultPath = path.resolve(path.join(GEMINI_DIR, 'system.md'));
vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', '1');
getCoreSystemPrompt(mockConfig);
expect(fs.writeFileSync).toHaveBeenCalledWith(

View File

@@ -17,9 +17,10 @@ import { ShellTool } from '../tools/shell.js';
import { WRITE_FILE_TOOL_NAME } from '../tools/tool-names.js';
import process from 'node:process';
import { isGitRepository } from '../utils/gitUtils.js';
import { MemoryTool, GEMINI_CONFIG_DIR } from '../tools/memoryTool.js';
import { MemoryTool } from '../tools/memoryTool.js';
import { CodebaseInvestigatorAgent } from '../agents/codebase-investigator.js';
import type { Config } from '../config/config.js';
import { GEMINI_DIR } from '../utils/paths.js';
export function resolvePathFromEnv(envVar?: string): {
isSwitch: boolean;
@@ -78,7 +79,7 @@ export function getCoreSystemPrompt(
// A flag to indicate whether the system prompt override is active.
let systemMdEnabled = false;
// The default path for the system prompt file. This can be overridden.
let systemMdPath = path.resolve(path.join(GEMINI_CONFIG_DIR, 'system.md'));
let systemMdPath = path.resolve(path.join(GEMINI_DIR, 'system.md'));
// Resolve the environment variable to get either a path or a switch value.
const systemMdResolution = resolvePathFromEnv(
process.env['GEMINI_SYSTEM_MD'],

View File

@@ -9,6 +9,7 @@ import { promises as fs } from 'node:fs';
import * as path from 'node:path';
import { FileTokenStorage } from './file-token-storage.js';
import type { OAuthCredentials } from './types.js';
import { GEMINI_DIR } from '../../utils/paths.js';
vi.mock('node:fs', () => ({
promises: {
@@ -135,7 +136,7 @@ describe('FileTokenStorage', () => {
await storage.setCredentials(credentials);
expect(mockFs.mkdir).toHaveBeenCalledWith(
path.join('/home/test', '.gemini'),
path.join('/home/test', GEMINI_DIR),
{ recursive: true, mode: 0o700 },
);
expect(mockFs.writeFile).toHaveBeenCalled();
@@ -201,7 +202,7 @@ describe('FileTokenStorage', () => {
await storage.deleteCredentials('test-server');
expect(mockFs.unlink).toHaveBeenCalledWith(
path.join('/home/test', '.gemini', 'mcp-oauth-tokens-v2.json'),
path.join('/home/test', GEMINI_DIR, 'mcp-oauth-tokens-v2.json'),
);
});
@@ -282,7 +283,7 @@ describe('FileTokenStorage', () => {
await storage.clearAll();
expect(mockFs.unlink).toHaveBeenCalledWith(
path.join('/home/test', '.gemini', 'mcp-oauth-tokens-v2.json'),
path.join('/home/test', GEMINI_DIR, 'mcp-oauth-tokens-v2.json'),
);
});

View File

@@ -10,6 +10,7 @@ import * as os from 'node:os';
import * as crypto from 'node:crypto';
import { BaseTokenStorage } from './base-token-storage.js';
import type { OAuthCredentials } from './types.js';
import { GEMINI_DIR } from '../../utils/paths.js';
export class FileTokenStorage extends BaseTokenStorage {
private readonly tokenFilePath: string;
@@ -17,7 +18,7 @@ export class FileTokenStorage extends BaseTokenStorage {
constructor(serviceName: string) {
super(serviceName);
const configDir = path.join(os.homedir(), '.gemini');
const configDir = path.join(os.homedir(), GEMINI_DIR);
this.tokenFilePath = path.join(configDir, 'mcp-oauth-tokens-v2.json');
this.encryptionKey = this.deriveEncryptionKey();
}

View File

@@ -18,6 +18,7 @@ import * as path from 'node:path';
import * as os from 'node:os';
import { ToolConfirmationOutcome } from './tools.js';
import { ToolErrorType } from './tool-error.js';
import { GEMINI_DIR } from '../utils/paths.js';
// Mock dependencies
vi.mock(import('node:fs/promises'), async (importOriginal) => {
@@ -105,7 +106,7 @@ describe('MemoryTool', () => {
beforeEach(() => {
testFilePath = path.join(
os.homedir(),
'.gemini',
GEMINI_DIR,
DEFAULT_CONTEXT_FILENAME,
);
});
@@ -237,7 +238,7 @@ describe('MemoryTool', () => {
// Use getCurrentGeminiMdFilename for the default expectation before any setGeminiMdFilename calls in a test
const expectedFilePath = path.join(
os.homedir(),
'.gemini',
GEMINI_DIR,
getCurrentGeminiMdFilename(), // This will be DEFAULT_CONTEXT_FILENAME unless changed by a test
);
@@ -317,9 +318,11 @@ describe('MemoryTool', () => {
expect(result).not.toBe(false);
if (result && result.type === 'edit') {
const expectedPath = path.join('~', '.gemini', 'GEMINI.md');
const expectedPath = path.join('~', GEMINI_DIR, 'GEMINI.md');
expect(result.title).toBe(`Confirm Memory Save: ${expectedPath}`);
expect(result.fileName).toContain(path.join('mock', 'home', '.gemini'));
expect(result.fileName).toContain(
path.join('mock', 'home', GEMINI_DIR),
);
expect(result.fileName).toContain('GEMINI.md');
expect(result.fileDiff).toContain('Index: GEMINI.md');
expect(result.fileDiff).toContain('+## Gemini Added Memories');
@@ -334,7 +337,7 @@ describe('MemoryTool', () => {
const params = { fact: 'Test fact' };
const memoryFilePath = path.join(
os.homedir(),
'.gemini',
GEMINI_DIR,
getCurrentGeminiMdFilename(),
);
@@ -352,7 +355,7 @@ describe('MemoryTool', () => {
const params = { fact: 'Test fact' };
const memoryFilePath = path.join(
os.homedir(),
'.gemini',
GEMINI_DIR,
getCurrentGeminiMdFilename(),
);
@@ -378,7 +381,7 @@ describe('MemoryTool', () => {
const params = { fact: 'Test fact' };
const memoryFilePath = path.join(
os.homedir(),
'.gemini',
GEMINI_DIR,
getCurrentGeminiMdFilename(),
);
@@ -415,7 +418,7 @@ describe('MemoryTool', () => {
expect(result).not.toBe(false);
if (result && result.type === 'edit') {
const expectedPath = path.join('~', '.gemini', 'GEMINI.md');
const expectedPath = path.join('~', GEMINI_DIR, 'GEMINI.md');
expect(result.title).toBe(`Confirm Memory Save: ${expectedPath}`);
expect(result.fileDiff).toContain('Index: GEMINI.md');
expect(result.fileDiff).toContain('+- New fact');

View File

@@ -60,7 +60,6 @@ Do NOT use this tool:
- \`fact\` (string, required): The specific fact or piece of information to remember. This should be a clear, self-contained statement. For example, if the user says "My favorite color is blue", the fact would be "My favorite color is blue".
`;
export const GEMINI_CONFIG_DIR = '.gemini';
export const DEFAULT_CONTEXT_FILENAME = 'GEMINI.md';
export const MEMORY_SECTION_HEADER = '## Gemini Added Memories';
@@ -98,7 +97,7 @@ interface SaveMemoryParams {
modified_content?: string;
}
function getGlobalMemoryFilePath(): string {
export function getGlobalMemoryFilePath(): string {
return path.join(Storage.getGlobalGeminiDir(), getCurrentGeminiMdFilename());
}

View File

@@ -11,6 +11,7 @@ import * as os from 'node:os';
import { getFolderStructure } from './getFolderStructure.js';
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
import * as path from 'node:path';
import { GEMINI_DIR } from './paths.js';
describe('getFolderStructure', () => {
let testRootDir: string;
@@ -255,8 +256,8 @@ ${testRootDir}${path.sep}
await createTestFile('file1.txt');
await createTestFile('node_modules', 'some-package', 'index.js');
await createTestFile('ignored.txt');
await createTestFile('.gemini', 'config.yaml');
await createTestFile('.gemini', 'logs.json');
await createTestFile(GEMINI_DIR, 'config.yaml');
await createTestFile(GEMINI_DIR, 'logs.json');
const fileService = new FileDiscoveryService(testRootDir);
const structure = await getFolderStructure(testRootDir, {
@@ -301,8 +302,8 @@ ${testRootDir}${path.sep}
await createTestFile('file1.txt');
await createTestFile('node_modules', 'some-package', 'index.js');
await createTestFile('ignored.txt');
await createTestFile('.gemini', 'config.yaml');
await createTestFile('.gemini', 'logs.json');
await createTestFile(GEMINI_DIR, 'config.yaml');
await createTestFile(GEMINI_DIR, 'logs.json');
const fileService = new FileDiscoveryService(testRootDir);
const structure = await getFolderStructure(testRootDir, {
@@ -321,8 +322,8 @@ ${testRootDir}${path.sep}
await createTestFile('file1.txt');
await createTestFile('node_modules', 'some-package', 'index.js');
await createTestFile('ignored.txt');
await createTestFile('.gemini', 'config.yaml');
await createTestFile('.gemini', 'logs.json');
await createTestFile(GEMINI_DIR, 'config.yaml');
await createTestFile(GEMINI_DIR, 'logs.json');
const fileService = new FileDiscoveryService(testRootDir);
const structure = await getFolderStructure(testRootDir, {

View File

@@ -11,6 +11,7 @@ import * as fs from 'node:fs';
import * as os from 'node:os';
import path from 'node:path';
import { randomUUID } from 'node:crypto';
import { GEMINI_DIR } from './paths.js';
vi.mock('node:fs', async (importOriginal) => {
const actual = await importOriginal<typeof import('node:fs')>();
@@ -41,7 +42,7 @@ describe('InstallationManager', () => {
let tempHomeDir: string;
let installationManager: InstallationManager;
const installationIdFile = () =>
path.join(tempHomeDir, '.gemini', 'installation_id');
path.join(tempHomeDir, GEMINI_DIR, 'installation_id');
beforeEach(() => {
tempHomeDir = fs.mkdtempSync(

View File

@@ -10,6 +10,7 @@ import { UserAccountManager } from './userAccountManager.js';
import * as fs from 'node:fs';
import * as os from 'node:os';
import path from 'node:path';
import { GEMINI_DIR } from './paths.js';
vi.mock('os', async (importOriginal) => {
const os = await importOriginal<typeof import('os')>();
@@ -30,7 +31,7 @@ describe('UserAccountManager', () => {
);
(os.homedir as Mock).mockReturnValue(tempHomeDir);
accountsFile = () =>
path.join(tempHomeDir, '.gemini', 'google_accounts.json');
path.join(tempHomeDir, GEMINI_DIR, 'google_accounts.json');
userAccountManager = new UserAccountManager();
});