Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/runtime/child-process.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,8 @@ interface Subprocess extends AsyncDisposable {
readonly stdin: FileSink | number | undefined | null;
readonly stdout: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null;
readonly stderr: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null;
readonly readable: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null;
readonly writable: FileSink | number | undefined | null; // same value as stdin
readonly readable: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null; // same value as stdout
readonly terminal: Terminal | undefined;
readonly pid: number;
readonly exited: Promise<number>;
Expand Down
8 changes: 8 additions & 0 deletions packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7578,6 +7578,14 @@ declare module "bun" {
*/
readonly stdio: [null, null, null, ...(number | null)[]];

/**
* The same value as {@link Subprocess.stdin}
*
* The counterpart of {@link Subprocess.readable}. With `stdin: "pipe"` this is
* the {@link FileSink} for the process's stdin, not a `WritableStream`.
*/
readonly writable: SpawnOptions.WritableToIO<In>;

/**
* The same value as {@link Subprocess.stdout}
*
Expand Down
29 changes: 29 additions & 0 deletions test/integration/bun-types/bun-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,35 @@ describe("@types/bun integration test", () => {
});
});

// Also runs on debug builds, where the in-process typeTest cases (which cover the whole
// fixture directory) are skipped: checks fixture/spawn.ts alone against the packed bun-types.
describe("Bun.spawn", () => {
test("fixture/spawn.ts type-checks", async () => {
const checkDir = join(TEMP_DIR, "spawn-fixture-check");
const tsconfig = structuredClone(sourceTsconfig);
tsconfig.files = [join(BASE_FIXTURE_DIR, "spawn.ts")];
tsconfig.compilerOptions.typeRoots = [join(BASE_FIXTURE_DIR, "node_modules", "@types")];
await mkdir(checkDir, { recursive: true });
await makeTree(checkDir, {
"tsconfig.json": JSON.stringify(tsconfig, null, 2),
});

await using proc = Bun.spawn({
cmd: [bunExe(), join(BASE_FIXTURE_DIR, "node_modules", "typescript", "bin", "tsc"), "-p", "."],
env: bunEnv,
cwd: checkDir,
stdout: "pipe",
stderr: "pipe",
});

const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stderr.trim()).toBe("");
expect(stdout.trim()).toBe("");
expect(exitCode).toBe(0);
});
});

describe("Test Globals", () => {
const code = `
const test_shouldBeAFunction: Function = test;
Expand Down
43 changes: 43 additions & 0 deletions test/integration/bun-types/fixture/spawn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,49 @@ function depromise<T>(_promise: Promise<T>): T {
});
tsd.expectType<number>(proc.stdin);
}

// `writable` and `readable` are the same values as `stdin` and `stdout`, so they have the same
// types as `stdin` and `stdout` for every stdio configuration.
{
function aliases<
In extends Bun.SpawnOptions.Writable,
Out extends Bun.SpawnOptions.Readable,
Err extends Bun.SpawnOptions.Readable,
>(proc: Bun.Subprocess<In, Out, Err>) {
tsd.expectType(proc.writable).is<typeof proc.stdin>();
tsd.expectType(proc.readable).is<typeof proc.stdout>();
}
aliases(Bun.spawn(["cat"]));
}
{
const proc = Bun.spawn(["cat"], { stdin: "pipe" });
tsd.expectType(proc.writable).is<FileSink>();
tsd.expectType(proc.readable).is<ReadableStream<Uint8Array<ArrayBuffer>>>();
proc.writable.write("hello");

// @ts-expect-error writable is a read-only getter, like stdin
proc.writable = proc.stdin;
}
{
const proc = Bun.spawn(["cat"], { stdin: 0, stdout: 1 });
tsd.expectType(proc.writable).is<number>();
tsd.expectType(proc.readable).is<number>();
}
{
const proc = Bun.spawn(["cat"], { stdout: "inherit" });
tsd.expectType(proc.writable).is<undefined>();
tsd.expectType(proc.readable).is<undefined>();
}
{
const proc = Bun.spawn(["cat"], { stdio: ["pipe", "pipe", "pipe"] });
tsd.expectType(proc.writable).is<FileSink>();
tsd.expectType(proc.readable).is<ReadableStream<Uint8Array<ArrayBuffer>>>();
}
tsd.expectType<PipedSubprocess["writable"]>().is<FileSink>();
tsd.expectType<WritableSubprocess["writable"]>().is<FileSink>();
tsd.expectType<NullSubprocess["writable"]>().is<undefined>();
tsd.expectType<ReadableSubprocess["readable"]>().is<ReadableStream<Uint8Array<ArrayBuffer>>>();

tsd.expectAssignable<PipedSubprocess>(Bun.spawn([], { stdio: ["pipe", "pipe", "pipe"] }));
tsd.expectAssignable<ReadableSubprocess>(Bun.spawn([], { stdio: ["ignore", "pipe", "pipe"] }));
tsd.expectAssignable<ReadableSubprocess>(Bun.spawn([], { stdio: ["pipe", "pipe", "pipe"] }));
Expand Down
Loading