diff --git a/packages/cli/index.ts b/packages/cli/index.ts index 5444fe1b74..a8119ea729 100644 --- a/packages/cli/index.ts +++ b/packages/cli/index.ts @@ -6,66 +6,116 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { main } from './src/gemini.js'; -import { FatalError, writeToStderr } from '@google/gemini-cli-core'; -import { runExitCleanup } from './src/utils/cleanup.js'; - -// --- Global Entry Point --- - -// Suppress known race condition error in node-pty on Windows -// Tracking bug: https://github.com/microsoft/node-pty/issues/827 -process.on('uncaughtException', (error) => { - if ( - process.platform === 'win32' && - error instanceof Error && - error.message === 'Cannot resize a pty that has already exited' - ) { - // This error happens on Windows with node-pty when resizing a pty that has just exited. - // It is a race condition in node-pty that we cannot prevent, so we silence it. - return; - } - - // For other errors, we rely on the default behavior, but since we attached a listener, - // we must manually replicate it. - if (error instanceof Error) { - writeToStderr(error.stack + '\n'); - } else { - writeToStderr(String(error) + '\n'); - } - process.exit(1); -}); - -main().catch(async (error) => { - // Set a timeout to force exit if cleanup hangs - const cleanupTimeout = setTimeout(() => { - writeToStderr('Cleanup timed out, forcing exit...\n'); - process.exit(1); - }, 5000); +import module from 'node:module'; +// Enable V8 compile cache for faster subsequent startups (Node >= 22) +// Cast to any because the project's @types/node is v20 which doesn't have this method. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +if (typeof (module as any).enableCompileCache === 'function') { try { - await runExitCleanup(); - } catch (cleanupError) { - writeToStderr( - `Error during final cleanup: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}\n`, - ); - } finally { - clearTimeout(cleanupTimeout); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (module as any).enableCompileCache(); + } catch { + // Ignore if not supported or already enabled } +} - if (error instanceof FatalError) { - let errorMessage = error.message; - if (!process.env['NO_COLOR']) { - errorMessage = `\x1b[31m${errorMessage}\x1b[0m`; - } - writeToStderr(errorMessage + '\n'); - process.exit(error.exitCode); - } +// --- Fast Path Intercept --- +// To achieve sub-100ms startup time for simple commands, we intercept them +// before statically importing the massive `gemini.js` chunk which takes ~400ms to parse. +const args = process.argv.slice(2); +if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) { + import('node:fs').then((fs) => { + import('node:path').then((path) => { + import('node:url').then((url) => { + const __filename = url.fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + let pkgPath = path.join(__dirname, '..', 'package.json'); + if (!fs.existsSync(pkgPath)) { + // production path in bundle + pkgPath = path.join(__dirname, 'package.json'); + } + if (fs.existsSync(pkgPath)) { + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + console.log(pkg.version); + } + process.exit(0); + }); + }); + }); +} else if (args.length === 1 && (args[0] === '--help' || args[0] === '-h')) { + import('./src/config/config.js').then(({ parseArguments }) => { + import('./src/config/settings.js').then(({ loadSettings }) => { + Promise.resolve(loadSettings()).then((settings) => { + parseArguments(settings.merged).catch(() => process.exit(0)); + }); + }); + }); +} else { + // Load the full CLI for all other commands + import('./src/gemini.js').then(({ main }) => { + import('@google/gemini-cli-core').then(({ FatalError, writeToStderr }) => { + import('./src/utils/cleanup.js').then(({ runExitCleanup }) => { + // --- Global Entry Point --- - writeToStderr('An unexpected critical error occurred:'); - if (error instanceof Error) { - writeToStderr(error.stack + '\n'); - } else { - writeToStderr(String(error) + '\n'); - } - process.exit(1); -}); + // Suppress known race condition error in node-pty on Windows + // Tracking bug: https://github.com/microsoft/node-pty/issues/827 + process.on('uncaughtException', (error) => { + if ( + process.platform === 'win32' && + error instanceof Error && + error.message === 'Cannot resize a pty that has already exited' + ) { + // This error happens on Windows with node-pty when resizing a pty that has just exited. + // It is a race condition in node-pty that we cannot prevent, so we silence it. + return; + } + + // For other errors, we rely on the default behavior, but since we attached a listener, + // we must manually replicate it. + if (error instanceof Error) { + writeToStderr(error.stack + '\n'); + } else { + writeToStderr(String(error) + '\n'); + } + process.exit(1); + }); + + main().catch(async (error) => { + // Set a timeout to force exit if cleanup hangs + const cleanupTimeout = setTimeout(() => { + writeToStderr('Cleanup timed out, forcing exit...\n'); + process.exit(1); + }, 5000); + + try { + await runExitCleanup(); + } catch (cleanupError) { + writeToStderr( + `Error during final cleanup: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}\n`, + ); + } finally { + clearTimeout(cleanupTimeout); + } + + if (error instanceof FatalError) { + let errorMessage = error.message; + if (!process.env['NO_COLOR']) { + errorMessage = `\x1b[31m${errorMessage}\x1b[0m`; + } + writeToStderr(errorMessage + '\n'); + process.exit(error.exitCode); + } + + writeToStderr('An unexpected critical error occurred:'); + if (error instanceof Error) { + writeToStderr(error.stack + '\n'); + } else { + writeToStderr(String(error) + '\n'); + } + process.exit(1); + }); + }); + }); + }); +}