Merge tag 'v0.1.15' into feature/yiheng/sync-gemini-cli-0.1.15

This commit is contained in:
奕桁
2025-08-01 23:06:11 +08:00
340 changed files with 36528 additions and 22931 deletions

View File

@@ -0,0 +1,14 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { EventEmitter } from 'events';
export enum AppEvent {
OpenDebugConsole = 'open-debug-console',
LogError = 'log-error',
}
export const appEvents = new EventEmitter();

View File

@@ -99,7 +99,7 @@ async function shouldUseCurrentUserInSandbox(): Promise<boolean> {
}
// docker does not allow container names to contain ':' or '/', so we
// parse those out and make the name a little shorter
// parse those out to shorten the name
function parseImageName(image: string): string {
const [fullName, tag] = image.split(':');
const name = fullName.split('/').at(-1) ?? 'unknown-image';
@@ -187,7 +187,7 @@ export async function start_sandbox(
if (config.command === 'sandbox-exec') {
// disallow BUILD_SANDBOX
if (process.env.BUILD_SANDBOX) {
console.error('ERROR: cannot BUILD_SANDBOX when using MacOS Seatbelt');
console.error('ERROR: cannot BUILD_SANDBOX when using macOS Seatbelt');
process.exit(1);
}
const profile = (process.env.SEATBELT_PROFILE ??= 'permissive-open');
@@ -536,6 +536,14 @@ export async function start_sandbox(
);
}
// copy GOOGLE_GENAI_USE_GCA
if (process.env.GOOGLE_GENAI_USE_GCA) {
args.push(
'--env',
`GOOGLE_GENAI_USE_GCA=${process.env.GOOGLE_GENAI_USE_GCA}`,
);
}
// copy GOOGLE_CLOUD_PROJECT
if (process.env.GOOGLE_CLOUD_PROJECT) {
args.push(
@@ -858,7 +866,7 @@ async function ensureSandboxImageIsPresent(
console.info(`Sandbox image ${image} not found locally.`);
if (image === LOCAL_DEV_SANDBOX_IMAGE_NAME) {
// user needs to build the image themself
// user needs to build the image themselves
return false;
}

View File

@@ -8,113 +8,80 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { getUserStartupWarnings } from './userStartupWarnings.js';
import * as os from 'os';
import fs from 'fs/promises';
import semver from 'semver';
import path from 'path';
vi.mock('os', () => ({
default: { homedir: vi.fn() },
homedir: vi.fn(),
}));
vi.mock('fs/promises', () => ({
default: { realpath: vi.fn() },
}));
vi.mock('semver', () => ({
default: {
major: vi.fn(),
},
major: vi.fn(),
}));
// Mock os.homedir to control the home directory in tests
vi.mock('os', async (importOriginal) => {
const actualOs = await importOriginal<typeof os>();
return {
...actualOs,
homedir: vi.fn(),
};
});
describe('getUserStartupWarnings', () => {
const homeDir = '/home/user';
let testRootDir: string;
let homeDir: string;
beforeEach(() => {
beforeEach(async () => {
testRootDir = await fs.mkdtemp(path.join(os.tmpdir(), 'warnings-test-'));
homeDir = path.join(testRootDir, 'home');
await fs.mkdir(homeDir, { recursive: true });
vi.mocked(os.homedir).mockReturnValue(homeDir);
vi.mocked(fs.realpath).mockImplementation(async (path) => path.toString());
});
afterEach(() => {
afterEach(async () => {
await fs.rm(testRootDir, { recursive: true, force: true });
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');
const projectDir = path.join(testRootDir, 'project');
await fs.mkdir(projectDir);
const warnings = await getUserStartupWarnings(projectDir);
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');
describe('root directory check', () => {
it('should return a warning when running in a root directory', async () => {
const rootDir = path.parse(testRootDir).root;
const warnings = await getUserStartupWarnings(rootDir);
expect(warnings).toContainEqual(
expect.stringContaining('Could not verify'),
expect.stringContaining('root directory'),
);
expect(warnings).toContainEqual(
expect.stringContaining('folder structure will be used'),
);
});
it('should not return a warning when running in a non-root directory', async () => {
const projectDir = path.join(testRootDir, 'project');
await fs.mkdir(projectDir);
const warnings = await getUserStartupWarnings(projectDir);
expect(warnings).not.toContainEqual(
expect.stringContaining('root directory'),
);
});
});
function setNodeVersionMajor(majorVersion: number) {
vi.mocked(semver.major).mockReturnValue(majorVersion);
}
describe('node version check', () => {
afterEach(() => {
setNodeVersionMajor(20);
});
it('should return a warning if Node.js version is less than minMajor', async () => {
setNodeVersionMajor(18);
const warnings = await getUserStartupWarnings('');
expect(warnings).toHaveLength(1);
expect(warnings[0]).toContain('Node.js');
expect(warnings[0]).toContain('requires Node.js 20 or higher');
});
it('should not return a warning if Node.js version is equal to minMajor', async () => {
setNodeVersionMajor(20);
const warnings = await getUserStartupWarnings('');
expect(warnings).toEqual([]);
});
it('should not return a warning if Node.js version is greater than minMajor', async () => {
setNodeVersionMajor(22);
const warnings = await getUserStartupWarnings('');
expect(warnings).toEqual([]);
});
it('should use default minMajor=20 if not provided', async () => {
setNodeVersionMajor(18);
const warnings = await getUserStartupWarnings('');
expect(warnings).toHaveLength(1);
expect(warnings[0]).toContain('Node.js');
expect(warnings[0]).toContain('requires Node.js 20 or higher');
describe('error handling', () => {
it('should handle errors when checking directory', async () => {
const nonExistentPath = path.join(testRootDir, 'non-existent');
const warnings = await getUserStartupWarnings(nonExistentPath);
const expectedWarning =
'Could not verify the current directory due to a file system error.';
expect(warnings).toEqual([expectedWarning, expectedWarning]);
});
});
// // 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
// });
});

View File

@@ -6,7 +6,7 @@
import fs from 'fs/promises';
import * as os from 'os';
import semver from 'semver';
import path from 'path';
type WarningCheck = {
id: string;
@@ -24,7 +24,7 @@ const homeDirectoryCheck: WarningCheck = {
]);
if (workspaceRealPath === homeRealPath) {
return 'You are running Qwen Code in your home directory. It is recommended to run in a project-specific directory.';
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) {
@@ -33,22 +33,30 @@ const homeDirectoryCheck: WarningCheck = {
},
};
const nodeVersionCheck: WarningCheck = {
id: 'node-version',
check: async (_workspaceRoot: string) => {
const minMajor = 20;
const major = semver.major(process.versions.node);
if (major < minMajor) {
return `You are using Node.js v${process.versions.node}. Gemini CLI requires Node.js ${minMajor} or higher for best results.`;
const rootDirectoryCheck: WarningCheck = {
id: 'root-directory',
check: async (workspaceRoot: string) => {
try {
const workspaceRealPath = await fs.realpath(workspaceRoot);
const errorMessage =
'Warning: You are running Qwen Code in the root directory. Your entire folder structure will be used for context. It is strongly recommended to run in a project-specific directory.';
// Check for Unix root directory
if (path.dirname(workspaceRealPath) === workspaceRealPath) {
return errorMessage;
}
return null;
} catch (_err: unknown) {
return 'Could not verify the current directory due to a file system error.';
}
return null;
},
};
// All warning checks
const WARNING_CHECKS: readonly WarningCheck[] = [
homeDirectoryCheck,
nodeVersionCheck,
rootDirectoryCheck,
];
export async function getUserStartupWarnings(