import { readdir, readFile } from 'fs/promises' import memoize from 'lodash-es/memoize.js' import { release as osRelease } from 'os' import { getFsImplementation } from './fsOperations.js' import { logError } from './log.js' export type Platform = 'macos' | 'windows' | 'wsl' | 'linux' | 'unknown' export const SUPPORTED_PLATFORMS: Platform[] = ['macos', 'wsl'] export const getPlatform = memoize((): Platform => { try { if (process.platform === 'darwin') { return 'macos' } if (process.platform === 'win32') { return 'windows' } if (process.platform === 'linux') { // Check if running in WSL (Windows Subsystem for Linux) try { const procVersion = getFsImplementation().readFileSync( '/proc/version', { encoding: 'utf8' }, ) if ( procVersion.toLowerCase().includes('microsoft') || procVersion.toLowerCase().includes('wsl') ) { return 'wsl' } } catch (error) { // Error reading /proc/version, assume regular Linux logError(error) } // Regular Linux return 'linux' } // Unknown platform return 'unknown' } catch (error) { logError(error) return 'unknown' } }) export const getWslVersion = memoize((): string | undefined => { // Only check for WSL on Linux systems if (process.platform !== 'linux') { return undefined } try { const procVersion = getFsImplementation().readFileSync('/proc/version', { encoding: 'utf8', }) // First check for explicit WSL version markers (e.g., "WSL2", "WSL3", etc.) const wslVersionMatch = procVersion.match(/WSL(\d+)/i) if (wslVersionMatch && wslVersionMatch[1]) { return wslVersionMatch[1] } // If no explicit WSL version but contains Microsoft, assume WSL1 // This handles the original WSL1 format: "4.4.0-19041-Microsoft" if (procVersion.toLowerCase().includes('microsoft')) { return '1' } // Not WSL or unable to determine version return undefined } catch (error) { logError(error) return undefined } }) export type LinuxDistroInfo = { linuxDistroId?: string linuxDistroVersion?: string linuxKernel?: string } export const getLinuxDistroInfo = memoize( async (): Promise => { if (process.platform !== 'linux') { return undefined } const result: LinuxDistroInfo = { linuxKernel: osRelease(), } try { const content = await readFile('/etc/os-release', 'utf8') for (const line of content.split('\n')) { const match = line.match(/^(ID|VERSION_ID)=(.*)$/) if (match && match[1] && match[2]) { const value = match[2].replace(/^"|"$/g, '') if (match[1] === 'ID') { result.linuxDistroId = value } else { result.linuxDistroVersion = value } } } } catch { // /etc/os-release may not exist on all Linux systems } return result }, ) const VCS_MARKERS: Array<[string, string]> = [ ['.git', 'git'], ['.hg', 'mercurial'], ['.svn', 'svn'], ['.p4config', 'perforce'], ['$tf', 'tfs'], ['.tfvc', 'tfs'], ['.jj', 'jujutsu'], ['.sl', 'sapling'], ] export async function detectVcs(dir?: string): Promise { const detected = new Set() // Check for Perforce via env var if (process.env.P4PORT) { detected.add('perforce') } try { const targetDir = dir ?? getFsImplementation().cwd() const entries = new Set(await readdir(targetDir)) for (const [marker, vcs] of VCS_MARKERS) { if (entries.has(marker)) { detected.add(vcs) } } } catch { // Directory may not be readable } return [...detected] }