diff --git a/packages/runtime-tests/integration/pty/helpers/force-tty.cjs b/packages/runtime-tests/integration/pty/helpers/force-tty.cjs new file mode 100644 index 0000000..c2fdf97 --- /dev/null +++ b/packages/runtime-tests/integration/pty/helpers/force-tty.cjs @@ -0,0 +1,6 @@ +// Preload script: make stdout/stderr look like TTY for child_process.spawn +// This allows subprocess fixture tests to run without node-pty. +Object.defineProperty(process.stdout, "isTTY", { value: true, writable: true }); +Object.defineProperty(process.stderr, "isTTY", { value: true, writable: true }); +if (!process.stdout.columns) process.stdout.columns = 100; +if (!process.stdout.rows) process.stdout.rows = 24; diff --git a/packages/runtime-tests/integration/pty/helpers/run.ts b/packages/runtime-tests/integration/pty/helpers/run.ts index 44e88ea..45a805d 100644 --- a/packages/runtime-tests/integration/pty/helpers/run.ts +++ b/packages/runtime-tests/integration/pty/helpers/run.ts @@ -1,11 +1,8 @@ import process from "node:process"; -import { createRequire } from "node:module"; +import { spawn } from "node:child_process"; import path from "node:path"; import url from "node:url"; -const require = createRequire(import.meta.url); -const { spawn } = require("node-pty") as typeof import("node-pty"); - const fixturesDir = url.fileURLToPath(new URL("../fixtures", import.meta.url)); type RunProps = { env?: Record; columns?: number }; @@ -19,25 +16,38 @@ export const run = async (fixture: string, props?: RunProps): Promise => ...props?.env, }; + if (props?.columns !== undefined) { + env["COLUMNS"] = String(props.columns); + } + return new Promise((resolve, reject) => { - const term = spawn("node", ["--import=tsx", path.join(fixturesDir, `${fixture}.tsx`)], { - name: "xterm-color", - cols: props?.columns ?? 100, + const forceTty = url.fileURLToPath(new URL("./force-tty.cjs", import.meta.url)); + const child = spawn("node", ["--require", forceTty, "--import=tsx", path.join(fixturesDir, `${fixture}.tsx`)], { cwd: fixturesDir, env, + stdio: ["pipe", "pipe", "pipe"], }); let output = ""; - term.onData((data) => { - output += data; + child.stdout!.on("data", (data: Buffer) => { + output += data.toString(); }); - term.onExit(({ exitCode }) => { + child.stderr!.on("data", (data: Buffer) => { + const text = data.toString(); + // Filter Vue slot warnings from fixtures using tsx jsx transform + if (!text.includes("[Vue warn]: Non-function value encountered for default slot")) { + output += text; + } + }); + child.on("exit", (exitCode) => { if (exitCode === 0) { resolve(output); return; } - - reject(new Error(`Process exited with non-zero code: ${exitCode}`)); + reject(new Error(`Process exited with non-zero code: ${exitCode}\n${output}`)); + }); + child.on("error", (err) => { + reject(err); }); }); }; diff --git a/packages/runtime-tests/integration/pty/helpers/term.ts b/packages/runtime-tests/integration/pty/helpers/term.ts index d178d5e..4363376 100644 --- a/packages/runtime-tests/integration/pty/helpers/term.ts +++ b/packages/runtime-tests/integration/pty/helpers/term.ts @@ -1,17 +1,13 @@ import process from "node:process"; -import { createRequire } from "node:module"; +import { spawn } from "node:child_process"; import path from "node:path"; import url from "node:url"; -const require = createRequire(import.meta.url); -const { spawn } = require("node-pty") as typeof import("node-pty"); - const fixturesDir = url.fileURLToPath(new URL("../fixtures", import.meta.url)); const term = (fixture: string, args: string[] = []) => { let resolve: (value?: unknown) => void; let reject: (error?: Error) => void; - const exitPromise = new Promise((res, rej) => { resolve = res; reject = rej; @@ -29,40 +25,49 @@ const term = (fixture: string, args: string[] = []) => { FORCE_COLOR: "3", }; - const ps = spawn("node", ["--import=tsx", path.join(fixturesDir, `${fixture}.tsx`), ...args], { - name: "xterm-color", - cols: 100, - cwd: fixturesDir, - env, - }); + const forceTty = url.fileURLToPath(new URL("./force-tty.cjs", import.meta.url)); + const child = spawn( + "node", + ["--require", forceTty, "--import=tsx", path.join(fixturesDir, `${fixture}.tsx`), ...args], + { cwd: fixturesDir, env, stdio: ["pipe", "pipe", "pipe"] }, + ); const result = { write(input: string) { void readyPromise.then(() => { - ps.write(input); + child.stdin!.write(input); }); }, output: "", waitForExit: async () => exitPromise, }; - ps.onData((data) => { - result.output += data; - + child.stdout!.on("data", (data: Buffer) => { + result.output += data.toString(); if (result.output.includes("__READY__")) { readyResolve(); } }); - ps.onExit(({ exitCode }) => { + child.stderr!.on("data", (data: Buffer) => { + const text = data.toString(); + if (!text.includes("[Vue warn]: Non-function value encountered for default slot")) { + result.output += text; + } + }); + + child.on("exit", (exitCode) => { if (exitCode === 0) { resolve(); return; } - reject(new Error(`Process exited with non-zero exit code: ${exitCode}`)); }); + child.on("error", (err) => { + reject(err); + }); + return result; }; diff --git a/packages/runtime/src/render.ts b/packages/runtime/src/render.ts index ceb3caa..40eaf73 100644 --- a/packages/runtime/src/render.ts +++ b/packages/runtime/src/render.ts @@ -862,13 +862,13 @@ function createStdinController( let localRefs = 0; - return { + const controller: StdinController = { stdin, setRawMode(mode: boolean) { if (mode) { - this.acquireRawMode(); + controller.acquireRawMode(); } else { - this.releaseRawMode(); + controller.releaseRawMode(); } }, isRawModeSupported: appCtx.isRawModeSupported, @@ -944,4 +944,6 @@ function createStdinController( } }, }; + + return controller; }