@redue/cli 0.1.0-alpha.16 → 0.1.0-alpha.18
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.
- package/CHANGELOG.md +97 -0
- package/README.md +1 -1
- package/bin/redue.mjs +25 -14
- package/docs/user/agents.md +11 -0
- package/docs/user/configuration.md +4 -0
- package/docs/user/direct-typescript.md +62 -5
- package/docs/user/pilot.md +6 -5
- package/docs/user/supported-projects.md +29 -7
- package/docs/user/tests-direct.md +28 -4
- package/package.json +2 -2
- package/src/agent-integration.mjs +2 -2
- package/src/claude-hook.mjs +100 -27
- package/src/cli.mjs +9 -4
- package/src/daemon.mjs +21 -7
- package/src/declared-plan.mjs +11 -4
- package/src/direct-typescript-probe.mjs +5 -3
- package/src/direct-typescript.mjs +58 -11
- package/src/glob.mjs +8 -0
- package/src/index.mjs +29 -12
- package/src/installed-inputs.mjs +4 -0
- package/src/node-onboarding.mjs +90 -47
- package/src/npm-integrity.mjs +124 -0
- package/src/npmrc.mjs +16 -0
- package/src/plan-worker.mjs +4 -1
- package/src/pnpm-inputs.mjs +111 -52
- package/src/pnpm-typecheck-probe.mjs +11 -6
- package/src/test-direct.mjs +198 -36
- package/src/test-recorder.cjs +3 -2
- package/src/typescript-input-facts.mjs +7 -7
- package/src/typescript-invocation.mjs +92 -0
- package/src/typescript-list.mjs +2 -2
- package/src/typescript-qualification.mjs +8 -9
- package/src/workspace-manifests.mjs +22 -0
package/src/pnpm-inputs.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// pnpm 9-12 node-modules installation provider. The engine sees only
|
|
2
2
|
// repository-relative input sets and resolution relationships, never pnpm
|
|
3
3
|
// virtual-store names as semantic identities.
|
|
4
4
|
import {probeTimeLeft} from './probe-deadline.mjs';
|
|
@@ -7,16 +7,18 @@ import fs from 'node:fs';
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import {createRequire} from 'node:module';
|
|
9
9
|
import {execFileSync,spawnSync} from 'node:child_process';
|
|
10
|
-
import micromatch from './glob.mjs';
|
|
10
|
+
import micromatch,{patternMatcher} from './glob.mjs';
|
|
11
11
|
import YAML from 'yaml';
|
|
12
12
|
import {resolveLinks} from './installed-inputs.mjs';
|
|
13
13
|
import {withinPath as within,realObservedPath,samePath} from './path-identity.mjs';
|
|
14
14
|
import {timing} from './decision-profile.mjs';
|
|
15
|
+
import {npmrc} from './npmrc.mjs';
|
|
16
|
+
import {runConfig,tscRun,tscRuns} from './typescript-invocation.mjs';
|
|
15
17
|
|
|
16
18
|
const rel=(root,file)=>path.relative(root,file).split(path.sep).join('/');
|
|
17
19
|
const read=file=>JSON.parse(fs.readFileSync(file,'utf8'));
|
|
18
20
|
const exists=file=>{try{fs.lstatSync(file);return true;}catch{return false;}};
|
|
19
|
-
function yaml(file){
|
|
21
|
+
export function yaml(file){
|
|
20
22
|
const doc=YAML.parseDocument(fs.readFileSync(file,'utf8'),{uniqueKeys:true});
|
|
21
23
|
if(doc.errors.length)throw Error(`${path.basename(file)} cannot be interpreted safely`);
|
|
22
24
|
return doc.toJS()||{};
|
|
@@ -27,40 +29,67 @@ function safePatterns(values){
|
|
|
27
29
|
throw Error('pnpm workspace patterns are unsupported');
|
|
28
30
|
return values;
|
|
29
31
|
}
|
|
32
|
+
// The supported pnpm majors. pnpm 9 reads settings from .npmrc, pnpm 10 from
|
|
33
|
+
// pnpm-workspace.yaml first and then .npmrc, pnpm 11 and 12 from
|
|
34
|
+
// pnpm-workspace.yaml only.
|
|
35
|
+
export const pnpmMajors=[9,10,11,12];
|
|
36
|
+
// Settings that move or copy installed files, or run code during install,
|
|
37
|
+
// where the contract does not look; by pnpm-workspace.yaml and .npmrc name.
|
|
38
|
+
// Other settings change only what gets installed, and the installed files are
|
|
39
|
+
// what the contract observes. These are refused when either file sets them,
|
|
40
|
+
// whichever file the installed pnpm reads. Only the project's files are read:
|
|
41
|
+
// user or global rc files and npm_config_* variables at install time are not.
|
|
42
|
+
const refused=[['modulesDir','modules-dir'],['virtualStoreDir','virtual-store-dir'],['lockfileDir','lockfile-dir'],
|
|
43
|
+
['enableGlobalVirtualStore','enable-global-virtual-store'],['injectWorkspacePackages','inject-workspace-packages'],
|
|
44
|
+
['sharedWorkspaceLockfile','shared-workspace-lockfile'],['pnpmfile','pnpmfile'],['globalPnpmfile','global-pnpmfile'],
|
|
45
|
+
['configDependencies','config-dependencies']];
|
|
46
|
+
// Whether a refused setting is set: present at all, except that only
|
|
47
|
+
// sharedWorkspaceLockfile=false and injectWorkspacePackages=true move files.
|
|
48
|
+
const setting=(key,value)=>value!==undefined&&
|
|
49
|
+
(key==='sharedWorkspaceLockfile'?value===false:key==='injectWorkspacePackages'?value===true:true);
|
|
30
50
|
export function pnpmProjectInfo(root){
|
|
31
51
|
root=realObservedPath(root);
|
|
32
52
|
const pkg=read(path.join(root,'package.json'));
|
|
33
|
-
|
|
34
|
-
|
|
53
|
+
// A pin is optional; when present it names pnpm 9-12, and the install must match it.
|
|
54
|
+
const pin=pkg.packageManager===undefined?null:/^pnpm@((\d+)\.\d+\.\d+)(?:\+[^\s]+)?$/.exec(pkg.packageManager);
|
|
55
|
+
if(pkg.packageManager!==undefined&&(!pin||!pnpmMajors.includes(Number(pin[2]))))
|
|
56
|
+
throw Error(`packageManager ${JSON.stringify(String(pkg.packageManager).split('+')[0])} is outside pnpm ${pnpmMajors[0]}-${pnpmMajors.at(-1)}`);
|
|
35
57
|
const workspaceFile=path.join(root,'pnpm-workspace.yaml');
|
|
36
|
-
const
|
|
37
|
-
const patterns=safePatterns(
|
|
38
|
-
|
|
39
|
-
|
|
58
|
+
const workspace=exists(workspaceFile)?yaml(workspaceFile):{},rc=npmrc(path.join(root,'.npmrc'));
|
|
59
|
+
const patterns=safePatterns(workspace.packages||[]);
|
|
60
|
+
// The linker the given pnpm major reads; without a major, the one any reads.
|
|
61
|
+
const linkerFor=major=>(major===9?rc['node-linker']:major>=11?workspace.nodeLinker:
|
|
62
|
+
workspace.nodeLinker??rc['node-linker'])||'isolated';
|
|
63
|
+
const major=pin?Number(pin[2]):null;
|
|
64
|
+
return {version:pin?.[1]??null,linker:linkerFor(major),linkerFor,patterns,settings:workspace,
|
|
65
|
+
refused:refused.filter(([key,name])=>setting(key,workspace[key])||setting(key,rc[name])).map(([key])=>key)};
|
|
40
66
|
}
|
|
41
67
|
export function pnpmInstallInfo(root){
|
|
42
68
|
const started=performance.now();
|
|
43
69
|
root=realObservedPath(root);
|
|
44
|
-
const
|
|
45
|
-
const {version,linker,patterns,settings}=pnpmProjectInfo(root);
|
|
70
|
+
const {version,linkerFor,patterns,settings,refused}=pnpmProjectInfo(root);
|
|
46
71
|
const workspaceFile=path.join(root,'pnpm-workspace.yaml');
|
|
47
|
-
if(
|
|
48
|
-
|
|
49
|
-
throw Error('custom pnpm modules or virtual-store location is recording-only');
|
|
50
|
-
if(settings.injectWorkspacePackages)
|
|
51
|
-
throw Error('injected workspace dependencies are recording-only');
|
|
52
|
-
if(settings.sharedWorkspaceLockfile===false)
|
|
53
|
-
throw Error('separate workspace lockfiles are recording-only');
|
|
54
|
-
if(exists(path.join(root,'.pnpmfile.cjs'))||exists(path.join(root,'.pnpmfile.mjs')))
|
|
72
|
+
if(refused.some(key=>['pnpmfile','globalPnpmfile','configDependencies'].includes(key))||
|
|
73
|
+
exists(path.join(root,'.pnpmfile.cjs'))||exists(path.join(root,'.pnpmfile.mjs')))
|
|
55
74
|
throw Error('custom pnpm installation hooks need review');
|
|
75
|
+
if(refused.some(key=>['modulesDir','virtualStoreDir','lockfileDir','enableGlobalVirtualStore'].includes(key)))
|
|
76
|
+
throw Error('custom pnpm modules or virtual-store location is recording-only');
|
|
77
|
+
if(refused.includes('injectWorkspacePackages'))throw Error('injected workspace dependencies are recording-only');
|
|
78
|
+
if(refused.includes('sharedWorkspaceLockfile'))throw Error('separate workspace lockfiles are recording-only');
|
|
56
79
|
const modulesFile=path.join(root,'node_modules','.modules.yaml');
|
|
57
80
|
if(!exists(modulesFile))throw Error('installed pnpm module metadata is missing');
|
|
58
81
|
const modules=yaml(modulesFile);
|
|
59
|
-
|
|
82
|
+
const installed=/^pnpm@((\d+)\.\d+\.\d+)$/.exec(String(modules.packageManager));
|
|
83
|
+
if(!installed||!pnpmMajors.includes(Number(installed[2])))
|
|
84
|
+
throw Error(`installed by ${JSON.stringify(String(modules.packageManager))}, outside pnpm ${pnpmMajors[0]}-${pnpmMajors.at(-1)}`);
|
|
85
|
+
const linker=linkerFor(Number(installed[2]));
|
|
86
|
+
if(!['isolated','hoisted'].includes(linker))throw Error(`pnpm ${linker} linker is recording-only`);
|
|
87
|
+
if(modules.nodeLinker!==linker||version!==null&&installed[1]!==version)
|
|
60
88
|
throw Error('installed pnpm linker or version differs from project configuration');
|
|
61
89
|
const virtualStore=path.join(root,'node_modules','.pnpm');
|
|
62
|
-
// pnpm
|
|
63
|
-
//
|
|
90
|
+
// pnpm can omit virtualStoreDir when using its project-local default
|
|
91
|
+
// (seen with pnpm 12 on Windows). Accept only the observed, ordinary local
|
|
92
|
+
// directory in that case.
|
|
64
93
|
const declared=modules.virtualStoreDir;
|
|
65
94
|
const selected=declared===undefined?virtualStore:
|
|
66
95
|
path.resolve(path.dirname(modulesFile),declared);
|
|
@@ -72,18 +101,8 @@ export function pnpmInstallInfo(root){
|
|
|
72
101
|
throw Error('nonstandard or incomplete project-local pnpm virtual store');
|
|
73
102
|
if(!exists(path.join(root,'pnpm-lock.yaml')))
|
|
74
103
|
throw Error('pnpm lockfile is missing');
|
|
75
|
-
if(exists(path.join(root,'.npmrc'))){
|
|
76
|
-
// pnpm 12 reads project settings from pnpm-workspace.yaml. Registry/auth
|
|
77
|
-
// entries affect acquisition, not a completed local TypeScript invocation.
|
|
78
|
-
for(const line of fs.readFileSync(path.join(root,'.npmrc'),'utf8').split(/\r?\n/)){
|
|
79
|
-
const trimmed=line.trim();if(!trimmed||/^[#;]/.test(trimmed))continue;
|
|
80
|
-
const key=trimmed.split('=')[0].trim();
|
|
81
|
-
if(key!=='registry'&&!/^\/\/[^\s]+\/:_authToken$/.test(key))
|
|
82
|
-
throw Error('project .npmrc has execution-relevant pnpm settings requiring review');
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
104
|
timing('pnpm.install_metadata',started);
|
|
86
|
-
return {version,linker,patterns,settings,modulesFile,
|
|
105
|
+
return {version:installed[1],linker,patterns,settings,modulesFile,
|
|
87
106
|
configurationInputs:['package.json','pnpm-lock.yaml',
|
|
88
107
|
...(exists(workspaceFile)?['pnpm-workspace.yaml']:[]),
|
|
89
108
|
...(exists(path.join(root,'.npmrc'))?['.npmrc']:[]),
|
|
@@ -173,16 +192,37 @@ function packageAt(root,requester,name){
|
|
|
173
192
|
}
|
|
174
193
|
return {physical:null,candidates};
|
|
175
194
|
}
|
|
176
|
-
|
|
177
|
-
|
|
195
|
+
// Files TypeScript may consume from its own include: each directory its
|
|
196
|
+
// include patterns watch for new files (TypeScript's own
|
|
197
|
+
// wildcardDirectories), and each file it lists. node_modules below them is
|
|
198
|
+
// left out, as TypeScript's wildcards leave it out; installed files a program
|
|
199
|
+
// reads are installed inputs. `exclude` entries stay inputs: an import can
|
|
200
|
+
// still reach an excluded file.
|
|
201
|
+
const sourceFiles='*.{ts,tsx,mts,cts,js,jsx,mjs,cjs,json}';
|
|
202
|
+
function sourcePatterns(root,parsed){
|
|
203
|
+
const patterns=new Set(),excluded=new Set();
|
|
204
|
+
for(const [folder,flags] of Object.entries(parsed.wildcardDirectories||{})){
|
|
205
|
+
// TypeScript 4 lowercases these keys on case-insensitive file systems;
|
|
206
|
+
// the native realpath gives the directory's own spelling.
|
|
207
|
+
let at;try{at=fs.realpathSync.native(folder);}catch{at=path.resolve(folder);}
|
|
208
|
+
if(!within(at,root))throw Error('TypeScript source membership escapes the checkout');
|
|
209
|
+
const prefix=rel(root,at)?rel(root,at)+'/':'';
|
|
210
|
+
if(flags&1){patterns.add(`${prefix}**/${sourceFiles}`);excluded.add(`!${prefix}**/node_modules/**`);}
|
|
211
|
+
else patterns.add(prefix+sourceFiles);
|
|
212
|
+
}
|
|
213
|
+
const included=patternMatcher([...patterns,...excluded]);
|
|
214
|
+
for(const file of parsed.fileNames.map(name=>path.resolve(name))){
|
|
215
|
+
if(!within(file,root))throw Error('TypeScript source membership escapes the checkout');
|
|
216
|
+
if(!included(rel(root,file)))patterns.add(rel(root,file));
|
|
217
|
+
}
|
|
218
|
+
return [...patterns,...excluded];
|
|
219
|
+
}
|
|
220
|
+
function parsedTypeScript(root,dir,ts,configName='tsconfig.json'){
|
|
221
|
+
const config=path.join(root,dir,configName),reads=new Set();
|
|
178
222
|
const host={...ts.sys,readFile(file){reads.add(path.resolve(file));return ts.sys.readFile(file);},
|
|
179
223
|
onUnRecoverableConfigFileDiagnostic(){}};
|
|
180
224
|
const parsed=ts.getParsedCommandLineOfConfigFile(config,{},host);
|
|
181
225
|
if(!parsed||parsed.errors?.length)throw Error('TypeScript configuration unavailable');
|
|
182
|
-
const raw=ts.readConfigFile(config,ts.sys.readFile);
|
|
183
|
-
if(raw.error||!Array.isArray(raw.config?.include)||raw.config.include.length!==1||
|
|
184
|
-
!/^src(?:$|\/)/.test(raw.config.include[0]))
|
|
185
|
-
throw Error('TypeScript source file membership is outside the supported src include');
|
|
186
226
|
if(parsed.options.plugins?.length||parsed.options.incremental||parsed.options.composite||
|
|
187
227
|
parsed.projectReferences?.length||parsed.options.paths||parsed.options.baseUrl||
|
|
188
228
|
parsed.options.rootDirs?.length||parsed.options.typeRoots?.length||
|
|
@@ -194,12 +234,13 @@ function parsedTypeScript(root,dir,ts){
|
|
|
194
234
|
throw Error('TypeScript module resolver is outside the supported Node contract');
|
|
195
235
|
for(const file of reads)if(!within(realObservedPath(file),root))
|
|
196
236
|
throw Error('TypeScript configuration escapes the checkout');
|
|
197
|
-
return {parsed,configs:[...reads].map(file=>rel(root,file)).sort()
|
|
237
|
+
return {parsed,configs:[...reads].map(file=>rel(root,file)).sort(),
|
|
238
|
+
sourcePatterns:sourcePatterns(root,parsed)};
|
|
198
239
|
}
|
|
199
|
-
function listedTypeScriptFiles(root,selected,tsc){
|
|
240
|
+
function listedTypeScriptFiles(root,selected,tsc,configName){
|
|
200
241
|
// --listFilesOnly resolves the compiler's effective module/file set but
|
|
201
242
|
// performs no typecheck and emits no project outputs.
|
|
202
|
-
const out=spawnSync(process.execPath,[tsc,'-p',
|
|
243
|
+
const out=spawnSync(process.execPath,[tsc,'-p',configName,'--listFilesOnly'],
|
|
203
244
|
{cwd:path.join(root,selected.dir),env:process.env,encoding:'utf8',
|
|
204
245
|
// The native NTFS public workspace takes 3-5 s to list ~770 inputs;
|
|
205
246
|
// 5 s sometimes expired despite bounded, progressing compiler work.
|
|
@@ -236,17 +277,23 @@ function sharedHardlinkIn(folder){
|
|
|
236
277
|
}
|
|
237
278
|
return false;
|
|
238
279
|
}
|
|
239
|
-
|
|
280
|
+
// `run`: the check's {script, part, args}, or a script name for its only run.
|
|
281
|
+
export function pnpmTypecheckInputs(root,workspace,run,{captureQueries=false}={}){
|
|
240
282
|
const totalStarted=performance.now();
|
|
241
283
|
root=realObservedPath(root);
|
|
242
284
|
const install=pnpmInstallInfo(root),members=pnpmWorkspacePackages(root,install.patterns);
|
|
243
285
|
const selected=workspace==='.'?{name:'.',dir:'.',manifest:'package.json',
|
|
244
286
|
pkg:read(path.join(root,'package.json'))}:members.get(workspace);
|
|
245
287
|
if(!selected)throw Error(`workspace ${workspace} is not declared`);
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
288
|
+
// A script name stands for its only run.
|
|
289
|
+
if(typeof run==='string'){const runs=tscRuns(selected.pkg.scripts?.[run]);run={script:run,args:runs?.length===1?runs[0].args:[]};}
|
|
290
|
+
const {run:own,issue}=tscRun(selected.pkg.scripts,run);
|
|
291
|
+
if(issue)throw Error(`typecheck must be standalone TypeScript runs: ${issue}`);
|
|
292
|
+
// `tsc -p .` without --noEmit predates the other forms.
|
|
293
|
+
if(!own.noEmit&&!['-p .','-p tsconfig.json'].includes(run.args.join(' ')))
|
|
294
|
+
throw Error('typecheck must pass --noEmit');
|
|
295
|
+
const configName=runConfig(path.join(root,selected.dir),run.args);
|
|
296
|
+
if(!configName)throw Error('TypeScript project is outside the package');
|
|
250
297
|
const selectedDir=path.join(root,selected.dir),lookup=createRequire(path.join(selectedDir,'package.json'));
|
|
251
298
|
let tsPackage;try{tsPackage=realObservedPath(lookup.resolve('typescript/package.json'));}
|
|
252
299
|
catch{throw Error('installed TypeScript compiler cannot be resolved');}
|
|
@@ -255,7 +302,8 @@ export function pnpmTypecheckInputs(root,workspace,script,{captureQueries=false}
|
|
|
255
302
|
const tsc=path.join(tsRoot,'bin','tsc');
|
|
256
303
|
if(!exists(tsc))throw Error('installed TypeScript executable is missing');
|
|
257
304
|
const configStarted=performance.now();
|
|
258
|
-
const {configs}=parsedTypeScript(root,selected.dir,ts);
|
|
305
|
+
const {configs,sourcePatterns:sources}=parsedTypeScript(root,selected.dir,ts,configName);
|
|
306
|
+
const isSource=patternMatcher(sources);
|
|
259
307
|
timing('pnpm.ts_config',configStarted,{configs:configs.length});
|
|
260
308
|
const closure=new Map(),installed=new Set(),generated=new Set(),
|
|
261
309
|
links=new Set(),candidates=new Set(),absences=[],instances=new Map();
|
|
@@ -305,12 +353,12 @@ export function pnpmTypecheckInputs(root,workspace,script,{captureQueries=false}
|
|
|
305
353
|
resolutionMs:Math.round(resolutionMs),workspaceLinks:links.size,
|
|
306
354
|
candidates:candidates.size});
|
|
307
355
|
const listingStarted=performance.now();
|
|
308
|
-
const captured=captureQueries?compilerFiles(tsRoot,path.join(root,selected.dir)):null;
|
|
309
|
-
const files=captured?captured.files.map(realObservedPath):listedTypeScriptFiles(root,selected,tsc);
|
|
356
|
+
const captured=captureQueries?compilerFiles(tsRoot,path.join(root,selected.dir),configName):null;
|
|
357
|
+
const files=captured?captured.files.map(realObservedPath):listedTypeScriptFiles(root,selected,tsc,configName);
|
|
310
358
|
timing('pnpm.ts_input_listing',listingStarted,{files:files.length});
|
|
311
359
|
const mappingStarted=performance.now();
|
|
312
360
|
for(const file of files){
|
|
313
|
-
if(
|
|
361
|
+
if(isSource(rel(root,file))||
|
|
314
362
|
[...closure.values()].some(member=>within(file,path.join(root,member.dir))))continue;
|
|
315
363
|
const packageRoot=installedPackageRoot(root,file);
|
|
316
364
|
if(!packageRoot)throw Error(`TypeScript consumes a file outside the observed pnpm contract: ${rel(root,file)}`);
|
|
@@ -321,6 +369,16 @@ export function pnpmTypecheckInputs(root,workspace,script,{captureQueries=false}
|
|
|
321
369
|
}
|
|
322
370
|
timing('pnpm.input_mapping',mappingStarted,{instances:instances.size,
|
|
323
371
|
installedPatterns:installed.size});
|
|
372
|
+
// A config extended from an installed package is an installed input too:
|
|
373
|
+
// source patterns leave node_modules out.
|
|
374
|
+
for(const config of configs){
|
|
375
|
+
const file=realObservedPath(path.join(root,config));
|
|
376
|
+
if(!within(file,path.join(root,'node_modules')))continue;
|
|
377
|
+
const packageRoot=installedPackageRoot(root,file);
|
|
378
|
+
if(!packageRoot)throw Error(`TypeScript configuration ${config} is outside an installed package`);
|
|
379
|
+
installed.add(`${rel(root,packageRoot)}/**`);
|
|
380
|
+
installed.add(`!${rel(root,packageRoot)}/node_modules/**`);
|
|
381
|
+
}
|
|
324
382
|
installed.add(`${rel(root,tsRoot)}/**`);
|
|
325
383
|
installed.add(`!${rel(root,tsRoot)}/node_modules/**`);
|
|
326
384
|
if(!instances.has(tsRoot))instances.set(tsRoot,{name:'typescript',physicalRoot:rel(root,tsRoot)});
|
|
@@ -329,11 +387,12 @@ export function pnpmTypecheckInputs(root,workspace,script,{captureQueries=false}
|
|
|
329
387
|
timing('pnpm.hardlink_inspection',sharedStarted,{instances:instances.size,
|
|
330
388
|
shared:shared.length});
|
|
331
389
|
const source=[...new Set([...install.configurationInputs,selected.manifest,
|
|
332
|
-
...configs
|
|
390
|
+
...configs,...sources,
|
|
333
391
|
...closure.values().map(member=>member.manifest),...generated])];
|
|
334
392
|
timing('pnpm.total_discovery',totalStarted,{instances:instances.size,
|
|
335
393
|
inputFiles:files.length});
|
|
336
394
|
return {install,workspace:selected,closure:[...closure.values()],source,
|
|
395
|
+
sourcePatterns:sources,args:run.args,
|
|
337
396
|
installed:[...installed],generated:[...generated],
|
|
338
397
|
// The read-only state probe consumes this same resolved compiler file set.
|
|
339
398
|
// It is deliberately omitted from the persisted user-facing input plan.
|
|
@@ -7,14 +7,17 @@ import fs from 'node:fs';
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import {createHash} from 'node:crypto';
|
|
9
9
|
import {pnpmTypecheckInputs} from './pnpm-inputs.mjs';
|
|
10
|
+
import {patternMatcher} from './glob.mjs';
|
|
10
11
|
import {withinPath as within,realObservedPath} from './path-identity.mjs';
|
|
12
|
+
import {runFromArgv} from './typescript-invocation.mjs';
|
|
11
13
|
|
|
12
|
-
const [rootArg,workspace
|
|
14
|
+
const [rootArg,workspace,...rest]=process.argv.slice(2).filter(value=>!value.startsWith('--redue-'));
|
|
13
15
|
const hash=value=>createHash('sha256').update(value).digest('hex');
|
|
14
16
|
const digest=file=>hash(fs.readFileSync(file));
|
|
15
17
|
const fail=code=>{throw Object.assign(Error(code),{code});};
|
|
16
18
|
try{
|
|
17
|
-
if(!rootArg||!workspace
|
|
19
|
+
if(!rootArg||!workspace)fail('probe-arguments');
|
|
20
|
+
const run=runFromArgv(rest);
|
|
18
21
|
const root=realObservedPath(rootArg);
|
|
19
22
|
if(process.env.NODE_OPTIONS||process.env.NODE_PATH||process.env.BASH_ENV||
|
|
20
23
|
process.env.ENV||Object.entries(process.env).some(([key,value])=>{
|
|
@@ -24,23 +27,25 @@ try{
|
|
|
24
27
|
return /^(PNPM_|COREPACK_|npm_config_|DYLD_|TSGO_)/i.test(key);
|
|
25
28
|
}))
|
|
26
29
|
fail('pnpm-execution-environment-unsupported');
|
|
27
|
-
const contextHash=hash(JSON.stringify([root,workspace,
|
|
30
|
+
const contextHash=hash(JSON.stringify([root,workspace,run,process.version]));
|
|
28
31
|
if(contextOnly(contextHash))process.exit(0);
|
|
29
|
-
const contract=pnpmTypecheckInputs(root,workspace,
|
|
32
|
+
const contract=pnpmTypecheckInputs(root,workspace,run,{captureQueries:process.argv.includes('--redue-checkpoint')});
|
|
30
33
|
if(contract.limitations.length)fail('pnpm-input-coverage-unavailable');
|
|
31
34
|
const selected=path.join(root,contract.workspace.dir),tsc=contract.tsc;
|
|
32
35
|
const inputs=contract.listedFiles;
|
|
33
36
|
if(!inputs.length)fail('typescript-input-list-unavailable');
|
|
34
|
-
const inputRoots=[
|
|
37
|
+
const inputRoots=[...contract.generated.map(pattern=>
|
|
35
38
|
path.join(root,pattern.slice(0,-3))),...contract.installed.filter(p=>!p.startsWith('!'))
|
|
36
39
|
.map(pattern=>path.join(root,pattern.slice(0,-3)))];
|
|
40
|
+
const isSource=patternMatcher(contract.sourcePatterns);
|
|
37
41
|
for(const file of inputs)if(!within(file,root)||
|
|
42
|
+
!isSource(path.relative(root,file).split(path.sep).join('/'))&&
|
|
38
43
|
!inputRoots.some(boundary=>within(file,boundary)))
|
|
39
44
|
fail('typescript-input-outside-pnpm-contract');
|
|
40
45
|
const files=[...new Set(inputs)].sort().map(file=>
|
|
41
46
|
[hash(path.relative(root,file)),digest(file)]);
|
|
42
47
|
const configs=contract.configs.map(file=>[hash(file),digest(path.join(root,file))]);
|
|
43
|
-
probeResult(hash(JSON.stringify({schema:1,workspace,
|
|
48
|
+
probeResult(hash(JSON.stringify({schema:1,workspace,run,
|
|
44
49
|
files,configs,node:process.version,tsc:digest(tsc),
|
|
45
50
|
packageManager:contract.install.version,linker:contract.install.linker})),contextHash,contract.discoveryQueries);
|
|
46
51
|
}catch(e){console.error(`VSTATE_REASON:${e.code&&/^[a-z-]+$/.test(e.code)?e.code:
|