Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript: pipeStdout (and other pipeXxx) is optional property #580

Closed
koshic opened this issue Oct 27, 2023 · 10 comments · Fixed by #757
Closed

TypeScript: pipeStdout (and other pipeXxx) is optional property #580

koshic opened this issue Oct 27, 2023 · 10 comments · Fixed by #757

Comments

@koshic
Copy link
Contributor

koshic commented Oct 27, 2023

What is the reason to keep it optional?

pipeStdout?<Target extends ExecaChildPromise<StdoutStderrAll>>(target: Target): Target;
pipeStdout?(target: WritableStream | string): ExecaChildProcess<StdoutStderrType>;

It can't be user without extra check or '!`:
image

@ehmicky
Copy link
Collaborator

ehmicky commented Oct 27, 2023

Hi @koshic,

pipeStdout() is undefined if the stdout option is 'ignore', 'ipc', a file descriptor integer, or a stream. Default value is 'pipe'.
The same applies to pipeStderr(), but using the stderr option.

pipeAll() is undefined if either:

  • Both the stdout and stderr options are 'ignore', 'ipc', a file descriptor integer, or a stream.
  • The all option is not true (default value is false).

execa/lib/pipe.js

Lines 31 to 33 in 834e372

if (spawned.stdout !== null) {
spawned.pipeStdout = pipeToTarget.bind(undefined, spawned, 'stdout');
}

Ideally the types would take this into account. Would you be interested in submitting a PR to fix them?

@koshic
Copy link
Contributor Author

koshic commented Oct 27, 2023

@ehmicky yeah, let me experiment a bit )

@koshic
Copy link
Contributor Author

koshic commented Oct 27, 2023

As you can see, if we will use current strategy - modify options via generic parameter, and provide all possible combinations as execa definitions, we have to multiply all definitions by 3 (new modifiers: stdout + stdin + all) which makes code unmaintainable:

image

I see only one way - keep modifiers for options, but produce exact ExecaChildProcess type by additional conditional type wrapper which will work like switch-case statement:

O extends { stdin: 'pipe' /* + default value check */} ? { pipeStdout: () => {} } : { }

@ehmicky what do you think about it?

@ehmicky
Copy link
Collaborator

ehmicky commented Oct 28, 2023

Yes, I think what you're highlighting was the reason I did not implement stricter typing in the first place when adding those pipe methods.

I agree with you. I'm not sure if this is what you are describing, but instead of method overloading, type inference could be used. I.e. only define execa() once and let Options<EncodingType> be inferred from the parameters passed as input. Something like:

export function execa<EncodingType extends EncodingOption = DefaultEncodingOption>(
	file: string,
	arguments?: readonly string[],
    // EncodingType is now inferred based on the `options` passed as input
	options?: Options<EncodingType>
// Which is used to guess whether `result.stdout|stderr|all` is a `string` or a `Buffer`
): ExecaChildProcess<EncodingType extends BufferEncodingOption ? Buffer : string>; 

What do you think?

@koshic
Copy link
Contributor Author

koshic commented Oct 28, 2023

Yes, I mean the same pattern - define function signature once, and use smart generic types. So, I propose to split that task into 2:

First PR - to replace overloading with type inference. Not a breaking change, I believe.
Second PR - to extend generics with PipeXxx options. May be a breaking change due to pipeStdout will be not present in resulting type in some cases.

@ehmicky
Copy link
Collaborator

ehmicky commented Oct 28, 2023

This sounds like a good plan! I do like breaking it down like this, it's going to be easier to review.

I actually believe that this might not be a breaking change, because the type will go from Function | undefined to either Function or undefined, depending on the option being passed, so it is actually stricter. That being said, I am planning on making an unrelated PR which will require a new major release, so we can always bundle it with it, to be safe.

Thanks for your help!

@ehmicky
Copy link
Collaborator

ehmicky commented Dec 14, 2023

@koshic Would you be interested in doing the second PR discussed above?

@koshic
Copy link
Contributor Author

koshic commented Dec 14, 2023

@ehmicky yeah, I need a few days to recover from my illness.

@ehmicky
Copy link
Collaborator

ehmicky commented Dec 14, 2023

Thanks @koshic. Take care! 👩‍⚕️

@ehmicky
Copy link
Collaborator

ehmicky commented May 8, 2024

This has been just been fixed in Execa 9.0.0, with the new subprocess.pipe() method. Please see (and share!) the release post and the changelog.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants