@redue/cli 0.1.0-alpha.18 → 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.
- package/CHANGELOG.md +43 -0
- package/README.md +8 -4
- package/bin/redue.mjs +10 -3
- package/docs/user/configuration.md +2 -0
- package/docs/user/getting-started.md +7 -7
- package/docs/user/node-script.md +153 -0
- package/docs/user/supported-projects.md +12 -10
- package/docs/user/tests-direct.md +5 -3
- package/package.json +1 -1
- package/src/agent-integration.mjs +14 -6
- package/src/claude-hook.mjs +24 -12
- package/src/cli.mjs +32 -14
- package/src/daemon.mjs +3 -2
- package/src/declared-plan.mjs +6 -3
- package/src/index.mjs +10 -4
- package/src/node-onboarding.mjs +39 -7
- package/src/node-script.mjs +362 -0
- package/src/pnpm-shim-templates.json +180 -0
- package/src/pnpm-shims.mjs +54 -0
- package/src/presentation.mjs +8 -1
- package/src/test-direct.mjs +42 -9
- package/src/test-recorder.cjs +101 -14
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
// The general recipe: a typecheck, test or lint script run through the
|
|
2
|
+
// project's package manager, recorded like the direct test recipe
|
|
3
|
+
// (test-direct.mjs). Its inputs are the whole checkout except installed files
|
|
4
|
+
// (observed by identity), runner caches and what the run itself writes, plus
|
|
5
|
+
// the package manager, Node, the exact script and the environment the run
|
|
6
|
+
// read. The recorder sees only Node processes, so every other program a run
|
|
7
|
+
// starts must be one REDUE can account for: a shell whose command, read as
|
|
8
|
+
// text, starts only Node programs, pnpm's launchers (pnpm-shims.mjs), or the
|
|
9
|
+
// pnpm executable. Anything else makes the result UNVERIFIED and names the
|
|
10
|
+
// program.
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import {pnpmShimTarget} from './pnpm-shims.mjs';
|
|
15
|
+
import {testPackageManager,testWorkspaceDirs,testEnvironment} from './test-direct.mjs';
|
|
16
|
+
|
|
17
|
+
export const scriptQualification='node-script-v1';
|
|
18
|
+
export const scriptInterpretation='node-script@1';
|
|
19
|
+
export const scriptKinds=['typecheck','test','lint'];
|
|
20
|
+
const fail=code=>{throw Object.assign(Error(code),{code});};
|
|
21
|
+
|
|
22
|
+
// Runner caches in the checkout. They are not inputs, and a run may read and
|
|
23
|
+
// write them: ESLint's and Prettier's caches, TypeScript build info, the
|
|
24
|
+
// cache directories of Vite, Vitest, Jest, SWC, Parcel, Next.js, Angular,
|
|
25
|
+
// Turborepo and Nx, and the temporary files pnpm writes and removes in the
|
|
26
|
+
// project at every run (_tmp_<pid>_<random>).
|
|
27
|
+
export const scriptCaches=['**/_tmp_+([0-9])_+([0-9a-f])','**/.cache/**','**/.vite/**','**/.vite-temp/**','**/.vitest/**','**/.vitest-cache/**',
|
|
28
|
+
'**/.turbo/**','**/.nx/**','**/.swc/**','**/.jest-cache/**','**/.parcel-cache/**','**/.angular/cache/**',
|
|
29
|
+
'**/.next/cache/**','**/.tmp/**','**/*.tsbuildinfo','**/.eslintcache','**/.stylelintcache','**/.prettiercache'];
|
|
30
|
+
// Installed state a package manager reads at every run: pnpm's and Yarn's
|
|
31
|
+
// records inside node_modules are installed files already; Yarn's install
|
|
32
|
+
// state is kept beside them.
|
|
33
|
+
const installState=['.yarn/install-state.gz'];
|
|
34
|
+
const modulesDirs=dirs=>['node_modules',...dirs.map(dir=>`${dir}/node_modules`)];
|
|
35
|
+
// Inputs: the whole checkout but node_modules, .git and runner caches. Every
|
|
36
|
+
// node_modules of the workspace is observed as installed files, without the
|
|
37
|
+
// caches tools keep there.
|
|
38
|
+
export function scriptCheckInputs(dirs=[]){
|
|
39
|
+
const modules=modulesDirs(dirs);
|
|
40
|
+
return {inputs:['**',...modules.map(at=>`!${at}/**`),'!.git/**',...installState.map(file=>`!${file}`),
|
|
41
|
+
...scriptCaches.map(pattern=>`!${pattern}`)],
|
|
42
|
+
installedInputs:[...modules.flatMap(at=>[`${at}/**`,`!${at}/.cache/**`,`!${at}/.vite/**`,`!${at}/.vite-temp/**`,
|
|
43
|
+
`!${at}/.vitest/**`,`!${at}/.vitest-cache/**`,`!${at}/**/*.tsbuildinfo`]),...installState]};
|
|
44
|
+
}
|
|
45
|
+
// Installed files a tool rewrites during a run: the workspace state pnpm
|
|
46
|
+
// writes at the first run after an install, and the global types vue-tsc
|
|
47
|
+
// writes before it reads them, in a workspace node_modules. They stay inputs;
|
|
48
|
+
// when they are a run's only change and the run wrote them, the run is kept
|
|
49
|
+
// with the files as the run left them.
|
|
50
|
+
const pnpmState=rel=>/^node_modules\/\.pnpm-workspace-state(?:-v\d+)?\.json$/.test(rel);
|
|
51
|
+
const vueTypes=rel=>{const parts=rel.split('/');
|
|
52
|
+
return /^[^/]+\.d\.ts$/.test(parts.at(-1))&&parts.at(-2)==='.vue-global-types'&&parts.at(-3)==='node_modules'&&
|
|
53
|
+
!parts.slice(0,-3).includes('node_modules')&&!rel.startsWith('external:');};
|
|
54
|
+
export const toolOutputPath=rel=>pnpmState(rel)||vueTypes(rel);
|
|
55
|
+
// The run wrote it: a recorded process did, or the pnpm executable ran, which
|
|
56
|
+
// writes its state natively.
|
|
57
|
+
export const toolOutput=(rel,guard)=>toolOutputPath(rel)&&(guard.wroteInstalled(rel)||pnpmState(rel)&&guard.opaque);
|
|
58
|
+
// The installed paths the recorder treats as observed by identity.
|
|
59
|
+
export const scriptInstalledPaths=(root,dirs=[])=>[...modulesDirs(dirs),...installState].map(at=>path.join(root,at));
|
|
60
|
+
// Variables a recorded run cannot be seen reading: Node's and libc's (as in
|
|
61
|
+
// the test recipe), and the package managers' own, which npm reads by listing
|
|
62
|
+
// the environment and pnpm's executable reads natively. Every package
|
|
63
|
+
// manager's executable on PATH is identified, the project's first: a script
|
|
64
|
+
// may run another one.
|
|
65
|
+
const managers=['npm','pnpm','yarn','corepack'];
|
|
66
|
+
export function scriptEnvironment(manager){
|
|
67
|
+
return {variables:[...testEnvironment.variables,'PATH','HOME','CI','XDG_CONFIG_HOME'],
|
|
68
|
+
prefixes:[...testEnvironment.prefixes,'npm_config_','pnpm_config_','PNPM_','COREPACK_','YARN_'],
|
|
69
|
+
pathExecutables:['node',manager,...managers.filter(name=>name!==manager)],executableIdentity:true};
|
|
70
|
+
}
|
|
71
|
+
// The configuration files the pnpm executable reads natively, outside the
|
|
72
|
+
// checkout: kept by content as run facts when it ran.
|
|
73
|
+
export function pnpmConfigFiles(env=process.env){
|
|
74
|
+
const home=os.homedir(),config=path.isAbsolute(env.XDG_CONFIG_HOME||'')?env.XDG_CONFIG_HOME:
|
|
75
|
+
process.platform==='darwin'?path.join(home,'Library','Preferences'):path.join(home,'.config');
|
|
76
|
+
const prefix=path.dirname(path.dirname(process.execPath));
|
|
77
|
+
return [...new Set([path.join(config,'pnpm','rc'),path.join(config,'pnpm','config.yaml'),path.join(home,'.config','pnpm','rc'),
|
|
78
|
+
path.join(home,'.npmrc'),path.join(prefix,'etc','npmrc'),...(path.isAbsolute(env.npm_config_userconfig||'')?[env.npm_config_userconfig]:[])])];
|
|
79
|
+
}
|
|
80
|
+
// The project facts the recipe needs, or an error code: the package manager
|
|
81
|
+
// and the workspace package directories whose node_modules are installed.
|
|
82
|
+
export function scriptProject(root,script){
|
|
83
|
+
const pkg=JSON.parse(fs.readFileSync(path.join(root,'package.json'),'utf8'));
|
|
84
|
+
const manager=testPackageManager(root,pkg,{workspaces:true});
|
|
85
|
+
if(typeof pkg.scripts?.[script]!=='string'||!pkg.scripts[script].trim())fail('script-missing');
|
|
86
|
+
return {manager,dirs:testWorkspaceDirs(root,pkg),text:pkg.scripts[script],scripts:pkg.scripts};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// The package managers' own installations, Corepack's downloads and npm's
|
|
90
|
+
// logs. They are toolchain, identified by the package manager's executable
|
|
91
|
+
// (pathExecutables), not inputs.
|
|
92
|
+
export function toolchainDirs(env=process.env){
|
|
93
|
+
const dirs=new Set();
|
|
94
|
+
for(const name of ['npm','npx','pnpm','pnpx','yarn','yarnpkg','corepack']){
|
|
95
|
+
const file=lookup(name,env.PATH,process.cwd());
|
|
96
|
+
const dir=file&&packageDir(file);
|
|
97
|
+
if(dir)dirs.add(dir);
|
|
98
|
+
}
|
|
99
|
+
const home=os.homedir(),cache=path.isAbsolute(env.XDG_CACHE_HOME||'')?env.XDG_CACHE_HOME:path.join(home,'.cache');
|
|
100
|
+
// Corepack's downloads, by version; lastKnownGood.json, which picks a
|
|
101
|
+
// version, stays a file read outside the checkout.
|
|
102
|
+
dirs.add(path.join(path.isAbsolute(env.COREPACK_HOME||'')?env.COREPACK_HOME:path.join(cache,'node','corepack'),'v1'));
|
|
103
|
+
dirs.add(path.join(path.isAbsolute(env.npm_config_cache||'')?env.npm_config_cache:path.join(home,'.npm'),'_logs'));
|
|
104
|
+
const real=dir=>{try{return fs.realpathSync(dir);}catch{return dir;}};
|
|
105
|
+
return [...new Set([...dirs].flatMap(dir=>[path.resolve(dir),real(dir)]))];
|
|
106
|
+
}
|
|
107
|
+
// The package directory of an installed package manager's file: the nearest
|
|
108
|
+
// package.json named npm, pnpm, @pnpm/exe*, yarn or corepack.
|
|
109
|
+
function packageDir(file){
|
|
110
|
+
for(let dir=path.dirname(file),i=0;i<5;i++,dir=path.dirname(dir)){
|
|
111
|
+
try{const name=JSON.parse(fs.readFileSync(path.join(dir,'package.json'),'utf8')).name;
|
|
112
|
+
if(['npm','pnpm','yarn','corepack','@pnpm/exe'].includes(name)||/^@pnpm\/exe\./.test(name))return dir;
|
|
113
|
+
return null;}
|
|
114
|
+
catch{}
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
// A command name looked up along PATH as a shell does: the first executable
|
|
119
|
+
// file. Returns the real path, or null.
|
|
120
|
+
export function lookup(name,searchPath,cwd){
|
|
121
|
+
const candidates=name.includes('/')?[path.resolve(cwd,name)]:
|
|
122
|
+
String(searchPath??'').split(path.delimiter).map(dir=>path.join(path.resolve(cwd,dir||'.'),name));
|
|
123
|
+
for(const file of candidates){
|
|
124
|
+
try{const st=fs.statSync(file);if(st.isFile()&&(st.mode&0o111))return fs.realpathSync(file);}catch{}
|
|
125
|
+
if(name.includes('/'))return null;
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const head=file=>{
|
|
130
|
+
let fd;try{fd=fs.openSync(file,'r');const buffer=Buffer.alloc(512);return buffer.subarray(0,fs.readSync(fd,buffer,0,512,0));}
|
|
131
|
+
catch{return null;}finally{if(fd!==undefined)fs.closeSync(fd);}
|
|
132
|
+
};
|
|
133
|
+
const nativeMagic=buffer=>buffer?.length>=4&&(buffer[0]===0x7f&&buffer.toString('latin1',1,4)==='ELF'||
|
|
134
|
+
[0xfeedface,0xfeedfacf,0xcefaedfe,0xcffaedfe,0xcafebabe].includes(buffer.readUInt32BE(0)));
|
|
135
|
+
// The pnpm executable: a native file named pnpm, pn or pnpm-native in the pnpm
|
|
136
|
+
// package (where Corepack downloads it beside pnpm's Node entry) or in an
|
|
137
|
+
// @pnpm/exe package. Returns its version, or null.
|
|
138
|
+
export function pnpmExecutable(file){
|
|
139
|
+
if(!['pnpm','pn','pnpm-native'].includes(path.basename(file))||!nativeMagic(head(file)))return null;
|
|
140
|
+
const dir=packageDir(file);
|
|
141
|
+
try{const pkg=JSON.parse(fs.readFileSync(path.join(dir,'package.json'),'utf8'));
|
|
142
|
+
return pkg.name!=='npm'&&pkg.name!=='yarn'&&pkg.name!=='corepack'&&typeof pkg.version==='string'?pkg.version:null;}
|
|
143
|
+
catch{return null;}
|
|
144
|
+
}
|
|
145
|
+
// Yarn 2 and later run a package's bin through a wrapper they write into a
|
|
146
|
+
// temporary directory: `exec "NODE" 'BIN' "$@"`.
|
|
147
|
+
const yarnWrapper=/^#!\/bin\/sh\nexec "([^"$`\\\n]+)" ((?:'[^'\n]*'(?: '[^'\n]*')*)?) "\$@"\n$/;
|
|
148
|
+
export const yarnWrapperNode=text=>{const match=yarnWrapper.exec(text);return match?match[1]:null;};
|
|
149
|
+
// What a file run as a program is: 'node' for Node itself, a script Node runs
|
|
150
|
+
// by its #! line, a pnpm launcher or a Yarn bin wrapper; 'pnpm' for the pnpm
|
|
151
|
+
// executable; else null. `text` is the script as the run saw it, when known.
|
|
152
|
+
export function programKind(file,known=null){
|
|
153
|
+
const base=path.basename(file),bytes=known!==null?Buffer.from(known):head(file);
|
|
154
|
+
if(!bytes)return null;
|
|
155
|
+
// Node itself is a native executable; a shim named node (asdf, nodenv) is a script.
|
|
156
|
+
if(base==='node'||base==='nodejs')return nativeMagic(bytes)?'node':null;
|
|
157
|
+
const text=bytes.toString('latin1');
|
|
158
|
+
if(text.startsWith('#!')){
|
|
159
|
+
const line=text.slice(2).split('\n')[0].trim().split(/\s+/);
|
|
160
|
+
// #!/usr/bin/env node, #!/usr/bin/env -S node --flag, or a path to node.
|
|
161
|
+
const words=path.basename(line[0]||'')==='env'?line.slice(1).filter(word=>word!=='-S'):line;
|
|
162
|
+
if(['node','nodejs'].includes(path.basename(words[0]||'')))return 'node';
|
|
163
|
+
if(!text.startsWith('#!/bin/sh\n'))return null;
|
|
164
|
+
let script=known;if(script===null)try{script=fs.readFileSync(file,'utf8');}catch{return null;}
|
|
165
|
+
if(pnpmShimTarget(file,script))return 'node';
|
|
166
|
+
const node=yarnWrapperNode(script),real=node&&lookup(node,'','/');
|
|
167
|
+
return real&&['node','nodejs'].includes(path.basename(node))&&programKind(real)==='node'?'node':null;
|
|
168
|
+
}
|
|
169
|
+
return pnpmExecutable(file)?'pnpm':null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// A shell command line as simple commands of words, or {issue}. It accepts
|
|
173
|
+
// words, single and double quotes without expansions, &&, ||, ;, | and
|
|
174
|
+
// redirections to /dev/null or between descriptors. Anything that could
|
|
175
|
+
// change which program runs or read a file unseen (variables, command
|
|
176
|
+
// substitution, input redirection, subshells, here-documents) is refused.
|
|
177
|
+
export function parseCommandLine(text){
|
|
178
|
+
const unsupported={issue:'the script uses shell syntax REDUE cannot read'};
|
|
179
|
+
const commands=[];let words=[],word=null,glob=false;
|
|
180
|
+
const end=()=>{if(word!==null){words.push({text:word,glob});word=null;glob=false;}};
|
|
181
|
+
const separate=()=>{end();if(words.length)commands.push(words);words=[];};
|
|
182
|
+
for(let i=0;i<text.length;i++){
|
|
183
|
+
const c=text[i],rest=text.slice(i);
|
|
184
|
+
if(c===' '||c==='\t'){end();continue;}
|
|
185
|
+
if(c==='\n'||c===';'){separate();continue;}
|
|
186
|
+
if(rest.startsWith('&&')||rest.startsWith('||')){separate();i++;continue;}
|
|
187
|
+
if(c==='|'){separate();continue;}
|
|
188
|
+
if(word===null){
|
|
189
|
+
const redirect=/^(?:\d?>&\d|&?\d?>>? *\/dev\/null)(?=[\s;&|]|$)/.exec(rest);
|
|
190
|
+
if(redirect){i+=redirect[0].length-1;continue;}
|
|
191
|
+
if(c==='#'||c==='~'||c==='!')return unsupported;
|
|
192
|
+
}
|
|
193
|
+
if(c==="'"){const close=text.indexOf("'",i+1);if(close<0)return unsupported;word=(word??'')+text.slice(i+1,close);i=close;continue;}
|
|
194
|
+
if(c==='"'){
|
|
195
|
+
const close=text.indexOf('"',i+1);
|
|
196
|
+
if(close<0||/[$`\\]/.test(text.slice(i+1,close)))return unsupported;
|
|
197
|
+
word=(word??'')+text.slice(i+1,close);i=close;continue;
|
|
198
|
+
}
|
|
199
|
+
if('$`<>(){}\\&'.includes(c))return unsupported;
|
|
200
|
+
if('*?['.includes(c))glob=true;
|
|
201
|
+
word=(word??'')+c;
|
|
202
|
+
}
|
|
203
|
+
separate();
|
|
204
|
+
return {commands};
|
|
205
|
+
}
|
|
206
|
+
// Variables a script may not set or clear: they would stop Node loading the
|
|
207
|
+
// recorder (npm and pnpm set NODE_OPTIONS from their node-options setting),
|
|
208
|
+
// or change which program a name runs.
|
|
209
|
+
const guarded=new Set(['NODE_OPTIONS','REDUE_RUN_RECORD','PATH','NPM_CONFIG_NODE_OPTIONS','PNPM_CONFIG_NODE_OPTIONS',
|
|
210
|
+
'NPM_CONFIG_SCRIPT_SHELL','PNPM_CONFIG_SCRIPT_SHELL','BASH_ENV','ENV']);
|
|
211
|
+
const isGuarded=name=>guarded.has(name.toUpperCase().replace(/-/g,'_'));
|
|
212
|
+
// Node options that make Node read a file natively, where the recorder cannot see it.
|
|
213
|
+
const nativeReads=/^--(?:env-file|env-file-if-exists|experimental-config-file|experimental-default-config-file)(?:=|$)/;
|
|
214
|
+
const builtins=new Set(['cd','echo','true','false',':','exit','printf','set','export','unset','exec','command','wait','shift']);
|
|
215
|
+
// pnpm commands that are not scripts.
|
|
216
|
+
const pnpmCommands=new Set(['add','audit','bin','config','create','dedupe','deploy','dlx','doctor','env','fetch','i','import',
|
|
217
|
+
'init','install','install-test','it','link','list','ln','ls','outdated','pack','patch','prune','publish','rebuild',
|
|
218
|
+
'recursive','remove','rm','root','self-update','server','setup','store','uninstall','unlink','up','update','why','x']);
|
|
219
|
+
// The PATH a package manager gives a script in `dir`: every node_modules/.bin
|
|
220
|
+
// from the directory up, then the caller's.
|
|
221
|
+
export const scriptPath=(dir,callerPath)=>{
|
|
222
|
+
const bins=[];for(let at=path.resolve(dir);;at=path.dirname(at)){bins.push(path.join(at,'node_modules','.bin'));if(path.dirname(at)===at)break;}
|
|
223
|
+
return [...bins,callerPath??''].join(path.delimiter);
|
|
224
|
+
};
|
|
225
|
+
// Why a shell command line could start a program REDUE does not record, or
|
|
226
|
+
// null. Names are looked up along `searchPath` from `cwd`. A script the pnpm
|
|
227
|
+
// executable runs is read the same way, since the recorder cannot see it.
|
|
228
|
+
export function commandLineIssue(text,{cwd,searchPath,depth=0}={}){
|
|
229
|
+
if(depth>8)return 'the scripts run each other too deeply for REDUE to read';
|
|
230
|
+
const parsed=parseCommandLine(text);
|
|
231
|
+
if(parsed.issue)return parsed.issue;
|
|
232
|
+
let dir=cwd;
|
|
233
|
+
for(let words of parsed.commands){
|
|
234
|
+
words=[...words];
|
|
235
|
+
while(words.length&&/^[A-Za-z_][A-Za-z0-9_]*=/.test(words[0].text)){
|
|
236
|
+
const name=words.shift().text.split('=')[0];
|
|
237
|
+
if(isGuarded(name))return `the script sets ${name}`;
|
|
238
|
+
}
|
|
239
|
+
for(;;){
|
|
240
|
+
if(!words.length)break;
|
|
241
|
+
const [first,...args]=words,name=first.text;
|
|
242
|
+
if(first.glob)return 'the script uses shell syntax REDUE cannot read';
|
|
243
|
+
if(builtins.has(name)){
|
|
244
|
+
if(name==='export'||name==='unset'){
|
|
245
|
+
const names=args.map(arg=>arg.text.split('=')[0]);
|
|
246
|
+
const set=names.find(isGuarded);
|
|
247
|
+
if(set)return `the script ${name==='unset'?'clears':'sets'} ${set}`;
|
|
248
|
+
}
|
|
249
|
+
if(name==='cd'){if(args.length===1&&!args[0].glob)dir=path.resolve(dir,args[0].text);else return 'the script uses shell syntax REDUE cannot read';}
|
|
250
|
+
// `exec X` and `command X` run X; `command -v X` only looks it up;
|
|
251
|
+
// `command -p X` runs X from a default PATH.
|
|
252
|
+
if(name==='command'&&args[0]?.text==='-p')return 'the script uses shell syntax REDUE cannot read';
|
|
253
|
+
if(name==='exec'||name==='command'&&args.length&&!args[0].text.startsWith('-')){words=args;continue;}
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
const file=lookup(name,searchPath,dir);
|
|
257
|
+
if(!file)return `runs ${name}, which REDUE cannot find`;
|
|
258
|
+
const kind=programKind(file);
|
|
259
|
+
if(kind==='node'){
|
|
260
|
+
const flag=['node','nodejs'].includes(path.basename(file))&&args.find(arg=>nativeReads.test(arg.text));
|
|
261
|
+
if(flag)return `runs node ${flag.text.split('=')[0]}, which reads a file REDUE cannot see`;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
if(kind==='pnpm'){const issue=pnpmRunIssue(args.map(arg=>arg.text),{cwd:dir,searchPath,depth});if(issue)return issue;break;}
|
|
265
|
+
return `runs ${path.basename(name)}, which is not a Node program`;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
// What the pnpm executable runs for `args` in `cwd`: a package script, or a
|
|
271
|
+
// bin with `exec`. Each script it runs is read as text; pnpm's own commands,
|
|
272
|
+
// filters and recursive runs are refused.
|
|
273
|
+
export function pnpmRunIssue(args,{cwd,searchPath,depth=0}){
|
|
274
|
+
const words=args.filter(arg=>!['--silent','-s','--reporter=silent'].includes(arg));
|
|
275
|
+
if(words[0]&&words[0].startsWith('-'))return `runs pnpm ${words[0]}, which REDUE cannot follow`;
|
|
276
|
+
let command=words[0]==='t'?'test':words[0],rest=words.slice(1);
|
|
277
|
+
if(command==='run'){command=rest[0];rest=rest.slice(1);if(!command)return 'runs pnpm run without a script';}
|
|
278
|
+
else if(command==='exec'){
|
|
279
|
+
if(!rest[0]||rest[0].startsWith('-'))return 'runs pnpm exec in a form REDUE cannot follow';
|
|
280
|
+
return commandLineIssue(rest.map(quote).join(' '),{cwd,searchPath:scriptPath(cwd,searchPath),depth:depth+1});
|
|
281
|
+
}else if(!command||pnpmCommands.has(command))return `runs pnpm ${command||''}, which REDUE cannot follow`.replace(' ,',',');
|
|
282
|
+
let scripts;
|
|
283
|
+
try{scripts=JSON.parse(fs.readFileSync(path.join(cwd,'package.json'),'utf8')).scripts||{};}
|
|
284
|
+
catch{return `runs pnpm ${command} where REDUE cannot read package.json`;}
|
|
285
|
+
const inner={cwd,searchPath:scriptPath(cwd,searchPath),depth:depth+1};
|
|
286
|
+
if(typeof scripts[command]!=='string'){
|
|
287
|
+
// `pnpm NAME` with no such script runs the bin NAME.
|
|
288
|
+
if(words[0]==='run')return `runs pnpm run ${command}, a script package.json does not define`;
|
|
289
|
+
return commandLineIssue([command,...rest].map(quote).join(' '),inner);
|
|
290
|
+
}
|
|
291
|
+
for(const name of ['pre'+command,command,'post'+command]){
|
|
292
|
+
if(typeof scripts[name]!=='string')continue;
|
|
293
|
+
const issue=commandLineIssue(name===command&&rest.length?`${scripts[name]} ${rest.map(quote).join(' ')}`:scripts[name],inner);
|
|
294
|
+
if(issue)return issue;
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
const quote=word=>/^[A-Za-z0-9_./:=,@+%^-]+$/.test(word)?word:`'${word.replace(/'/g,'')}'`;
|
|
299
|
+
const shells=new Set(['sh','bash','dash','zsh','ksh','mksh','fish','csh','tcsh']);
|
|
300
|
+
// Why a program a recorded process started (a C or S row) is not accounted
|
|
301
|
+
// for, or null; `needsHeader` says whether it must have loaded the recorder,
|
|
302
|
+
// and `opaque` that REDUE cannot see what it starts (the pnpm executable).
|
|
303
|
+
// `env` is what the child's environment says (test-recorder.cjs): a program
|
|
304
|
+
// that needs no recorder itself must pass it on. `toolchain` holds the
|
|
305
|
+
// package managers' directories; `parentMain` is the starting process's main
|
|
306
|
+
// script.
|
|
307
|
+
export function childProgram({file,args,cwd,searchPath,env=null,script=null},{toolchain=[],parentMain=null}={}){
|
|
308
|
+
const resolved=script?.file??lookup(file,searchPath,cwd||process.cwd());
|
|
309
|
+
const name=path.basename(file);
|
|
310
|
+
if(!resolved)return {issue:`ran ${name}, which REDUE cannot find`};
|
|
311
|
+
const passesRecorder=()=>env?.preload===true?null:
|
|
312
|
+
{issue:`ran ${name} without REDUE's recorder in NODE_OPTIONS (a node-options setting replaces it)`};
|
|
313
|
+
if(shells.has(path.basename(resolved))||shells.has(name)){
|
|
314
|
+
// A non-interactive sh or dash reads no startup file; bash -c sources
|
|
315
|
+
// BASH_ENV; zsh and the others always read their own files.
|
|
316
|
+
if(!['sh','dash','bash'].includes(name)||name==='bash'&&env?.bashEnv!==false)
|
|
317
|
+
return {issue:`ran ${name}, a shell that reads startup files REDUE cannot see`};
|
|
318
|
+
if(!Array.isArray(args)||args[0]!=='-c'||typeof args[1]!=='string')return {issue:`ran ${name} in a form REDUE cannot read`};
|
|
319
|
+
const issue=commandLineIssue(args[1],{cwd,searchPath});
|
|
320
|
+
return issue?{issue}:passesRecorder()??{needsHeader:false};
|
|
321
|
+
}
|
|
322
|
+
const kind=programKind(resolved,typeof script?.text==='string'?script.text:null);
|
|
323
|
+
if(kind==='node')return {needsHeader:true};
|
|
324
|
+
if(kind==='pnpm'){const issue=pnpmRunIssue(args||[],{cwd,searchPath});return issue?{issue}:passesRecorder()??{needsHeader:false,opaque:true};}
|
|
325
|
+
// pnpm, loaded into Corepack, lists processes to stop a script's children.
|
|
326
|
+
const within=(item,dir)=>item===dir||item.startsWith(dir+path.sep);
|
|
327
|
+
let main=null;try{main=parentMain&&fs.realpathSync(parentMain);}catch{}
|
|
328
|
+
if(name==='ps'&&JSON.stringify(args)===JSON.stringify(['-A','-o','ppid,pid'])&&main&&toolchain.some(dir=>within(main,dir)))
|
|
329
|
+
return {needsHeader:false};
|
|
330
|
+
return {issue:`ran ${name}, a program REDUE does not record`};
|
|
331
|
+
}
|
|
332
|
+
// What a recorded run needs from the check: the workspace package directories
|
|
333
|
+
// whose node_modules its installed inputs list. The script must still exist.
|
|
334
|
+
export function scriptRunFacts(root,check){
|
|
335
|
+
const dirs=(check.installedInputs||[]).map(p=>/^([^!*]+)\/node_modules\/\*\*$/.exec(p)?.[1]).filter(Boolean);
|
|
336
|
+
const pkg=JSON.parse(fs.readFileSync(path.join(root,'package.json'),'utf8'));
|
|
337
|
+
if(typeof pkg.scripts?.[check.script]!=='string')fail('script-changed');
|
|
338
|
+
return {dirs};
|
|
339
|
+
}
|
|
340
|
+
// Plan qualification: the check is what `init` writes for the project now.
|
|
341
|
+
export function qualifyNodeScript(root,selected){
|
|
342
|
+
const issues=[];
|
|
343
|
+
if(!['darwin','linux'].includes(process.platform))issues.push('the general recipe records runs on macOS and Linux only');
|
|
344
|
+
try{
|
|
345
|
+
if(selected.cwd&&selected.cwd!=='.')fail('the general recipe runs scripts at the project root');
|
|
346
|
+
const project=scriptProject(root,selected.script);
|
|
347
|
+
if(JSON.stringify(selected.command?.slice(1))!==JSON.stringify(['run',selected.script])||
|
|
348
|
+
selected.environment?.pathExecutables?.[1]!==project.manager)
|
|
349
|
+
issues.push('the package manager or script changed; run redue init again');
|
|
350
|
+
const expected=scriptCheckInputs(project.dirs),sorted=list=>JSON.stringify([...(list||[])].sort());
|
|
351
|
+
if(sorted(selected.inputs)!==sorted(expected.inputs)||sorted(selected.installedInputs)!==sorted(expected.installedInputs))
|
|
352
|
+
issues.push('inputs must be the whole checkout, with every node_modules observed as installed files; run redue init again');
|
|
353
|
+
const environment=scriptEnvironment(project.manager);
|
|
354
|
+
if(!selected.environment?.executableIdentity||
|
|
355
|
+
!environment.variables.every(key=>selected.environment?.variables?.includes(key))||
|
|
356
|
+
!environment.prefixes.every(key=>selected.environment?.prefixes?.includes(key)))
|
|
357
|
+
issues.push('execution context incomplete; run redue init again');
|
|
358
|
+
}catch(e){issues.push(e.code||'script prerequisites unavailable');}
|
|
359
|
+
if(selected.probes?.length)issues.push('the general recipe takes no probes');
|
|
360
|
+
if(selected.generatedInputs?.length)issues.push('the general recipe takes no generated inputs');
|
|
361
|
+
return {qualified:issues.length===0,issues};
|
|
362
|
+
}
|
|
@@ -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
|
+
}
|