1import { execFileSync, execSync, ExecSyncOptionsWithStringEncoding } from 'child_process'; 2import * as path from 'path'; 3 4const debug = require('debug')('expo:utils:getRunningProcess') as typeof console.log; 5 6const defaultOptions: ExecSyncOptionsWithStringEncoding = { 7 encoding: 'utf8', 8 stdio: ['pipe', 'pipe', 'ignore'], 9}; 10 11/** Returns a pid value for a running port like `63828` or null if nothing is running on the given port. */ 12export function getPID(port: number): number | null { 13 try { 14 const results = execFileSync('lsof', [`-i:${port}`, '-P', '-t', '-sTCP:LISTEN'], defaultOptions) 15 .split('\n')[0] 16 .trim(); 17 const pid = Number(results); 18 debug(`pid: ${pid} for port: ${port}`); 19 return pid; 20 } catch (error: any) { 21 debug(`No pid found for port: ${port}. Error: ${error}`); 22 return null; 23 } 24} 25 26/** Get `package.json` `name` field for a given directory. Returns `null` if none exist. */ 27function getPackageName(packageRoot: string): string | null { 28 const packageJson = path.join(packageRoot, 'package.json'); 29 try { 30 return require(packageJson).name || null; 31 } catch { 32 return null; 33 } 34} 35 36/** Returns a command like `node /Users/evanbacon/.../bin/expo start` or the package.json name. */ 37function getProcessCommand(pid: number, procDirectory: string): string { 38 const name = getPackageName(procDirectory); 39 40 if (name) { 41 return name; 42 } 43 return execSync(`ps -o command -p ${pid} | sed -n 2p`, defaultOptions).replace(/\n$/, '').trim(); 44} 45 46/** Get directory for a given process ID. */ 47export function getDirectoryOfProcessById(processId: number): string { 48 return execSync( 49 `lsof -p ${processId} | awk '$4=="cwd" {for (i=9; i<=NF; i++) printf "%s ", $i}'`, 50 defaultOptions 51 ).trim(); 52} 53 54/** Get information about a running process given a port. Returns null if no process is running on the given port. */ 55export function getRunningProcess(port: number): { 56 /** The PID value for the port. */ 57 pid: number; 58 /** Get the directory for the running process. */ 59 directory: string; 60 /** The command running the process like `node /Users/evanbacon/.../bin/expo start` or the `package.json` name like `my-app`. */ 61 command: string; 62} | null { 63 // 63828 64 const pid = getPID(port); 65 if (!pid) { 66 return null; 67 } 68 69 try { 70 // /Users/evanbacon/Documents/GitHub/lab/myapp 71 const directory = getDirectoryOfProcessById(pid); 72 // /Users/evanbacon/Documents/GitHub/lab/myapp/package.json 73 const command = getProcessCommand(pid, directory); 74 // TODO: Have a better message for reusing another process. 75 return { pid, directory, command }; 76 } catch { 77 return null; 78 } 79} 80