import { execFileSync } from 'node:child_process' import fs from 'node:fs' // Bridges WSL/POSIX paths into forms the *Windows host* can open, for the case // where the desktop UI runs on Windows and the gateway runs inside WSL (remote // mode). Only the Windows-side direction lives here: the native folder dialog's // defaultPath and the fs read path. The reverse (whatever path the backend // receives → POSIX) is handled once, gateway-side, in // hermes_constants.translate_cwd_for_wsl_backend, so it stays picker-agnostic. const IS_WINDOWS = process.platform === 'win32' const WIN_DRIVE_RE = /^([A-Za-z]):[\\/]/ // `/mnt/c` and `/mnt/c/...` (drvfs default automount root). const WSL_MOUNT_RE = /^\/mnt\/([a-z])(?:\/(.*))?$/i let cachedDistro: null | string = null let cachedUncBase: null | string = null /** * WSL path eligibility belongs to the backend profile that produced the path. * A single desktop process can keep a local primary backend and a remote pool * backend alive simultaneously, so a process-global boolean can bleed between * them. Unknown profiles retain the historical local default until Electron * resolves and records their actual backend mode. */ const DEFAULT_WSL_BRIDGE_PROFILE = 'default' const wslBridgeProfiles = new Map() let activeWslBridgeProfile = DEFAULT_WSL_BRIDGE_PROFILE function normalizeWslBridgeProfile(profile?: null | string): string { return String(profile || '').trim() || DEFAULT_WSL_BRIDGE_PROFILE } /** Select the profile used by legacy callers that cannot pass one explicitly. */ export function setActiveGatewayProfile(profile?: null | string): void { activeWslBridgeProfile = normalizeWslBridgeProfile(profile) } /** Record whether paths returned by one profile belong to this host's WSL. */ export function setWslBridgeProfileState(profile: null | string, active: boolean): void { wslBridgeProfiles.set(normalizeWslBridgeProfile(profile), Boolean(active)) } /** Backward-compatible toggle: update only the current fallback profile. */ export function setWslBridgeActive(active: boolean): void { setWslBridgeProfileState(activeWslBridgeProfile, active) } export function isWslBridgeActive(profile?: null | string): boolean { const key = profile == null ? activeWslBridgeProfile : normalizeWslBridgeProfile(profile) return wslBridgeProfiles.get(key) ?? true } /** * Pick the default distro from `wsl.exe -l -q` output. * * `wsl.exe` emits UTF-16LE without a BOM unless `WSL_UTF8=1` (WSL >= 0.64), so * older builds leave NUL bytes between characters even when we ask for utf8 — * strip them defensively before splitting. The default distro is the first * (`*`-marked, decoration removed by `-q`) entry. See microsoft/WSL#4607. */ export function parseDefaultDistro(raw: string): null | string { return ( String(raw || '') .replace(/\0/g, '') .split(/\r?\n/) .map(line => line.replace(/^\*?\s*/, '').trim()) .find(Boolean) || null ) } /** Default WSL distro name (cached). Falls back to `Ubuntu`. */ export function resolveDefaultWslDistro(): string { if (cachedDistro) { return cachedDistro } if (!IS_WINDOWS) { cachedDistro = 'Ubuntu' return cachedDistro } try { const out = execFileSync('wsl.exe', ['-l', '-q'], { encoding: 'utf8', env: { ...process.env, WSL_UTF8: '1' }, // On WSL-less machines wsl.exe prints "The Windows Subsystem for Linux // is not installed..." to stderr; stderr is inherited by default, so // that banner leaks into whatever console the app is attached to // (visible e.g. during the update hand-off). Discard it. (#80184) stdio: ['ignore', 'pipe', 'ignore'], timeout: 2000, windowsHide: true }) cachedDistro = parseDefaultDistro(out) || 'Ubuntu' } catch { cachedDistro = 'Ubuntu' } return cachedDistro } // `\\wsl.localhost\` (Win11 / Win10 >= 21364) with a `\\wsl$\` // fallback for older builds. Probed once; defaults to wsl.localhost. function wslUncBase(distro: string): string { if (cachedUncBase) { return cachedUncBase } const modern = `\\\\wsl.localhost\\${distro}` const legacy = `\\\\wsl$\\${distro}` try { if (!fs.existsSync(modern) && fs.existsSync(legacy)) { cachedUncBase = legacy return cachedUncBase } } catch { // Network-path probe failed — prefer the modern form. } cachedUncBase = modern return cachedUncBase } /** * A WSL/POSIX path → a path the Windows host can open: `/mnt/c/...` → `C:\...` * (drvfs mount), any other absolute POSIX path → `\\wsl.localhost\\...`. * Non-absolute or already-Windows paths pass through. */ export function wslPosixToWindowsAccessible(posixPath: string, distro: string = resolveDefaultWslDistro()): string { const value = String(posixPath || '').trim() const normalized = value.replace(/\\/g, '/') if (!normalized.startsWith('/')) { return value } const mount = normalized.match(WSL_MOUNT_RE) if (mount) { const tail = (mount[2] || '').replace(/\//g, '\\') return tail ? `${mount[1].toUpperCase()}:\\${tail}` : `${mount[1].toUpperCase()}:\\` } const relative = normalized.replace(/^\/+/, '').replace(/\//g, '\\') return `${wslUncBase(distro)}\\${relative}` } /** Native folder dialog `defaultPath`: open a WSL cwd in the Windows picker. */ export function resolvePickerDefaultPath( defaultPath: string | undefined, distro?: string, profile?: null | string ): string | undefined { if (!defaultPath) { return undefined } // Remote-gateway POSIX paths can't be opened via wsl.exe — no-op the bridge // so the native dialog gets the raw path (it falls back gracefully) instead // of triggering a wsl.exe spawn / install prompt. (#66433) if (!isWslBridgeActive(profile)) { return defaultPath } const value = String(defaultPath).trim() return value.startsWith('/') && !WIN_DRIVE_RE.test(value) ? wslPosixToWindowsAccessible(value, distro ?? resolveDefaultWslDistro()) : defaultPath } /** fs read path: on Windows, make a WSL cwd readable via its UNC / drive form. */ export function resolveLocalReadPath(dirPath: string, distro?: string, profile?: null | string): string { const value = String(dirPath || '').trim() // In remote-gateway mode the POSIX paths belong to a host the Windows // desktop cannot open locally — skip the WSL bridge entirely (no distro // probe, no wsl.exe) so the file panel never spawns the install prompt on // WSL-less machines. (#66433) if (!isWslBridgeActive(profile)) { return value } return IS_WINDOWS && value.startsWith('/') && !WIN_DRIVE_RE.test(value) ? wslPosixToWindowsAccessible(value, distro ?? resolveDefaultWslDistro()) : value }