Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
22 changes: 15 additions & 7 deletions packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8157,19 +8157,27 @@ declare module "bun" {
type WritableSubprocess = Subprocess<"pipe", any, any>;
/** Utility type for any process from {@link Bun.spawn()} with stdin, stdout, stderr all set to `"pipe"`. A combination of {@link ReadableSubprocess} and {@link WritableSubprocess} */
type PipedSubprocess = Subprocess<"pipe", "pipe", "pipe">;
/** Utility type for any process from {@link Bun.spawn()} with stdin, stdout, stderr all set to `null` or similar. */
/**
* Utility type for any process from {@link Bun.spawn()} with stdin, stdout, stderr all set to `"ignore"`, `"inherit"`
* or `null`, so none of them is exposed on the process.
*
* An `undefined` option means the slot's default. That qualifies for stdin (`"ignore"`) and stderr (`"inherit"`),
* but not for stdout, whose default is `"pipe"`.
*/
type NullSubprocess = Subprocess<
"ignore" | "inherit" | null | undefined,
"ignore" | "inherit" | null | undefined,
"ignore" | "inherit" | null,
"ignore" | "inherit" | null | undefined
>;
/** Utility type for any process from {@link Bun.spawnSync()} with both stdout and stderr set to `"pipe"` */
type ReadableSyncSubprocess = SyncSubprocess<"pipe", "pipe">;
/** Utility type for any process from {@link Bun.spawnSync()} with both stdout and stderr set to `null` or similar */
type NullSyncSubprocess = SyncSubprocess<
"ignore" | "inherit" | null | undefined,
"ignore" | "inherit" | null | undefined
>;
/**
* Utility type for any process from {@link Bun.spawnSync()} with both stdout and stderr set to `"ignore"`, `"inherit"`
* or `null`, so neither is captured.
*
* An `undefined` option does not qualify: {@link Bun.spawnSync()} defaults both slots to `"pipe"`.
*/
type NullSyncSubprocess = SyncSubprocess<"ignore" | "inherit" | null, "ignore" | "inherit" | null>;

/**
* Options for creating a pseudo-terminal (PTY).
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
17 changes: 17 additions & 0 deletions test/integration/bun-types/fixture/spawn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,23 @@ tsd.expectAssignable<WritableSubprocess>(Bun.spawn([], { stdio: ["pipe", "pipe",
tsd.expectAssignable<WritableSubprocess>(Bun.spawn([], { stdio: ["pipe", "ignore", "inherit"] }));
tsd.expectAssignable<NullSubprocess>(Bun.spawn([], { stdio: ["ignore", "inherit", "ignore"] }));
tsd.expectAssignable<NullSubprocess>(Bun.spawn([], { stdio: [null, null, null] }));
// A Null* process exposes none of its stdio slots. An undefined option means the slot's default: "ignore"
// for Bun.spawn's stdin and "inherit" for its stderr, but "pipe" for Bun.spawn's stdout and for both
// Bun.spawnSync slots. So the aliases accept undefined in Bun.spawn's stdin and stderr slots only.
tsd.expectType<NullSubprocess["stdin"]>().is<undefined>();
tsd.expectType<NullSubprocess["stdout"]>().is<undefined>();
tsd.expectType<Bun.NullSyncSubprocess["stdout"]>().is<undefined>();
tsd.expectType<Bun.NullSyncSubprocess["stderr"]>().is<undefined>();
tsd.expectAssignable<NullSubprocess>(Bun.spawn([], { stdio: [undefined, "ignore", undefined] }));
tsd.expectAssignable<NullSubprocess>(Bun.spawn([], { stdout: "ignore" }));
// @ts-expect-error stdout: undefined means "pipe", so this process has a stdout stream
tsd.expectAssignable<NullSubprocess>(Bun.spawn([], { stdio: ["ignore", undefined, "ignore"] }));
tsd.expectAssignable<Bun.NullSyncSubprocess>(Bun.spawnSync([], { stdio: ["ignore", "ignore", "inherit"] }));
tsd.expectAssignable<Bun.NullSyncSubprocess>(Bun.spawnSync([], { stdio: [null, null, null] }));
// @ts-expect-error stdout: undefined means "pipe", so this process has a stdout Buffer
tsd.expectAssignable<Bun.NullSyncSubprocess>(Bun.spawnSync([], { stdio: ["ignore", undefined, "ignore"] }));
// @ts-expect-error stderr: undefined means "pipe" in spawnSync, so this process has a stderr Buffer
tsd.expectAssignable<Bun.NullSyncSubprocess>(Bun.spawnSync([], { stdio: ["ignore", "ignore", undefined] }));

tsd.expectAssignable<SyncSubprocess<Bun.SpawnOptions.Readable, Bun.SpawnOptions.Readable>>(Bun.spawnSync([], {}));

Expand Down
Loading