UNPKG

16.1 kBJavaScriptView Raw
1'use strict';
2cmdShim.ifExists = cmdShimIfExists;
3const util_1 = require("util");
4const path = require("path");
5const isWindows = require("is-windows");
6const CMD_EXTENSION = require("cmd-extension");
7const shebangExpr = /^#!\s*(?:\/usr\/bin\/env)?\s*([^ \t]+)(.*)$/;
8const DEFAULT_OPTIONS = {
9 // Create PowerShell file by default if the option hasn't been specified
10 createPwshFile: true,
11 createCmdFile: isWindows(),
12 fs: require('fs')
13};
14/**
15 * Map from extensions of files that this module is frequently used for to their runtime.
16 * @type {Map<string, string>}
17 */
18const extensionToProgramMap = new Map([
19 ['.js', 'node'],
20 ['.cjs', 'node'],
21 ['.mjs', 'node'],
22 ['.cmd', 'cmd'],
23 ['.bat', 'cmd'],
24 ['.ps1', 'pwsh'],
25 ['.sh', 'sh']
26]);
27function ingestOptions(opts) {
28 const opts_ = { ...DEFAULT_OPTIONS, ...opts };
29 const fs = opts_.fs;
30 opts_.fs_ = {
31 chmod: fs.chmod ? util_1.promisify(fs.chmod) : (async () => { }),
32 mkdir: util_1.promisify(fs.mkdir),
33 readFile: util_1.promisify(fs.readFile),
34 stat: util_1.promisify(fs.stat),
35 unlink: util_1.promisify(fs.unlink),
36 writeFile: util_1.promisify(fs.writeFile)
37 };
38 return opts_;
39}
40/**
41 * Try to create shims.
42 *
43 * @param src Path to program (executable or script).
44 * @param to Path to shims.
45 * Don't add an extension if you will create multiple types of shims.
46 * @param opts Options.
47 * @throws If `src` is missing.
48 */
49async function cmdShim(src, to, opts) {
50 const opts_ = ingestOptions(opts);
51 await opts_.fs_.stat(src);
52 await cmdShim_(src, to, opts_);
53}
54/**
55 * Try to create shims.
56 *
57 * Does nothing if `src` doesn't exist.
58 *
59 * @param src Path to program (executable or script).
60 * @param to Path to shims.
61 * Don't add an extension if you will create multiple types of shims.
62 * @param opts Options.
63 */
64function cmdShimIfExists(src, to, opts) {
65 return cmdShim(src, to, opts).catch(() => { });
66}
67/**
68 * Try to unlink, but ignore errors.
69 * Any problems will surface later.
70 *
71 * @param path File to be removed.
72 */
73function rm(path, opts) {
74 return opts.fs_.unlink(path).catch(() => { });
75}
76/**
77 * Try to create shims **even if `src` is missing**.
78 *
79 * @param src Path to program (executable or script).
80 * @param to Path to shims.
81 * Don't add an extension if you will create multiple types of shims.
82 * @param opts Options.
83 */
84async function cmdShim_(src, to, opts) {
85 const srcRuntimeInfo = await searchScriptRuntime(src, opts);
86 // Always tries to create all types of shims by calling `writeAllShims` as of now.
87 // Append your code here to change the behavior in response to `srcRuntimeInfo`.
88 // Create 3 shims for (Ba)sh in Cygwin / MSYS, no extension) & CMD (.cmd) & PowerShell (.ps1)
89 await writeShimsPreCommon(to, opts);
90 return writeAllShims(src, to, srcRuntimeInfo, opts);
91}
92/**
93 * Do processes before **all** shims are created.
94 * This must be called **only once** for one call of `cmdShim(IfExists)`.
95 *
96 * @param target Path of shims that are going to be created.
97 */
98function writeShimsPreCommon(target, opts) {
99 return opts.fs_.mkdir(path.dirname(target), { recursive: true });
100}
101/**
102 * Write all types (sh & cmd & pwsh) of shims to files.
103 * Extensions (`.cmd` and `.ps1`) are appended to cmd and pwsh shims.
104 *
105 *
106 * @param src Path to program (executable or script).
107 * @param to Path to shims **without extensions**.
108 * Extensions are added for CMD and PowerShell shims.
109 * @param srcRuntimeInfo Return value of `await searchScriptRuntime(src)`.
110 * @param opts Options.
111 */
112function writeAllShims(src, to, srcRuntimeInfo, opts) {
113 const opts_ = ingestOptions(opts);
114 const generatorAndExts = [{ generator: generateShShim, extension: '' }];
115 if (opts_.createCmdFile) {
116 generatorAndExts.push({ generator: generateCmdShim, extension: CMD_EXTENSION });
117 }
118 if (opts_.createPwshFile) {
119 generatorAndExts.push({ generator: generatePwshShim, extension: '.ps1' });
120 }
121 return Promise.all(generatorAndExts.map((generatorAndExt) => writeShim(src, to + generatorAndExt.extension, srcRuntimeInfo, generatorAndExt.generator, opts_)));
122}
123/**
124 * Do processes before writing shim.
125 *
126 * @param target Path to shim that is going to be created.
127 */
128function writeShimPre(target, opts) {
129 return rm(target, opts);
130}
131/**
132 * Do processes after writing the shim.
133 *
134 * @param target Path to just created shim.
135 */
136function writeShimPost(target, opts) {
137 // Only chmoding shims as of now.
138 // Some other processes may be appended.
139 return chmodShim(target, opts);
140}
141/**
142 * Look into runtime (e.g. `node` & `sh` & `pwsh`) and its arguments
143 * of the target program (script or executable).
144 *
145 * @param target Path to the executable or script.
146 * @return Promise of infomation of runtime of `target`.
147 */
148async function searchScriptRuntime(target, opts) {
149 const data = await opts.fs_.readFile(target, 'utf8');
150 // First, check if the bin is a #! of some sort.
151 const firstLine = data.trim().split(/\r*\n/)[0];
152 const shebang = firstLine.match(shebangExpr);
153 if (!shebang) {
154 // If not, infer script type from its extension.
155 // If the inference fails, it's something that'll be compiled, or some other
156 // sort of script, and just call it directly.
157 const targetExtension = path.extname(target).toLowerCase();
158 return {
159 // undefined if extension is unknown but it's converted to null.
160 program: extensionToProgramMap.get(targetExtension) || null,
161 additionalArgs: ''
162 };
163 }
164 return {
165 program: shebang[1],
166 additionalArgs: shebang[2]
167 };
168}
169/**
170 * Write shim to the file system while executing the pre- and post-processes
171 * defined in `WriteShimPre` and `WriteShimPost`.
172 *
173 * @param src Path to the executable or script.
174 * @param to Path to the (sh) shim(s) that is going to be created.
175 * @param srcRuntimeInfo Result of `await searchScriptRuntime(src)`.
176 * @param generateShimScript Generator of shim script.
177 * @param opts Other options.
178 */
179async function writeShim(src, to, srcRuntimeInfo, generateShimScript, opts) {
180 const defaultArgs = opts.preserveSymlinks ? '--preserve-symlinks' : '';
181 // `Array.prototype.filter` removes ''.
182 // ['--foo', '--bar'].join(' ') and [].join(' ') returns '--foo --bar' and '' respectively.
183 const args = [srcRuntimeInfo.additionalArgs, defaultArgs].filter(arg => arg).join(' ');
184 opts = Object.assign({}, opts, {
185 prog: srcRuntimeInfo.program,
186 args: args
187 });
188 await writeShimPre(to, opts);
189 await opts.fs_.writeFile(to, generateShimScript(src, to, opts), 'utf8');
190 return writeShimPost(to, opts);
191}
192/**
193 * Generate the content of a shim for CMD.
194 *
195 * @param src Path to the executable or script.
196 * @param to Path to the shim to be created.
197 * It is highly recommended to end with `.cmd` (or `.bat`).
198 * @param opts Options.
199 * @return The content of shim.
200 */
201function generateCmdShim(src, to, opts) {
202 // `shTarget` is not used to generate the content.
203 const shTarget = path.relative(path.dirname(to), src);
204 let target = shTarget.split('/').join('\\');
205 const quotedPathToTarget = path.isAbsolute(target) ? `"${target}"` : `"%~dp0\\${target}"`;
206 let longProg;
207 let prog = opts.prog;
208 let args = opts.args || '';
209 const nodePath = normalizePathEnvVar(opts.nodePath).win32;
210 if (!prog) {
211 prog = quotedPathToTarget;
212 args = '';
213 target = '';
214 }
215 else if (prog === 'node' && opts.nodeExecPath) {
216 prog = `"${opts.nodeExecPath}"`;
217 target = quotedPathToTarget;
218 }
219 else {
220 longProg = `"%~dp0\\${prog}.exe"`;
221 target = quotedPathToTarget;
222 }
223 let progArgs = opts.progArgs ? `${opts.progArgs.join(` `)} ` : '';
224 // @IF EXIST "%~dp0\node.exe" (
225 // "%~dp0\node.exe" "%~dp0\.\node_modules\npm\bin\npm-cli.js" %*
226 // ) ELSE (
227 // SETLOCAL
228 // SET PATHEXT=%PATHEXT:;.JS;=;%
229 // node "%~dp0\.\node_modules\npm\bin\npm-cli.js" %*
230 // )
231 let cmd = '@SETLOCAL\r\n';
232 if (nodePath) {
233 cmd += `\
234@IF NOT DEFINED NODE_PATH (\r
235 @SET "NODE_PATH=${nodePath}"\r
236) ELSE (\r
237 @SET "NODE_PATH=%NODE_PATH%;${nodePath}"\r
238)\r
239`;
240 }
241 if (longProg) {
242 cmd += `\
243@IF EXIST ${longProg} (\r
244 ${longProg} ${args} ${target} ${progArgs}%*\r
245) ELSE (\r
246 @SET PATHEXT=%PATHEXT:;.JS;=;%\r
247 ${prog} ${args} ${target} ${progArgs}%*\r
248)\r
249`;
250 }
251 else {
252 cmd += `@${prog} ${args} ${target} ${progArgs}%*\r\n`;
253 }
254 return cmd;
255}
256/**
257 * Generate the content of a shim for (Ba)sh in, for example, Cygwin and MSYS(2).
258 *
259 * @param src Path to the executable or script.
260 * @param to Path to the shim to be created.
261 * It is highly recommended to end with `.sh` or to contain no extension.
262 * @param opts Options.
263 * @return The content of shim.
264 */
265function generateShShim(src, to, opts) {
266 let shTarget = path.relative(path.dirname(to), src);
267 let shProg = opts.prog && opts.prog.split('\\').join('/');
268 let shLongProg;
269 shTarget = shTarget.split('\\').join('/');
270 const quotedPathToTarget = path.isAbsolute(shTarget) ? `"${shTarget}"` : `"$basedir/${shTarget}"`;
271 let args = opts.args || '';
272 const shNodePath = normalizePathEnvVar(opts.nodePath).posix;
273 if (!shProg) {
274 shProg = quotedPathToTarget;
275 args = '';
276 shTarget = '';
277 }
278 else if (opts.prog === 'node' && opts.nodeExecPath) {
279 shProg = `"${opts.nodeExecPath}"`;
280 shTarget = quotedPathToTarget;
281 }
282 else {
283 shLongProg = `"$basedir/${opts.prog}"`;
284 shTarget = quotedPathToTarget;
285 }
286 let progArgs = opts.progArgs ? `${opts.progArgs.join(` `)} ` : '';
287 // #!/bin/sh
288 // basedir=`dirname "$0"`
289 //
290 // case `uname` in
291 // *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
292 // esac
293 //
294 // export NODE_PATH="<nodepath>"
295 //
296 // if [ -x "$basedir/node.exe" ]; then
297 // exec "$basedir/node.exe" "$basedir/node_modules/npm/bin/npm-cli.js" "$@"
298 // else
299 // exec node "$basedir/node_modules/npm/bin/npm-cli.js" "$@"
300 // fi
301 let sh = `\
302#!/bin/sh
303basedir=$(dirname "$(echo "$0" | sed -e 's,\\\\,/,g')")
304
305case \`uname\` in
306 *CYGWIN*) basedir=\`cygpath -w "$basedir"\`;;
307esac
308
309`;
310 if (opts.nodePath) {
311 sh += `\
312if [ -z "$NODE_PATH" ]; then
313 export NODE_PATH="${shNodePath}"
314else
315 export NODE_PATH="$NODE_PATH:${shNodePath}"
316fi
317`;
318 }
319 if (shLongProg) {
320 sh += `\
321if [ -x ${shLongProg} ]; then
322 exec ${shLongProg} ${args} ${shTarget} ${progArgs}"$@"
323else
324 exec ${shProg} ${args} ${shTarget} ${progArgs}"$@"
325fi
326`;
327 }
328 else {
329 sh += `\
330${shProg} ${args} ${shTarget} ${progArgs}"$@"
331exit $?
332`;
333 }
334 return sh;
335}
336/**
337 * Generate the content of a shim for PowerShell.
338 *
339 * @param src Path to the executable or script.
340 * @param to Path to the shim to be created.
341 * It is highly recommended to end with `.ps1`.
342 * @param opts Options.
343 * @return The content of shim.
344 */
345function generatePwshShim(src, to, opts) {
346 let shTarget = path.relative(path.dirname(to), src);
347 const shProg = opts.prog && opts.prog.split('\\').join('/');
348 let pwshProg = shProg && `"${shProg}$exe"`;
349 let pwshLongProg;
350 shTarget = shTarget.split('\\').join('/');
351 const quotedPathToTarget = path.isAbsolute(shTarget) ? `"${shTarget}"` : `"$basedir/${shTarget}"`;
352 let args = opts.args || '';
353 let normalizedPathEnvVar = normalizePathEnvVar(opts.nodePath);
354 const nodePath = normalizedPathEnvVar.win32;
355 const shNodePath = normalizedPathEnvVar.posix;
356 if (!pwshProg) {
357 pwshProg = quotedPathToTarget;
358 args = '';
359 shTarget = '';
360 }
361 else if (opts.prog === 'node' && opts.nodeExecPath) {
362 pwshProg = `"${opts.nodeExecPath}"`;
363 shTarget = quotedPathToTarget;
364 }
365 else {
366 pwshLongProg = `"$basedir/${opts.prog}$exe"`;
367 shTarget = quotedPathToTarget;
368 }
369 let progArgs = opts.progArgs ? `${opts.progArgs.join(` `)} ` : '';
370 // #!/usr/bin/env pwsh
371 // $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
372 //
373 // $ret=0
374 // $exe = ""
375 // if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
376 // # Fix case when both the Windows and Linux builds of Node
377 // # are installed in the same directory
378 // $exe = ".exe"
379 // }
380 // if (Test-Path "$basedir/node") {
381 // # Support pipeline input
382 // if ($MyInvocation.ExpectingInput) {
383 // $input | & "$basedir/node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
384 // } else {
385 // & "$basedir/node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
386 // }
387 // $ret=$LASTEXITCODE
388 // } else {
389 // # Support pipeline input
390 // if ($MyInvocation.ExpectingInput) {
391 // $input | & "node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
392 // } else {
393 // & "node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
394 // }
395 // $ret=$LASTEXITCODE
396 // }
397 // exit $ret
398 let pwsh = `\
399#!/usr/bin/env pwsh
400$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
401
402$exe=""
403${opts.nodePath ? `\
404$pathsep=":"
405$env_node_path=$env:NODE_PATH
406$new_node_path="${nodePath}"
407` : ''}\
408if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
409 # Fix case when both the Windows and Linux builds of Node
410 # are installed in the same directory
411 $exe=".exe"
412${opts.nodePath ? ' $pathsep=";"\n' : ''}\
413}`;
414 if (opts.nodePath) {
415 pwsh += `\
416 else {
417 $new_node_path="${shNodePath}"
418}
419if ([string]::IsNullOrEmpty($env_node_path)) {
420 $env:NODE_PATH=$new_node_path
421} else {
422 $env:NODE_PATH="$env_node_path$pathsep$new_node_path"
423}
424`;
425 }
426 if (pwshLongProg) {
427 pwsh += `
428$ret=0
429if (Test-Path ${pwshLongProg}) {
430 # Support pipeline input
431 if ($MyInvocation.ExpectingInput) {
432 $input | & ${pwshLongProg} ${args} ${shTarget} ${progArgs}$args
433 } else {
434 & ${pwshLongProg} ${args} ${shTarget} ${progArgs}$args
435 }
436 $ret=$LASTEXITCODE
437} else {
438 # Support pipeline input
439 if ($MyInvocation.ExpectingInput) {
440 $input | & ${pwshProg} ${args} ${shTarget} ${progArgs}$args
441 } else {
442 & ${pwshProg} ${args} ${shTarget} ${progArgs}$args
443 }
444 $ret=$LASTEXITCODE
445}
446${opts.nodePath ? '$env:NODE_PATH=$env_node_path\n' : ''}\
447exit $ret
448`;
449 }
450 else {
451 pwsh += `
452# Support pipeline input
453if ($MyInvocation.ExpectingInput) {
454 $input | & ${pwshProg} ${args} ${shTarget} ${progArgs}$args
455} else {
456 & ${pwshProg} ${args} ${shTarget} ${progArgs}$args
457}
458${opts.nodePath ? '$env:NODE_PATH=$env_node_path\n' : ''}\
459exit $LASTEXITCODE
460`;
461 }
462 return pwsh;
463}
464/**
465 * Chmod just created shim and make it executable
466 *
467 * @param to Path to shim.
468 */
469function chmodShim(to, opts) {
470 return opts.fs_.chmod(to, 0o755);
471}
472function normalizePathEnvVar(nodePath) {
473 if (!nodePath) {
474 return {
475 win32: '',
476 posix: ''
477 };
478 }
479 let split = (typeof nodePath === 'string' ? nodePath.split(path.delimiter) : Array.from(nodePath));
480 let result = {};
481 for (let i = 0; i < split.length; i++) {
482 const win32 = split[i].split('/').join('\\');
483 const posix = isWindows() ? split[i].split('\\').join('/').replace(/^([^:\\/]*):/, (_, $1) => `/mnt/${$1.toLowerCase()}`) : split[i];
484 result.win32 = result.win32 ? `${result.win32};${win32}` : win32;
485 result.posix = result.posix ? `${result.posix}:${posix}` : posix;
486 result[i] = { win32, posix };
487 }
488 return result;
489}
490module.exports = cmdShim;
491//# sourceMappingURL=index.js.map
\No newline at end of file