@redue/cli 0.1.0-alpha.12 → 0.1.0-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,318 @@
1
+ // Preload for a recorded test run: NODE_OPTIONS="--require <this>" and
2
+ // REDUE_RUN_RECORD naming the run's guard file. Every Node process and worker
3
+ // thread of the run loads it. It never changes an answer, an output or the
4
+ // exit code. It appends, one JSON line at a time, what the guard needs
5
+ // (test-direct.mjs): content reads and writes of checkout files outside
6
+ // node_modules, lookups outside the checkout and the temporary directory,
7
+ // direct reads of environment variables, network use, and the child
8
+ // processes and threads it started. A line is written when its event happens,
9
+ // so a terminated thread loses nothing. A failure here only makes the record
10
+ // unusable.
11
+ 'use strict';
12
+ const fs=require('node:fs'),path=require('node:path'),{createHash}=require('node:crypto');
13
+ const configFile=process.env.REDUE_RUN_RECORD;
14
+ if(configFile)install();
15
+ function install(){
16
+ const original={openSync:fs.openSync,writeSync:fs.writeSync,readFileSync:fs.readFileSync,
17
+ statSync:fs.statSync,readdirSync:fs.readdirSync,real:fs.realpathSync.native};
18
+ const {threadId}=require('node:worker_threads');
19
+ let config,fd;
20
+ try{config=JSON.parse(original.readFileSync(configFile,'utf8'));
21
+ fd=original.openSync(path.join(config.dir,`${process.pid}-${threadId}.jsonl`),'a',0o600);}
22
+ catch{return;}
23
+ const emit=row=>{try{const line=Buffer.from(JSON.stringify(row)+'\n');
24
+ for(let at=0;at<line.length;)at+=original.writeSync(fd,line,at,line.length-at);}catch{}};
25
+ const failed=reason=>emit(['U',reason]);
26
+ emit(['H',{pid:process.pid,ppid:process.ppid,threadId}]);
27
+ const sep=path.sep,root=config.root,installed=path.join(root,'node_modules'),
28
+ tmp=config.tmp;
29
+ const within=(file,dir)=>file===dir||file.startsWith(dir+sep);
30
+ const digest=value=>createHash('sha256').update(value).digest('hex');
31
+ // Directory realpaths, so a checkout path spelled through a link is still
32
+ // classified as the checkout.
33
+ const realDirs=new Map();
34
+ function realDir(dir){
35
+ let value=realDirs.get(dir);
36
+ if(value===undefined){
37
+ try{value=original.real(dir);}
38
+ catch{const parent=path.dirname(dir);value=parent===dir?dir:path.join(realDir(parent),path.basename(dir));}
39
+ realDirs.set(dir,value);
40
+ }
41
+ return value;
42
+ }
43
+ // The absolute path an argument names; undefined for a descriptor, whose
44
+ // open was recorded; null for anything else.
45
+ function located(file){
46
+ if(typeof file==='number')return undefined;
47
+ if(Buffer.isBuffer(file))file=file.toString();
48
+ else if(file instanceof URL){if(file.protocol!=='file:')return null;file=require('node:url').fileURLToPath(file);}
49
+ if(typeof file!=='string')return null;
50
+ const full=path.resolve(file);
51
+ return within(full,root)?full:path.join(realDir(path.dirname(full)),path.basename(full));
52
+ }
53
+ const seen=new Set();
54
+ const once=row=>{const key=JSON.stringify(row);if(seen.has(key))return;seen.add(key);emit(row);};
55
+ // One answer per question and process: asked first, answered once.
56
+ const asked=new Set(),first=key=>{if(asked.has(key))return false;asked.add(key);return true;};
57
+ const missing=error=>['ENOENT','ENOTDIR'].includes(error?.code)?null:'error';
58
+ const kindOf=st=>st.isFile()?'f':st.isDirectory()?'d':'o';
59
+ function statKind(full){try{return kindOf(original.statSync(full));}catch(e){return missing(e);}}
60
+ // A stat-like answer as a kind, following links as fileExists does.
61
+ const answerKind=(full,result)=>result===false?null:result&&typeof result.isFile==='function'&&!result.isSymbolicLink()?
62
+ kindOf(result):statKind(full);
63
+ // A listing as queryAnswer's `entries` computes it.
64
+ function entries(full){
65
+ try{return original.readdirSync(full,{withFileTypes:true})
66
+ .map(entry=>[entry.name,entry.isDirectory()?'d':entry.isSymbolicLink()?'l':'f']).sort();}
67
+ catch(e){return missing(e);}
68
+ }
69
+ // Paths this process created from nothing: files it truncated or created,
70
+ // directories it made. Reading them back reads the run's own output.
71
+ // Appends, renames and links keep earlier content, so they are not here.
72
+ const produced=new Set();
73
+ function isProduced(full){
74
+ if(!produced.size)return false;
75
+ for(let at=full;;at=path.dirname(at)){if(produced.has(at))return true;if(path.dirname(at)===at)return false;}
76
+ }
77
+ const runnerData=config.runnerData||[];
78
+ const runnerCache=full=>tmp.some(dir=>within(full,dir)&&full.slice(dir.length+1).split(sep)[0].startsWith('jest_'))||
79
+ runnerData.some(dir=>within(full,dir));
80
+ // One filesystem access by its role: read (content), produce, touch (a write
81
+ // that keeps earlier content), mkdir, mkdtemp, list, stat or realpath.
82
+ function access(role,file,error=null,result=undefined,recursive=false){
83
+ try{
84
+ const full=located(file);
85
+ if(full===undefined)return;
86
+ if(full===null){if(!['produce','touch','mkdir'].includes(role))failed(`${role} of an unsupported path argument`);return;}
87
+ // Installed files are observed by identity.
88
+ if(within(full,installed))return;
89
+ if(role==='produce'){if(!error)produced.add(full);if(!error&&within(full,root))once(['P',rel(full)]);return;}
90
+ // A directory the run created held nothing before it, so everything in
91
+ // it is the run's output, whichever process reads it.
92
+ if(role==='mkdir'||role==='mkdtemp'){
93
+ const made=error?null:typeof result==='string'?located(result):role==='mkdir'&&!recursive?full:null;
94
+ if(made){produced.add(made);once(['M',made]);}
95
+ return;
96
+ }
97
+ if(role==='touch')return;
98
+ // This recorder is REDUE's own file; stack-trace printers read it.
99
+ if(full===__filename||isProduced(full))return;
100
+ if(!first(role+'\0'+full))return;
101
+ if(within(full,root)){
102
+ if(role==='read')emit(['R',rel(full),!error]);
103
+ else if(role==='stat')emit(['L',rel(full),error?missing(error):answerKind(full,result)]);
104
+ else if(role==='list')emit(['D',rel(full),error?missing(error):entries(full)]);
105
+ return;
106
+ }
107
+ if(runnerCache(full))return;
108
+ // A small file read outside the checkout is kept by its content, as
109
+ // queryAnswer's readFile hashes it, so status can ask again.
110
+ if(role==='read'&&!error){
111
+ let answer='present';
112
+ try{if(original.statSync(full).size<=1024*1024)answer={content:digest(JSON.stringify(original.readFileSync(full,'utf8')))};}catch{}
113
+ once(['O','read',full,answer]);return;
114
+ }
115
+ if(role==='read'||role==='list'){once(['O',role,full,error?missing(error):'present']);return;}
116
+ if(role==='realpath'){once(['O','realpath',full,error?missing(error):String(result)]);return;}
117
+ if(error){once(['O','stat',full,missing(error)]);return;}
118
+ // A link outside the checkout is kept as its resolution.
119
+ if(result?.isSymbolicLink?.()){let real=null;try{real=original.real(full);}catch(e){real=missing(e);}
120
+ once(['O','realpath',full,real]);return;}
121
+ once(['O','stat',full,answerKind(full,result)]);
122
+ }catch{failed('recorder failure');}
123
+ }
124
+ const rel=full=>path.relative(root,full).split(sep).join('/');
125
+ // Roles of an open by its flags.
126
+ function openRoles(flags){
127
+ if(typeof flags==='number'){
128
+ const {O_RDONLY,O_RDWR,O_TRUNC,O_CREAT,O_EXCL}=fs.constants,access=flags&3;
129
+ if(access===O_RDONLY)return ['read'];
130
+ if(flags&O_TRUNC||flags&O_CREAT&&flags&O_EXCL)return ['produce'];
131
+ return access===O_RDWR?['read','touch']:['touch'];
132
+ }
133
+ if(flags===undefined||flags===null)return ['read'];
134
+ if(typeof flags!=='string')return ['read','touch'];
135
+ if(flags.startsWith('r'))return flags.includes('+')?['read','touch']:['read'];
136
+ if(flags.startsWith('w'))return ['produce'];
137
+ return flags.includes('+')?['read','touch']:['touch'];
138
+ }
139
+ const flagOf=options=>typeof options==='object'&&options!==null?options.flag??options.flags:undefined;
140
+ const opened=args=>openRoles(args[1]).map(role=>[role,args[0]]);
141
+ const written=args=>openRoles(flagOf(args[2])??'w').map(role=>[role,args[0]]);
142
+ const appended=args=>openRoles(flagOf(args[2])??'a').map(role=>[role,args[0]]);
143
+ const copied=args=>[['read',args[0]],['produce',args[1]]];
144
+ const moved=args=>[['touch',args[1]]];
145
+ // Wraps a synchronous, callback or promise API without changing what it does.
146
+ function wrap(holder,name,roles,style){
147
+ const call=holder[name];if(typeof call!=='function')return;
148
+ const pick=args=>typeof roles==='function'?roles(args):roles.map(role=>[role,args[0]]);
149
+ const recursive=args=>name.startsWith('mkdir')&&Boolean(args[1]?.recursive);
150
+ const note=(args,error,result)=>{for(const [role,file] of pick(args))access(role,file,error,result,recursive(args));};
151
+ let wrapped;
152
+ if(style==='sync')wrapped=function(...args){
153
+ let result;try{result=call.apply(this,args);}catch(error){note(args,error);throw error;}
154
+ note(args,null,result);return result;};
155
+ else if(style==='callback')wrapped=function(...args){
156
+ const at=args.findLastIndex(value=>typeof value==='function');
157
+ if(at<0)return call.apply(this,args);
158
+ const done=args[at],given=args.slice(0,at);
159
+ args[at]=name==='exists'?function(value){note(given,null,value);return done.apply(this,arguments);}:
160
+ function(error,result){note(given,error,result);return done.apply(this,arguments);};
161
+ return call.apply(this,args);};
162
+ else wrapped=function(...args){
163
+ return call.apply(this,args).then(result=>{note(args,null,result);return result;},error=>{note(args,error);throw error;});};
164
+ Object.defineProperties(wrapped,Object.getOwnPropertyDescriptors(call));
165
+ holder[name]=wrapped;
166
+ }
167
+ const apis=[
168
+ ['readFile',['read']],['open',opened],['readlink',['read']],['copyFile',copied],['cp',copied],
169
+ ['writeFile',written],['appendFile',appended],['mkdir',['mkdir']],['mkdtemp',['mkdtemp']],
170
+ ['rename',moved],['symlink',moved],['link',moved],['truncate',['touch']],
171
+ ['stat',['stat']],['lstat',['stat']],['access',['stat']],['readdir',['list']],['opendir',['list']],
172
+ ['realpath',['realpath']],
173
+ ];
174
+ for(const [name,roles] of apis){
175
+ wrap(fs,name+'Sync',roles,'sync');wrap(fs,name,roles,'callback');wrap(fs.promises,name,roles,'promise');
176
+ }
177
+ wrap(fs,'existsSync',['stat'],'sync');wrap(fs,'exists',['stat'],'callback');
178
+ wrap(fs.realpathSync,'native',['realpath'],'sync');wrap(fs.realpath,'native',['realpath'],'callback');
179
+ for(const [name,roles] of [['createReadStream',()=>['read']],['createWriteStream',options=>openRoles(flagOf(options)??'w')]]){
180
+ const call=fs[name];
181
+ fs[name]=Object.defineProperties(function(file,options,...rest){
182
+ for(const role of roles(options))access(role,file);return call.call(this,file,options,...rest);},
183
+ Object.getOwnPropertyDescriptors(call));
184
+ }
185
+ // Environment: direct reads, compared with the caller's environment by
186
+ // digest. A read that copies the whole environment (a spread, or a child
187
+ // process's environment) is an enumeration, not a read of each name.
188
+ const caller=config.env,envSeen=new Set();
189
+ // A copy lists the keys, then asks for each key's descriptor and value right
190
+ // after (a spread, Object.assign). Only that pair is not a read. Node's own
191
+ // modules (child_process, worker_threads, util) list the keys, then read
192
+ // every value; there each listed key's first read is part of the copy.
193
+ let enumeration=null;
194
+ function nodeInternal(){
195
+ const prepare=Error.prepareStackTrace,holder={};
196
+ try{Error.prepareStackTrace=(_,sites)=>sites;Error.captureStackTrace(holder,nodeInternal);
197
+ for(const site of holder.stack){const file=site.getFileName?.();
198
+ if(file&&file!==__filename)return file.startsWith('node:');}
199
+ return false;}
200
+ catch{return false;}
201
+ finally{Error.prepareStackTrace=prepare;}
202
+ }
203
+ function envRead(source,key){
204
+ if(typeof key!=='string'||envSeen.has(key))return;
205
+ try{
206
+ // The caller's value as a digest, or null when the caller left it unset.
207
+ if(!Object.hasOwn(caller,key)){envSeen.add(key);emit(['E',key,null]);return;}
208
+ const value=source[key];
209
+ // A value the run set itself is not the caller's.
210
+ if(typeof value!=='string'||digest(value)!==caller[key])return;
211
+ envSeen.add(key);emit(['E',key,caller[key]]);
212
+ }catch{failed('recorder failure');}
213
+ }
214
+ const recordingEnv=source=>new Proxy(source,{
215
+ ownKeys(t){const keys=Reflect.ownKeys(t),current={keys:new Set(keys),last:null,copy:nodeInternal()};enumeration=current;
216
+ queueMicrotask(()=>{if(enumeration===current)enumeration=null;});return keys;},
217
+ getOwnPropertyDescriptor(t,key){
218
+ if(enumeration?.copy&&enumeration.keys.has(key)){}
219
+ else if(enumeration?.keys.delete(key))enumeration.last=key;else{if(enumeration)enumeration.last=null;envRead(t,key);}
220
+ return Reflect.getOwnPropertyDescriptor(t,key);},
221
+ get(t,key){
222
+ if(enumeration?.copy&&enumeration.keys.delete(key)){}
223
+ else if(enumeration&&enumeration.last===key)enumeration.last=null;else{if(enumeration)enumeration.last=null;envRead(t,key);}
224
+ return Reflect.get(t,key);},
225
+ has(t,key){if(enumeration)enumeration.last=null;envRead(t,key);return Reflect.has(t,key);},
226
+ set(t,key,value){t[key]=value;return true;},
227
+ });
228
+ function wrapEnv(holder){
229
+ let current=recordingEnv(holder.env);
230
+ // Code that replaces the environment object keeps being recorded.
231
+ try{Object.defineProperty(holder,'env',{configurable:true,enumerable:true,get(){return current;},
232
+ set(value){current=value&&typeof value==='object'?recordingEnv(value):value;}});}
233
+ catch{failed('environment not recordable');}
234
+ }
235
+ wrapEnv(process);
236
+ // Jest 29 gives each test file a copy of process.env (jest-util's
237
+ // createProcessObject). Test code reads that copy, so it is recorded too.
238
+ const Module=require('node:module'),compile=Module.prototype._compile;
239
+ Module.prototype._compile=function(content,filename,...rest){
240
+ const result=compile.call(this,content,filename,...rest);
241
+ try{if(typeof filename==='string'&&filename.split(sep).join('/').endsWith('/jest-util/build/createProcessObject.js')&&
242
+ typeof this.exports?.default==='function'){
243
+ const create=this.exports.default;
244
+ this.exports.default=function(...args){const created=create.apply(this,args);
245
+ try{wrapEnv(created);}catch{failed('environment not recordable');}
246
+ return created;};
247
+ }}catch{failed('recorder failure');}
248
+ return result;
249
+ };
250
+ // Network: connections other than loopback or local sockets, and DNS
251
+ // questions about anything but localhost.
252
+ const loopback=host=>{const value=String(host).replace(/^\[|\]$/g,'').toLowerCase();
253
+ return value==='localhost'||value.endsWith('.localhost')||/^127\./.test(value)||value==='::1'||
254
+ value==='0.0.0.0'||value==='::'||/^::ffff:127\./.test(value);};
255
+ const localSocket=file=>{const full=located(file);
256
+ return typeof full==='string'&&(within(full,root)||tmp.some(dir=>within(full,dir)));};
257
+ const net=require('node:net'),connect=net.Socket.prototype.connect;
258
+ net.Socket.prototype.connect=function(...args){
259
+ try{
260
+ let options=args[0];
261
+ if(Array.isArray(options))options=options[0];
262
+ else if(typeof options==='number'||typeof options==='string'&&/^\d+$/.test(options))
263
+ options={port:options,host:typeof args[1]==='string'?args[1]:undefined};
264
+ else if(typeof options==='string')options={path:options};
265
+ if(options?.path!=null){if(!localSocket(options.path))emit(['N',`socket ${String(options.path)}`]);}
266
+ else if(options&&!loopback(options.host??'localhost'))emit(['N',`${options.host}:${options.port}`]);
267
+ }catch{failed('recorder failure');}
268
+ return connect.apply(this,args);
269
+ };
270
+ const dgram=require('node:dgram');
271
+ for(const name of ['send','connect']){
272
+ const call=dgram.Socket.prototype[name];
273
+ dgram.Socket.prototype[name]=function(...args){
274
+ try{const host=args.find((value,i)=>i>0&&typeof value==='string');
275
+ if(host===undefined?name==='connect':!loopback(host))emit(['N',`udp ${host??'default address'}`]);}
276
+ catch{failed('recorder failure');}
277
+ return call.apply(this,args);};
278
+ }
279
+ const dns=require('node:dns');
280
+ const questions=['lookup','lookupService','resolve','resolve4','resolve6','resolveAny','resolveCaa','resolveCname',
281
+ 'resolveMx','resolveNaptr','resolveNs','resolvePtr','resolveSoa','resolveSrv','resolveTxt','reverse'];
282
+ for(const holder of [dns,dns.promises,dns.Resolver.prototype,dns.promises.Resolver.prototype])
283
+ for(const name of questions){
284
+ const call=holder[name];if(typeof call!=='function')continue;
285
+ holder[name]=function(host,...rest){
286
+ try{if(!loopback(host))emit(['N',`dns ${String(host)}`]);}catch{failed('recorder failure');}
287
+ return call.call(this,host,...rest);};
288
+ }
289
+ // Child processes and threads must load this recorder too; the guard checks
290
+ // for their headers. Children of execSync/execFileSync report no PID, so
291
+ // they are counted instead.
292
+ const childProcess=require('node:child_process'),spawn=childProcess.ChildProcess.prototype.spawn;
293
+ childProcess.ChildProcess.prototype.spawn=function(options){
294
+ const result=spawn.call(this,options);
295
+ try{if(this.pid)emit(['C',this.pid,String(options?.file)]);}catch{failed('recorder failure');}
296
+ return result;
297
+ };
298
+ const spawnSync=childProcess.spawnSync;
299
+ childProcess.spawnSync=function(file,...rest){
300
+ const result=spawnSync.call(this,file,...rest);
301
+ try{if(result?.pid)emit(['C',result.pid,String(file)]);}catch{failed('recorder failure');}
302
+ return result;
303
+ };
304
+ for(const name of ['execSync','execFileSync']){
305
+ const call=childProcess[name];
306
+ childProcess[name]=function(file,...rest){
307
+ let started=true;
308
+ try{return call.call(this,file,...rest);}
309
+ catch(error){if(error?.code==='ENOENT'&&!error.pid)started=false;throw error;}
310
+ finally{if(started)emit(['S',String(file)]);}
311
+ };
312
+ }
313
+ const workers=require('node:worker_threads'),Worker=workers.Worker;
314
+ workers.Worker=class extends Worker{
315
+ constructor(...args){super(...args);try{emit(['T',this.threadId]);}catch{failed('recorder failure');}}
316
+ };
317
+ try{Module.syncBuiltinESMExports();}catch{failed('recorder failure');}
318
+ }
@@ -3,6 +3,7 @@ import fs from 'node:fs';import path from 'node:path';import {createHash} from '
3
3
  import {createRequire} from 'node:module';import {spawnSync} from 'node:child_process';
4
4
  import {compilerFiles} from './typescript-list.mjs';
5
5
  import {buildInfoFor} from './direct-typescript.mjs';
6
+ import {probeTimeLeft} from './probe-deadline.mjs';
6
7
  import {withinPath as within,realObservedPath} from './path-identity.mjs';
7
8
  import {timing} from './decision-profile.mjs';
8
9
  const require=createRequire(import.meta.url),hash=value=>createHash('sha256').update(value).digest('hex');
@@ -45,7 +46,7 @@ export function typeScriptInputFacts(root,tsRoot,tsc){
45
46
  if(/"(?:extends|references|plugins|incremental|composite)"\s*:/.test(raw))
46
47
  fail('typescript-config-graph-unsupported');
47
48
  const shown=spawnSync(process.execPath,[tsc,'--showConfig'],{cwd:root,
48
- env:process.env,encoding:'utf8',timeout:4000,maxBuffer:16*1024*1024,
49
+ env:process.env,encoding:'utf8',timeout:probeTimeLeft(4000),maxBuffer:16*1024*1024,
49
50
  stdio:['ignore','pipe','pipe']});
50
51
  if(shown.error||shown.signal||shown.status!==0)fail('typescript-config-unavailable');
51
52
  const effective=JSON.parse(shown.stdout);
@@ -59,7 +60,7 @@ export function typeScriptInputFacts(root,tsRoot,tsc){
59
60
  const listingStarted=performance.now();
60
61
  const captured=process.argv.includes('--redue-checkpoint')?compilerFiles(tsRoot,root):null;
61
62
  const listed=captured?{status:0,stdout:captured.files.join('\n')}:spawnSync(process.execPath,[tsc,'--noEmit','--listFilesOnly'],
62
- {cwd:root,env:process.env,encoding:'utf8',timeout:process.platform==='win32'?10000:4000,
63
+ {cwd:root,env:process.env,encoding:'utf8',timeout:probeTimeLeft(process.platform==='win32'?10000:4000),
63
64
  maxBuffer:16*1024*1024,
64
65
  stdio:['ignore','pipe','pipe']});
65
66
  timing('typescript_contract.list_files',listingStarted,{status:listed.status??null,
@@ -44,11 +44,12 @@ export function queryAnswer(kind,file,options) {
44
44
  export function runFactChange(facts) {
45
45
  if(!Array.isArray(facts))return 'an unreadable run fact';
46
46
  for(const fact of facts){
47
- if(!Array.isArray(fact)||!['fileExists','directoryExists','realpath','readFile'].includes(fact[0])||
47
+ if(!Array.isArray(fact)||!['fileExists','directoryExists','realpath','readFile','entries'].includes(fact[0])||
48
48
  typeof fact[1]!=='string')return 'an unreadable run fact';
49
49
  const [kind,file,answer]=fact;
50
50
  let now;try{now=queryAnswer(kind,file);}catch{return file;}
51
- if(now!==answer)return file;
51
+ // A test run's directory listings are arrays.
52
+ if(kind==='entries'?JSON.stringify(now)!==JSON.stringify(answer):now!==answer)return file;
52
53
  }
53
54
  return null;
54
55
  }
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // Read-only applicability probe. Its output is one digest or a fixed reason;
3
3
  // source, configuration, environment values, and command output are not logged.
4
+ import {probeTimeLeft} from './probe-deadline.mjs';
4
5
  import {compilerFiles} from './typescript-list.mjs';
5
6
  import {contextOnly,probeResult} from './probe-result.mjs';
6
7
  import fs from 'node:fs';
@@ -79,7 +80,7 @@ try{
79
80
  const tsc=yarnLocalTsc(root);
80
81
  const captured=process.argv.includes('--redue-checkpoint')?compilerFiles(path.join(root,'node_modules/typescript'),cwd):null;
81
82
  const listed=captured?{status:0,stdout:captured.files.join('\n')}:spawnSync(process.execPath,[tsc,'-p','.','--listFilesOnly'],
82
- {cwd,env:process.env,encoding:'utf8',timeout:4000,maxBuffer:16*1024*1024,
83
+ {cwd,env:process.env,encoding:'utf8',timeout:probeTimeLeft(4000),maxBuffer:16*1024*1024,
83
84
  stdio:['ignore','pipe','pipe']});
84
85
  if(listed.error||listed.signal||listed.status!==0)fail('typescript-input-list-unavailable');
85
86
  const inputs=listed.stdout.trim().split(/\r?\n/).filter(Boolean)