Repository: denoland/deno
Pull request: fix(node/process): make `process.stdin.isTTY` writable

## Summary

In Node.js, `tty.ReadStream` sets `isTTY` as a simple instance property (`this.isTTY = true`), making it naturally writable. Deno's polyfill used a getter-only property definition, preventing assignment.

Some npm packages need to override `isTTY` at runtime. For example, Playwright's test server sets `process.stdin.isTTY = undefined` to prevent reporters from blocking on user input during UI mode (microsoft/playwright#37867).

This aligns stdin with the stdout/stderr fix from #26130, ensuring all stdio streams behave consistently with Node.js.

## Changes

  - Modified `ext/node/polyfills/_process/streams.mjs` to add a setter to `stdin.isTTY`
  - Added test in `tests/unit_node/process_test.ts`
  

## Refs
- https://github.com/nodejs/node/blob/6a1a3ba045ad7d8d46ca443c5bb73b172d6828c3/lib/tty.js#L71
- https://github.com/microsoft/playwright/blob/3a5a32d26da3c4da1bd45b71c8b9864e1ea480c9/packages/playwright/src/runner/testServer.ts#L257-L266


<!--
Before submitting a PR, please read https://docs.deno.com/runtime/manual/references/contributing

1. Give the PR a descriptive title.

  Examples of good title:
    - fix(std/http): Fix race condition in server
    - docs(console): Update docstrings
    - feat(doc): Handle nested reexports

  Examples of bad title:
    - fix #7123
    - update docs
    - fix bugs

2. Ensure there is a related issue and it is referenced in the PR text.
3. Ensure there are tests that cover the changes.
4. Ensure `cargo test` passes.
5. Ensure `./tools/format.js` passes without changing files.
6. Ensure `./tools/lint.js` passes.
7. Open as a draft PR if your work is still in progress. The CI won't run
   all steps, but you can add '[ci]' to a commit message to force it to.
8. If you would like to run the benchmarks on the CI, add the 'ci-bench' label.
-->


Changed files:
filename: ext/node/polyfills/_process/streams.mjs
patch: @@ -258,12 +258,18 @@ export const initStdin = (warmup = false) => {
   // so that the process can close down.
   stdin.on("pause", () => nextTick(onpause));
 
+  // Allow users to overwrite isTTY for test isolation and terminal mocking.
+  // This mirrors the stdout/stderr behavior added in #26130.
+  let getStdinIsTTY = () => io.stdin?.isTerminal();
   ObjectDefineProperty(stdin, "isTTY", {
     __proto__: null,
     enumerable: true,
     configurable: true,
     get() {
-      return io.stdin.isTerminal();
+      return getStdinIsTTY();
+    },
+    set(value) {
+      getStdinIsTTY = () => value;
     },
   });
   stdin._isRawMode = false;

filename: tests/unit_node/process_test.ts
patch: @@ -544,7 +544,19 @@ Deno.test({
   fn() {
     // @ts-ignore `Deno.stdin.rid` was soft-removed in Deno 2.
     assertEquals(process.stdin.fd, Deno.stdin.rid);
-    assertEquals(process.stdin.isTTY, Deno.stdin.isTerminal());
+    const isTTY = Deno.stdin.isTerminal();
+    assertEquals(process.stdin.isTTY, isTTY);
+
+    // Allows overwriting `process.stdin.isTTY` (mirrors stdout/stderr from #26130)
+    const original = process.stdin.isTTY;
+    try {
+      // @ts-ignore isTTY is defined as readonly in types but we allow setting it
+      process.stdin.isTTY = !isTTY;
+      assertEquals(process.stdin.isTTY, !isTTY);
+    } finally {
+      // @ts-ignore isTTY is defined as readonly in types but we allow setting it
+      process.stdin.isTTY = original;
+    }
   },
 });