import { execFileSync } from "node:child_process"; const PATH_CAPTURE_START = "__T3CODE_PATH_START__"; const PATH_CAPTURE_END = "__T3CODE_PATH_END__"; const SHELL_ENV_NAME_PATTERN = /^[A-Z0-9_]+$/; type ExecFileSyncLike = ( file: string, args: ReadonlyArray, options: { encoding: "utf8"; timeout: number }, ) => string; export function extractPathFromShellOutput(output: string): string | null { const startIndex = output.indexOf(PATH_CAPTURE_START); if (startIndex === -1) return null; const valueStartIndex = startIndex + PATH_CAPTURE_START.length; const endIndex = output.indexOf(PATH_CAPTURE_END, valueStartIndex); if (endIndex === -1) return null; const pathValue = output.slice(valueStartIndex, endIndex).trim(); return pathValue.length > 0 ? pathValue : null; } export function readPathFromLoginShell( shell: string, execFile: ExecFileSyncLike = execFileSync, ): string | undefined { return readEnvironmentFromLoginShell(shell, ["PATH"], execFile).PATH; } function envCaptureStart(name: string): string { return `__T3CODE_ENV_${name}_START__`; } function envCaptureEnd(name: string): string { return `__T3CODE_ENV_${name}_END__`; } function buildEnvironmentCaptureCommand(names: ReadonlyArray): string { return names .map((name) => { if (!SHELL_ENV_NAME_PATTERN.test(name)) { throw new Error(`Unsupported environment variable name: ${name}`); } return [ `printf '%s\\n' '${envCaptureStart(name)}'`, `printenv ${name} || true`, `printf '%s\\n' '${envCaptureEnd(name)}'`, ].join("; "); }) .join("; "); } function extractEnvironmentValue(output: string, name: string): string | undefined { const startMarker = envCaptureStart(name); const endMarker = envCaptureEnd(name); const startIndex = output.indexOf(startMarker); if (startIndex === -1) return undefined; const valueStartIndex = startIndex + startMarker.length; const endIndex = output.indexOf(endMarker, valueStartIndex); if (endIndex === -1) return undefined; let value = output.slice(valueStartIndex, endIndex); if (value.startsWith("\n")) { value = value.slice(1); } if (value.endsWith("\n")) { value = value.slice(0, -1); } return value.length > 0 ? value : undefined; } export type ShellEnvironmentReader = ( shell: string, names: ReadonlyArray, execFile?: ExecFileSyncLike, ) => Partial>; export const readEnvironmentFromLoginShell: ShellEnvironmentReader = ( shell, names, execFile = execFileSync, ) => { if (names.length === 0) { return {}; } const output = execFile(shell, ["-ilc", buildEnvironmentCaptureCommand(names)], { encoding: "utf8", timeout: 5000, }); const environment: Partial> = {}; for (const name of names) { const value = extractEnvironmentValue(output, name); if (value !== undefined) { environment[name] = value; } } return environment; };