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