| |
| |
| |
| |
| |
|
|
| import { promises as fs } from 'node:fs'; |
| import { join } from 'node:path'; |
| import { |
| Storage, |
| shutdownTelemetry, |
| isTelemetrySdkInitialized, |
| ExitCodes, |
| resetBrowserSession, |
| } from '@google/gemini-cli-core'; |
| import type { Config } from '@google/gemini-cli-core'; |
|
|
| const cleanupFunctions: Array<(() => void) | (() => Promise<void>)> = []; |
| const syncCleanupFunctions: Array<() => void> = []; |
| let configForTelemetry: Config | null = null; |
| let isShuttingDown = false; |
|
|
| export function registerCleanup(fn: (() => void) | (() => Promise<void>)) { |
| cleanupFunctions.push(fn); |
| } |
|
|
| export function removeCleanup(fn: (() => void) | (() => Promise<void>)) { |
| const index = cleanupFunctions.indexOf(fn); |
| if (index !== -1) { |
| cleanupFunctions.splice(index, 1); |
| } |
| } |
|
|
| export function registerSyncCleanup(fn: () => void) { |
| syncCleanupFunctions.push(fn); |
| } |
|
|
| export function removeSyncCleanup(fn: () => void) { |
| const index = syncCleanupFunctions.indexOf(fn); |
| if (index !== -1) { |
| syncCleanupFunctions.splice(index, 1); |
| } |
| } |
|
|
| |
| |
| |
| |
| export function resetCleanupForTesting() { |
| cleanupFunctions.length = 0; |
| syncCleanupFunctions.length = 0; |
| configForTelemetry = null; |
| isShuttingDown = false; |
| } |
|
|
| export function runSyncCleanup() { |
| for (const fn of syncCleanupFunctions) { |
| try { |
| fn(); |
| } catch { |
| |
| } |
| } |
| syncCleanupFunctions.length = 0; |
| } |
|
|
| |
| |
| |
| |
| export function registerTelemetryConfig(config: Config) { |
| configForTelemetry = config; |
| } |
|
|
| export async function runExitCleanup() { |
| |
| |
| await drainStdin(); |
|
|
| runSyncCleanup(); |
| for (const fn of cleanupFunctions) { |
| try { |
| await fn(); |
| } catch { |
| |
| } |
| } |
| cleanupFunctions.length = 0; |
|
|
| |
| try { |
| await resetBrowserSession(); |
| } catch { |
| |
| } |
|
|
| if (configForTelemetry) { |
| try { |
| await configForTelemetry.dispose(); |
| } catch { |
| |
| } |
| } |
|
|
| |
| |
| if (configForTelemetry && isTelemetrySdkInitialized()) { |
| try { |
| await shutdownTelemetry(configForTelemetry); |
| } catch { |
| |
| } |
| } |
| } |
|
|
| async function drainStdin() { |
| if (!process.stdin?.isTTY) return; |
| |
| |
| process.stdin |
| .resume() |
| .removeAllListeners('data') |
| .on('data', () => {}); |
| |
| await new Promise((resolve) => setTimeout(resolve, 50)); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| async function gracefulShutdown(_reason: string) { |
| if (isShuttingDown) { |
| return; |
| } |
| isShuttingDown = true; |
|
|
| await runExitCleanup(); |
| process.exit(ExitCodes.SUCCESS); |
| } |
|
|
| export function setupSignalHandlers() { |
| process.on('SIGHUP', () => gracefulShutdown('SIGHUP')); |
| process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); |
| process.on('SIGINT', () => gracefulShutdown('SIGINT')); |
| } |
|
|
| export function setupTtyCheck(): () => void { |
| let intervalId: ReturnType<typeof setInterval> | null = null; |
| let isCheckingTty = false; |
|
|
| intervalId = setInterval(async () => { |
| if (isCheckingTty || isShuttingDown) { |
| return; |
| } |
|
|
| if (process.env['SANDBOX']) { |
| return; |
| } |
|
|
| if (!process.stdin.isTTY && !process.stdout.isTTY) { |
| isCheckingTty = true; |
|
|
| if (intervalId) { |
| clearInterval(intervalId); |
| intervalId = null; |
| } |
|
|
| await gracefulShutdown('TTY loss'); |
| } |
| }, 5000); |
|
|
| |
| intervalId.unref(); |
|
|
| return () => { |
| if (intervalId) { |
| clearInterval(intervalId); |
| intervalId = null; |
| } |
| }; |
| } |
|
|
| export async function cleanupCheckpoints() { |
| const storage = new Storage(process.cwd()); |
| await storage.initialize(); |
| const tempDir = storage.getProjectTempDir(); |
| const checkpointsDir = join(tempDir, 'checkpoints'); |
| try { |
| await fs.rm(checkpointsDir, { recursive: true, force: true }); |
| } catch { |
| |
| } |
| } |
|
|