mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-12 20:37:08 -07:00
Introduce GEMINI_CLI_HOME for strict test isolation (#15907)
This commit is contained in:
@@ -9,9 +9,8 @@ import { HybridTokenStorage } from '../mcp/token-storage/hybrid-token-storage.js
|
||||
import { OAUTH_FILE } from '../config/storage.js';
|
||||
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';
|
||||
import { GEMINI_DIR, homedir } from '../utils/paths.js';
|
||||
import { coreEvents } from '../utils/events.js';
|
||||
|
||||
const KEYCHAIN_SERVICE_NAME = 'gemini-cli-oauth';
|
||||
@@ -91,7 +90,7 @@ export class OAuthCredentialStorage {
|
||||
await this.storage.deleteCredentials(MAIN_ACCOUNT_KEY);
|
||||
|
||||
// Also try to remove the old file if it exists
|
||||
const oldFilePath = path.join(os.homedir(), GEMINI_DIR, OAUTH_FILE);
|
||||
const oldFilePath = path.join(homedir(), GEMINI_DIR, OAUTH_FILE);
|
||||
await fs.rm(oldFilePath, { force: true }).catch(() => {});
|
||||
} catch (error: unknown) {
|
||||
coreEvents.emitFeedback(
|
||||
@@ -107,7 +106,7 @@ export class OAuthCredentialStorage {
|
||||
* Migrate credentials from old file-based storage to keychain
|
||||
*/
|
||||
private static async migrateFromFileStorage(): Promise<Credentials | null> {
|
||||
const oldFilePath = path.join(os.homedir(), GEMINI_DIR, OAUTH_FILE);
|
||||
const oldFilePath = path.join(homedir(), GEMINI_DIR, OAUTH_FILE);
|
||||
|
||||
let credsJson: string;
|
||||
try {
|
||||
|
||||
@@ -26,16 +26,24 @@ 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';
|
||||
import { GEMINI_DIR, homedir as pathsHomedir } from '../utils/paths.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
import { writeToStdout } from '../utils/stdio.js';
|
||||
import { FatalCancellationError } from '../utils/errors.js';
|
||||
import process from 'node:process';
|
||||
|
||||
vi.mock('os', async (importOriginal) => {
|
||||
const os = await importOriginal<typeof import('os')>();
|
||||
vi.mock('node:os', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('node:os')>();
|
||||
return {
|
||||
...os,
|
||||
...actual,
|
||||
homedir: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../utils/paths.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('../utils/paths.js')>();
|
||||
return {
|
||||
...actual,
|
||||
homedir: vi.fn(),
|
||||
};
|
||||
});
|
||||
@@ -89,6 +97,7 @@ describe('oauth2', () => {
|
||||
path.join(os.tmpdir(), 'gemini-cli-test-home-'),
|
||||
);
|
||||
vi.mocked(os.homedir).mockReturnValue(tempHomeDir);
|
||||
vi.mocked(pathsHomedir).mockReturnValue(tempHomeDir);
|
||||
});
|
||||
afterEach(() => {
|
||||
fs.rmSync(tempHomeDir, { recursive: true, force: true });
|
||||
@@ -1129,15 +1138,10 @@ describe('oauth2', () => {
|
||||
() => mockHttpServer as unknown as http.Server,
|
||||
);
|
||||
|
||||
// Mock process.on to immediately trigger SIGINT
|
||||
// Mock process.on to capture SIGINT handler
|
||||
const processOnSpy = vi
|
||||
.spyOn(process, 'on')
|
||||
.mockImplementation((event, listener: () => void) => {
|
||||
if (event === 'SIGINT') {
|
||||
listener();
|
||||
}
|
||||
return process;
|
||||
});
|
||||
.mockImplementation(() => process);
|
||||
|
||||
const processRemoveListenerSpy = vi.spyOn(process, 'removeListener');
|
||||
|
||||
@@ -1146,6 +1150,24 @@ describe('oauth2', () => {
|
||||
mockConfig,
|
||||
);
|
||||
|
||||
// Wait for the SIGINT handler to be registered
|
||||
let sigIntHandler: (() => void) | undefined;
|
||||
await vi.waitFor(() => {
|
||||
const sigintCall = processOnSpy.mock.calls.find(
|
||||
(call) => call[0] === 'SIGINT',
|
||||
);
|
||||
sigIntHandler = sigintCall?.[1] as (() => void) | undefined;
|
||||
if (!sigIntHandler)
|
||||
throw new Error('SIGINT handler not registered yet');
|
||||
});
|
||||
|
||||
expect(sigIntHandler).toBeDefined();
|
||||
|
||||
// Trigger SIGINT
|
||||
if (sigIntHandler) {
|
||||
sigIntHandler();
|
||||
}
|
||||
|
||||
await expect(clientPromise).rejects.toThrow(FatalCancellationError);
|
||||
expect(processRemoveListenerSpy).toHaveBeenCalledWith(
|
||||
'SIGINT',
|
||||
@@ -1180,17 +1202,10 @@ describe('oauth2', () => {
|
||||
() => mockHttpServer as unknown as http.Server,
|
||||
);
|
||||
|
||||
// Spy on process.stdin.on and immediately trigger Ctrl+C
|
||||
// Spy on process.stdin.on to capture data handler
|
||||
const stdinOnSpy = vi
|
||||
.spyOn(process.stdin, 'on')
|
||||
.mockImplementation(
|
||||
(event: string, listener: (data: Buffer) => void) => {
|
||||
if (event === 'data') {
|
||||
listener(Buffer.from([0x03]));
|
||||
}
|
||||
return process.stdin;
|
||||
},
|
||||
);
|
||||
.mockImplementation(() => process.stdin);
|
||||
|
||||
const stdinRemoveListenerSpy = vi.spyOn(
|
||||
process.stdin,
|
||||
@@ -1202,6 +1217,23 @@ describe('oauth2', () => {
|
||||
mockConfig,
|
||||
);
|
||||
|
||||
// Wait for the stdin handler to be registered
|
||||
let dataHandler: ((data: Buffer) => void) | undefined;
|
||||
await vi.waitFor(() => {
|
||||
const dataCall = stdinOnSpy.mock.calls.find(
|
||||
(call: [string, ...unknown[]]) => call[0] === 'data',
|
||||
);
|
||||
dataHandler = dataCall?.[1] as ((data: Buffer) => void) | undefined;
|
||||
if (!dataHandler) throw new Error('stdin handler not registered yet');
|
||||
});
|
||||
|
||||
expect(dataHandler).toBeDefined();
|
||||
|
||||
// Trigger Ctrl+C
|
||||
if (dataHandler) {
|
||||
dataHandler(Buffer.from([0x03]));
|
||||
}
|
||||
|
||||
await expect(clientPromise).rejects.toThrow(FatalCancellationError);
|
||||
expect(stdinRemoveListenerSpy).toHaveBeenCalledWith(
|
||||
'data',
|
||||
@@ -1302,7 +1334,8 @@ describe('oauth2', () => {
|
||||
tempHomeDir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'gemini-cli-test-home-'),
|
||||
);
|
||||
(os.homedir as Mock).mockReturnValue(tempHomeDir);
|
||||
vi.mocked(os.homedir).mockReturnValue(tempHomeDir);
|
||||
vi.mocked(pathsHomedir).mockReturnValue(tempHomeDir);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -336,7 +336,7 @@ async function initOauthClient(
|
||||
|
||||
// Note that SIGINT might not get raised on Ctrl+C in raw mode
|
||||
// so we also need to look for Ctrl+C directly in stdin.
|
||||
stdinHandler = (data) => {
|
||||
stdinHandler = (data: Buffer) => {
|
||||
if (data.includes(0x03)) {
|
||||
reject(
|
||||
new FatalCancellationError('Authentication cancelled by user.'),
|
||||
|
||||
@@ -8,7 +8,7 @@ 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';
|
||||
import { GEMINI_DIR, homedir } from '../utils/paths.js';
|
||||
|
||||
export const GOOGLE_ACCOUNTS_FILENAME = 'google_accounts.json';
|
||||
export const OAUTH_FILE = 'oauth_creds.json';
|
||||
@@ -23,7 +23,7 @@ export class Storage {
|
||||
}
|
||||
|
||||
static getGlobalGeminiDir(): string {
|
||||
const homeDir = os.homedir();
|
||||
const homeDir = homedir();
|
||||
if (!homeDir) {
|
||||
return path.join(os.tmpdir(), GEMINI_DIR);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import {
|
||||
EDIT_TOOL_NAME,
|
||||
GLOB_TOOL_NAME,
|
||||
@@ -23,7 +22,7 @@ import process from 'node:process';
|
||||
import { isGitRepository } from '../utils/gitUtils.js';
|
||||
import { CodebaseInvestigatorAgent } from '../agents/codebase-investigator.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import { GEMINI_DIR } from '../utils/paths.js';
|
||||
import { GEMINI_DIR, homedir } from '../utils/paths.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
import { WriteTodosTool } from '../tools/write-todos.js';
|
||||
import { resolveModel, isPreviewModel } from '../config/models.js';
|
||||
@@ -53,7 +52,7 @@ export function resolvePathFromEnv(envVar?: string): {
|
||||
// Safely expand the tilde (~) character to the user's home directory.
|
||||
if (customPath.startsWith('~/') || customPath === '~') {
|
||||
try {
|
||||
const home = os.homedir(); // This is the call that can throw an error.
|
||||
const home = homedir(); // This is the call that can throw an error.
|
||||
if (customPath === '~') {
|
||||
customPath = home;
|
||||
} else {
|
||||
|
||||
@@ -14,8 +14,15 @@ vi.mock('node:child_process', async (importOriginal) => {
|
||||
spawnSync: vi.fn(() => ({ status: 0 })),
|
||||
};
|
||||
});
|
||||
vi.mock('fs');
|
||||
vi.mock('os');
|
||||
vi.mock('node:fs');
|
||||
vi.mock('node:os');
|
||||
vi.mock('../utils/paths.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('../utils/paths.js')>();
|
||||
return {
|
||||
...actual,
|
||||
homedir: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { getIdeInstaller } from './ide-installer.js';
|
||||
@@ -24,12 +31,14 @@ import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { IDE_DEFINITIONS, type IdeInfo } from './detect-ide.js';
|
||||
import { homedir as pathsHomedir } from '../utils/paths.js';
|
||||
|
||||
describe('ide-installer', () => {
|
||||
const HOME_DIR = '/home/user';
|
||||
|
||||
beforeEach(() => {
|
||||
vi.spyOn(os, 'homedir').mockReturnValue(HOME_DIR);
|
||||
vi.mocked(pathsHomedir).mockReturnValue(HOME_DIR);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -8,9 +8,9 @@ import * as child_process from 'node:child_process';
|
||||
import * as process from 'node:process';
|
||||
import * as path from 'node:path';
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import { IDE_DEFINITIONS, type IdeInfo } from './detect-ide.js';
|
||||
import { GEMINI_CLI_COMPANION_EXTENSION_NAME } from './constants.js';
|
||||
import { homedir } from '../utils/paths.js';
|
||||
|
||||
export interface IdeInstaller {
|
||||
install(): Promise<InstallResult>;
|
||||
@@ -49,7 +49,7 @@ async function findCommand(
|
||||
|
||||
// 2. Check common installation locations.
|
||||
const locations: string[] = [];
|
||||
const homeDir = os.homedir();
|
||||
const homeDir = homedir();
|
||||
|
||||
if (command === 'code' || command === 'code.cmd') {
|
||||
if (platform === 'darwin') {
|
||||
|
||||
@@ -50,6 +50,7 @@ export * from './code_assist/telemetry.js';
|
||||
export * from './core/apiKeyCredentialStorage.js';
|
||||
|
||||
// Export utilities
|
||||
export { homedir, tmpdir } from './utils/paths.js';
|
||||
export * from './utils/paths.js';
|
||||
export * from './utils/schemaValidator.js';
|
||||
export * from './utils/errors.js';
|
||||
|
||||
@@ -10,7 +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';
|
||||
import { GEMINI_DIR, homedir } from '../../utils/paths.js';
|
||||
|
||||
export class FileTokenStorage extends BaseTokenStorage {
|
||||
private readonly tokenFilePath: string;
|
||||
@@ -18,7 +18,7 @@ export class FileTokenStorage extends BaseTokenStorage {
|
||||
|
||||
constructor(serviceName: string) {
|
||||
super(serviceName);
|
||||
const configDir = path.join(os.homedir(), GEMINI_DIR);
|
||||
const configDir = path.join(homedir(), GEMINI_DIR);
|
||||
this.tokenFilePath = path.join(configDir, 'mcp-oauth-tokens-v2.json');
|
||||
this.encryptionKey = this.deriveEncryptionKey();
|
||||
}
|
||||
|
||||
@@ -18,7 +18,11 @@ import { Storage } from '../config/storage.js';
|
||||
import * as path from 'node:path';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as os from 'node:os';
|
||||
import { getProjectHash, GEMINI_DIR } from '../utils/paths.js';
|
||||
import {
|
||||
getProjectHash,
|
||||
GEMINI_DIR,
|
||||
homedir as pathsHomedir,
|
||||
} from '../utils/paths.js';
|
||||
import { spawnAsync } from '../utils/shell-utils.js';
|
||||
|
||||
vi.mock('../utils/shell-utils.js', () => ({
|
||||
@@ -52,7 +56,7 @@ vi.mock('../utils/gitUtils.js', () => ({
|
||||
}));
|
||||
|
||||
const hoistedMockHomedir = vi.hoisted(() => vi.fn());
|
||||
vi.mock('os', async (importOriginal) => {
|
||||
vi.mock('node:os', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof os>();
|
||||
return {
|
||||
...actual,
|
||||
@@ -60,6 +64,14 @@ vi.mock('os', async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../utils/paths.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('../utils/paths.js')>();
|
||||
return {
|
||||
...actual,
|
||||
homedir: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const hoistedMockDebugLogger = vi.hoisted(() => ({
|
||||
debug: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
@@ -93,6 +105,7 @@ describe('GitService', () => {
|
||||
});
|
||||
|
||||
hoistedMockHomedir.mockReturnValue(homedir);
|
||||
(pathsHomedir as Mock).mockReturnValue(homedir);
|
||||
|
||||
hoistedMockEnv.mockImplementation(() => ({
|
||||
checkIsRepo: hoistedMockCheckIsRepo,
|
||||
|
||||
@@ -11,7 +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';
|
||||
import { GEMINI_DIR, homedir as pathsHomedir } from './paths.js';
|
||||
import { debugLogger } from './debugLogger.js';
|
||||
|
||||
vi.mock('node:fs', async (importOriginal) => {
|
||||
@@ -23,22 +23,30 @@ vi.mock('node:fs', async (importOriginal) => {
|
||||
} as typeof actual;
|
||||
});
|
||||
|
||||
vi.mock('os', async (importOriginal) => {
|
||||
const os = await importOriginal<typeof import('os')>();
|
||||
vi.mock('node:os', async (importOriginal) => {
|
||||
const os = await importOriginal<typeof import('node:os')>();
|
||||
return {
|
||||
...os,
|
||||
homedir: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('crypto', async (importOriginal) => {
|
||||
const crypto = await importOriginal<typeof import('crypto')>();
|
||||
vi.mock('node:crypto', async (importOriginal) => {
|
||||
const crypto = await importOriginal<typeof import('node:crypto')>();
|
||||
return {
|
||||
...crypto,
|
||||
randomUUID: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('./paths.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('./paths.js')>();
|
||||
return {
|
||||
...actual,
|
||||
homedir: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('InstallationManager', () => {
|
||||
let tempHomeDir: string;
|
||||
let installationManager: InstallationManager;
|
||||
@@ -49,6 +57,7 @@ describe('InstallationManager', () => {
|
||||
tempHomeDir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'gemini-cli-test-home-'),
|
||||
);
|
||||
(pathsHomedir as Mock).mockReturnValue(tempHomeDir);
|
||||
(os.homedir as Mock).mockReturnValue(tempHomeDir);
|
||||
installationManager = new InstallationManager();
|
||||
});
|
||||
|
||||
@@ -35,6 +35,16 @@ vi.mock('os', async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../utils/paths.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('../utils/paths.js')>();
|
||||
return {
|
||||
...actual,
|
||||
homedir: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
import { homedir as pathsHomedir } from './paths.js';
|
||||
|
||||
describe('memoryDiscovery', () => {
|
||||
const DEFAULT_FOLDER_TRUST = true;
|
||||
let testRootDir: string;
|
||||
@@ -67,6 +77,7 @@ describe('memoryDiscovery', () => {
|
||||
cwd = await createEmptyDir(path.join(projectRoot, 'src'));
|
||||
homedir = await createEmptyDir(path.join(testRootDir, 'userhome'));
|
||||
vi.mocked(os.homedir).mockReturnValue(homedir);
|
||||
vi.mocked(pathsHomedir).mockReturnValue(homedir);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
||||
@@ -7,14 +7,13 @@
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as fsSync from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
import { bfsFileSearch } from './bfsFileSearch.js';
|
||||
import { getAllGeminiMdFilenames } from '../tools/memoryTool.js';
|
||||
import type { FileDiscoveryService } from '../services/fileDiscoveryService.js';
|
||||
import { processImports } from './memoryImportProcessor.js';
|
||||
import type { FileFilteringOptions } from '../config/constants.js';
|
||||
import { DEFAULT_MEMORY_FILE_FILTERING_OPTIONS } from '../config/constants.js';
|
||||
import { GEMINI_DIR } from './paths.js';
|
||||
import { GEMINI_DIR, homedir } from './paths.js';
|
||||
import type { ExtensionLoader } from './extensionLoader.js';
|
||||
import { debugLogger } from './debugLogger.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import process from 'node:process';
|
||||
import * as crypto from 'node:crypto';
|
||||
|
||||
export const GEMINI_DIR = '.gemini';
|
||||
@@ -18,13 +19,33 @@ export const GOOGLE_ACCOUNTS_FILENAME = 'google_accounts.json';
|
||||
*/
|
||||
export const SHELL_SPECIAL_CHARS = /[ \t()[\]{};|*?$`'"#&<>!~]/;
|
||||
|
||||
/**
|
||||
* Returns the home directory.
|
||||
* If GEMINI_CLI_HOME environment variable is set, it returns its value.
|
||||
* Otherwise, it returns the user's home directory.
|
||||
*/
|
||||
export function homedir(): string {
|
||||
const envHome = process.env['GEMINI_CLI_HOME'];
|
||||
if (envHome) {
|
||||
return envHome;
|
||||
}
|
||||
return os.homedir();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the operating system's default directory for temporary files.
|
||||
*/
|
||||
export function tmpdir(): string {
|
||||
return os.tmpdir();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the home directory with a tilde.
|
||||
* @param path - The path to tildeify.
|
||||
* @returns The tildeified path.
|
||||
*/
|
||||
export function tildeifyPath(path: string): string {
|
||||
const homeDir = os.homedir();
|
||||
const homeDir = homedir();
|
||||
if (path.startsWith(homeDir)) {
|
||||
return path.replace(homeDir, '~');
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ 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';
|
||||
import { GEMINI_DIR, homedir as pathsHomedir } from './paths.js';
|
||||
import { debugLogger } from './debugLogger.js';
|
||||
|
||||
vi.mock('os', async (importOriginal) => {
|
||||
const os = await importOriginal<typeof import('os')>();
|
||||
vi.mock('./paths.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('./paths.js')>();
|
||||
return {
|
||||
...os,
|
||||
...actual,
|
||||
homedir: vi.fn(),
|
||||
};
|
||||
});
|
||||
@@ -30,7 +30,7 @@ describe('UserAccountManager', () => {
|
||||
tempHomeDir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'gemini-cli-test-home-'),
|
||||
);
|
||||
(os.homedir as Mock).mockReturnValue(tempHomeDir);
|
||||
(pathsHomedir as Mock).mockReturnValue(tempHomeDir);
|
||||
accountsFile = () =>
|
||||
path.join(tempHomeDir, GEMINI_DIR, 'google_accounts.json');
|
||||
userAccountManager = new UserAccountManager();
|
||||
|
||||
Reference in New Issue
Block a user