source dump of claude code
at main 151 lines 5.3 kB view raw
1import { feature } from 'bun:bundle' 2import { stat } from 'fs/promises' 3import memoize from 'lodash-es/memoize.js' 4import { env, JETBRAINS_IDES } from './env.js' 5import { isEnvTruthy } from './envUtils.js' 6import { execFileNoThrow } from './execFileNoThrow.js' 7import { getAncestorCommandsAsync } from './genericProcessUtils.js' 8 9// Functions that require execFileNoThrow and thus cannot be in env.ts 10 11const getIsDocker = memoize(async (): Promise<boolean> => { 12 if (process.platform !== 'linux') return false 13 // Check for .dockerenv file 14 const { code } = await execFileNoThrow('test', ['-f', '/.dockerenv']) 15 return code === 0 16}) 17 18function getIsBubblewrapSandbox(): boolean { 19 return ( 20 process.platform === 'linux' && 21 isEnvTruthy(process.env.CLAUDE_CODE_BUBBLEWRAP) 22 ) 23} 24 25// Cache for the runtime musl detection fallback (node/unbundled only). 26// In native linux builds, feature flags resolve this at compile time, so the 27// cache is only consulted when both IS_LIBC_MUSL and IS_LIBC_GLIBC are false. 28let muslRuntimeCache: boolean | null = null 29 30// Fire-and-forget: populate the musl cache for the node fallback path. 31// Native builds never reach this (feature flags short-circuit), so this only 32// matters for unbundled node on Linux. Installer calls on native builds are 33// unaffected since feature() resolves at compile time. 34if (process.platform === 'linux') { 35 const muslArch = process.arch === 'x64' ? 'x86_64' : 'aarch64' 36 void stat(`/lib/libc.musl-${muslArch}.so.1`).then( 37 () => { 38 muslRuntimeCache = true 39 }, 40 () => { 41 muslRuntimeCache = false 42 }, 43 ) 44} 45 46/** 47 * Checks if the system is using MUSL libc instead of glibc. 48 * In native linux builds, this is statically known at compile time via IS_LIBC_MUSL/IS_LIBC_GLIBC flags. 49 * In node (unbundled), both flags are false and we fall back to a runtime async stat check 50 * whose result is cached at module load. If the cache isn't populated yet, returns false. 51 */ 52function isMuslEnvironment(): boolean { 53 if (feature('IS_LIBC_MUSL')) return true 54 if (feature('IS_LIBC_GLIBC')) return false 55 56 // Fallback for node: runtime detection via pre-populated cache 57 if (process.platform !== 'linux') return false 58 return muslRuntimeCache ?? false 59} 60 61// Cache for async JetBrains detection 62let jetBrainsIDECache: string | null | undefined 63 64async function detectJetBrainsIDEFromParentProcessAsync(): Promise< 65 string | null 66> { 67 if (jetBrainsIDECache !== undefined) { 68 return jetBrainsIDECache 69 } 70 71 if (process.platform === 'darwin') { 72 jetBrainsIDECache = null 73 return null // macOS uses bundle ID detection which is already handled 74 } 75 76 try { 77 // Get ancestor commands in a single call (avoids sync bash in loop) 78 const commands = await getAncestorCommandsAsync(process.pid, 10) 79 80 for (const command of commands) { 81 const lowerCommand = command.toLowerCase() 82 // Check for specific JetBrains IDEs in the command line 83 for (const ide of JETBRAINS_IDES) { 84 if (lowerCommand.includes(ide)) { 85 jetBrainsIDECache = ide 86 return ide 87 } 88 } 89 } 90 } catch { 91 // Silently fail - this is a best-effort detection 92 } 93 94 jetBrainsIDECache = null 95 return null 96} 97 98export async function getTerminalWithJetBrainsDetectionAsync(): Promise< 99 string | null 100> { 101 // Check for JetBrains terminal on Linux/Windows 102 if (process.env.TERMINAL_EMULATOR === 'JetBrains-JediTerm') { 103 // For macOS, bundle ID detection above already handles JetBrains IDEs 104 if (env.platform !== 'darwin') { 105 const specificIDE = await detectJetBrainsIDEFromParentProcessAsync() 106 return specificIDE || 'pycharm' 107 } 108 } 109 return env.terminal 110} 111 112// Synchronous version that returns cached result or falls back to env.terminal 113// Used for backward compatibility - callers should migrate to async version 114export function getTerminalWithJetBrainsDetection(): string | null { 115 // Check for JetBrains terminal on Linux/Windows 116 if (process.env.TERMINAL_EMULATOR === 'JetBrains-JediTerm') { 117 // For macOS, bundle ID detection above already handles JetBrains IDEs 118 if (env.platform !== 'darwin') { 119 // Return cached value if available, otherwise fall back to generic detection 120 // The async version should be called early in app initialization to populate cache 121 if (jetBrainsIDECache !== undefined) { 122 return jetBrainsIDECache || 'pycharm' 123 } 124 // Fall back to generic 'pycharm' if cache not populated yet 125 return 'pycharm' 126 } 127 } 128 return env.terminal 129} 130 131/** 132 * Initialize JetBrains IDE detection asynchronously. 133 * Call this early in app initialization to populate the cache. 134 * After this resolves, getTerminalWithJetBrainsDetection() will return accurate results. 135 */ 136export async function initJetBrainsDetection(): Promise<void> { 137 if (process.env.TERMINAL_EMULATOR === 'JetBrains-JediTerm') { 138 await detectJetBrainsIDEFromParentProcessAsync() 139 } 140} 141 142// Combined export that includes all env properties plus dynamic functions 143export const envDynamic = { 144 ...env, // Include all properties from env 145 terminal: getTerminalWithJetBrainsDetection(), 146 getIsDocker, 147 getIsBubblewrapSandbox, 148 isMuslEnvironment, 149 getTerminalWithJetBrainsDetectionAsync, 150 initJetBrainsDetection, 151}