File size: 4,001 Bytes
84aa3bf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | /**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { runExitCleanup } from './cleanup.js';
import { waitForUpdateCompletion } from './handleAutoUpdate.js';
/**
* Exit code used to signal that the CLI should be relaunched.
*/
export const RELAUNCH_EXIT_CODE = 199;
/**
* Exits the process with a special code to signal that the parent process should relaunch it.
*/
let isRelaunching = false;
/** @internal only for testing */
export function _resetRelaunchStateForTesting(): void {
isRelaunching = false;
}
export async function relaunchApp(): Promise<void> {
if (isRelaunching) return;
isRelaunching = true;
await waitForUpdateCompletion();
await runExitCleanup();
process.exit(RELAUNCH_EXIT_CODE);
}
export interface ProcessWithSea extends NodeJS.Process {
isSea?: () => boolean;
}
/**
* Determines whether the current process is a "standard" SEA (Single Executable Application)
* where the user arguments start at index 1 instead of index 2.
* A relaunched SEA child will have process.argv[0] === process.argv[1] (because we inject execPath),
* so it will return false here and correctly slice from index 2.
*/
export function isStandardSea(): boolean {
return (
process.argv[0] !== process.argv[1] &&
(process.env['IS_BINARY'] === 'true' ||
(process as ProcessWithSea).isSea?.() === true)
);
}
/**
* Extracts the user-provided script arguments from process.argv,
* accounting for the differences in SEA execution modes.
*/
export function getScriptArgs(): string[] {
return process.argv.slice(isStandardSea() ? 1 : 2);
}
/**
* Determines if the current process is running in any SEA environment
* (either the initial launch or a relaunched child).
*/
export function isSeaEnvironment(): boolean {
return (
process.env['IS_BINARY'] === 'true' ||
(process as ProcessWithSea).isSea?.() === true ||
process.argv[0] === process.argv[1]
);
}
/**
* Constructs the arguments and environment for spawning a child process during relaunch.
* Handles differences between standard Node and SEA binary modes.
*/
export function getSpawnConfig(
nodeArgs: string[],
scriptArgs: string[],
): {
spawnArgs: string[];
env: NodeJS.ProcessEnv;
} {
const isBinary = isSeaEnvironment();
const newEnv: NodeJS.ProcessEnv = {
...process.env,
GEMINI_CLI_NO_RELAUNCH: 'true',
};
const finalSpawnArgs: string[] = [];
if (isBinary) {
// In SEA mode, Node flags must be passed via NODE_OPTIONS, as the binary
// passes all CLI arguments directly to the application.
// We only need to append the *new* nodeArgs (e.g., memory flags).
// Existing execArgv are inherited via the environment or baked into the binary.
if (nodeArgs.length > 0) {
for (const arg of nodeArgs) {
if (/[\s"'\\]/.test(arg)) {
throw new Error(
`Unsupported node argument for SEA relaunch: ${arg}. Complex escaping is not supported.`,
);
}
}
const existingNodeOptions = process.env['NODE_OPTIONS'] || '';
// nodeArgs in our codebase are simple flags like --max-old-space-size=X
// that do not contain spaces and do not require complex escaping.
newEnv['NODE_OPTIONS'] =
`${existingNodeOptions} ${nodeArgs.join(' ')}`.trim();
}
// Binary is its own entry point. To maintain the [node, script, ...args]
// structure expected by the application (which uses slice(2)),
// we must provide a placeholder for the script path.
// We explicitly use process.execPath to break the cycle and prevent
// compounding argument duplication on subsequent relaunches.
finalSpawnArgs.push(process.execPath, ...scriptArgs);
} else {
// Standard Node mode: pass all flags via command line.
finalSpawnArgs.push(
...process.execArgv,
...nodeArgs,
process.argv[1],
...scriptArgs,
);
}
return {
spawnArgs: finalSpawnArgs,
env: newEnv,
};
}
|