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,
  };
}