main-sync.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import {spawnSync} from 'node:child_process';
  2. import {handleCommand} from '../arguments/command.js';
  3. import {normalizeOptions} from '../arguments/options.js';
  4. import {makeError, makeEarlyError, makeSuccessResult} from '../return/result.js';
  5. import {handleResult} from '../return/reject.js';
  6. import {handleStdioSync} from '../stdio/handle-sync.js';
  7. import {stripNewline} from '../io/strip-newline.js';
  8. import {addInputOptionsSync} from '../io/input-sync.js';
  9. import {transformOutputSync} from '../io/output-sync.js';
  10. import {getMaxBufferSync} from '../io/max-buffer.js';
  11. import {getAllSync} from '../resolve/all-sync.js';
  12. import {getExitResultSync} from '../resolve/exit-sync.js';
  13. // Main shared logic for all sync methods: `execaSync()`, `$.sync()`
  14. export const execaCoreSync = (rawFile, rawArguments, rawOptions) => {
  15. const {file, commandArguments, command, escapedCommand, startTime, verboseInfo, options, fileDescriptors} = handleSyncArguments(rawFile, rawArguments, rawOptions);
  16. const result = spawnSubprocessSync({
  17. file,
  18. commandArguments,
  19. options,
  20. command,
  21. escapedCommand,
  22. verboseInfo,
  23. fileDescriptors,
  24. startTime,
  25. });
  26. return handleResult(result, verboseInfo, options);
  27. };
  28. // Compute arguments to pass to `child_process.spawnSync()`
  29. const handleSyncArguments = (rawFile, rawArguments, rawOptions) => {
  30. const {command, escapedCommand, startTime, verboseInfo} = handleCommand(rawFile, rawArguments, rawOptions);
  31. const syncOptions = normalizeSyncOptions(rawOptions);
  32. const {file, commandArguments, options} = normalizeOptions(rawFile, rawArguments, syncOptions);
  33. validateSyncOptions(options);
  34. const fileDescriptors = handleStdioSync(options, verboseInfo);
  35. return {
  36. file,
  37. commandArguments,
  38. command,
  39. escapedCommand,
  40. startTime,
  41. verboseInfo,
  42. options,
  43. fileDescriptors,
  44. };
  45. };
  46. // Options normalization logic specific to sync methods
  47. const normalizeSyncOptions = options => options.node && !options.ipc ? {...options, ipc: false} : options;
  48. // Options validation logic specific to sync methods
  49. const validateSyncOptions = ({ipc, ipcInput, detached, cancelSignal}) => {
  50. if (ipcInput) {
  51. throwInvalidSyncOption('ipcInput');
  52. }
  53. if (ipc) {
  54. throwInvalidSyncOption('ipc: true');
  55. }
  56. if (detached) {
  57. throwInvalidSyncOption('detached: true');
  58. }
  59. if (cancelSignal) {
  60. throwInvalidSyncOption('cancelSignal');
  61. }
  62. };
  63. const throwInvalidSyncOption = value => {
  64. throw new TypeError(`The "${value}" option cannot be used with synchronous methods.`);
  65. };
  66. const spawnSubprocessSync = ({file, commandArguments, options, command, escapedCommand, verboseInfo, fileDescriptors, startTime}) => {
  67. const syncResult = runSubprocessSync({
  68. file,
  69. commandArguments,
  70. options,
  71. command,
  72. escapedCommand,
  73. fileDescriptors,
  74. startTime,
  75. });
  76. if (syncResult.failed) {
  77. return syncResult;
  78. }
  79. const {resultError, exitCode, signal, timedOut, isMaxBuffer} = getExitResultSync(syncResult, options);
  80. const {output, error = resultError} = transformOutputSync({
  81. fileDescriptors,
  82. syncResult,
  83. options,
  84. isMaxBuffer,
  85. verboseInfo,
  86. });
  87. const stdio = output.map((stdioOutput, fdNumber) => stripNewline(stdioOutput, options, fdNumber));
  88. const all = stripNewline(getAllSync(output, options), options, 'all');
  89. return getSyncResult({
  90. error,
  91. exitCode,
  92. signal,
  93. timedOut,
  94. isMaxBuffer,
  95. stdio,
  96. all,
  97. options,
  98. command,
  99. escapedCommand,
  100. startTime,
  101. });
  102. };
  103. const runSubprocessSync = ({file, commandArguments, options, command, escapedCommand, fileDescriptors, startTime}) => {
  104. try {
  105. addInputOptionsSync(fileDescriptors, options);
  106. const normalizedOptions = normalizeSpawnSyncOptions(options);
  107. return spawnSync(file, commandArguments, normalizedOptions);
  108. } catch (error) {
  109. return makeEarlyError({
  110. error,
  111. command,
  112. escapedCommand,
  113. fileDescriptors,
  114. options,
  115. startTime,
  116. isSync: true,
  117. });
  118. }
  119. };
  120. // The `encoding` option is handled by Execa, not by `child_process.spawnSync()`
  121. const normalizeSpawnSyncOptions = ({encoding, maxBuffer, ...options}) => ({...options, encoding: 'buffer', maxBuffer: getMaxBufferSync(maxBuffer)});
  122. const getSyncResult = ({error, exitCode, signal, timedOut, isMaxBuffer, stdio, all, options, command, escapedCommand, startTime}) => error === undefined
  123. ? makeSuccessResult({
  124. command,
  125. escapedCommand,
  126. stdio,
  127. all,
  128. ipcOutput: [],
  129. options,
  130. startTime,
  131. })
  132. : makeError({
  133. error,
  134. command,
  135. escapedCommand,
  136. timedOut,
  137. isCanceled: false,
  138. isGracefullyCanceled: false,
  139. isMaxBuffer,
  140. isForcefullyTerminated: false,
  141. exitCode,
  142. signal,
  143. stdio,
  144. all,
  145. ipcOutput: [],
  146. options,
  147. startTime,
  148. isSync: true,
  149. });