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*([^ \t]+)(.*)$/;
|
8 | const DEFAULT_OPTIONS = {
|
9 |
|
10 | createPwshFile: true,
|
11 | createCmdFile: isWindows(),
|
12 | fs: require('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 ? 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 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | async 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 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 | function cmdShimIfExists(src, to, opts) {
|
65 | return cmdShim(src, to, opts).catch(() => { });
|
66 | }
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 | function rm(path, opts) {
|
74 | return opts.fs_.unlink(path).catch(() => { });
|
75 | }
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 | async function cmdShim_(src, to, opts) {
|
85 | const srcRuntimeInfo = await searchScriptRuntime(src, opts);
|
86 |
|
87 |
|
88 |
|
89 | await writeShimsPreCommon(to, opts);
|
90 | return writeAllShims(src, to, srcRuntimeInfo, opts);
|
91 | }
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 | function writeShimsPreCommon(target, opts) {
|
99 | return opts.fs_.mkdir(path.dirname(target), { recursive: true });
|
100 | }
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 | function 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 |
|
125 |
|
126 |
|
127 |
|
128 | function writeShimPre(target, opts) {
|
129 | return rm(target, opts);
|
130 | }
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 | function writeShimPost(target, opts) {
|
137 |
|
138 |
|
139 | return chmodShim(target, opts);
|
140 | }
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 | async function searchScriptRuntime(target, opts) {
|
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 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 | async function writeShim(src, to, srcRuntimeInfo, generateShimScript, opts) {
|
180 | const defaultArgs = opts.preserveSymlinks ? '--preserve-symlinks' : '';
|
181 |
|
182 |
|
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 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 | function generateCmdShim(src, to, opts) {
|
202 |
|
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 |
|
225 |
|
226 |
|
227 |
|
228 |
|
229 |
|
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 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 | function 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 |
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 |
|
297 |
|
298 |
|
299 |
|
300 |
|
301 | let sh = `\
|
302 | #!/bin/sh
|
303 | basedir=$(dirname "$(echo "$0" | sed -e 's,\\\\,/,g')")
|
304 |
|
305 | case \`uname\` in
|
306 | *CYGWIN*) basedir=\`cygpath -w "$basedir"\`;;
|
307 | esac
|
308 |
|
309 | `;
|
310 | if (opts.nodePath) {
|
311 | sh += `\
|
312 | if [ -z "$NODE_PATH" ]; then
|
313 | export NODE_PATH="${shNodePath}"
|
314 | else
|
315 | export NODE_PATH="$NODE_PATH:${shNodePath}"
|
316 | fi
|
317 | `;
|
318 | }
|
319 | if (shLongProg) {
|
320 | sh += `\
|
321 | if [ -x ${shLongProg} ]; then
|
322 | exec ${shLongProg} ${args} ${shTarget} ${progArgs}"$@"
|
323 | else
|
324 | exec ${shProg} ${args} ${shTarget} ${progArgs}"$@"
|
325 | fi
|
326 | `;
|
327 | }
|
328 | else {
|
329 | sh += `\
|
330 | ${shProg} ${args} ${shTarget} ${progArgs}"$@"
|
331 | exit $?
|
332 | `;
|
333 | }
|
334 | return sh;
|
335 | }
|
336 |
|
337 |
|
338 |
|
339 |
|
340 |
|
341 |
|
342 |
|
343 |
|
344 |
|
345 | function 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 |
|
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 |
|
379 |
|
380 |
|
381 |
|
382 |
|
383 |
|
384 |
|
385 |
|
386 |
|
387 |
|
388 |
|
389 |
|
390 |
|
391 |
|
392 |
|
393 |
|
394 |
|
395 |
|
396 |
|
397 |
|
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 | ` : ''}\
|
408 | if ($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 | }
|
419 | if ([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
|
429 | if (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' : ''}\
|
447 | exit $ret
|
448 | `;
|
449 | }
|
450 | else {
|
451 | pwsh += `
|
452 | # Support pipeline input
|
453 | if ($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' : ''}\
|
459 | exit $LASTEXITCODE
|
460 | `;
|
461 | }
|
462 | return pwsh;
|
463 | }
|
464 |
|
465 |
|
466 |
|
467 |
|
468 |
|
469 | function chmodShim(to, opts) {
|
470 | return opts.fs_.chmod(to, 0o755);
|
471 | }
|
472 | function 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 | }
|
490 | module.exports = cmdShim;
|
491 |
|
\ | No newline at end of file |