@@ -34,6 +34,76 @@ const request = (overrides = {}) => ({
3434 ...overrides ,
3535} ) ;
3636
37+ // oxlint-disable-next-line t3code/no-global-process-runtime -- Exercises native console handles in the standalone Windows helper.
38+ test . skipIf ( process . platform !== "win32" ) (
39+ "interactive children receive console input with read and write access" ,
40+ async ( ) => {
41+ const directory = await NodeFSP . mkdtemp ( NodePath . join ( NodeOS . tmpdir ( ) , "t3-console-access-" ) ) ;
42+ const powershell = NodePath . join (
43+ process . env . SystemRoot ,
44+ "System32/WindowsPowerShell/v1.0/powershell.exe" ,
45+ ) ;
46+ const encode = ( script ) => Buffer . from ( script , "utf16le" ) . toString ( "base64" ) ;
47+ const checkAccess = encode ( `
48+ $ErrorActionPreference = 'Stop'
49+ Add-Type @'
50+ using System;
51+ using System.Runtime.InteropServices;
52+ public static class InputAccess {
53+ [DllImport("kernel32.dll")] public static extern IntPtr GetStdHandle(int id);
54+ [DllImport("kernel32.dll", SetLastError=true)] public static extern bool GetConsoleMode(IntPtr handle, out uint mode);
55+ [DllImport("kernel32.dll", SetLastError=true)] public static extern bool FlushConsoleInputBuffer(IntPtr handle);
56+ }
57+ '@
58+ $handle = [InputAccess]::GetStdHandle(-10)
59+ [uint32]$mode = 0
60+ if (-not [InputAccess]::GetConsoleMode($handle, [ref]$mode)) { throw 'Console read access is missing' }
61+ if (-not [InputAccess]::FlushConsoleInputBuffer($handle)) { throw 'Console write access is missing' }
62+ Write-Output 'console-read-write-ok'
63+ ` ) ;
64+ // Allocate a private console so the access check never flushes a user's input.
65+ const launch = encode ( `
66+ $ErrorActionPreference = 'Stop'
67+ Add-Type @'
68+ using System.Runtime.InteropServices;
69+ public static class PrivateConsole {
70+ [DllImport("kernel32.dll")] public static extern bool FreeConsole();
71+ [DllImport("kernel32.dll")] public static extern bool AllocConsole();
72+ }
73+ '@
74+ [PrivateConsole]::FreeConsole() | Out-Null
75+ if (-not [PrivateConsole]::AllocConsole()) { throw 'Could not allocate test console' }
76+ try {
77+ $child = Start-Process -FilePath $env:T3_TEST_CONSOLE_RUNTIME -ArgumentList ('"' + $env:T3_TEST_CONSOLE_SCRIPT + '"') -NoNewWindow -Wait -PassThru
78+ exit $child.ExitCode
79+ } finally { [PrivateConsole]::FreeConsole() | Out-Null }
80+ ` ) ;
81+ try {
82+ const script = NodePath . join ( directory , "check.mjs" ) ;
83+ const transport = new URL ( "transport.mjs" , import . meta. url ) . href ;
84+ await NodeFSP . writeFile (
85+ script ,
86+ `import { run } from ${ JSON . stringify ( transport ) } ;
87+ console.log(await run(${ JSON . stringify ( powershell ) } , ['-NoProfile', '-EncodedCommand', ${ JSON . stringify ( checkAccess ) } ], { capture: true, timeout: 15000 }));
88+ ` ,
89+ ) ;
90+ const output = await run ( powershell , [ "-NoProfile" , "-EncodedCommand" , launch ] , {
91+ input : "" ,
92+ capture : true ,
93+ timeout : 30000 ,
94+ env : {
95+ ...process . env ,
96+ T3_TEST_CONSOLE_RUNTIME : process . execPath ,
97+ T3_TEST_CONSOLE_SCRIPT : script ,
98+ } ,
99+ } ) ;
100+ NodeAssert . match ( output , / c o n s o l e - r e a d - w r i t e - o k / u) ;
101+ } finally {
102+ await NodeFSP . rm ( directory , { recursive : true , force : true } ) ;
103+ }
104+ } ,
105+ ) ;
106+
37107test ( "target bytes remain data across token and Windows Terminal grammar" , ( ) => {
38108 for ( const name of [
39109 "spaces here" ,
0 commit comments