output-sync.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import {writeFileSync, appendFileSync} from 'node:fs';
  2. import {shouldLogOutput, logLinesSync} from '../verbose/output.js';
  3. import {runGeneratorsSync} from '../transform/generator.js';
  4. import {splitLinesSync} from '../transform/split.js';
  5. import {joinToString, joinToUint8Array, bufferToUint8Array} from '../utils/uint-array.js';
  6. import {FILE_TYPES} from '../stdio/type.js';
  7. import {truncateMaxBufferSync} from './max-buffer.js';
  8. // Apply `stdout`/`stderr` options, after spawning, in sync mode
  9. export const transformOutputSync = ({fileDescriptors, syncResult: {output}, options, isMaxBuffer, verboseInfo}) => {
  10. if (output === null) {
  11. return {output: Array.from({length: 3})};
  12. }
  13. const state = {};
  14. const outputFiles = new Set([]);
  15. const transformedOutput = output.map((result, fdNumber) =>
  16. transformOutputResultSync({
  17. result,
  18. fileDescriptors,
  19. fdNumber,
  20. state,
  21. outputFiles,
  22. isMaxBuffer,
  23. verboseInfo,
  24. }, options));
  25. return {output: transformedOutput, ...state};
  26. };
  27. const transformOutputResultSync = (
  28. {result, fileDescriptors, fdNumber, state, outputFiles, isMaxBuffer, verboseInfo},
  29. {buffer, encoding, lines, stripFinalNewline, maxBuffer},
  30. ) => {
  31. if (result === null) {
  32. return;
  33. }
  34. const truncatedResult = truncateMaxBufferSync(result, isMaxBuffer, maxBuffer);
  35. const uint8ArrayResult = bufferToUint8Array(truncatedResult);
  36. const {stdioItems, objectMode} = fileDescriptors[fdNumber];
  37. const chunks = runOutputGeneratorsSync([uint8ArrayResult], stdioItems, encoding, state);
  38. const {serializedResult, finalResult = serializedResult} = serializeChunks({
  39. chunks,
  40. objectMode,
  41. encoding,
  42. lines,
  43. stripFinalNewline,
  44. fdNumber,
  45. });
  46. logOutputSync({
  47. serializedResult,
  48. fdNumber,
  49. state,
  50. verboseInfo,
  51. encoding,
  52. stdioItems,
  53. objectMode,
  54. });
  55. const returnedResult = buffer[fdNumber] ? finalResult : undefined;
  56. try {
  57. if (state.error === undefined) {
  58. writeToFiles(serializedResult, stdioItems, outputFiles);
  59. }
  60. return returnedResult;
  61. } catch (error) {
  62. state.error = error;
  63. return returnedResult;
  64. }
  65. };
  66. // Applies transform generators to `stdout`/`stderr`
  67. const runOutputGeneratorsSync = (chunks, stdioItems, encoding, state) => {
  68. try {
  69. return runGeneratorsSync(chunks, stdioItems, encoding, false);
  70. } catch (error) {
  71. state.error = error;
  72. return chunks;
  73. }
  74. };
  75. // The contents is converted to three stages:
  76. // - serializedResult: used when the target is a file path/URL or a file descriptor (including 'inherit')
  77. // - finalResult/returnedResult: returned as `result.std*`
  78. const serializeChunks = ({chunks, objectMode, encoding, lines, stripFinalNewline, fdNumber}) => {
  79. if (objectMode) {
  80. return {serializedResult: chunks};
  81. }
  82. if (encoding === 'buffer') {
  83. return {serializedResult: joinToUint8Array(chunks)};
  84. }
  85. const serializedResult = joinToString(chunks, encoding);
  86. if (lines[fdNumber]) {
  87. return {serializedResult, finalResult: splitLinesSync(serializedResult, !stripFinalNewline[fdNumber], objectMode)};
  88. }
  89. return {serializedResult};
  90. };
  91. const logOutputSync = ({serializedResult, fdNumber, state, verboseInfo, encoding, stdioItems, objectMode}) => {
  92. if (!shouldLogOutput({
  93. stdioItems,
  94. encoding,
  95. verboseInfo,
  96. fdNumber,
  97. })) {
  98. return;
  99. }
  100. const linesArray = splitLinesSync(serializedResult, false, objectMode);
  101. try {
  102. logLinesSync(linesArray, fdNumber, verboseInfo);
  103. } catch (error) {
  104. state.error ??= error;
  105. }
  106. };
  107. // When the `std*` target is a file path/URL or a file descriptor
  108. const writeToFiles = (serializedResult, stdioItems, outputFiles) => {
  109. for (const {path, append} of stdioItems.filter(({type}) => FILE_TYPES.has(type))) {
  110. const pathString = typeof path === 'string' ? path : path.toString();
  111. if (append || outputFiles.has(pathString)) {
  112. appendFileSync(path, serializedResult);
  113. } else {
  114. outputFiles.add(pathString);
  115. writeFileSync(path, serializedResult);
  116. }
  117. }
  118. };