@redue/cli 0.1.0-alpha.17 → 0.1.0-alpha.19

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.
@@ -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()||{};
@@ -41,20 +43,6 @@ const refused=[['modulesDir','modules-dir'],['virtualStoreDir','virtual-store-di
41
43
  ['enableGlobalVirtualStore','enable-global-virtual-store'],['injectWorkspacePackages','inject-workspace-packages'],
42
44
  ['sharedWorkspaceLockfile','shared-workspace-lockfile'],['pnpmfile','pnpmfile'],['globalPnpmfile','global-pnpmfile'],
43
45
  ['configDependencies','config-dependencies']];
44
- // .npmrc read as pnpm's ini parser reads it: a BOM is dropped, a bare key is
45
- // true, quotes are removed, and "true"/"false" become booleans.
46
- function npmrc(file){
47
- const settings={};
48
- if(!exists(file))return settings;
49
- for(const line of fs.readFileSync(file,'utf8').replace(/^\uFEFF/,'').split(/\r?\n/)){
50
- const trimmed=line.trim();if(!trimmed||/^[#;]/.test(trimmed))continue;
51
- const at=trimmed.indexOf('='),key=(at<0?trimmed:trimmed.slice(0,at)).trim();
52
- let value=at<0?true:trimmed.slice(at+1).trim();
53
- if(typeof value==='string'&&/^(["']).*\1$/.test(value))value=value.slice(1,-1);
54
- settings[key]=value==='true'?true:value==='false'?false:value;
55
- }
56
- return settings;
57
- }
58
46
  // Whether a refused setting is set: present at all, except that only
59
47
  // sharedWorkspaceLockfile=false and injectWorkspacePackages=true move files.
60
48
  const setting=(key,value)=>value!==undefined&&
@@ -204,16 +192,37 @@ function packageAt(root,requester,name){
204
192
  }
205
193
  return {physical:null,candidates};
206
194
  }
207
- function parsedTypeScript(root,dir,ts){
208
- const config=path.join(root,dir,'tsconfig.json'),reads=new Set();
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();
209
222
  const host={...ts.sys,readFile(file){reads.add(path.resolve(file));return ts.sys.readFile(file);},
210
223
  onUnRecoverableConfigFileDiagnostic(){}};
211
224
  const parsed=ts.getParsedCommandLineOfConfigFile(config,{},host);
212
225
  if(!parsed||parsed.errors?.length)throw Error('TypeScript configuration unavailable');
213
- const raw=ts.readConfigFile(config,ts.sys.readFile);
214
- if(raw.error||!Array.isArray(raw.config?.include)||raw.config.include.length!==1||
215
- !/^src(?:$|\/)/.test(raw.config.include[0]))
216
- throw Error('TypeScript source file membership is outside the supported src include');
217
226
  if(parsed.options.plugins?.length||parsed.options.incremental||parsed.options.composite||
218
227
  parsed.projectReferences?.length||parsed.options.paths||parsed.options.baseUrl||
219
228
  parsed.options.rootDirs?.length||parsed.options.typeRoots?.length||
@@ -225,12 +234,13 @@ function parsedTypeScript(root,dir,ts){
225
234
  throw Error('TypeScript module resolver is outside the supported Node contract');
226
235
  for(const file of reads)if(!within(realObservedPath(file),root))
227
236
  throw Error('TypeScript configuration escapes the checkout');
228
- 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)};
229
239
  }
230
- function listedTypeScriptFiles(root,selected,tsc){
240
+ function listedTypeScriptFiles(root,selected,tsc,configName){
231
241
  // --listFilesOnly resolves the compiler's effective module/file set but
232
242
  // performs no typecheck and emits no project outputs.
233
- const out=spawnSync(process.execPath,[tsc,'-p','tsconfig.json','--listFilesOnly'],
243
+ const out=spawnSync(process.execPath,[tsc,'-p',configName,'--listFilesOnly'],
234
244
  {cwd:path.join(root,selected.dir),env:process.env,encoding:'utf8',
235
245
  // The native NTFS public workspace takes 3-5 s to list ~770 inputs;
236
246
  // 5 s sometimes expired despite bounded, progressing compiler work.
@@ -267,17 +277,23 @@ function sharedHardlinkIn(folder){
267
277
  }
268
278
  return false;
269
279
  }
270
- export function pnpmTypecheckInputs(root,workspace,script,{captureQueries=false}={}){
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}={}){
271
282
  const totalStarted=performance.now();
272
283
  root=realObservedPath(root);
273
284
  const install=pnpmInstallInfo(root),members=pnpmWorkspacePackages(root,install.patterns);
274
285
  const selected=workspace==='.'?{name:'.',dir:'.',manifest:'package.json',
275
286
  pkg:read(path.join(root,'package.json'))}:members.get(workspace);
276
287
  if(!selected)throw Error(`workspace ${workspace} is not declared`);
277
- if(!/^(?:tsc --noEmit|tsc -p (?:\.|tsconfig\.json)(?: --noEmit)?)$/.test(
278
- selected.pkg.scripts?.[script]?.trim()||'')||
279
- selected.pkg.scripts?.['pre'+script]||selected.pkg.scripts?.['post'+script])
280
- throw Error('typecheck must be one standalone TypeScript invocation');
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');
281
297
  const selectedDir=path.join(root,selected.dir),lookup=createRequire(path.join(selectedDir,'package.json'));
282
298
  let tsPackage;try{tsPackage=realObservedPath(lookup.resolve('typescript/package.json'));}
283
299
  catch{throw Error('installed TypeScript compiler cannot be resolved');}
@@ -286,7 +302,8 @@ export function pnpmTypecheckInputs(root,workspace,script,{captureQueries=false}
286
302
  const tsc=path.join(tsRoot,'bin','tsc');
287
303
  if(!exists(tsc))throw Error('installed TypeScript executable is missing');
288
304
  const configStarted=performance.now();
289
- const {configs}=parsedTypeScript(root,selected.dir,ts);
305
+ const {configs,sourcePatterns:sources}=parsedTypeScript(root,selected.dir,ts,configName);
306
+ const isSource=patternMatcher(sources);
290
307
  timing('pnpm.ts_config',configStarted,{configs:configs.length});
291
308
  const closure=new Map(),installed=new Set(),generated=new Set(),
292
309
  links=new Set(),candidates=new Set(),absences=[],instances=new Map();
@@ -336,12 +353,12 @@ export function pnpmTypecheckInputs(root,workspace,script,{captureQueries=false}
336
353
  resolutionMs:Math.round(resolutionMs),workspaceLinks:links.size,
337
354
  candidates:candidates.size});
338
355
  const listingStarted=performance.now();
339
- const captured=captureQueries?compilerFiles(tsRoot,path.join(root,selected.dir)):null;
340
- 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);
341
358
  timing('pnpm.ts_input_listing',listingStarted,{files:files.length});
342
359
  const mappingStarted=performance.now();
343
360
  for(const file of files){
344
- if(within(file,path.join(selectedDir,'src'))||
361
+ if(isSource(rel(root,file))||
345
362
  [...closure.values()].some(member=>within(file,path.join(root,member.dir))))continue;
346
363
  const packageRoot=installedPackageRoot(root,file);
347
364
  if(!packageRoot)throw Error(`TypeScript consumes a file outside the observed pnpm contract: ${rel(root,file)}`);
@@ -352,6 +369,16 @@ export function pnpmTypecheckInputs(root,workspace,script,{captureQueries=false}
352
369
  }
353
370
  timing('pnpm.input_mapping',mappingStarted,{instances:instances.size,
354
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
+ }
355
382
  installed.add(`${rel(root,tsRoot)}/**`);
356
383
  installed.add(`!${rel(root,tsRoot)}/node_modules/**`);
357
384
  if(!instances.has(tsRoot))instances.set(tsRoot,{name:'typescript',physicalRoot:rel(root,tsRoot)});
@@ -360,11 +387,12 @@ export function pnpmTypecheckInputs(root,workspace,script,{captureQueries=false}
360
387
  timing('pnpm.hardlink_inspection',sharedStarted,{instances:instances.size,
361
388
  shared:shared.length});
362
389
  const source=[...new Set([...install.configurationInputs,selected.manifest,
363
- ...configs,`${selected.dir==='.'?'':selected.dir+'/'}src/**`,
390
+ ...configs,...sources,
364
391
  ...closure.values().map(member=>member.manifest),...generated])];
365
392
  timing('pnpm.total_discovery',totalStarted,{instances:instances.size,
366
393
  inputFiles:files.length});
367
394
  return {install,workspace:selected,closure:[...closure.values()],source,
395
+ sourcePatterns:sources,args:run.args,
368
396
  installed:[...installed],generated:[...generated],
369
397
  // The read-only state probe consumes this same resolved compiler file set.
370
398
  // It is deliberately omitted from the persisted user-facing input plan.
@@ -0,0 +1,180 @@
1
+ {
2
+ "schema": 1,
3
+ "templates": [
4
+ {
5
+ "versions": [
6
+ "8.0.0",
7
+ "8.1.1",
8
+ "8.2.0",
9
+ "8.3.1",
10
+ "8.4.0",
11
+ "8.5.1",
12
+ "8.6.12",
13
+ "8.7.6",
14
+ "8.8.0",
15
+ "8.9.2",
16
+ "8.10.5",
17
+ "8.11.0",
18
+ "8.12.1",
19
+ "8.13.1",
20
+ "8.14.3",
21
+ "8.15.9",
22
+ "9.0.6",
23
+ "9.1.4",
24
+ "9.2.0",
25
+ "9.3.0",
26
+ "9.4.0",
27
+ "9.5.0",
28
+ "9.6.0",
29
+ "9.7.1",
30
+ "9.8.0",
31
+ "9.9.0",
32
+ "9.10.0",
33
+ "9.11.0",
34
+ "9.12.3",
35
+ "9.13.2",
36
+ "9.14.4",
37
+ "9.15.4",
38
+ "9.15.9",
39
+ "10.0.0",
40
+ "10.1.0",
41
+ "10.2.1",
42
+ "10.3.0",
43
+ "10.4.1",
44
+ "10.5.2",
45
+ "10.6.5",
46
+ "10.7.1",
47
+ "10.8.1",
48
+ "10.9.0"
49
+ ],
50
+ "template": "#!/bin/sh\nbasedir=$(dirname \"$(echo \"$0\" | sed -e 's,\\\\,/,g')\")\n\ncase `uname` in\n *CYGWIN*) basedir=`cygpath -w \"$basedir\"`;;\nesac\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nfi\n"
51
+ },
52
+ {
53
+ "versions": [
54
+ "10.10.0",
55
+ "10.11.1",
56
+ "10.12.4",
57
+ "10.13.1",
58
+ "10.14.0",
59
+ "10.15.1",
60
+ "10.16.1",
61
+ "10.17.1",
62
+ "10.18.3",
63
+ "10.19.0",
64
+ "10.20.0",
65
+ "10.21.0",
66
+ "10.22.0",
67
+ "10.23.0",
68
+ "10.24.0",
69
+ "10.25.0",
70
+ "10.26.2",
71
+ "10.27.0",
72
+ "10.28.2",
73
+ "10.29.3",
74
+ "10.30.3",
75
+ "10.31.0",
76
+ "10.32.1",
77
+ "10.33.2",
78
+ "10.33.4",
79
+ "10.34.7"
80
+ ],
81
+ "template": "#!/bin/sh\nbasedir=$(dirname \"$(echo \"$0\" | sed -e 's,\\\\,/,g')\")\n\ncase `uname` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if command -v cygpath > /dev/null 2>&1; then\n basedir=`cygpath -w \"$basedir\"`\n fi\n ;;\nesac\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nfi\n"
82
+ },
83
+ {
84
+ "versions": [
85
+ "11.0.9",
86
+ "11.1.1",
87
+ "11.1.3",
88
+ "11.2.2",
89
+ "11.3.0",
90
+ "11.4.0",
91
+ "11.5.3",
92
+ "11.6.0"
93
+ ],
94
+ "template": "#!/bin/sh\nbasedir=$(dirname \"$(echo \"$0\" | sed -e 's,\\\\,/,g')\")\n\ncase `uname` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if command -v cygpath > /dev/null 2>&1; then\n basedir=`cygpath -w \"$basedir\"`\n fi\n ;;\nesac\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
95
+ },
96
+ {
97
+ "versions": [
98
+ "11.7.0",
99
+ "11.8.0",
100
+ "11.9.0",
101
+ "11.10.0",
102
+ "11.11.0",
103
+ "11.12.0",
104
+ "11.13.1",
105
+ "11.14.0",
106
+ "11.15.1",
107
+ "11.16.0",
108
+ "11.17.0",
109
+ "11.18.0",
110
+ "11.19.0",
111
+ "11.20.0"
112
+ ],
113
+ "template": "#!/bin/sh\nbasedir=$(dirname \"$(echo \"$0\" | sed -e 's,\\\\,/,g')\")\nbasedir_win=\"$basedir\"\nexe=\"\"\nmsys=\"\"\n\ncase `uname -a` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if command -v cygpath > /dev/null 2>&1; then\n basedir_win=`cygpath -w \"$basedir\"`\n fi\n exe=\".exe\"\n msys=\"true\"\n ;;\n *WSL2*)\n if command -v wslpath > /dev/null 2>&1; then\n basedir_win=\"$(wslpath -w \"$basedir\" 2> /dev/null)\"\n if [ $? -ne 0 ] || [ -z \"$basedir_win\" ]; then\n basedir_win=\"$basedir\"\n else\n exe=\".exe\"\n fi\n fi\n ;;\nesac\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/@@TARGET@@\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir/@@TARGET@@\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
114
+ },
115
+ {
116
+ "versions": [
117
+ "11.21.0",
118
+ "11.22.0",
119
+ "11.23.0",
120
+ "11.24.0",
121
+ "11.25.0",
122
+ "11.26.0",
123
+ "12.0.0",
124
+ "12.1.0",
125
+ "12.2.1",
126
+ "12.3.4"
127
+ ],
128
+ "template": "#!/bin/sh\n# Resolve $0 through symlinks so basedir is the shim's real directory.\n# Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim.\nlink=\"$0\"\nhops=0\nwhile [ -L \"$link\" ] && [ \"$hops\" -lt 40 ]; do\n hops=$((hops+1))\n target=$(readlink \"$link\")\n case \"$target\" in\n /*) link=\"$target\" ;;\n *) link=\"$(dirname \"$link\")/$target\" ;;\n esac\ndone\nbasedir=$(dirname \"$(echo \"$link\" | sed -e 's,\\\\,/,g')\")\nbasedir_win=\"$basedir\"\nexe=\"\"\nmsys=\"\"\n\ncase `uname -a` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if command -v cygpath > /dev/null 2>&1; then\n basedir_win=`cygpath -w \"$basedir\"`\n fi\n exe=\".exe\"\n msys=\"true\"\n ;;\n *WSL2*)\n if command -v wslpath > /dev/null 2>&1; then\n basedir_win=\"$(wslpath -w \"$basedir\" 2> /dev/null)\"\n if [ $? -ne 0 ] || [ -z \"$basedir_win\" ]; then\n basedir_win=\"$basedir\"\n else\n exe=\".exe\"\n fi\n fi\n ;;\nesac\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/@@TARGET@@\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir/@@TARGET@@\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
129
+ },
130
+ {
131
+ "versions": [
132
+ "11.27.1"
133
+ ],
134
+ "template": "#!/bin/sh\n# Resolve $0 through symlinks so basedir is the shim's real directory.\n# Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim.\n#\n# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed,\n# uname, and printf go through `command -p`, which searches the system default\n# path instead. A dependency's bin cannot stand in for one of them and take over\n# the shim before it reaches its target. Directories come from `${link%/*}`,\n# which needs no helper at all.\nlink=\"$0\"\n# `${link%/*}` needs a separator to strip. A bare name came from a PATH lookup\n# and stands for a file in the current directory.\ncase \"$link\" in\n */*|*\\\\*) ;;\n *) link=\"./$link\" ;;\nesac\nhops=0\nwhile [ -L \"$link\" ] && [ \"$hops\" -lt 40 ]; do\n hops=$((hops+1))\n target=$(command -p readlink \"$link\")\n case \"$target\" in\n /*) link=\"$target\" ;;\n *) link=\"${link%/*}/$target\" ;;\n esac\ndone\nbasedir=$(command -p printf '%s\\n' \"$link\" | command -p sed -e 's,\\\\,/,g')\nbasedir=\"${basedir%/*}\"\nbasedir_win=\"$basedir\"\nexe=\"\"\nmsys=\"\"\n\ncase `command -p uname -a` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if converted=$(command -p cygpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n elif command -v cygpath > /dev/null 2>&1; then\n basedir_win=`cygpath -w \"$basedir\"`\n fi\n exe=\".exe\"\n msys=\"true\"\n ;;\n *WSL2*)\n if converted=$(command -p wslpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n exe=\".exe\"\n elif command -v wslpath > /dev/null 2>&1; then\n basedir_win=\"$(wslpath -w \"$basedir\" 2> /dev/null)\"\n if [ $? -ne 0 ] || [ -z \"$basedir_win\" ]; then\n basedir_win=\"$basedir\"\n else\n exe=\".exe\"\n fi\n fi\n ;;\nesac\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/@@TARGET@@\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir/@@TARGET@@\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
135
+ },
136
+ {
137
+ "versions": [
138
+ "11.28.2"
139
+ ],
140
+ "template": "#!/bin/sh\n# Resolve $0 through symlinks so basedir is the shim's real directory.\n# Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim.\n#\n# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed,\n# uname, and printf go through `command -p`, which searches the system default\n# path instead. A dependency's bin cannot stand in for one of them and take over\n# the shim before it reaches its target. Directories come from `${link%/*}`,\n# which needs no helper at all.\n#\n# Where no default path is compiled in, as on Nix, `command -p` searches PATH\n# instead, so the helpers run with node_modules and relative entries dropped from\n# PATH.\ncaller_path_set=${PATH+set}\ncaller_path=${PATH-}\nhelper_path=\nrest=$caller_path:\nwhile [ -n \"$rest\" ]; do\n dir=${rest%%:*}\n rest=${rest#*:}\n case \"$dir\" in\n */node_modules/*|*/node_modules) ;;\n /*) helper_path=${helper_path:+$helper_path:}$dir ;;\n esac\ndone\n# An empty PATH searches the current directory.\nPATH=${helper_path:-/}\nlink=\"$0\"\n# `${link%/*}` needs a separator to strip. A bare name came from a PATH lookup\n# and stands for a file in the current directory.\ncase \"$link\" in\n */*|*\\\\*) ;;\n *) link=\"./$link\" ;;\nesac\nhops=0\nwhile [ -L \"$link\" ] && [ \"$hops\" -lt 40 ]; do\n hops=$((hops+1))\n target=$(command -p readlink \"$link\")\n case \"$target\" in\n /*) link=\"$target\" ;;\n *) link=\"${link%/*}/$target\" ;;\n esac\ndone\nbasedir=$(command -p printf '%s\\n' \"$link\" | command -p sed -e 's,\\\\,/,g')\nbasedir=\"${basedir%/*}\"\nbasedir_abs=$(CDPATH= cd -P -- \"$basedir\" && pwd -P) || exit $?\nbasedir=\"$basedir_abs\"\nbasedir_win=\"$basedir\"\nexe=\"\"\nmsys=\"\"\n\ncase `command -p uname -a` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if converted=$(command -p cygpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n elif command -v cygpath > /dev/null 2>&1; then\n basedir_win=`cygpath -w \"$basedir\"`\n fi\n exe=\".exe\"\n msys=\"true\"\n ;;\n *WSL2*)\n if converted=$(command -p wslpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n exe=\".exe\"\n elif command -v wslpath > /dev/null 2>&1; then\n basedir_win=\"$(wslpath -w \"$basedir\" 2> /dev/null)\"\n if [ $? -ne 0 ] || [ -z \"$basedir_win\" ]; then\n basedir_win=\"$basedir\"\n else\n exe=\".exe\"\n fi\n fi\n ;;\nesac\nif [ -n \"$caller_path_set\" ]; then PATH=$caller_path; else unset PATH; fi\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/@@TARGET@@\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir_abs/@@TARGET@@\" \"$@\"\nelif [ -n \"$msys\" ] && command -v node >/dev/null 2>&1; then\n exec node \"$basedir_win/@@TARGET@@\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir_abs/@@TARGET@@\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir_abs/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
141
+ },
142
+ {
143
+ "versions": [
144
+ "11.28.6",
145
+ "12.8.2",
146
+ "12.9.1",
147
+ "12.10.1",
148
+ "12.11.2",
149
+ "12.12.1",
150
+ "12.13.0",
151
+ "12.14.0"
152
+ ],
153
+ "template": "#!/bin/sh\n# Resolve $0 through symlinks so basedir is the shim's real directory.\n# Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim.\n#\n# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed,\n# uname, and printf go through `command -p`, which searches the system default\n# path instead. A dependency's bin cannot stand in for one of them and take over\n# the shim before it reaches its target. Directories come from `${link%/*}`,\n# which needs no helper at all.\n#\n# A helper the default path lacks, as in a Nix build sandbox, comes from PATH\n# instead, with node_modules and relative entries dropped from it.\ncaller_path_set=${PATH+set}\ncaller_path=${PATH-}\nhelper_path=\nrest=$caller_path:\nwhile [ -n \"$rest\" ]; do\n dir=${rest%%:*}\n rest=${rest#*:}\n case \"$dir\" in\n */node_modules/*|*/node_modules) ;;\n /*) helper_path=${helper_path:+$helper_path:}$dir ;;\n esac\ndone\n# An empty PATH searches the current directory.\nPATH=${helper_path:-/}\n# A helper comes from PATH only when the default path lacks it and PATH has it.\n# The bash 3.2 that macOS ships as sh answers `command -p -v` from PATH.\nrun_helper() {\n if command -p -v \"$1\" >/dev/null 2>&1 || ! command -v \"$1\" >/dev/null 2>&1; then command -p \"$@\"; else command \"$@\"; fi\n}\nlink=\"$0\"\n# `${link%/*}` needs a separator to strip. A bare name came from a PATH lookup\n# and stands for a file in the current directory.\ncase \"$link\" in\n */*|*\\\\*) ;;\n *) link=\"./$link\" ;;\nesac\nhops=0\nwhile [ -L \"$link\" ] && [ \"$hops\" -lt 40 ]; do\n hops=$((hops+1))\n target=$(run_helper readlink \"$link\")\n case \"$target\" in\n /*) link=\"$target\" ;;\n *) link=\"${link%/*}/$target\" ;;\n esac\ndone\nbasedir=$(run_helper printf '%s\\n' \"$link\" | run_helper sed -e 's,\\\\,/,g')\nbasedir=\"${basedir%/*}\"\nbasedir_abs=$(CDPATH= cd -P -- \"$basedir\" && pwd -P) || exit $?\nbasedir=\"$basedir_abs\"\nbasedir_win=\"$basedir\"\nexe=\"\"\nmsys=\"\"\n\ncase `run_helper uname -a` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if converted=$(command -p cygpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n elif command -v cygpath > /dev/null 2>&1; then\n basedir_win=`cygpath -w \"$basedir\"`\n fi\n exe=\".exe\"\n msys=\"true\"\n ;;\n *WSL2*)\n if converted=$(command -p wslpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n exe=\".exe\"\n elif command -v wslpath > /dev/null 2>&1; then\n basedir_win=\"$(wslpath -w \"$basedir\" 2> /dev/null)\"\n if [ $? -ne 0 ] || [ -z \"$basedir_win\" ]; then\n basedir_win=\"$basedir\"\n else\n exe=\".exe\"\n fi\n fi\n ;;\nesac\nif [ -n \"$caller_path_set\" ]; then PATH=$caller_path; else unset PATH; fi\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/@@TARGET@@\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir_abs/@@TARGET@@\" \"$@\"\nelif [ -n \"$msys\" ] && command -v node >/dev/null 2>&1; then\n exec node \"$basedir_win/@@TARGET@@\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir_abs/@@TARGET@@\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir_abs/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
154
+ },
155
+ {
156
+ "versions": [
157
+ "12.4.2"
158
+ ],
159
+ "template": "#!/bin/sh\n# Resolve $0 through symlinks so basedir is the shim's real directory.\n# Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim.\n#\n# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and\n# uname go through `command -p`, which searches the system default path instead.\n# A dependency's bin cannot stand in for one of them and take over the shim\n# before it reaches its target. Directories come from `${link%/*}`, which needs\n# no helper at all.\nlink=\"$0\"\n# `${link%/*}` needs a separator to strip. A bare name came from a PATH lookup\n# and stands for a file in the current directory.\ncase \"$link\" in\n */*|*\\\\*) ;;\n *) link=\"./$link\" ;;\nesac\nhops=0\nwhile [ -L \"$link\" ] && [ \"$hops\" -lt 40 ]; do\n hops=$((hops+1))\n target=$(command -p readlink \"$link\")\n case \"$target\" in\n /*) link=\"$target\" ;;\n *) link=\"${link%/*}/$target\" ;;\n esac\ndone\nbasedir=$(echo \"$link\" | command -p sed -e 's,\\\\,/,g')\nbasedir=\"${basedir%/*}\"\nbasedir_win=\"$basedir\"\nexe=\"\"\nmsys=\"\"\n\ncase `command -p uname -a` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if command -v cygpath > /dev/null 2>&1; then\n basedir_win=`cygpath -w \"$basedir\"`\n fi\n exe=\".exe\"\n msys=\"true\"\n ;;\n *WSL2*)\n if command -v wslpath > /dev/null 2>&1; then\n basedir_win=\"$(wslpath -w \"$basedir\" 2> /dev/null)\"\n if [ $? -ne 0 ] || [ -z \"$basedir_win\" ]; then\n basedir_win=\"$basedir\"\n else\n exe=\".exe\"\n fi\n fi\n ;;\nesac\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/@@TARGET@@\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir/@@TARGET@@\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
160
+ },
161
+ {
162
+ "versions": [
163
+ "12.5.1"
164
+ ],
165
+ "template": "#!/bin/sh\n# Resolve $0 through symlinks so basedir is the shim's real directory.\n# Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim.\n#\n# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed,\n# uname, and printf go through `command -p`, which searches the system default\n# path instead. A dependency's bin cannot stand in for one of them and take over\n# the shim before it reaches its target. Directories come from `${link%/*}`,\n# which needs no helper at all.\nlink=\"$0\"\n# `${link%/*}` needs a separator to strip. A bare name came from a PATH lookup\n# and stands for a file in the current directory.\ncase \"$link\" in\n */*|*\\\\*) ;;\n *) link=\"./$link\" ;;\nesac\nhops=0\nwhile [ -L \"$link\" ] && [ \"$hops\" -lt 40 ]; do\n hops=$((hops+1))\n target=$(command -p readlink \"$link\")\n case \"$target\" in\n /*) link=\"$target\" ;;\n *) link=\"${link%/*}/$target\" ;;\n esac\ndone\nbasedir=$(command -p printf '%s\\n' \"$link\" | command -p sed -e 's,\\\\,/,g')\nbasedir=\"${basedir%/*}\"\nbasedir_win=\"$basedir\"\nexe=\"\"\nmsys=\"\"\n\ncase `command -p uname -a` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if command -v cygpath > /dev/null 2>&1; then\n basedir_win=`cygpath -w \"$basedir\"`\n fi\n exe=\".exe\"\n msys=\"true\"\n ;;\n *WSL2*)\n if command -v wslpath > /dev/null 2>&1; then\n basedir_win=\"$(wslpath -w \"$basedir\" 2> /dev/null)\"\n if [ $? -ne 0 ] || [ -z \"$basedir_win\" ]; then\n basedir_win=\"$basedir\"\n else\n exe=\".exe\"\n fi\n fi\n ;;\nesac\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/@@TARGET@@\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir/@@TARGET@@\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
166
+ },
167
+ {
168
+ "versions": [
169
+ "12.6.0"
170
+ ],
171
+ "template": "#!/bin/sh\n# Resolve $0 through symlinks so basedir is the shim's real directory.\n# Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim.\n#\n# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed,\n# uname, and printf go through `command -p`, which searches the system default\n# path instead. A dependency's bin cannot stand in for one of them and take over\n# the shim before it reaches its target. Directories come from `${link%/*}`,\n# which needs no helper at all.\nlink=\"$0\"\n# `${link%/*}` needs a separator to strip. A bare name came from a PATH lookup\n# and stands for a file in the current directory.\ncase \"$link\" in\n */*|*\\\\*) ;;\n *) link=\"./$link\" ;;\nesac\nhops=0\nwhile [ -L \"$link\" ] && [ \"$hops\" -lt 40 ]; do\n hops=$((hops+1))\n target=$(command -p readlink \"$link\")\n case \"$target\" in\n /*) link=\"$target\" ;;\n *) link=\"${link%/*}/$target\" ;;\n esac\ndone\nbasedir=$(command -p printf '%s\\n' \"$link\" | command -p sed -e 's,\\\\,/,g')\nbasedir=\"${basedir%/*}\"\nbasedir_win=\"$basedir\"\nexe=\"\"\nmsys=\"\"\n\ncase `command -p uname -a` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if converted=$(command -p cygpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n elif command -v cygpath > /dev/null 2>&1; then\n basedir_win=`cygpath -w \"$basedir\"`\n fi\n exe=\".exe\"\n msys=\"true\"\n ;;\n *WSL2*)\n if converted=$(command -p wslpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n exe=\".exe\"\n elif command -v wslpath > /dev/null 2>&1; then\n basedir_win=\"$(wslpath -w \"$basedir\" 2> /dev/null)\"\n if [ $? -ne 0 ] || [ -z \"$basedir_win\" ]; then\n basedir_win=\"$basedir\"\n else\n exe=\".exe\"\n fi\n fi\n ;;\nesac\n\nbasedir_abs=$(CDPATH= cd -P -- \"$basedir\" && pwd -P) || exit $?\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/@@TARGET@@\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir_abs/@@TARGET@@\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir_abs/@@TARGET@@\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir_abs/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
172
+ },
173
+ {
174
+ "versions": [
175
+ "12.7.0"
176
+ ],
177
+ "template": "#!/bin/sh\n# Resolve $0 through symlinks so basedir is the shim's real directory.\n# Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim.\n#\n# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed,\n# uname, and printf go through `command -p`, which searches the system default\n# path instead. A dependency's bin cannot stand in for one of them and take over\n# the shim before it reaches its target. Directories come from `${link%/*}`,\n# which needs no helper at all.\n#\n# Where no default path is compiled in, as on Nix, `command -p` searches PATH\n# instead, so the helpers run with node_modules and relative entries dropped from\n# PATH.\ncaller_path_set=${PATH+set}\ncaller_path=${PATH-}\nhelper_path=\nrest=$caller_path:\nwhile [ -n \"$rest\" ]; do\n dir=${rest%%:*}\n rest=${rest#*:}\n case \"$dir\" in\n */node_modules/*|*/node_modules) ;;\n /*) helper_path=${helper_path:+$helper_path:}$dir ;;\n esac\ndone\n# An empty PATH searches the current directory.\nPATH=${helper_path:-/}\nlink=\"$0\"\n# `${link%/*}` needs a separator to strip. A bare name came from a PATH lookup\n# and stands for a file in the current directory.\ncase \"$link\" in\n */*|*\\\\*) ;;\n *) link=\"./$link\" ;;\nesac\nhops=0\nwhile [ -L \"$link\" ] && [ \"$hops\" -lt 40 ]; do\n hops=$((hops+1))\n target=$(command -p readlink \"$link\")\n case \"$target\" in\n /*) link=\"$target\" ;;\n *) link=\"${link%/*}/$target\" ;;\n esac\ndone\nbasedir=$(command -p printf '%s\\n' \"$link\" | command -p sed -e 's,\\\\,/,g')\nbasedir=\"${basedir%/*}\"\nbasedir_win=\"$basedir\"\nexe=\"\"\nmsys=\"\"\n\ncase `command -p uname -a` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if converted=$(command -p cygpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n elif command -v cygpath > /dev/null 2>&1; then\n basedir_win=`cygpath -w \"$basedir\"`\n fi\n exe=\".exe\"\n msys=\"true\"\n ;;\n *WSL2*)\n if converted=$(command -p wslpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n exe=\".exe\"\n elif command -v wslpath > /dev/null 2>&1; then\n basedir_win=\"$(wslpath -w \"$basedir\" 2> /dev/null)\"\n if [ $? -ne 0 ] || [ -z \"$basedir_win\" ]; then\n basedir_win=\"$basedir\"\n else\n exe=\".exe\"\n fi\n fi\n ;;\nesac\nif [ -n \"$caller_path_set\" ]; then PATH=$caller_path; else unset PATH; fi\n\nbasedir_abs=$(CDPATH= cd -P -- \"$basedir\" && pwd -P) || exit $?\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/@@TARGET@@\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir_abs/@@TARGET@@\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir_abs/@@TARGET@@\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir_abs/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
178
+ }
179
+ ]
180
+ }
@@ -0,0 +1,54 @@
1
+ // pnpm's launchers. On macOS and Linux, pnpm writes each node_modules/.bin
2
+ // entry as a `#!/bin/sh` script that finds its own directory (with sed,
3
+ // dirname, readlink, uname or printf, depending on the pnpm release), sets
4
+ // NODE_PATH and execs Node on the package's bin file. A launcher is trusted only
5
+ // when its text is one of the templates pnpm releases write
6
+ // (pnpm-shim-templates.json, built by eval/pnpm-shim-templates.mjs), with only
7
+ // the NODE_PATH value, the bin file and the trailing target comment filled in.
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+
11
+ const templates=JSON.parse(fs.readFileSync(new URL('pnpm-shim-templates.json',import.meta.url),'utf8')).templates;
12
+ const known=new Set(templates.map(row=>row.template));
13
+ // A value with no quote, expansion, escape or line break: it stays literal in
14
+ // the shell.
15
+ const literal=/^[^"$`\\\n]*$/;
16
+
17
+ // The launcher's text as a template, with the values it fills in, or null
18
+ // when it does not have pnpm's form.
19
+ export function shimTemplate(text){
20
+ if(typeof text!=='string'||!text.startsWith('#!/bin/sh\n'))return null;
21
+ let template=text,nodePath=null;
22
+ // NODE_PATH, set alone or in front of the caller's.
23
+ const alone=/\n export NODE_PATH="([^"\n]*)"\n/.exec(text);
24
+ if(alone){
25
+ nodePath=alone[1];
26
+ // Each entry is literal, or relative to the launcher's own directory
27
+ // ($basedir_abs, which pnpm 12.6 and later write).
28
+ if(!nodePath.split(':').every(entry=>literal.test(entry.replace(/^\$basedir(?:_abs)?\//,'')))||
29
+ !text.includes(`\n export NODE_PATH="${nodePath}:$NODE_PATH"\n`))return null;
30
+ template=template.split(`\n export NODE_PATH="${nodePath}:$NODE_PATH"\n`).join('\n export NODE_PATH="@@NODE_PATH@@:$NODE_PATH"\n')
31
+ .split(`\n export NODE_PATH="${nodePath}"\n`).join('\n export NODE_PATH="@@NODE_PATH@@"\n');
32
+ }
33
+ // The bin file, relative to the launcher's directory, after $basedir,
34
+ // $basedir_abs or $basedir_win; `node` and `node.exe` there are Node itself.
35
+ const targets=new Set([...template.matchAll(/"\$basedir(?:_abs|_win)?\/([^"\n]+)"/g)].map(match=>match[1])
36
+ .filter(value=>value!=='node'&&value!=='node.exe'));
37
+ if(targets.size!==1)return null;
38
+ const [target]=targets;
39
+ if(!literal.test(target))return null;
40
+ for(const prefix of ['$basedir','$basedir_abs','$basedir_win'])
41
+ template=template.split(`"${prefix}/${target}"`).join(`"${prefix}/@@TARGET@@"`);
42
+ // pnpm 11 and later end with a comment naming the target.
43
+ template=template.replace(/\n# cmd-shim-target=[^\n]*\n?$/,'\n# cmd-shim-target=@@COMMENT@@\n');
44
+ return {template,nodePath,target};
45
+ }
46
+ // The bin file a pnpm launcher runs with Node, as an absolute path, or null
47
+ // when `file` is not a launcher pnpm writes.
48
+ export function pnpmShimTarget(file,text=null){
49
+ try{
50
+ text??=fs.readFileSync(file,'utf8');
51
+ const shim=shimTemplate(text);
52
+ return shim&&known.has(shim.template)?path.resolve(path.dirname(file),shim.target):null;
53
+ }catch{return null;}
54
+ }
@@ -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,script]=process.argv.slice(2);
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||!script)fail('probe-arguments');
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,script,process.version]));
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,script,{captureQueries:process.argv.includes('--redue-checkpoint')});
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=[path.join(selected,'src'),...contract.generated.map(pattern=>
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,script,
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:
@@ -1,4 +1,9 @@
1
1
  // Presentation consumes evidence; it never grants applicability.
2
+ // The recipe that made a receipt, by the qualification it names.
3
+ const recipes={'npm-typescript-direct-v1':'direct TypeScript recipe','npm-tests-direct-v1':'direct test recipe',
4
+ 'node-script-v1':'general recipe','pnpm-tsc-v1':'pnpm TypeScript recipe','yarn-workspace-tsc-v1':'Yarn workspace TypeScript recipe',
5
+ 'typescript-noemit-v1':'npm TypeScript recipe'};
6
+ export const recipeLabel=recipe=>recipes[recipe]??null;
2
7
  export function checkLabel(row) {
3
8
  return row.result==='FAIL'?'FAILED':row.freshness;
4
9
  }
@@ -21,7 +26,8 @@ export function renderStatus(value,{short=false}={}) {
21
26
  const label=checkLabel(row),symbol=label==='CURRENT'&&row.result==='PASS'?'✓':label==='FAILED'?'✗':'?';
22
27
  const mark=label==='STALE'?'⚠':symbol;
23
28
  const historical=row.result?` / ${row.result}`:'';
24
- lines.push(`${mark} ${row.name} ${label}${historical}`);
29
+ const recipe=label==='CURRENT'?recipeLabel(row.recipe):null;
30
+ lines.push(`${mark} ${row.name} ${label}${historical}${recipe?` (${recipe})`:''}`);
25
31
  if(label!=='CURRENT')lines.push(` ${row.result==='FAIL'?`Historical failure; applicability ${row.freshness}. `:''}${explanation(row)}`);
26
32
  }
27
33
  if(!lines.length)lines.push('UNVERIFIED — observation is initializing; no check state is available yet.');
@@ -34,6 +40,7 @@ export function renderExplain(value,name) {
34
40
  const lines=[`${row.name}: ${checkLabel(row)}${row.result?` / ${row.result}`:''}`];
35
41
  if(row.result==='FAIL')lines.push(`Historical failure; applicability: ${row.freshness}.`);
36
42
  if(row.verification_recipe==='npm-direct-typescript@1')lines.push('Recorded recipe: direct local TypeScript compiler; npm and lifecycle scripts were not executed.');
43
+ else if(recipeLabel(row.recipe))lines.push(`Recorded recipe: ${recipeLabel(row.recipe)}.`);
37
44
  lines.push(explanation(row));
38
45
  if(row.changed_inputs?.length)lines.push('Relevant inputs changed:',...row.changed_inputs.map(file=>` ${file}`));
39
46
  if(row.freshness==='STALE')lines.push(`Due again when verification is needed: redue run ${JSON.stringify(row.name)}`);