Improve types and autocomplete for Bun.spawn (fixes #17274) (#19162)

This commit is contained in:
Alistair Smith
2025-04-24 22:04:28 -07:00
committed by GitHub
parent e6c516465e
commit a1eb595d86
4 changed files with 71 additions and 51 deletions

View File

@@ -6393,11 +6393,7 @@ declare module "bun" {
| Response
| Request;
interface OptionsObject<
In extends Writable = Writable,
Out extends Readable = Readable,
Err extends Readable = Readable,
> {
interface OptionsObject<In extends Writable, Out extends Readable, Err extends Readable> {
/**
* The current working directory of the process
*
@@ -6624,22 +6620,6 @@ declare module "bun" {
maxBuffer?: number;
}
type OptionsToSubprocess<Opts extends OptionsObject> =
Opts extends OptionsObject<infer In, infer Out, infer Err>
? Subprocess<
// "Writable extends In" means "if In === Writable",
// aka if true that means the user didn't specify anything
Writable extends In ? "ignore" : In,
Readable extends Out ? "pipe" : Out,
Readable extends Err ? "inherit" : Err
>
: Subprocess<Writable, Readable, Readable>;
type OptionsToSyncSubprocess<Opts extends OptionsObject> =
Opts extends OptionsObject<any, infer Out, infer Err>
? SyncSubprocess<Readable extends Out ? "pipe" : Out, Readable extends Err ? "pipe" : Err>
: SyncSubprocess<Readable, Readable>;
type ReadableIO = ReadableStream<Uint8Array> | number | undefined;
type ReadableToIO<X extends Readable> = X extends "pipe" | undefined
@@ -6746,9 +6726,9 @@ declare module "bun" {
* - {@link NullSubprocess} (ignore, ignore, ignore)
*/
interface Subprocess<
In extends SpawnOptions.Writable = SpawnOptions.Writable,
Out extends SpawnOptions.Readable = SpawnOptions.Readable,
Err extends SpawnOptions.Readable = SpawnOptions.Readable,
In extends SpawnOptions.Writable,
Out extends SpawnOptions.Readable,
Err extends SpawnOptions.Readable,
> extends AsyncDisposable {
readonly stdin: SpawnOptions.WritableToIO<In>;
readonly stdout: SpawnOptions.ReadableToIO<Out>;
@@ -6853,10 +6833,7 @@ declare module "bun" {
* - {@link ReadableSyncSubprocess} (pipe, pipe)
* - {@link NullSyncSubprocess} (ignore, ignore)
*/
interface SyncSubprocess<
Out extends SpawnOptions.Readable = SpawnOptions.Readable,
Err extends SpawnOptions.Readable = SpawnOptions.Readable,
> {
interface SyncSubprocess<Out extends SpawnOptions.Readable, Err extends SpawnOptions.Readable> {
stdout: SpawnOptions.ReadableToSyncIO<Out>;
stderr: SpawnOptions.ReadableToSyncIO<Err>;
exitCode: number;
@@ -6888,8 +6865,12 @@ declare module "bun" {
*
* Internally, this uses [posix_spawn(2)](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/posix_spawn.2.html)
*/
function spawn<Opts extends SpawnOptions.OptionsObject>(
options: Opts & {
function spawn<
const In extends SpawnOptions.Writable = "ignore",
const Out extends SpawnOptions.Readable = "pipe",
const Err extends SpawnOptions.Readable = "inherit",
>(
options: SpawnOptions.OptionsObject<In, Out, Err> & {
/**
* The command to run
*
@@ -6906,7 +6887,7 @@ declare module "bun" {
*/
cmd: string[]; // to support dynamically constructed commands
},
): SpawnOptions.OptionsToSubprocess<Opts>;
): Subprocess<In, Out, Err>;
/**
* Spawn a new process
@@ -6919,7 +6900,11 @@ declare module "bun" {
*
* Internally, this uses [posix_spawn(2)](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/posix_spawn.2.html)
*/
function spawn<Opts extends SpawnOptions.OptionsObject>(
function spawn<
const In extends SpawnOptions.Writable = "ignore",
const Out extends SpawnOptions.Readable = "pipe",
const Err extends SpawnOptions.Readable = "inherit",
>(
/**
* The command to run
*
@@ -6935,8 +6920,8 @@ declare module "bun" {
* ```
*/
cmds: string[],
options?: Opts,
): SpawnOptions.OptionsToSubprocess<Opts>;
options?: SpawnOptions.OptionsObject<In, Out, Err>,
): Subprocess<In, Out, Err>;
/**
* Spawn a new process
@@ -6952,8 +6937,11 @@ declare module "bun" {
*
* Internally, this uses [posix_spawn(2)](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/posix_spawn.2.html)
*/
function spawnSync<Opts extends SpawnOptions.OptionsObject>(
options: Opts & {
function spawnSync<
const Out extends SpawnOptions.Readable = "pipe",
const Err extends SpawnOptions.Readable = "inherit",
>(
options: SpawnOptions.OptionsObject<"ignore", Out, Err> & {
/**
* The command to run
*
@@ -6972,7 +6960,7 @@ declare module "bun" {
onExit?: never;
},
): SpawnOptions.OptionsToSyncSubprocess<Opts>;
): SyncSubprocess<Out, Err>;
/**
* Synchronously spawn a new process
@@ -6984,7 +6972,10 @@ declare module "bun" {
*
* Internally, this uses [posix_spawn(2)](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/posix_spawn.2.html)
*/
function spawnSync<Opts extends SpawnOptions.OptionsObject>(
function spawnSync<
const Out extends SpawnOptions.Readable = "pipe",
const Err extends SpawnOptions.Readable = "inherit",
>(
/**
* The command to run
*
@@ -7000,8 +6991,8 @@ declare module "bun" {
* ```
*/
cmds: string[],
options?: Opts,
): SpawnOptions.OptionsToSyncSubprocess<Opts>;
options?: SpawnOptions.OptionsObject<"ignore", Out, Err>,
): SyncSubprocess<Out, Err>;
/** Utility type for any process from {@link Bun.spawn()} with both stdout and stderr set to `"pipe"` */
type ReadableSubprocess = Subprocess<any, "pipe", "pipe">;

View File

@@ -6,7 +6,7 @@ declare module "bun" {
| Array<ShellExpression>
| string
| { raw: string }
| Subprocess
| Subprocess<SpawnOptions.Writable, SpawnOptions.Readable, SpawnOptions.Readable>
| SpawnOptions.Readable
| SpawnOptions.Writable
| ReadableStream;

View File

@@ -73,7 +73,11 @@ beforeEach(async () => {
afterAll(async () => {
if (TEMP_DIR) {
await rm(TEMP_DIR, { recursive: true, force: true });
if (Bun.env.TYPES_INTEGRATION_TEST_KEEP_TEMP_DIR === "true") {
console.log(`Keeping temp dir ${TEMP_DIR} for debugging`);
} else {
await rm(TEMP_DIR, { recursive: true, force: true });
}
}
});

View File

@@ -14,6 +14,30 @@ function depromise<T>(_promise: Promise<T>): T {
return "asdf" as any as T;
}
{
// Test cases for https://github.com/oven-sh/bun/issues/17274
{
const proc = Bun.spawn(["cat"], {
stdin: "pipe",
});
proc.stdin.write("hello");
}
{
const proc = Bun.spawn(["cat"], {
stdin: "pipe",
onExit(proc, exitCode, signalCode, error) {
tsd.expectType(proc).is<Bun.Subprocess<"pipe", "pipe", "inherit">>();
console.log(`Process exited: ${exitCode}`);
},
});
proc.stdin.write("hello");
}
}
{
const proc = Bun.spawn(["echo", "hello"], {
cwd: "./path/to/subdir", // specify a working direcory
@@ -23,11 +47,11 @@ function depromise<T>(_promise: Promise<T>): T {
},
});
proc.pid; // process ID of subprocess
tsd.expectType(proc.pid).is<number>();
tsd.expectType<ReadableStream<Uint8Array>>(proc.stdout);
tsd.expectType<undefined>(proc.stderr);
tsd.expectType<undefined>(proc.stdin);
tsd.expectType(proc.stdout).is<ReadableStream<Uint8Array<ArrayBufferLike>>>();
tsd.expectType(proc.stderr).is<undefined>();
tsd.expectType(proc.stdin).is<undefined>();
}
{
@@ -115,14 +139,16 @@ function depromise<T>(_promise: Promise<T>): T {
const proc = Bun.spawn(["echo", "hello"], {
stdio: [null, null, null],
});
tsd.expectType<undefined>(proc.stdin);
tsd.expectType<undefined>(proc.stdout);
tsd.expectType<undefined>(proc.stderr);
tsd.expectType(proc.stdin).is<undefined>();
tsd.expectType(proc.stdout).is<undefined>();
tsd.expectType(proc.stderr).is<undefined>();
}
{
const proc = Bun.spawn(["echo", "hello"], {
stdio: [new Request("1"), null, null],
});
tsd.expectType<number>(proc.stdin);
}
{
@@ -150,5 +176,4 @@ tsd.expectAssignable<NullSubprocess>(Bun.spawn([], { stdio: [null, null, null] }
tsd.expectNotAssignable<ReadableSubprocess>(Bun.spawn([], {}));
tsd.expectNotAssignable<PipedSubprocess>(Bun.spawn([], {}));
tsd.expectAssignable<SyncSubprocess>(Bun.spawnSync([], {}));
tsd.expectAssignable<SyncSubprocess>(Bun.spawnSync([], {}));
tsd.expectAssignable<SyncSubprocess<Bun.SpawnOptions.Readable, Bun.SpawnOptions.Readable>>(Bun.spawnSync([], {}));