1 | 'use strict';
|
2 | cmdShim.ifExists = cmdShimIfExists;
|
3 | const util_1 = require("util");
|
4 | const path = require("path");
|
5 | const isWindows = require("is-windows");
|
6 | const CMD_EXTENSION = require("cmd-extension");
|
7 | const shebangExpr = /^#!\s*(?:\/usr\/bin\/env(?:\s+-S\s*)?)?\s*([^ \t]+)(.*)$/;
|
8 | const DEFAULT_OPTIONS = {
|
9 |
|
10 | createPwshFile: true,
|
11 | createCmdFile: isWindows(),
|
12 | fs: require('graceful-fs')
|
13 | };
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | const 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 | ]);
|
27 | function ingestOptions(opts) {
|
28 | const opts_ = { ...DEFAULT_OPTIONS, ...opts };
|
29 | const fs = opts_.fs;
|
30 | opts_.fs_ = {
|
31 | chmod: fs.chmod ? (0, util_1.promisify)(fs.chmod) : (async () => { }),
|
32 | mkdir: (0, util_1.promisify)(fs.mkdir),
|
33 | readFile: (0, util_1.promisify)(fs.readFile),
|
34 | stat: (0, util_1.promisify)(fs.stat),
|
35 | unlink: (0, util_1.promisify)(fs.unlink),
|
36 | writeFile: (0, util_1.promisify)(fs.writeFile)
|
37 | };
|
38 | return opts_;
|
39 | }
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | async function cmdShim(src, to, opts) {
|
50 | const opts_ = ingestOptions(opts);
|
51 | await cmdShim_(src, to, opts_);
|
52 | }
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 | function cmdShimIfExists(src, to, opts) {
|
64 | return cmdShim(src, to, opts).catch(() => { });
|
65 | }
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 | function rm(path, opts) {
|
73 | return opts.fs_.unlink(path).catch(() => { });
|
74 | }
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 | async function cmdShim_(src, to, opts) {
|
84 | const srcRuntimeInfo = await searchScriptRuntime(src, opts);
|
85 |
|
86 |
|
87 |
|
88 | await writeShimsPreCommon(to, opts);
|
89 | return writeAllShims(src, to, srcRuntimeInfo, opts);
|
90 | }
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 | function writeShimsPreCommon(target, opts) {
|
98 | return opts.fs_.mkdir(path.dirname(target), { recursive: true });
|
99 | }
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 | function writeAllShims(src, to, srcRuntimeInfo, opts) {
|
112 | const opts_ = ingestOptions(opts);
|
113 | const generatorAndExts = [{ generator: generateShShim, extension: '' }];
|
114 | if (opts_.createCmdFile) {
|
115 | generatorAndExts.push({ generator: generateCmdShim, extension: CMD_EXTENSION });
|
116 | }
|
117 | if (opts_.createPwshFile) {
|
118 | generatorAndExts.push({ generator: generatePwshShim, extension: '.ps1' });
|
119 | }
|
120 | return Promise.all(generatorAndExts.map((generatorAndExt) => writeShim(src, to + generatorAndExt.extension, srcRuntimeInfo, generatorAndExt.generator, opts_)));
|
121 | }
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 | function writeShimPre(target, opts) {
|
128 | return rm(target, opts);
|
129 | }
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 | function writeShimPost(target, opts) {
|
136 |
|
137 |
|
138 | return chmodShim(target, opts);
|
139 | }
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 | async function searchScriptRuntime(target, opts) {
|
148 | try {
|
149 | const data = await opts.fs_.readFile(target, 'utf8');
|
150 |
|
151 | const firstLine = data.trim().split(/\r*\n/)[0];
|
152 | const shebang = firstLine.match(shebangExpr);
|
153 | if (!shebang) {
|
154 |
|
155 |
|
156 |
|
157 | const targetExtension = path.extname(target).toLowerCase();
|
158 | return {
|
159 |
|
160 | program: extensionToProgramMap.get(targetExtension) || null,
|
161 | additionalArgs: ''
|
162 | };
|
163 | }
|
164 | return {
|
165 | program: shebang[1],
|
166 | additionalArgs: shebang[2]
|
167 | };
|
168 | }
|
169 | catch (err) {
|
170 | if (!isWindows() || err.code !== 'ENOENT')
|
171 | throw err;
|
172 | if (await opts.fs_.stat(`${target}${getExeExtension()}`)) {
|
173 | return {
|
174 | program: null,
|
175 | additionalArgs: '',
|
176 | };
|
177 | }
|
178 | throw err;
|
179 | }
|
180 | }
|
181 | function getExeExtension() {
|
182 | let cmdExtension;
|
183 | if (process.env.PATHEXT) {
|
184 | cmdExtension = process.env.PATHEXT
|
185 | .split(path.delimiter)
|
186 | .find(ext => ext.toLowerCase() === '.exe');
|
187 | }
|
188 | return cmdExtension || '.exe';
|
189 | }
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 |
|
200 | async function writeShim(src, to, srcRuntimeInfo, generateShimScript, opts) {
|
201 | const defaultArgs = opts.preserveSymlinks ? '--preserve-symlinks' : '';
|
202 |
|
203 |
|
204 | const args = [srcRuntimeInfo.additionalArgs, defaultArgs].filter(arg => arg).join(' ');
|
205 | opts = Object.assign({}, opts, {
|
206 | prog: srcRuntimeInfo.program,
|
207 | args: args
|
208 | });
|
209 | await writeShimPre(to, opts);
|
210 | await opts.fs_.writeFile(to, generateShimScript(src, to, opts), 'utf8');
|
211 | return writeShimPost(to, opts);
|
212 | }
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 | function generateCmdShim(src, to, opts) {
|
223 |
|
224 | const shTarget = path.relative(path.dirname(to), src);
|
225 | let target = shTarget.split('/').join('\\');
|
226 | const quotedPathToTarget = path.isAbsolute(target) ? `"${target}"` : `"%~dp0\\${target}"`;
|
227 | let longProg;
|
228 | let prog = opts.prog;
|
229 | let args = opts.args || '';
|
230 | const nodePath = normalizePathEnvVar(opts.nodePath).win32;
|
231 | const prependToPath = normalizePathEnvVar(opts.prependToPath).win32;
|
232 | if (!prog) {
|
233 | prog = quotedPathToTarget;
|
234 | args = '';
|
235 | target = '';
|
236 | }
|
237 | else if (prog === 'node' && opts.nodeExecPath) {
|
238 | prog = `"${opts.nodeExecPath}"`;
|
239 | target = quotedPathToTarget;
|
240 | }
|
241 | else {
|
242 | longProg = `"%~dp0\\${prog}.exe"`;
|
243 | target = quotedPathToTarget;
|
244 | }
|
245 | let progArgs = opts.progArgs ? `${opts.progArgs.join(` `)} ` : '';
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 | let cmd = '@SETLOCAL\r\n';
|
254 | if (prependToPath) {
|
255 | cmd += `@SET "PATH=${prependToPath}:%PATH%"\r\n`;
|
256 | }
|
257 | if (nodePath) {
|
258 | cmd += `\
|
259 | @IF NOT DEFINED NODE_PATH (\r
|
260 | @SET "NODE_PATH=${nodePath}"\r
|
261 | ) ELSE (\r
|
262 | @SET "NODE_PATH=${nodePath};%NODE_PATH%"\r
|
263 | )\r
|
264 | `;
|
265 | }
|
266 | if (longProg) {
|
267 | cmd += `\
|
268 | @IF EXIST ${longProg} (\r
|
269 | ${longProg} ${args} ${target} ${progArgs}%*\r
|
270 | ) ELSE (\r
|
271 | @SET PATHEXT=%PATHEXT:;.JS;=;%\r
|
272 | ${prog} ${args} ${target} ${progArgs}%*\r
|
273 | )\r
|
274 | `;
|
275 | }
|
276 | else {
|
277 | cmd += `@${prog} ${args} ${target} ${progArgs}%*\r\n`;
|
278 | }
|
279 | return cmd;
|
280 | }
|
281 |
|
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 | function generateShShim(src, to, opts) {
|
291 | let shTarget = path.relative(path.dirname(to), src);
|
292 | let shProg = opts.prog && opts.prog.split('\\').join('/');
|
293 | let shLongProg;
|
294 | shTarget = shTarget.split('\\').join('/');
|
295 | const quotedPathToTarget = path.isAbsolute(shTarget) ? `"${shTarget}"` : `"$basedir/${shTarget}"`;
|
296 | let args = opts.args || '';
|
297 | const shNodePath = normalizePathEnvVar(opts.nodePath).posix;
|
298 | if (!shProg) {
|
299 | shProg = quotedPathToTarget;
|
300 | args = '';
|
301 | shTarget = '';
|
302 | }
|
303 | else if (opts.prog === 'node' && opts.nodeExecPath) {
|
304 | shProg = `"${opts.nodeExecPath}"`;
|
305 | shTarget = quotedPathToTarget;
|
306 | }
|
307 | else {
|
308 | shLongProg = `"$basedir/${opts.prog}"`;
|
309 | shTarget = quotedPathToTarget;
|
310 | }
|
311 | let progArgs = opts.progArgs ? `${opts.progArgs.join(` `)} ` : '';
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
324 |
|
325 |
|
326 | let sh = `\
|
327 | #!/bin/sh
|
328 | basedir=$(dirname "$(echo "$0" | sed -e 's,\\\\,/,g')")
|
329 |
|
330 | case \`uname\` in
|
331 | *CYGWIN*) basedir=\`cygpath -w "$basedir"\`;;
|
332 | esac
|
333 |
|
334 | `;
|
335 | if (opts.prependToPath) {
|
336 | sh += `\
|
337 | export PATH="${opts.prependToPath}:$PATH"
|
338 | `;
|
339 | }
|
340 | if (shNodePath) {
|
341 | sh += `\
|
342 | if [ -z "$NODE_PATH" ]; then
|
343 | export NODE_PATH="${shNodePath}"
|
344 | else
|
345 | export NODE_PATH="${shNodePath}:$NODE_PATH"
|
346 | fi
|
347 | `;
|
348 | }
|
349 | if (shLongProg) {
|
350 | sh += `\
|
351 | if [ -x ${shLongProg} ]; then
|
352 | exec ${shLongProg} ${args} ${shTarget} ${progArgs}"$@"
|
353 | else
|
354 | exec ${shProg} ${args} ${shTarget} ${progArgs}"$@"
|
355 | fi
|
356 | `;
|
357 | }
|
358 | else {
|
359 | sh += `\
|
360 | ${shProg} ${args} ${shTarget} ${progArgs}"$@"
|
361 | exit $?
|
362 | `;
|
363 | }
|
364 | return sh;
|
365 | }
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 |
|
372 |
|
373 |
|
374 |
|
375 | function generatePwshShim(src, to, opts) {
|
376 | let shTarget = path.relative(path.dirname(to), src);
|
377 | const shProg = opts.prog && opts.prog.split('\\').join('/');
|
378 | let pwshProg = shProg && `"${shProg}$exe"`;
|
379 | let pwshLongProg;
|
380 | shTarget = shTarget.split('\\').join('/');
|
381 | const quotedPathToTarget = path.isAbsolute(shTarget) ? `"${shTarget}"` : `"$basedir/${shTarget}"`;
|
382 | let args = opts.args || '';
|
383 | let normalizedNodePathEnvVar = normalizePathEnvVar(opts.nodePath);
|
384 | const nodePath = normalizedNodePathEnvVar.win32;
|
385 | const shNodePath = normalizedNodePathEnvVar.posix;
|
386 | let normalizedPrependPathEnvVar = normalizePathEnvVar(opts.prependToPath);
|
387 | const prependPath = normalizedPrependPathEnvVar.win32;
|
388 | const shPrependPath = normalizedPrependPathEnvVar.posix;
|
389 | if (!pwshProg) {
|
390 | pwshProg = quotedPathToTarget;
|
391 | args = '';
|
392 | shTarget = '';
|
393 | }
|
394 | else if (opts.prog === 'node' && opts.nodeExecPath) {
|
395 | pwshProg = `"${opts.nodeExecPath}"`;
|
396 | shTarget = quotedPathToTarget;
|
397 | }
|
398 | else {
|
399 | pwshLongProg = `"$basedir/${opts.prog}$exe"`;
|
400 | shTarget = quotedPathToTarget;
|
401 | }
|
402 | let progArgs = opts.progArgs ? `${opts.progArgs.join(` `)} ` : '';
|
403 |
|
404 |
|
405 |
|
406 |
|
407 |
|
408 |
|
409 |
|
410 |
|
411 |
|
412 |
|
413 |
|
414 |
|
415 |
|
416 |
|
417 |
|
418 |
|
419 |
|
420 |
|
421 |
|
422 |
|
423 |
|
424 |
|
425 |
|
426 |
|
427 |
|
428 |
|
429 |
|
430 |
|
431 | let pwsh = `\
|
432 | #!/usr/bin/env pwsh
|
433 | $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
434 |
|
435 | $exe=""
|
436 | ${(nodePath || prependPath) ? '$pathsep=":"\n' : ''}\
|
437 | ${nodePath ? `\
|
438 | $env_node_path=$env:NODE_PATH
|
439 | $new_node_path="${nodePath}"
|
440 | ` : ''}\
|
441 | ${prependPath ? `\
|
442 | $env_path=$env:PATH
|
443 | $prepend_path="${prependPath}"
|
444 | ` : ''}\
|
445 | if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
446 | # Fix case when both the Windows and Linux builds of Node
|
447 | # are installed in the same directory
|
448 | $exe=".exe"
|
449 | ${(nodePath || prependPath) ? ' $pathsep=";"\n' : ''}\
|
450 | }`;
|
451 | if (shNodePath || shPrependPath) {
|
452 | pwsh += `\
|
453 | else {
|
454 | ${shNodePath ? ` $new_node_path="${shNodePath}"\n` : ''}\
|
455 | ${shPrependPath ? ` $prepend_path="${shPrependPath}"\n` : ''}\
|
456 | }
|
457 | `;
|
458 | }
|
459 | if (shNodePath) {
|
460 | pwsh += `\
|
461 | if ([string]::IsNullOrEmpty($env_node_path)) {
|
462 | $env:NODE_PATH=$new_node_path
|
463 | } else {
|
464 | $env:NODE_PATH="$new_node_path$pathsep$env_node_path"
|
465 | }
|
466 | `;
|
467 | }
|
468 | if (opts.prependToPath) {
|
469 | pwsh += `
|
470 | $env:PATH="$prepend_path$pathsep$env:PATH"
|
471 | `;
|
472 | }
|
473 | if (pwshLongProg) {
|
474 | pwsh += `
|
475 | $ret=0
|
476 | if (Test-Path ${pwshLongProg}) {
|
477 | # Support pipeline input
|
478 | if ($MyInvocation.ExpectingInput) {
|
479 | $input | & ${pwshLongProg} ${args} ${shTarget} ${progArgs}$args
|
480 | } else {
|
481 | & ${pwshLongProg} ${args} ${shTarget} ${progArgs}$args
|
482 | }
|
483 | $ret=$LASTEXITCODE
|
484 | } else {
|
485 | # Support pipeline input
|
486 | if ($MyInvocation.ExpectingInput) {
|
487 | $input | & ${pwshProg} ${args} ${shTarget} ${progArgs}$args
|
488 | } else {
|
489 | & ${pwshProg} ${args} ${shTarget} ${progArgs}$args
|
490 | }
|
491 | $ret=$LASTEXITCODE
|
492 | }
|
493 | ${nodePath ? '$env:NODE_PATH=$env_node_path\n' : ''}\
|
494 | ${prependPath ? '$env:PATH=$env_path\n' : ''}\
|
495 | exit $ret
|
496 | `;
|
497 | }
|
498 | else {
|
499 | pwsh += `
|
500 | # Support pipeline input
|
501 | if ($MyInvocation.ExpectingInput) {
|
502 | $input | & ${pwshProg} ${args} ${shTarget} ${progArgs}$args
|
503 | } else {
|
504 | & ${pwshProg} ${args} ${shTarget} ${progArgs}$args
|
505 | }
|
506 | ${nodePath ? '$env:NODE_PATH=$env_node_path\n' : ''}\
|
507 | ${prependPath ? '$env:PATH=$env_path\n' : ''}\
|
508 | exit $LASTEXITCODE
|
509 | `;
|
510 | }
|
511 | return pwsh;
|
512 | }
|
513 |
|
514 |
|
515 |
|
516 |
|
517 |
|
518 | function chmodShim(to, opts) {
|
519 | return opts.fs_.chmod(to, 0o755);
|
520 | }
|
521 | function normalizePathEnvVar(nodePath) {
|
522 | if (!nodePath || !nodePath.length) {
|
523 | return {
|
524 | win32: '',
|
525 | posix: ''
|
526 | };
|
527 | }
|
528 | let split = (typeof nodePath === 'string' ? nodePath.split(path.delimiter) : Array.from(nodePath));
|
529 | let result = {};
|
530 | for (let i = 0; i < split.length; i++) {
|
531 | const win32 = split[i].split('/').join('\\');
|
532 | const posix = isWindows() ? split[i].split('\\').join('/').replace(/^([^:\\/]*):/, (_, $1) => `/mnt/${$1.toLowerCase()}`) : split[i];
|
533 | result.win32 = result.win32 ? `${result.win32};${win32}` : win32;
|
534 | result.posix = result.posix ? `${result.posix}:${posix}` : posix;
|
535 | result[i] = { win32, posix };
|
536 | }
|
537 | return result;
|
538 | }
|
539 | module.exports = cmdShim;
|
540 |
|
\ | No newline at end of file |