@redue/cli 0.1.0-alpha.4 → 0.1.0-alpha.5

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,34 @@
1
+ // Shared project selection only; evidence and caller-context semantics stay in the kernel.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+ import {createHash} from 'node:crypto';
6
+ import {realObservedPath} from './path-identity.mjs';
7
+ import {stateIdentity,userStateBase} from './platform-state.mjs';
8
+
9
+ export function discoverConfig(cwd=process.cwd()) {
10
+ let dir=path.resolve(cwd);const found=[];
11
+ for(;;){
12
+ for(const name of ['redue.config.json','vstate.config.json']) {
13
+ const file=path.join(dir,name);
14
+ if(fs.existsSync(file))found.push(file);
15
+ }
16
+ if(fs.existsSync(path.join(dir,'.git'))||path.dirname(dir)===dir)break;
17
+ dir=path.dirname(dir);
18
+ }
19
+ if(found.length>1)throw Error('multiple REDUE configurations apply; select the intended one with --config FILE');
20
+ return found[0]||path.resolve(cwd,'redue.config.json');
21
+ }
22
+ export function canonicalConfig(configFile) {
23
+ return path.join(realObservedPath(path.dirname(configFile)),path.basename(configFile));
24
+ }
25
+ export function projectState(root,configFile) {
26
+ return path.join(userStateBase(process.platform,process.env,os.homedir()),'vstate',
27
+ `vstate-${createHash('sha256').update(stateIdentity(root)+'\0'+stateIdentity(canonicalConfig(configFile))).digest('hex').slice(0,16)}`);
28
+ }
29
+ export function readProject(configFile) {
30
+ const config=JSON.parse(fs.readFileSync(configFile,'utf8'));
31
+ if(config.schema!==1||!Array.isArray(config.checks)||!config.checks.length)
32
+ throw Error('config needs schema 1 and at least one check');
33
+ return {config,root:realObservedPath(path.resolve(path.dirname(configFile),config.root||'.'))};
34
+ }
@@ -0,0 +1,65 @@
1
+ // Extracted from the existing npm TypeScript probe; shared compiler input logic.
2
+ import fs from 'node:fs';import path from 'node:path';import {createHash} from 'node:crypto';
3
+ import {createRequire} from 'node:module';import {spawnSync} from 'node:child_process';
4
+ import {compilerFiles} from './typescript-list.mjs';
5
+ import {withinPath as within,realObservedPath} from './path-identity.mjs';
6
+ import {timing} from './decision-profile.mjs';
7
+ const require=createRequire(import.meta.url),hash=value=>createHash('sha256').update(value).digest('hex');
8
+ const digestFile=file=>hash(fs.readFileSync(file));
9
+ const fail=code=>{throw Object.assign(Error(code),{code});};
10
+ export function typeScriptInputFacts(root,tsRoot,tsc){
11
+ const configReads=new Set(),visited=new Set();
12
+ let ts;try{ts=require(tsRoot);}catch{}
13
+ if(typeof ts?.getParsedCommandLineOfConfigFile==='function'){
14
+ const host={...ts.sys,readFile(file){configReads.add(path.resolve(file));
15
+ return ts.sys.readFile(file);},onUnRecoverableConfigFileDiagnostic(){}};
16
+ const parseConfig=file=>{
17
+ const absolute=path.resolve(file);
18
+ if(!within(absolute,root))fail('typescript-config-outside-repository');
19
+ if(visited.has(absolute))return;visited.add(absolute);
20
+ if(visited.size>64)fail('typescript-config-graph-too-large');
21
+ const parsed=ts.getParsedCommandLineOfConfigFile(absolute,{},host);
22
+ if(!parsed||parsed.errors?.length)fail('typescript-config-unavailable');
23
+ if(parsed.options?.plugins?.length)fail('typescript-plugin-unsupported');
24
+ if(parsed.options?.incremental||parsed.options?.composite)
25
+ fail('typescript-incremental-unsupported');
26
+ for(const reference of parsed.projectReferences||[])
27
+ parseConfig(reference.path.endsWith('.json')?reference.path:
28
+ path.join(reference.path,'tsconfig.json'));
29
+ };
30
+ parseConfig(path.join(root,'tsconfig.json'));
31
+ }else{
32
+ // TypeScript 7's native compiler does not expose the previous config API.
33
+ // Its CLI reports the effective config; restrict this path to one direct
34
+ // config rather than guessing about extends/reference provenance.
35
+ const config=path.join(root,'tsconfig.json'),raw=fs.readFileSync(config,'utf8');
36
+ if(/"(?:extends|references|plugins|incremental|composite)"\s*:/.test(raw))
37
+ fail('typescript-config-graph-unsupported');
38
+ const shown=spawnSync(process.execPath,[tsc,'--showConfig'],{cwd:root,
39
+ env:process.env,encoding:'utf8',timeout:4000,maxBuffer:16*1024*1024,
40
+ stdio:['ignore','pipe','pipe']});
41
+ if(shown.error||shown.signal||shown.status!==0)fail('typescript-config-unavailable');
42
+ const effective=JSON.parse(shown.stdout);
43
+ if(effective.compilerOptions?.plugins?.length||
44
+ effective.compilerOptions?.incremental||effective.compilerOptions?.composite)
45
+ fail('typescript-config-graph-unsupported');
46
+ configReads.add(config);
47
+ }
48
+ for(const file of configReads)if(!within(realObservedPath(file),root))
49
+ fail('typescript-config-outside-repository');
50
+ const listingStarted=performance.now();
51
+ const captured=process.argv.includes('--redue-checkpoint')?compilerFiles(tsRoot,root):null;
52
+ const listed=captured?{status:0,stdout:captured.files.join('\n')}:spawnSync(process.execPath,[tsc,'--noEmit','--listFilesOnly'],
53
+ {cwd:root,env:process.env,encoding:'utf8',timeout:process.platform==='win32'?10000:4000,
54
+ maxBuffer:16*1024*1024,
55
+ stdio:['ignore','pipe','pipe']});
56
+ timing('typescript_contract.list_files',listingStarted,{status:listed.status??null,
57
+ error:listed.error?.code||null});
58
+ if(listed.error||listed.signal||listed.status!==0)fail('typescript-input-list-unavailable');
59
+ const files=listed.stdout.trim().split(/\r?\n/).filter(Boolean)
60
+ .map(file=>path.resolve(file));
61
+ if(!files.length||files.some(file=>!within(realObservedPath(file),root)))
62
+ fail('typescript-input-outside-repository');
63
+ return {configs:[...configReads].sort().map(file=>[hash(file),digestFile(file)]),
64
+ files:[...new Set(files)].sort().map(file=>[hash(file),digestFile(file)]),queries:captured?.queries};
65
+ }
@@ -1,3 +1,4 @@
1
+ import {directProject,directQualification,directEnvironment} from './direct-typescript.mjs';
1
2
  import fs from 'node:fs';
2
3
  import path from 'node:path';
3
4
  import {realObservedPath} from './path-identity.mjs';
@@ -6,6 +7,25 @@ import {realObservedPath} from './path-identity.mjs';
6
7
  // The read-only runtime probe separately checks the effective TypeScript file
7
8
  // list, config graph, npm toolchain and caller context on every sync/run.
8
9
  export function qualifyTypeScript(root,selected,discovered={}) {
10
+ if(selected.qualification===directQualification){
11
+ const issues=[];
12
+ try{
13
+ const compiler=directProject(root,selected.script);
14
+ if(selected.command?.length!==3||selected.command[0]!==process.execPath||
15
+ selected.command[1]!==compiler.entry||selected.command[2]!=='--noEmit'||
16
+ selected.cwd&&selected.cwd!=='.')issues.push('direct compiler invocation changed');
17
+ }catch(e){issues.push(e.code||'direct compiler prerequisites unavailable');}
18
+ if(!selected.inputs?.includes('**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs,json,jsonc}')||
19
+ !selected.inputs?.includes('package.json')||!selected.inputs?.includes('tsconfig.json')||
20
+ !selected.installedInputs?.includes('node_modules/**'))issues.push('direct compiler source/config/installed inputs incomplete');
21
+ if(!selected.environment?.executableIdentity||
22
+ !directEnvironment.variables.every(key=>selected.environment?.variables?.includes(key))||
23
+ !directEnvironment.prefixes.every(key=>selected.environment?.prefixes?.includes(key)))
24
+ issues.push('direct compiler execution context incomplete');
25
+ if(selected.probes?.length!==1||!selected.probes?.some(argv=>path.basename(argv[1]||'')==='direct-typescript-probe.mjs'&&
26
+ argv[0]===process.execPath&&argv[2]===root&&argv[3]===selected.script))issues.push('direct compiler probe missing');
27
+ return {qualified:issues.length===0,issues};
28
+ }
9
29
  if(selected.qualification==='pnpm-tsc-v1'){
10
30
  const issues=[];
11
31
  const workspace=selected.workspace||'.';