mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-08 00:57:16 -07:00
fix(cli): resolve devtools build issues and update third-party notices
This commit is contained in:
@@ -31,6 +31,7 @@
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.12.0",
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
"@google/gemini-cli-devtools": "file:../devtools",
|
||||
"@google/genai": "1.30.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@modelcontextprotocol/sdk": "^1.23.0",
|
||||
@@ -81,6 +82,7 @@
|
||||
"@types/ws": "^8.5.10",
|
||||
"@types/yargs": "^17.0.32",
|
||||
"@xterm/headless": "^5.5.0",
|
||||
"ink-testing-library": "^4.0.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vitest": "^3.1.1"
|
||||
},
|
||||
|
||||
@@ -292,9 +292,8 @@ System using model: \${MODEL_NAME}
|
||||
});
|
||||
expect(extension.skills![0].body).toContain('Value is: first');
|
||||
|
||||
const { updateSetting, ExtensionSettingScope } = await import(
|
||||
'./extensions/extensionSettings.js'
|
||||
);
|
||||
const { updateSetting, ExtensionSettingScope } =
|
||||
await import('./extensions/extensionSettings.js');
|
||||
const extensionConfig =
|
||||
await extensionManager.loadExtensionConfig(extensionPath);
|
||||
|
||||
|
||||
@@ -746,9 +746,8 @@ describe('gemini.tsx main function kitty protocol', () => {
|
||||
});
|
||||
|
||||
it.skip('should log error when cleanupExpiredSessions fails', async () => {
|
||||
const { cleanupExpiredSessions } = await import(
|
||||
'./utils/sessionCleanup.js'
|
||||
);
|
||||
const { cleanupExpiredSessions } =
|
||||
await import('./utils/sessionCleanup.js');
|
||||
vi.mocked(cleanupExpiredSessions).mockRejectedValue(
|
||||
new Error('Cleanup failed'),
|
||||
);
|
||||
|
||||
@@ -568,10 +568,14 @@ export async function main() {
|
||||
adminControlsListner.setConfig(config);
|
||||
|
||||
if (config.isInteractive() && settings.merged.general.devtools) {
|
||||
const { setupInitialActivityLogger } = await import(
|
||||
'./utils/devtoolsService.js'
|
||||
);
|
||||
const { setupInitialActivityLogger, stopDevTools } =
|
||||
await import('./utils/devtoolsService.js');
|
||||
const { ActivityLogger } = await import('./utils/activityLogger.js');
|
||||
await setupInitialActivityLogger(config);
|
||||
registerCleanup(async () => {
|
||||
await stopDevTools();
|
||||
ActivityLogger.getInstance().dispose();
|
||||
});
|
||||
}
|
||||
|
||||
// Register config for telemetry shutdown
|
||||
|
||||
@@ -180,9 +180,8 @@ describe('gemini.tsx main function cleanup', () => {
|
||||
});
|
||||
|
||||
it('should log error when cleanupExpiredSessions fails', async () => {
|
||||
const { loadCliConfig, parseArguments } = await import(
|
||||
'./config/config.js'
|
||||
);
|
||||
const { loadCliConfig, parseArguments } =
|
||||
await import('./config/config.js');
|
||||
const { loadSettings } = await import('./config/settings.js');
|
||||
cleanupMockState.shouldThrow = true;
|
||||
cleanupMockState.called = false;
|
||||
|
||||
@@ -212,9 +212,8 @@ describe('runNonInteractive', () => {
|
||||
computeMergedSettings: vi.fn(),
|
||||
} as unknown as LoadedSettings;
|
||||
|
||||
const { handleAtCommand } = await import(
|
||||
'./ui/hooks/atCommandProcessor.js'
|
||||
);
|
||||
const { handleAtCommand } =
|
||||
await import('./ui/hooks/atCommandProcessor.js');
|
||||
vi.mocked(handleAtCommand).mockImplementation(async ({ query }) => ({
|
||||
processedQuery: [{ text: query }],
|
||||
}));
|
||||
@@ -636,9 +635,8 @@ describe('runNonInteractive', () => {
|
||||
|
||||
it('should preprocess @include commands before sending to the model', async () => {
|
||||
// 1. Mock the imported atCommandProcessor
|
||||
const { handleAtCommand } = await import(
|
||||
'./ui/hooks/atCommandProcessor.js'
|
||||
);
|
||||
const { handleAtCommand } =
|
||||
await import('./ui/hooks/atCommandProcessor.js');
|
||||
const mockHandleAtCommand = vi.mocked(handleAtCommand);
|
||||
|
||||
// 2. Define the raw input and the expected processed output
|
||||
@@ -995,9 +993,8 @@ describe('runNonInteractive', () => {
|
||||
});
|
||||
|
||||
it('should handle slash commands', async () => {
|
||||
const nonInteractiveCliCommands = await import(
|
||||
'./nonInteractiveCliCommands.js'
|
||||
);
|
||||
const nonInteractiveCliCommands =
|
||||
await import('./nonInteractiveCliCommands.js');
|
||||
const handleSlashCommandSpy = vi.spyOn(
|
||||
nonInteractiveCliCommands,
|
||||
'handleSlashCommand',
|
||||
@@ -1276,13 +1273,11 @@ describe('runNonInteractive', () => {
|
||||
|
||||
it('should instantiate CommandService with correct loaders for slash commands', async () => {
|
||||
// This test indirectly checks that handleSlashCommand is using the right loaders.
|
||||
const { FileCommandLoader } = await import(
|
||||
'./services/FileCommandLoader.js'
|
||||
);
|
||||
const { FileCommandLoader } =
|
||||
await import('./services/FileCommandLoader.js');
|
||||
const { McpPromptLoader } = await import('./services/McpPromptLoader.js');
|
||||
const { BuiltinCommandLoader } = await import(
|
||||
'./services/BuiltinCommandLoader.js'
|
||||
);
|
||||
const { BuiltinCommandLoader } =
|
||||
await import('./services/BuiltinCommandLoader.js');
|
||||
mockGetCommands.mockReturnValue([]); // No commands found, so it will fall through
|
||||
const events: ServerGeminiStreamEvent[] = [
|
||||
{ type: GeminiEventType.Content, value: 'Acknowledged' },
|
||||
|
||||
@@ -72,9 +72,8 @@ export async function runNonInteractive({
|
||||
});
|
||||
|
||||
if (process.env['GEMINI_CLI_ACTIVITY_LOG_TARGET']) {
|
||||
const { setupInitialActivityLogger } = await import(
|
||||
'./utils/devtoolsService.js'
|
||||
);
|
||||
const { setupInitialActivityLogger } =
|
||||
await import('./utils/devtoolsService.js');
|
||||
await setupInitialActivityLogger(config);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,9 +42,8 @@ import { AuthState } from '../ui/types.js';
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const original =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
const { MockShellExecutionService: MockService } = await import(
|
||||
'./MockShellExecutionService.js'
|
||||
);
|
||||
const { MockShellExecutionService: MockService } =
|
||||
await import('./MockShellExecutionService.js');
|
||||
// Register the real execution logic so MockShellExecutionService can fall back to it
|
||||
MockService.setOriginalImplementation(original.ShellExecutionService.execute);
|
||||
|
||||
|
||||
@@ -357,7 +357,8 @@ export const render = (
|
||||
debug: false,
|
||||
exitOnCtrlC: false,
|
||||
patchConsole: false,
|
||||
onRender: (metrics: { output: string; staticOutput?: string }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onRender: (metrics: any) => {
|
||||
stdout.onRender(metrics.staticOutput ?? '', metrics.output);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3292,9 +3292,8 @@ describe('AppContainer State Management', () => {
|
||||
|
||||
describe('Permission Handling', () => {
|
||||
it('shows permission dialog when checkPermissions returns paths', async () => {
|
||||
const { checkPermissions } = await import(
|
||||
'./hooks/atCommandProcessor.js'
|
||||
);
|
||||
const { checkPermissions } =
|
||||
await import('./hooks/atCommandProcessor.js');
|
||||
vi.mocked(checkPermissions).mockResolvedValue(['/test/file.txt']);
|
||||
|
||||
let unmount: () => void;
|
||||
@@ -3316,9 +3315,8 @@ describe('AppContainer State Management', () => {
|
||||
it.each([true, false])(
|
||||
'handles permissions when allowed is %s',
|
||||
async (allowed) => {
|
||||
const { checkPermissions } = await import(
|
||||
'./hooks/atCommandProcessor.js'
|
||||
);
|
||||
const { checkPermissions } =
|
||||
await import('./hooks/atCommandProcessor.js');
|
||||
vi.mocked(checkPermissions).mockResolvedValue(['/test/file.txt']);
|
||||
const addReadOnlyPathSpy = vi.spyOn(
|
||||
mockConfig.getWorkspaceContext(),
|
||||
|
||||
@@ -1392,9 +1392,9 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
}, []);
|
||||
const shouldShowIdePrompt = Boolean(
|
||||
currentIDE &&
|
||||
!config.getIdeMode() &&
|
||||
!settings.merged.ide.hasSeenNudge &&
|
||||
!idePromptAnswered,
|
||||
!config.getIdeMode() &&
|
||||
!settings.merged.ide.hasSeenNudge &&
|
||||
!idePromptAnswered,
|
||||
);
|
||||
|
||||
const [showErrorDetails, setShowErrorDetails] = useState<boolean>(false);
|
||||
@@ -1656,9 +1656,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
if (keyMatchers[Command.SHOW_ERROR_DETAILS](key)) {
|
||||
if (settings.merged.general.devtools) {
|
||||
void (async () => {
|
||||
const { toggleDevToolsPanel } = await import(
|
||||
'../utils/devtoolsService.js'
|
||||
);
|
||||
const { toggleDevToolsPanel } =
|
||||
await import('../utils/devtoolsService.js');
|
||||
await toggleDevToolsPanel(
|
||||
config,
|
||||
showErrorDetails,
|
||||
@@ -2073,11 +2072,9 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
let isMounted = true;
|
||||
|
||||
const fetchBannerTexts = async () => {
|
||||
const [defaultBanner, warningBanner] = await Promise.all([
|
||||
// TODO: temporarily disabling the banner, it will be re-added.
|
||||
'',
|
||||
config.getBannerTextCapacityIssues(),
|
||||
]);
|
||||
// TODO: temporarily disabling the banner, it will be re-added.
|
||||
const defaultBanner = '';
|
||||
const warningBanner = await config.getBannerTextCapacityIssues();
|
||||
|
||||
if (isMounted) {
|
||||
setDefaultBannerText(defaultBanner);
|
||||
|
||||
@@ -73,9 +73,8 @@ describe('authCommand', () => {
|
||||
const logoutCommand = authCommand.subCommands?.[1];
|
||||
expect(logoutCommand?.name).toBe('logout');
|
||||
|
||||
const { clearCachedCredentialFile } = await import(
|
||||
'@google/gemini-cli-core'
|
||||
);
|
||||
const { clearCachedCredentialFile } =
|
||||
await import('@google/gemini-cli-core');
|
||||
|
||||
await logoutCommand!.action!(mockContext, '');
|
||||
|
||||
|
||||
@@ -1048,9 +1048,8 @@ describe('extensionsCommand', () => {
|
||||
const prompts = (await import('prompts')).default;
|
||||
vi.mocked(prompts).mockResolvedValue({ overwrite: true });
|
||||
|
||||
const { getScopedEnvContents } = await import(
|
||||
'../../config/extensions/extensionSettings.js'
|
||||
);
|
||||
const { getScopedEnvContents } =
|
||||
await import('../../config/extensions/extensionSettings.js');
|
||||
vi.mocked(getScopedEnvContents).mockResolvedValue({});
|
||||
});
|
||||
|
||||
|
||||
@@ -142,15 +142,15 @@ const inScreen = (): boolean =>
|
||||
const isSSH = (): boolean =>
|
||||
Boolean(
|
||||
process.env['SSH_TTY'] ||
|
||||
process.env['SSH_CONNECTION'] ||
|
||||
process.env['SSH_CLIENT'],
|
||||
process.env['SSH_CONNECTION'] ||
|
||||
process.env['SSH_CLIENT'],
|
||||
);
|
||||
|
||||
const isWSL = (): boolean =>
|
||||
Boolean(
|
||||
process.env['WSL_DISTRO_NAME'] ||
|
||||
process.env['WSLENV'] ||
|
||||
process.env['WSL_INTEROP'],
|
||||
process.env['WSLENV'] ||
|
||||
process.env['WSL_INTEROP'],
|
||||
);
|
||||
|
||||
const isWindowsTerminal = (): boolean =>
|
||||
|
||||
@@ -220,9 +220,8 @@ describe('rewindFileOps', () => {
|
||||
});
|
||||
|
||||
it('reverts exact match', async () => {
|
||||
const { getFileDiffFromResultDisplay } = await import(
|
||||
'@google/gemini-cli-core'
|
||||
);
|
||||
const { getFileDiffFromResultDisplay } =
|
||||
await import('@google/gemini-cli-core');
|
||||
vi.mocked(getFileDiffFromResultDisplay).mockReturnValue({
|
||||
filePath: '/abs/path/test.ts',
|
||||
fileName: 'test.ts',
|
||||
@@ -270,9 +269,8 @@ describe('rewindFileOps', () => {
|
||||
});
|
||||
|
||||
it('deletes new file on revert', async () => {
|
||||
const { getFileDiffFromResultDisplay } = await import(
|
||||
'@google/gemini-cli-core'
|
||||
);
|
||||
const { getFileDiffFromResultDisplay } =
|
||||
await import('@google/gemini-cli-core');
|
||||
vi.mocked(getFileDiffFromResultDisplay).mockReturnValue({
|
||||
filePath: '/abs/path/new.ts',
|
||||
fileName: 'new.ts',
|
||||
@@ -317,9 +315,8 @@ describe('rewindFileOps', () => {
|
||||
});
|
||||
|
||||
it('handles smart revert (patching) successfully', async () => {
|
||||
const { getFileDiffFromResultDisplay } = await import(
|
||||
'@google/gemini-cli-core'
|
||||
);
|
||||
const { getFileDiffFromResultDisplay } =
|
||||
await import('@google/gemini-cli-core');
|
||||
vi.mocked(getFileDiffFromResultDisplay).mockReturnValue({
|
||||
filePath: '/abs/path/test.ts',
|
||||
fileName: 'test.ts',
|
||||
@@ -369,9 +366,8 @@ describe('rewindFileOps', () => {
|
||||
});
|
||||
|
||||
it('emits warning on smart revert failure', async () => {
|
||||
const { getFileDiffFromResultDisplay } = await import(
|
||||
'@google/gemini-cli-core'
|
||||
);
|
||||
const { getFileDiffFromResultDisplay } =
|
||||
await import('@google/gemini-cli-core');
|
||||
vi.mocked(getFileDiffFromResultDisplay).mockReturnValue({
|
||||
filePath: '/abs/path/test.ts',
|
||||
fileName: 'test.ts',
|
||||
@@ -421,9 +417,8 @@ describe('rewindFileOps', () => {
|
||||
});
|
||||
|
||||
it('emits error if fs.readFile fails with a generic error', async () => {
|
||||
const { getFileDiffFromResultDisplay } = await import(
|
||||
'@google/gemini-cli-core'
|
||||
);
|
||||
const { getFileDiffFromResultDisplay } =
|
||||
await import('@google/gemini-cli-core');
|
||||
vi.mocked(getFileDiffFromResultDisplay).mockReturnValue({
|
||||
filePath: '/abs/path/test.ts',
|
||||
fileName: 'test.ts',
|
||||
|
||||
@@ -47,13 +47,14 @@ describe('textUtils', () => {
|
||||
describe('getCachedStringWidth', () => {
|
||||
it('should handle unicode characters that crash string-width', () => {
|
||||
// U+0602 caused string-width to crash (see #16418)
|
||||
// Newer versions of string-width correctly identify it as width 0.
|
||||
const char = '';
|
||||
expect(getCachedStringWidth(char)).toBe(1);
|
||||
expect(getCachedStringWidth(char)).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle unicode characters that crash string-width with ANSI codes', () => {
|
||||
const charWithAnsi = '\u001b[31m' + '' + '\u001b[0m';
|
||||
expect(getCachedStringWidth(charWithAnsi)).toBe(1);
|
||||
expect(getCachedStringWidth(charWithAnsi)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -561,6 +561,13 @@ export class ActivityLogger extends EventEmitter {
|
||||
}
|
||||
this.emit('console', enriched);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.networkLoggingEnabled = false;
|
||||
this.removeAllListeners();
|
||||
this.clearBufferedLogs();
|
||||
this.emit('dispose');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -820,6 +827,12 @@ function setupNetworkLogging(
|
||||
flushBuffer();
|
||||
});
|
||||
|
||||
capture.on('dispose', () => {
|
||||
if (reconnectTimer) clearTimeout(reconnectTimer);
|
||||
if (ws) ws.close();
|
||||
cleanup();
|
||||
});
|
||||
|
||||
// Cleanup on process exit
|
||||
process.on('exit', () => {
|
||||
if (reconnectTimer) clearTimeout(reconnectTimer);
|
||||
|
||||
@@ -100,14 +100,14 @@ async function drainStdin() {
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
}
|
||||
|
||||
export async function cleanupCheckpoints() {
|
||||
const storage = new Storage(process.cwd());
|
||||
await storage.initialize();
|
||||
const tempDir = storage.getProjectTempDir();
|
||||
const checkpointsDir = join(tempDir, 'checkpoints');
|
||||
export async function cleanupCheckpoints(cwd?: string) {
|
||||
try {
|
||||
const storage = new Storage(cwd || process.cwd());
|
||||
await storage.initialize();
|
||||
const tempDir = storage.getProjectTempDir();
|
||||
const checkpointsDir = join(tempDir, 'checkpoints');
|
||||
await fs.rm(checkpointsDir, { recursive: true, force: true });
|
||||
} catch {
|
||||
// Ignore errors if the directory doesn't exist or fails to delete.
|
||||
// Ignore errors if the directory doesn't exist, process.cwd() fails, or it fails to delete.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,9 +230,8 @@ export async function toggleDevToolsPanel(
|
||||
}
|
||||
|
||||
try {
|
||||
const { openBrowserSecurely, shouldLaunchBrowser } = await import(
|
||||
'@google/gemini-cli-core'
|
||||
);
|
||||
const { openBrowserSecurely, shouldLaunchBrowser } =
|
||||
await import('@google/gemini-cli-core');
|
||||
const url = await startDevToolsServer(config);
|
||||
if (shouldLaunchBrowser()) {
|
||||
try {
|
||||
@@ -257,3 +256,18 @@ export function resetForTesting() {
|
||||
serverStartPromise = null;
|
||||
connectedUrl = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the DevTools server if it was started by this process.
|
||||
*/
|
||||
export async function stopDevTools() {
|
||||
try {
|
||||
const mod = await import('@google/gemini-cli-devtools');
|
||||
const devtools: IDevTools = mod.DevTools.getInstance();
|
||||
await devtools.stop();
|
||||
serverStartPromise = null;
|
||||
connectedUrl = null;
|
||||
} catch (err) {
|
||||
debugLogger.debug('Failed to stop DevTools:', err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,5 +14,5 @@
|
||||
"./package.json"
|
||||
],
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"references": [{ "path": "../core" }]
|
||||
"references": [{ "path": "../core" }, { "path": "../devtools" }]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user