fd-options.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import {parseFd} from './specific.js';
  2. // Retrieve stream targeted by the `to` option
  3. export const getToStream = (destination, to = 'stdin') => {
  4. const isWritable = true;
  5. const {options, fileDescriptors} = SUBPROCESS_OPTIONS.get(destination);
  6. const fdNumber = getFdNumber(fileDescriptors, to, isWritable);
  7. const destinationStream = destination.stdio[fdNumber];
  8. if (destinationStream === null) {
  9. throw new TypeError(getInvalidStdioOptionMessage(fdNumber, to, options, isWritable));
  10. }
  11. return destinationStream;
  12. };
  13. // Retrieve stream targeted by the `from` option
  14. export const getFromStream = (source, from = 'stdout') => {
  15. const isWritable = false;
  16. const {options, fileDescriptors} = SUBPROCESS_OPTIONS.get(source);
  17. const fdNumber = getFdNumber(fileDescriptors, from, isWritable);
  18. const sourceStream = fdNumber === 'all' ? source.all : source.stdio[fdNumber];
  19. if (sourceStream === null || sourceStream === undefined) {
  20. throw new TypeError(getInvalidStdioOptionMessage(fdNumber, from, options, isWritable));
  21. }
  22. return sourceStream;
  23. };
  24. // Keeps track of the options passed to each Execa call
  25. export const SUBPROCESS_OPTIONS = new WeakMap();
  26. const getFdNumber = (fileDescriptors, fdName, isWritable) => {
  27. const fdNumber = parseFdNumber(fdName, isWritable);
  28. validateFdNumber(fdNumber, fdName, isWritable, fileDescriptors);
  29. return fdNumber;
  30. };
  31. const parseFdNumber = (fdName, isWritable) => {
  32. const fdNumber = parseFd(fdName);
  33. if (fdNumber !== undefined) {
  34. return fdNumber;
  35. }
  36. const {validOptions, defaultValue} = isWritable
  37. ? {validOptions: '"stdin"', defaultValue: 'stdin'}
  38. : {validOptions: '"stdout", "stderr", "all"', defaultValue: 'stdout'};
  39. throw new TypeError(`"${getOptionName(isWritable)}" must not be "${fdName}".
  40. It must be ${validOptions} or "fd3", "fd4" (and so on).
  41. It is optional and defaults to "${defaultValue}".`);
  42. };
  43. const validateFdNumber = (fdNumber, fdName, isWritable, fileDescriptors) => {
  44. const fileDescriptor = fileDescriptors[getUsedDescriptor(fdNumber)];
  45. if (fileDescriptor === undefined) {
  46. throw new TypeError(`"${getOptionName(isWritable)}" must not be ${fdName}. That file descriptor does not exist.
  47. Please set the "stdio" option to ensure that file descriptor exists.`);
  48. }
  49. if (fileDescriptor.direction === 'input' && !isWritable) {
  50. throw new TypeError(`"${getOptionName(isWritable)}" must not be ${fdName}. It must be a readable stream, not writable.`);
  51. }
  52. if (fileDescriptor.direction !== 'input' && isWritable) {
  53. throw new TypeError(`"${getOptionName(isWritable)}" must not be ${fdName}. It must be a writable stream, not readable.`);
  54. }
  55. };
  56. const getInvalidStdioOptionMessage = (fdNumber, fdName, options, isWritable) => {
  57. if (fdNumber === 'all' && !options.all) {
  58. return 'The "all" option must be true to use "from: \'all\'".';
  59. }
  60. const {optionName, optionValue} = getInvalidStdioOption(fdNumber, options);
  61. return `The "${optionName}: ${serializeOptionValue(optionValue)}" option is incompatible with using "${getOptionName(isWritable)}: ${serializeOptionValue(fdName)}".
  62. Please set this option with "pipe" instead.`;
  63. };
  64. const getInvalidStdioOption = (fdNumber, {stdin, stdout, stderr, stdio}) => {
  65. const usedDescriptor = getUsedDescriptor(fdNumber);
  66. if (usedDescriptor === 0 && stdin !== undefined) {
  67. return {optionName: 'stdin', optionValue: stdin};
  68. }
  69. if (usedDescriptor === 1 && stdout !== undefined) {
  70. return {optionName: 'stdout', optionValue: stdout};
  71. }
  72. if (usedDescriptor === 2 && stderr !== undefined) {
  73. return {optionName: 'stderr', optionValue: stderr};
  74. }
  75. return {optionName: `stdio[${usedDescriptor}]`, optionValue: stdio[usedDescriptor]};
  76. };
  77. const getUsedDescriptor = fdNumber => fdNumber === 'all' ? 1 : fdNumber;
  78. const getOptionName = isWritable => isWritable ? 'to' : 'from';
  79. export const serializeOptionValue = value => {
  80. if (typeof value === 'string') {
  81. return `'${value}'`;
  82. }
  83. return typeof value === 'number' ? `${value}` : 'Stream';
  84. };