| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { execSync } from 'child_process'; |
| import * as path from 'path'; |
|
|
| |
| |
| |
| |
| function getWslExePath(): string { |
| |
| const systemRoot = process.env.SystemRoot || process.env.SYSTEMROOT || 'C:\\Windows'; |
| return path.join(systemRoot, 'System32', 'wsl.exe'); |
| } |
|
|
| |
| export interface WslCliResult { |
| |
| wslPath: string; |
| |
| distribution?: string; |
| } |
|
|
| |
| export interface WslOptions { |
| |
| distribution?: string; |
| |
| timeout?: number; |
| |
| logger?: (message: string) => void; |
| } |
|
|
| |
| let wslAvailableCache: boolean | null = null; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export function isWslAvailable(options: WslOptions = {}): boolean { |
| const { timeout = 5000, logger = () => {} } = options; |
|
|
| |
| if (process.platform !== 'win32') { |
| return false; |
| } |
|
|
| |
| if (wslAvailableCache !== null) { |
| return wslAvailableCache; |
| } |
|
|
| try { |
| |
| execSync('wsl.exe echo ok', { |
| encoding: 'utf8', |
| timeout, |
| stdio: ['pipe', 'pipe', 'pipe'], |
| windowsHide: true, |
| }); |
| wslAvailableCache = true; |
| logger('WSL is available'); |
| return true; |
| } catch { |
| |
| try { |
| execSync('wsl.exe --status', { |
| encoding: 'utf8', |
| timeout, |
| stdio: ['pipe', 'pipe', 'pipe'], |
| windowsHide: true, |
| }); |
| wslAvailableCache = true; |
| logger('WSL is available (via --status)'); |
| return true; |
| } catch { |
| wslAvailableCache = false; |
| logger('WSL is not available'); |
| return false; |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| export function clearWslCache(): void { |
| wslAvailableCache = null; |
| } |
|
|
| |
| |
| |
| export function getDefaultWslDistribution(options: WslOptions = {}): string | null { |
| const { timeout = 5000 } = options; |
|
|
| if (!isWslAvailable(options)) { |
| return null; |
| } |
|
|
| try { |
| |
| const result = execSync('wsl.exe -l -q', { |
| encoding: 'utf16le', |
| timeout, |
| stdio: ['pipe', 'pipe', 'pipe'], |
| windowsHide: true, |
| }).trim(); |
|
|
| |
| const lines = result.split(/\r?\n/).filter((l) => l.trim()); |
| return lines[0]?.replace(/\0/g, '').trim() || null; |
| } catch { |
| return null; |
| } |
| } |
|
|
| |
| |
| |
| export function getWslDistributions(options: WslOptions = {}): string[] { |
| const { timeout = 5000, logger = () => {} } = options; |
|
|
| if (!isWslAvailable(options)) { |
| return []; |
| } |
|
|
| try { |
| const result = execSync('wsl.exe -l -q', { |
| encoding: 'utf16le', |
| timeout, |
| stdio: ['pipe', 'pipe', 'pipe'], |
| windowsHide: true, |
| }).trim(); |
|
|
| const distributions = result |
| .split(/\r?\n/) |
| .map((l) => l.replace(/\0/g, '').trim()) |
| .filter((l) => l && !l.includes('docker-desktop')); |
|
|
| logger(`Found WSL distributions: ${distributions.join(', ')}`); |
| return distributions; |
| } catch { |
| return []; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function findCliInWsl(cliName: string, options: WslOptions = {}): WslCliResult | null { |
| const { distribution, timeout = 10000, logger = () => {} } = options; |
|
|
| if (!isWslAvailable(options)) { |
| return null; |
| } |
|
|
| |
| const searchInDistribution = (distro: string | undefined): WslCliResult | null => { |
| const wslPrefix = distro ? `wsl.exe -d ${distro}` : 'wsl.exe'; |
| const distroLabel = distro || 'default'; |
|
|
| |
| try { |
| const result = execSync(`${wslPrefix} which ${cliName}`, { |
| encoding: 'utf8', |
| timeout, |
| stdio: ['pipe', 'pipe', 'pipe'], |
| windowsHide: true, |
| }).trim(); |
|
|
| if (result && !result.includes('not found') && result.startsWith('/')) { |
| logger(`Found ${cliName} in WSL (${distroLabel}) via 'which': ${result}`); |
| return { wslPath: result, distribution: distro }; |
| } |
| } catch { |
| |
| } |
|
|
| |
| |
| const commonPaths = ['$HOME/.local/bin', '/usr/local/bin', '/usr/bin']; |
|
|
| for (const basePath of commonPaths) { |
| try { |
| |
| const checkCmd = `${wslPrefix} sh -c "test -x ${basePath}/${cliName} && echo ${basePath}/${cliName}"`; |
| const result = execSync(checkCmd, { |
| encoding: 'utf8', |
| timeout, |
| stdio: ['pipe', 'pipe', 'pipe'], |
| windowsHide: true, |
| }).trim(); |
|
|
| if (result && result.startsWith('/')) { |
| logger(`Found ${cliName} in WSL (${distroLabel}) at: ${result}`); |
| return { wslPath: result, distribution: distro }; |
| } |
| } catch { |
| |
| } |
| } |
|
|
| return null; |
| }; |
|
|
| |
| if (distribution) { |
| return searchInDistribution(distribution); |
| } |
|
|
| |
| const distributions = getWslDistributions(options); |
|
|
| |
| const priorityDistros = ['Ubuntu', 'Debian', 'openSUSE', 'Fedora', 'Arch']; |
| const sortedDistros = distributions.sort((a, b) => { |
| const aIndex = priorityDistros.findIndex((p) => a.toLowerCase().includes(p.toLowerCase())); |
| const bIndex = priorityDistros.findIndex((p) => b.toLowerCase().includes(p.toLowerCase())); |
| if (aIndex === -1 && bIndex === -1) return 0; |
| if (aIndex === -1) return 1; |
| if (bIndex === -1) return -1; |
| return aIndex - bIndex; |
| }); |
|
|
| logger(`Searching for ${cliName} in WSL distributions: ${sortedDistros.join(', ')}`); |
|
|
| for (const distro of sortedDistros) { |
| const result = searchInDistribution(distro); |
| if (result) { |
| return result; |
| } |
| } |
|
|
| |
| const defaultResult = searchInDistribution(undefined); |
| if (defaultResult) { |
| return defaultResult; |
| } |
|
|
| logger(`${cliName} not found in any WSL distribution`); |
| return null; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function execInWsl(command: string, options: WslOptions = {}): string | null { |
| const { distribution, timeout = 30000 } = options; |
|
|
| if (!isWslAvailable(options)) { |
| return null; |
| } |
|
|
| const wslPrefix = distribution ? `wsl.exe -d ${distribution}` : 'wsl.exe'; |
|
|
| try { |
| return execSync(`${wslPrefix} ${command}`, { |
| encoding: 'utf8', |
| timeout, |
| stdio: ['pipe', 'pipe', 'pipe'], |
| windowsHide: true, |
| }).trim(); |
| } catch { |
| return null; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function createWslCommand( |
| wslCliPath: string, |
| args: string[], |
| options: WslOptions = {} |
| ): { command: string; args: string[] } { |
| const { distribution } = options; |
| |
| const wslExe = getWslExePath(); |
|
|
| if (distribution) { |
| return { |
| command: wslExe, |
| args: ['-d', distribution, wslCliPath, ...args], |
| }; |
| } |
|
|
| return { |
| command: wslExe, |
| args: [wslCliPath, ...args], |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function windowsToWslPath(windowsPath: string): string { |
| |
| if (windowsPath.startsWith('\\\\')) { |
| |
| return windowsPath; |
| } |
|
|
| |
| const match = windowsPath.match(/^([A-Za-z]):\\(.*)$/); |
| if (match) { |
| const [, drive, rest] = match; |
| const wslPath = `/mnt/${drive.toLowerCase()}/${rest.replace(/\\/g, '/')}`; |
| return wslPath; |
| } |
|
|
| |
| return windowsPath.replace(/\\/g, '/'); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function wslToWindowsPath(wslPath: string): string { |
| const match = wslPath.match(/^\/mnt\/([a-z])\/(.*)$/); |
| if (match) { |
| const [, drive, rest] = match; |
| return `${drive.toUpperCase()}:\\${rest.replace(/\//g, '\\')}`; |
| } |
|
|
| |
| return wslPath; |
| } |
|
|