index.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import { spawn as nodeSpawn, } from 'child_process';
  2. import crossSpawn from 'cross-spawn';
  3. import { onExit } from 'signal-exit';
  4. import { proxySignals } from './proxy-signals.js';
  5. import { watchdog } from './watchdog.js';
  6. /* c8 ignore start */
  7. const spawn = process?.platform === 'win32' ? crossSpawn : nodeSpawn;
  8. /**
  9. * Normalizes the arguments passed to `foregroundChild`.
  10. *
  11. * Exposed for testing.
  12. *
  13. * @internal
  14. */
  15. export const normalizeFgArgs = (fgArgs) => {
  16. let [program, args = [], spawnOpts = {}, cleanup = () => { }] = fgArgs;
  17. if (typeof args === 'function') {
  18. cleanup = args;
  19. spawnOpts = {};
  20. args = [];
  21. }
  22. else if (!!args && typeof args === 'object' && !Array.isArray(args)) {
  23. if (typeof spawnOpts === 'function')
  24. cleanup = spawnOpts;
  25. spawnOpts = args;
  26. args = [];
  27. }
  28. else if (typeof spawnOpts === 'function') {
  29. cleanup = spawnOpts;
  30. spawnOpts = {};
  31. }
  32. if (Array.isArray(program)) {
  33. const [pp, ...pa] = program;
  34. program = pp;
  35. args = pa;
  36. }
  37. return [program, args, { ...spawnOpts }, cleanup];
  38. };
  39. export function foregroundChild(...fgArgs) {
  40. const [program, args, spawnOpts, cleanup] = normalizeFgArgs(fgArgs);
  41. spawnOpts.stdio = [0, 1, 2];
  42. if (process.send) {
  43. spawnOpts.stdio.push('ipc');
  44. }
  45. const child = spawn(program, args, spawnOpts);
  46. const childHangup = () => {
  47. try {
  48. child.kill('SIGHUP');
  49. /* c8 ignore start */
  50. }
  51. catch (_) {
  52. // SIGHUP is weird on windows
  53. child.kill('SIGTERM');
  54. }
  55. /* c8 ignore stop */
  56. };
  57. const removeOnExit = onExit(childHangup);
  58. proxySignals(child);
  59. const dog = watchdog(child);
  60. let done = false;
  61. child.on('close', async (code, signal) => {
  62. /* c8 ignore start */
  63. if (done)
  64. return;
  65. /* c8 ignore stop */
  66. done = true;
  67. const result = cleanup(code, signal, {
  68. watchdogPid: dog.pid,
  69. });
  70. const res = isPromise(result) ? await result : result;
  71. removeOnExit();
  72. if (res === false)
  73. return;
  74. else if (typeof res === 'string') {
  75. signal = res;
  76. code = null;
  77. }
  78. else if (typeof res === 'number') {
  79. code = res;
  80. signal = null;
  81. }
  82. if (signal) {
  83. // If there is nothing else keeping the event loop alive,
  84. // then there's a race between a graceful exit and getting
  85. // the signal to this process. Put this timeout here to
  86. // make sure we're still alive to get the signal, and thus
  87. // exit with the intended signal code.
  88. /* istanbul ignore next */
  89. setTimeout(() => { }, 2000);
  90. try {
  91. process.kill(process.pid, signal);
  92. /* c8 ignore start */
  93. }
  94. catch (_) {
  95. process.kill(process.pid, 'SIGTERM');
  96. }
  97. /* c8 ignore stop */
  98. }
  99. else {
  100. process.exit(code || 0);
  101. }
  102. });
  103. if (process.send) {
  104. process.removeAllListeners('message');
  105. child.on('message', (message, sendHandle) => {
  106. process.send?.(message, sendHandle);
  107. });
  108. process.on('message', (message, sendHandle) => {
  109. child.send(message, sendHandle);
  110. });
  111. }
  112. return child;
  113. }
  114. const isPromise = (o) => !!o && typeof o === 'object' && typeof o.then === 'function';
  115. //# sourceMappingURL=index.js.map