{"version":3,"file":"index.cjs","names":["Transform"],"sources":["../src/utils.ts","../src/dev.ts"],"sourcesContent":["import { execSync, spawn } from 'child_process';\nimport { Transform } from 'stream';\nimport dnsLookupSync from 'dns-lookup-sync';\nimport killSync from 'kill-sync';\nimport netstat, { SyncResult } from 'node-netstat';\nimport slync from 'slync';\nimport { Options } from './types';\n\n/**\n * Get the PID number from a given port\n * @param port\n */\n/* v8 ignore start */\nconst getPortPid = (port: number): number | null => {\n  try {\n    if (process.platform === 'darwin' || process.platform === 'linux') {\n      const output = execSync(`lsof -ti:${port}`).toString().split('\\n').filter(Boolean);\n      return output.length ? Number(output[0]) : null;\n    } else if (process.platform === 'win32') {\n      const output = execSync(\n        `for /f \"tokens=5\" %a in ('netstat -ano ^| findstr :${port}') do @echo %a`,\n      )\n        .toString()\n        .split('\\n')\n        .filter(Boolean);\n      return output.length ? Number(output[0]) : null;\n    }\n    return null;\n  } catch {\n    return null;\n  }\n};\n/* v8 ignore stop */\n\n/**\n * Get the netstat information for a given port and host.\n * - https://github.com/danielkrainas/node-netstat\n *\n * @param {number} port - The port to check.\n * @param {string} [host] - The host to check (optional)\n * @returns {SyncResult} - The synchronous netstat result\n */\nexport const getNetstat = (port: number, host?: string): SyncResult => {\n  let results: SyncResult;\n  const address = host === 'localhost' ? dnsLookupSync(host, 4).address : host;\n\n  const local = {\n    port,\n    ...(address ? { address } : {}),\n  };\n  netstat(\n    {\n      sync: true,\n      filter: {\n        local,\n      },\n      limit: 1,\n    },\n    /* v8 ignore next 3 */\n    (ret) => {\n      results = ret;\n    },\n  );\n  return results;\n};\n\n/**\n * Blocks the event-loop to synchronously \"await\" for the server to either\n * start or stop depending on the given boolean\n *\n * @param {Required<Options>} opts - includes port/host/timeout\n * @param {boolean} toStart - wait for start or wait for stop\n * @returns {boolean} - True if started/stopped within timeout, false otherwise\n */\nexport const waitForServerToStartOrStop = (opts: Required<Options>, toStart: boolean): boolean => {\n  const ms = 200;\n  const isReadyFn = opts.isServerReadyFn ?? (() => getNetstat(opts.port, opts.host));\n  for (let time = 0; time < opts.timeout; time += ms) {\n    if (Boolean(isReadyFn()) === toStart) {\n      return true;\n    }\n    slync(ms);\n  }\n  return false;\n};\n\n/**\n * Kill a process recursively by its pid\n *\n * @param {number | undefined} pid process identifier to kill\n * @param signal IPC signal, e.g. SIGTERM\n * @throws Error if the given pid is undefined\n */\nexport const killPid = (pid: number | undefined, signal?: string | number) => {\n  if (pid === undefined) {\n    throw new Error('The given server child process has undefined pid!');\n  }\n  /* v8 ignore next 10 */\n  try {\n    killSync(pid, signal, true);\n  } catch (error: unknown) {\n    console.error(`\\\nWARNING - failed to kill server with pid ${pid} using signal ${signal ?? 'SIGTERM'}.\n\nERROR STACK:\n  ${(error as Error).stack}\n    `);\n  }\n};\n\n/**\n * Kill a server synchronously and \"await\" for it to stop.\n * - Ohttps://github.com/nktnet1/kill-sync\n *\n * @param {number} pid - The process ID of the server to kill.\n * @param {Required<Options>} opts - The options for killing the server.\n * @throws Error if the given pid is undefined or the server cannot be killed\n */\nexport const killServerSync = (opts: Required<Options>, pid: number) => {\n  killPid(pid, opts.signal);\n\n  if (!waitForServerToStartOrStop(opts, false)) {\n    throw new Error(`\n>>> Failed to kill server.\n\n>>> Options:\n${JSON.stringify(opts, null, 2)}\n    `);\n  }\n};\n\n/**\n * Throws error if another server is running and opts.error is specified.\n * Kills the conflicting server if opts.kill is pecified.\n *\n * @param {Required<Options>} opts - The options for handling a used port.\n * @param {SyncResult} netstatResult - The netstat result for the used port\n */\nexport const handleUsedPortErrorOrKill = (\n  opts: Required<Options>,\n  netstatResult: SyncResult,\n  isActive: boolean,\n) => {\n  if (isActive) {\n    if (opts.usedPortAction === 'error') {\n      throw new Error(`Port ${opts.port} is already taken.`);\n    }\n    if (opts.usedPortAction === 'kill') {\n      /* v8 ignore next */\n      const pid = netstatResult?.pid ?? getPortPid(opts.port);\n\n      /* v8 ignore next 3 */\n      if (!pid) {\n        throw new Error('Failed to kill existing server - missing pid');\n      }\n      killServerSync(opts, pid);\n      /* v8 ignore next 3 */\n      if (!waitForServerToStartOrStop(opts, false)) {\n        throw new Error('Failed to stop existing server within timeout');\n      }\n    }\n  }\n};\n\n/**\n * Creates a server child process that's actively listening for requests\n *\n * @param {string} cmd command/script to run, e.g. 'npm', 'node'\n * @param {string[]} args arguments to pass to this command\n * @param {Required<Options>} opts options from startServer\n * @returns a server child process that is guaranteed to be running\n */\nexport const createServerSync = (cmd: string, args: string[], opts: Required<Options>) => {\n  const server = spawn(cmd, args, { env: Object.assign(process.env, opts.env) });\n  if (!waitForServerToStartOrStop(opts, true)) {\n    killPid(server.pid, opts.signal);\n    throw new Error(`\nFailed to start server after ${opts.timeout} milliseconds.\n\nPlease double check that you've specified the correct options, as\nthe default options may not meet your needs (e.g. opts.port):\n\n${JSON.stringify(opts, null, 2)}\n    `);\n  }\n\n  const serverLogPrefixer = new Transform({\n    /* v8 ignore next 4 */\n    transform(chunk, _encoding, callback) {\n      this.push(`[sync-dev-server] ${chunk.toString()}`);\n      callback();\n    },\n  });\n\n  if (opts.debug) {\n    server.stdout.pipe(serverLogPrefixer).pipe(process.stdout);\n  } else {\n    /* v8 ignore next */\n    server.stdout.on('data', () => {\n      /* nothing to do */\n    });\n  }\n\n  return server;\n};\n","import { sync as commandExistsSync } from 'command-exists';\nimport { Options, SyncServer } from './types';\nimport {\n  handleUsedPortErrorOrKill,\n  getNetstat,\n  killPid,\n  createServerSync,\n  waitForServerToStartOrStop,\n} from './utils';\n\nconst defaultOptions: Required<Options> = {\n  port: 5000,\n  host: '',\n  timeout: 10000,\n  signal: 'SIGTERM',\n  debug: true,\n  usedPortAction: 'error',\n  env: {},\n  isServerReadyFn: null,\n};\n\n/**\n * Stops the given server\n *\n * @param {ChildProcess | null} server - The server's child process or null\n * @param {number|string} [signal='SIGTERM'] - Signal to terminate the server\n */\nexport const stopServer = (\n  server: SyncServer | null,\n  signal: number | string = 'SIGTERM',\n): void => {\n  if (server === null) {\n    return;\n  }\n  killPid(server.process.pid, signal);\n  waitForServerToStartOrStop(server.opts, false);\n};\n\n/**\n * Start a server process based on the provided command and options.\n *\n * @param {string} command - The command to start the server\n * @param {Options} [options={}] - The options for starting the server\n * @returns {ChildProcess | null} - The spawned server child process\n */\nexport type StartServerReturn<T extends Options | undefined> = T extends {\n  usedPortAction: 'ignore';\n}\n  ? SyncServer | null\n  : SyncServer;\n\nexport function startServer<T extends Options | undefined = Options>(\n  command: string,\n  options?: T,\n): StartServerReturn<T> {\n  const opts = { ...defaultOptions, ...options };\n  const hasNetstat = commandExistsSync('netstat');\n\n  /* v8 ignore next 8 */\n  if (!hasNetstat && !opts.isServerReadyFn) {\n    console.warn(`\\\nWARNING: \"netstat\" command not found in Path.\n\nPlease install net-tools: https://net-tools.sourceforge.io/, or otherwise provide\nopts.isServerReadyFn\n    `);\n  }\n\n  const args = command.split(' ');\n  const cmd = args.shift();\n  if (!cmd) {\n    throw new Error(`Command is empty: ${command}`);\n  }\n\n  /* v8 ignore next */\n  const netstatResult = hasNetstat ? getNetstat(opts.port, opts.host) : undefined;\n  const isActive = opts.isServerReadyFn?.() ?? netstatResult !== undefined;\n  if (isActive && opts.usedPortAction === 'ignore') {\n    return null as StartServerReturn<T>;\n  }\n  handleUsedPortErrorOrKill(opts, netstatResult, isActive);\n  return {\n    process: createServerSync(cmd, args, opts),\n    opts,\n  };\n}\n"],"mappings":"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"}