18d307f52SEvan Baconimport { execFileSync, execSync, ExecSyncOptionsWithStringEncoding } from 'child_process';
28d307f52SEvan Baconimport * as path from 'path';
38d307f52SEvan Bacon
4*474a7a4bSEvan Baconconst debug = require('debug')('expo:utils:getRunningProcess') as typeof console.log;
5*474a7a4bSEvan Bacon
68d307f52SEvan Baconconst defaultOptions: ExecSyncOptionsWithStringEncoding = {
78d307f52SEvan Bacon  encoding: 'utf8',
88d307f52SEvan Bacon  stdio: ['pipe', 'pipe', 'ignore'],
98d307f52SEvan Bacon};
108d307f52SEvan Bacon
118d307f52SEvan Bacon/** Returns a pid value for a running port like `63828` or null if nothing is running on the given port. */
128d307f52SEvan Baconexport function getPID(port: number): number | null {
138d307f52SEvan Bacon  try {
148d307f52SEvan Bacon    const results = execFileSync('lsof', [`-i:${port}`, '-P', '-t', '-sTCP:LISTEN'], defaultOptions)
158d307f52SEvan Bacon      .split('\n')[0]
168d307f52SEvan Bacon      .trim();
17*474a7a4bSEvan Bacon    const pid = Number(results);
18*474a7a4bSEvan Bacon    debug(`pid: ${pid} for port: ${port}`);
19*474a7a4bSEvan Bacon    return pid;
20*474a7a4bSEvan Bacon  } catch (error: any) {
21*474a7a4bSEvan Bacon    debug(`No pid found for port: ${port}. Error: ${error}`);
228d307f52SEvan Bacon    return null;
238d307f52SEvan Bacon  }
248d307f52SEvan Bacon}
258d307f52SEvan Bacon
268d307f52SEvan Bacon/** Get `package.json` `name` field for a given directory. Returns `null` if none exist. */
278d307f52SEvan Baconfunction getPackageName(packageRoot: string): string | null {
288d307f52SEvan Bacon  const packageJson = path.join(packageRoot, 'package.json');
298d307f52SEvan Bacon  try {
308d307f52SEvan Bacon    return require(packageJson).name || null;
318d307f52SEvan Bacon  } catch {
328d307f52SEvan Bacon    return null;
338d307f52SEvan Bacon  }
348d307f52SEvan Bacon}
358d307f52SEvan Bacon
368d307f52SEvan Bacon/** Returns a command like `node /Users/evanbacon/.../bin/expo start` or the package.json name. */
378d307f52SEvan Baconfunction getProcessCommand(pid: number, procDirectory: string): string {
388d307f52SEvan Bacon  const name = getPackageName(procDirectory);
398d307f52SEvan Bacon
408d307f52SEvan Bacon  if (name) {
418d307f52SEvan Bacon    return name;
428d307f52SEvan Bacon  }
438d307f52SEvan Bacon  return execSync(`ps -o command -p ${pid} | sed -n 2p`, defaultOptions).replace(/\n$/, '').trim();
448d307f52SEvan Bacon}
458d307f52SEvan Bacon
468d307f52SEvan Bacon/** Get directory for a given process ID. */
478d307f52SEvan Baconexport function getDirectoryOfProcessById(processId: number): string {
488d307f52SEvan Bacon  return execSync(
498d307f52SEvan Bacon    `lsof -p ${processId} | awk '$4=="cwd" {for (i=9; i<=NF; i++) printf "%s ", $i}'`,
508d307f52SEvan Bacon    defaultOptions
518d307f52SEvan Bacon  ).trim();
528d307f52SEvan Bacon}
538d307f52SEvan Bacon
548d307f52SEvan Bacon/** Get information about a running process given a port. Returns null if no process is running on the given port. */
558d307f52SEvan Baconexport function getRunningProcess(port: number): {
568d307f52SEvan Bacon  /** The PID value for the port. */
578d307f52SEvan Bacon  pid: number;
588d307f52SEvan Bacon  /** Get the directory for the running process. */
598d307f52SEvan Bacon  directory: string;
608d307f52SEvan Bacon  /** The command running the process like `node /Users/evanbacon/.../bin/expo start` or the `package.json` name like `my-app`. */
618d307f52SEvan Bacon  command: string;
628d307f52SEvan Bacon} | null {
638d307f52SEvan Bacon  // 63828
648d307f52SEvan Bacon  const pid = getPID(port);
658d307f52SEvan Bacon  if (!pid) {
668d307f52SEvan Bacon    return null;
678d307f52SEvan Bacon  }
688d307f52SEvan Bacon
698d307f52SEvan Bacon  try {
708d307f52SEvan Bacon    // /Users/evanbacon/Documents/GitHub/lab/myapp
718d307f52SEvan Bacon    const directory = getDirectoryOfProcessById(pid);
728d307f52SEvan Bacon    // /Users/evanbacon/Documents/GitHub/lab/myapp/package.json
738d307f52SEvan Bacon    const command = getProcessCommand(pid, directory);
748d307f52SEvan Bacon    // TODO: Have a better message for reusing another process.
758d307f52SEvan Bacon    return { pid, directory, command };
768d307f52SEvan Bacon  } catch {
778d307f52SEvan Bacon    return null;
788d307f52SEvan Bacon  }
798d307f52SEvan Bacon}
80