Files
bun.sh/test/regression/issue/246-child_process_object_assign_compatibility.test.ts
robobun 40b310c208 Fix child_process stdio properties not enumerable for Object.assign() compatibility (#22322)
## Summary

Fixes compatibility issue with Node.js libraries that use
`Object.assign(promise, childProcess)` pattern, specifically `tinyspawn`
(used by `youtube-dl-exec`).

## Problem

In Node.js, child process stdio properties (`stdin`, `stdout`, `stderr`,
`stdio`) are enumerable own properties that can be copied by
`Object.assign()`. In Bun, they were non-enumerable getters on the
prototype, causing `Object.assign()` to fail copying them.

This broke libraries like:
- `tinyspawn` - uses `Object.assign(promise, childProcess)` to merge
properties
- `youtube-dl-exec` - depends on tinyspawn internally

## Solution

Make stdio properties enumerable own properties during spawn while
preserving:
-  Lazy initialization (streams created only when accessed)
-  Original getter functionality and caching
-  Performance (minimal overhead)

## Testing

- Added comprehensive regression tests
- Verified compatibility with `tinyspawn` and `youtube-dl-exec`
- Existing child_process tests still pass

## Related

- Fixes: https://github.com/microlinkhq/youtube-dl-exec/issues/246

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-09-06 01:40:36 -07:00

64 lines
2.4 KiB
TypeScript

// Regression test for https://github.com/microlinkhq/youtube-dl-exec/issues/246
// Child process stdio properties should be enumerable for Object.assign() compatibility
import { expect, test } from "bun:test";
import { spawn } from "child_process";
test("child process stdio properties should be enumerable for Object.assign()", () => {
const child = spawn(process.execPath, ["-e", 'console.log("hello")']);
// The real issue: stdio properties must be enumerable for Object.assign() to work
// This is what libraries like tinyspawn depend on
expect(Object.keys(child)).toContain("stdin");
expect(Object.keys(child)).toContain("stdout");
expect(Object.keys(child)).toContain("stderr");
expect(Object.keys(child)).toContain("stdio");
// Property descriptors should show enumerable: true
for (const key of ["stdin", "stdout", "stderr", "stdio"] as const) {
expect(Object.getOwnPropertyDescriptor(child, key)?.enumerable).toBe(true);
}
});
test("Object.assign should copy child process stdio properties", () => {
const child = spawn(process.execPath, ["-e", 'console.log("hello")']);
// This is what tinyspawn does: Object.assign(promise, childProcess)
const merged = {};
Object.assign(merged, child);
// The merged object should have the stdio properties
expect(merged.stdout).toBeTruthy();
expect(merged.stderr).toBeTruthy();
expect(merged.stdin).toBeTruthy();
expect(merged.stdio).toBeTruthy();
// Should maintain stream functionality
expect(typeof merged.stdout.pipe).toBe("function");
expect(typeof merged.stdout.on).toBe("function");
});
test("tinyspawn-like library usage should work", () => {
// Simulate the exact pattern from tinyspawn library
let childProcess;
const promise = new Promise(resolve => {
childProcess = spawn(process.execPath, ["-e", 'console.log("test")']);
childProcess.on("exit", () => resolve(childProcess));
});
// This is the critical line that was failing in Bun
const subprocess = Object.assign(promise, childProcess);
// Should have stdio properties immediately after Object.assign
expect(subprocess.stdout).toBeTruthy();
expect(subprocess.stderr).toBeTruthy();
expect(subprocess.stdin).toBeTruthy();
// Should still be a Promise
expect(subprocess instanceof Promise).toBe(true);
// Should have stream methods available
expect(typeof subprocess.stdout.pipe).toBe("function");
expect(typeof subprocess.stdout.on).toBe("function");
});