duplicate.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import {
  2. SPECIAL_DUPLICATE_TYPES_SYNC,
  3. SPECIAL_DUPLICATE_TYPES,
  4. FORBID_DUPLICATE_TYPES,
  5. TYPE_TO_MESSAGE,
  6. } from './type.js';
  7. // Duplicates in the same file descriptor is most likely an error.
  8. // However, this can be useful with generators.
  9. export const filterDuplicates = stdioItems => stdioItems.filter((stdioItemOne, indexOne) =>
  10. stdioItems.every((stdioItemTwo, indexTwo) => stdioItemOne.value !== stdioItemTwo.value
  11. || indexOne >= indexTwo
  12. || stdioItemOne.type === 'generator'
  13. || stdioItemOne.type === 'asyncGenerator'));
  14. // Check if two file descriptors are sharing the same target.
  15. // For example `{stdout: {file: './output.txt'}, stderr: {file: './output.txt'}}`.
  16. export const getDuplicateStream = ({stdioItem: {type, value, optionName}, direction, fileDescriptors, isSync}) => {
  17. const otherStdioItems = getOtherStdioItems(fileDescriptors, type);
  18. if (otherStdioItems.length === 0) {
  19. return;
  20. }
  21. if (isSync) {
  22. validateDuplicateStreamSync({
  23. otherStdioItems,
  24. type,
  25. value,
  26. optionName,
  27. direction,
  28. });
  29. return;
  30. }
  31. if (SPECIAL_DUPLICATE_TYPES.has(type)) {
  32. return getDuplicateStreamInstance({
  33. otherStdioItems,
  34. type,
  35. value,
  36. optionName,
  37. direction,
  38. });
  39. }
  40. if (FORBID_DUPLICATE_TYPES.has(type)) {
  41. validateDuplicateTransform({
  42. otherStdioItems,
  43. type,
  44. value,
  45. optionName,
  46. });
  47. }
  48. };
  49. // Values shared by multiple file descriptors
  50. const getOtherStdioItems = (fileDescriptors, type) => fileDescriptors
  51. .flatMap(({direction, stdioItems}) => stdioItems
  52. .filter(stdioItem => stdioItem.type === type)
  53. .map((stdioItem => ({...stdioItem, direction}))));
  54. // With `execaSync()`, do not allow setting a file path both in input and output
  55. const validateDuplicateStreamSync = ({otherStdioItems, type, value, optionName, direction}) => {
  56. if (SPECIAL_DUPLICATE_TYPES_SYNC.has(type)) {
  57. getDuplicateStreamInstance({
  58. otherStdioItems,
  59. type,
  60. value,
  61. optionName,
  62. direction,
  63. });
  64. }
  65. };
  66. // When two file descriptors share the file or stream, we need to re-use the same underlying stream.
  67. // Otherwise, the stream would be closed twice when piping ends.
  68. // This is only an issue with output file descriptors.
  69. // This is not a problem with generator functions since those create a new instance for each file descriptor.
  70. // We also forbid input and output file descriptors sharing the same file or stream, since that does not make sense.
  71. const getDuplicateStreamInstance = ({otherStdioItems, type, value, optionName, direction}) => {
  72. const duplicateStdioItems = otherStdioItems.filter(stdioItem => hasSameValue(stdioItem, value));
  73. if (duplicateStdioItems.length === 0) {
  74. return;
  75. }
  76. const differentStdioItem = duplicateStdioItems.find(stdioItem => stdioItem.direction !== direction);
  77. throwOnDuplicateStream(differentStdioItem, optionName, type);
  78. return direction === 'output' ? duplicateStdioItems[0].stream : undefined;
  79. };
  80. const hasSameValue = ({type, value}, secondValue) => {
  81. if (type === 'filePath') {
  82. return value.file === secondValue.file;
  83. }
  84. if (type === 'fileUrl') {
  85. return value.href === secondValue.href;
  86. }
  87. return value === secondValue;
  88. };
  89. // We do not allow two file descriptors to share the same Duplex or TransformStream.
  90. // This is because those are set directly to `subprocess.std*`.
  91. // For example, this could result in `subprocess.stdout` and `subprocess.stderr` being the same value.
  92. // This means reading from either would get data from both stdout and stderr.
  93. const validateDuplicateTransform = ({otherStdioItems, type, value, optionName}) => {
  94. const duplicateStdioItem = otherStdioItems.find(({value: {transform}}) => transform === value.transform);
  95. throwOnDuplicateStream(duplicateStdioItem, optionName, type);
  96. };
  97. const throwOnDuplicateStream = (stdioItem, optionName, type) => {
  98. if (stdioItem !== undefined) {
  99. throw new TypeError(`The \`${stdioItem.optionName}\` and \`${optionName}\` options must not target ${TYPE_TO_MESSAGE[type]} that is the same.`);
  100. }
  101. };