@redue/cli 0.1.0-alpha.21 → 0.1.0-alpha.23

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.
@@ -15,7 +15,7 @@ if(configFile)install();
15
15
  function install(){
16
16
  const original={openSync:fs.openSync,writeSync:fs.writeSync,readFileSync:fs.readFileSync,
17
17
  statSync:fs.statSync,readdirSync:fs.readdirSync,real:fs.realpathSync.native};
18
- const {threadId}=require('node:worker_threads');
18
+ const {threadId,isInternalThread}=require('node:worker_threads');
19
19
  let config,fd;
20
20
  try{config=JSON.parse(original.readFileSync(configFile,'utf8'));
21
21
  fd=original.openSync(path.join(config.dir,`${process.pid}-${threadId}.jsonl`),'a',0o600);}
@@ -25,7 +25,10 @@ function install(){
25
25
  const failed=reason=>emit(['U',reason]);
26
26
  // The main script tells the guard which program a process runs (a package
27
27
  // manager may start helpers of its own).
28
- emit(['H',{pid:process.pid,ppid:process.ppid,threadId,main:typeof process.argv[1]==='string'?process.argv[1]:null}]);
28
+ // Node's own threads (the ESM hooks thread) load it too; the guard does not
29
+ // count them as threads the run started.
30
+ emit(['H',{pid:process.pid,ppid:process.ppid,threadId,main:typeof process.argv[1]==='string'?process.argv[1]:null,
31
+ ...(isInternalThread===true?{internal:true}:{})}]);
29
32
  // Every node_modules observed as installed: the root's and each workspace package's.
30
33
  const sep=path.sep,root=config.root,installed=Array.isArray(config.installed)?config.installed:[path.join(root,'node_modules')],
31
34
  tmp=config.tmp;
@@ -395,6 +398,32 @@ function install(){
395
398
  catch{failed('recorder failure');}}
396
399
  };
397
400
  }
401
+ // process.execve replaces this process with another program, under the
402
+ // same PID: the row comes first, since a successful call does not return
403
+ // (a failed one aborts the process). Node refuses it in a worker and for
404
+ // bad arguments by throwing; the row is then withdrawn. The kernel does not
405
+ // search PATH, so the file is taken from the working directory. A Node
406
+ // program that replaces this one loads the recorder again and appends a
407
+ // header to the same record.
408
+ if(typeof process.execve==='function'){const execve=process.execve;
409
+ process.execve=function(file,args,env,...rest){
410
+ let row=false;
411
+ if(threadId===0)try{const options={env:env??process.env},full=path.resolve(String(file)),where_=where(options);
412
+ emit(['X',full,argsOf(Array.isArray(args)?args.slice(1):[]),...where_,scriptOf(full,where_[0],null)]);row=true;}
413
+ catch{failed('recorder failure');}
414
+ try{return execve.call(this,file,args,env,...rest);}
415
+ catch(error){if(row)emit(['Y']);throw error;}};}
416
+ // Bindings that read files, use the network or start programs out of the
417
+ // recorder's sight. Only bindings without such access are allowed.
418
+ const harmlessBindings=new Set(['async_wrap','buffer','config','constants','contextify','crypto','http_parser','icu',
419
+ 'js_stream','natives','os','signal_wrap','stream_wrap','tty_wrap','url','util','uv','v8','zlib']);
420
+ if(typeof process.binding==='function'){const binding=process.binding;
421
+ process.binding=function(name,...rest){
422
+ const result=binding.call(this,name,...rest);
423
+ if(!harmlessBindings.has(name))failed(`process.binding('${String(name)}') reads files, uses the network or starts programs out of REDUE's sight`);
424
+ return result;};}
425
+ // Node's internal modules include child_process's unhooked internals.
426
+ if(process.execArgv.some(arg=>/^--expose[-_]internals(=|$)/.test(arg)))failed('a Node process exposed its internal modules (--expose-internals)');
398
427
  const workers=require('node:worker_threads'),Worker=workers.Worker;
399
428
  workers.Worker=class extends Worker{
400
429
  constructor(...args){super(...args);try{emit(['T',this.threadId]);}catch{failed('recorder failure');}}
@@ -63,6 +63,14 @@ export function queriesMatch(queries) {
63
63
  return queries.every(([kind,file,answer,options])=>
64
64
  JSON.stringify(queryAnswer(kind,file,options))===JSON.stringify(answer));
65
65
  }
66
+ // Why a recipe built on TypeScript's compiler API cannot use the TypeScript
67
+ // installed at `tsRoot`, or null: TypeScript 7 is a native compiler and
68
+ // ships no such API.
69
+ export function compilerApiIssue(tsRoot) {
70
+ let version=null;try{version=JSON.parse(fs.readFileSync(path.join(tsRoot,'package.json'),'utf8')).version;}catch{}
71
+ return Number(String(version).split('.')[0])>=7?
72
+ `TypeScript ${version} is a native compiler without the compiler API this recipe reads; REDUE does not support TypeScript 7 yet`:null;
73
+ }
66
74
  export function compilerFiles(tsRoot,cwd,config='tsconfig.json') {
67
75
  const require=createRequire(import.meta.url),ts=require(tsRoot);
68
76
  if(typeof ts.createProgram!=='function'||typeof ts.createCompilerHost!=='function')return null;
@@ -2,6 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import {execFileSync} from 'node:child_process';
4
4
  import {createRequire} from 'node:module';
5
+ import {compilerApiIssue} from './typescript-list.mjs';
5
6
  import micromatch from './glob.mjs';
6
7
  import {withinPath as within,realObservedPath} from './path-identity.mjs';
7
8
 
@@ -58,6 +59,8 @@ export function yarnWorkspacePackages(root){
58
59
  }
59
60
 
60
61
  function parsedTypeScript(root,dir){
62
+ const apiIssue=compilerApiIssue(path.join(root,'node_modules','typescript'));
63
+ if(apiIssue)throw Error(apiIssue);
61
64
  const require=createRequire(path.join(root,'package.json'));
62
65
  const ts=require(path.join(root,'node_modules','typescript'));
63
66
  const config=path.join(root,dir,'tsconfig.json'),reads=new Set();