123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- // On windows, create a .cmd file.
- // Read the #! in the file to see what it uses. The vast majority
- // of the time, this will be either:
- // "#!/usr/bin/env <prog> <args...>"
- // or:
- // "#!<prog> <args...>"
- //
- // Write a binroot/pkg.bin + ".cmd" file that has this line in it:
- // @<prog> <args...> %dp0%<target> %*
- const {promisify} = require('util')
- const fs = require('fs')
- const writeFile = promisify(fs.writeFile)
- const readFile = promisify(fs.readFile)
- const chmod = promisify(fs.chmod)
- const stat = promisify(fs.stat)
- const unlink = promisify(fs.unlink)
- const {dirname, relative} = require('path')
- const mkdir = require('mkdirp-infer-owner')
- const toBatchSyntax = require('./lib/to-batch-syntax')
- const shebangExpr = /^#\!\s*(?:\/usr\/bin\/env)?\s*([^ \t]+=[^ \t]+\s+)*\s*([^ \t]+)(.*)$/
- const cmdShimIfExists = (from, to) =>
- stat(from).then(() => cmdShim(from, to), () => {})
- // Try to unlink, but ignore errors.
- // Any problems will surface later.
- const rm = path => unlink(path).catch(() => {})
- const cmdShim = (from, to) =>
- stat(from).then(() => cmdShim_(from, to))
- const cmdShim_ = (from, to) => Promise.all([
- rm(to),
- rm(to + '.cmd'),
- rm(to + '.ps1'),
- ]).then(() => writeShim(from, to))
- const writeShim = (from, to) =>
- // make a cmd file and a sh script
- // First, check if the bin is a #! of some sort.
- // If not, then assume it's something that'll be compiled, or some other
- // sort of script, and just call it directly.
- mkdir(dirname(to))
- .then(() => readFile(from, 'utf8'))
- .then(data => {
- const firstLine = data.trim().split(/\r*\n/)[0]
- const shebang = firstLine.match(shebangExpr)
- if (!shebang) return writeShim_(from, to)
- const vars = shebang[1] || ''
- const prog = shebang[2]
- const args = shebang[3] || ''
- return writeShim_(from, to, prog, args, vars)
- }, er => writeShim_(from, to))
- const writeShim_ = (from, to, prog, args, variables) => {
- let shTarget = relative(dirname(to), from)
- let target = shTarget.split('/').join('\\')
- let longProg
- let shProg = prog && prog.split('\\').join('/')
- let shLongProg
- let pwshProg = shProg && `"${shProg}$exe"`
- let pwshLongProg
- shTarget = shTarget.split('\\').join('/')
- args = args || ''
- variables = variables || ''
- if (!prog) {
- prog = `"%dp0%\\${target}"`
- shProg = `"$basedir/${shTarget}"`
- pwshProg = shProg
- args = ''
- target = ''
- shTarget = ''
- } else {
- longProg = `"%dp0%\\${prog}.exe"`
- shLongProg = `"$basedir/${prog}"`
- pwshLongProg = `"$basedir/${prog}$exe"`
- target = `"%dp0%\\${target}"`
- shTarget = `"$basedir/${shTarget}"`
- }
- // Subroutine trick to fix https://github.com/npm/cmd-shim/issues/10
- // and https://github.com/npm/cli/issues/969
- const head = '@ECHO off\r\n' +
- 'GOTO start\r\n' +
- ':find_dp0\r\n' +
- 'SET dp0=%~dp0\r\n' +
- 'EXIT /b\r\n' +
- ':start\r\n' +
- 'SETLOCAL\r\n' +
- 'CALL :find_dp0\r\n'
- let cmd
- if (longProg) {
- shLongProg = shLongProg.trim();
- args = args.trim();
- const variablesBatch = toBatchSyntax.convertToSetCommands(variables)
- cmd = head
- + variablesBatch
- + '\r\n'
- + `IF EXIST ${longProg} (\r\n`
- + ` SET "_prog=${longProg.replace(/(^")|("$)/g, '')}"\r\n`
- + ') ELSE (\r\n'
- + ` SET "_prog=${prog.replace(/(^")|("$)/g, '')}"\r\n`
- + ' SET PATHEXT=%PATHEXT:;.JS;=;%\r\n'
- + ')\r\n'
- + '\r\n'
- // prevent "Terminate Batch Job? (Y/n)" message
- // https://github.com/npm/cli/issues/969#issuecomment-737496588
- + 'endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & '
- + `"%_prog%" ${args} ${target} %*\r\n`
- } else {
- cmd = `${head}${prog} ${args} ${target} %*\r\n`
- }
- // #!/bin/sh
- // basedir=`dirname "$0"`
- //
- // case `uname` in
- // *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
- // esac
- //
- // if [ -x "$basedir/node.exe" ]; then
- // exec "$basedir/node.exe" "$basedir/node_modules/npm/bin/npm-cli.js" "$@"
- // else
- // exec node "$basedir/node_modules/npm/bin/npm-cli.js" "$@"
- // fi
- let sh = "#!/bin/sh\n"
- sh = sh
- + `basedir=$(dirname "$(echo "$0" | sed -e 's,\\\\,/,g')")\n`
- + '\n'
- + 'case `uname` in\n'
- + ' *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;\n'
- + 'esac\n'
- + '\n'
- if (shLongProg) {
- sh = sh
- + `if [ -x ${shLongProg} ]; then\n`
- + ` exec ${variables}${shLongProg} ${args} ${shTarget} "$@"\n`
- + 'else \n'
- + ` exec ${variables}${shProg} ${args} ${shTarget} "$@"\n`
- + 'fi\n'
- } else {
- sh = sh
- + `exec ${shProg} ${args} ${shTarget} "$@"\n`
- }
- // #!/usr/bin/env pwsh
- // $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
- //
- // $ret=0
- // $exe = ""
- // if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
- // # Fix case when both the Windows and Linux builds of Node
- // # are installed in the same directory
- // $exe = ".exe"
- // }
- // if (Test-Path "$basedir/node") {
- // # Suport pipeline input
- // if ($MyInvocation.ExpectingInput) {
- // input | & "$basedir/node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
- // } else {
- // & "$basedir/node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
- // }
- // $ret=$LASTEXITCODE
- // } else {
- // # Support pipeline input
- // if ($MyInvocation.ExpectingInput) {
- // $input | & "node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
- // } else {
- // & "node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
- // }
- // $ret=$LASTEXITCODE
- // }
- // exit $ret
- let pwsh = '#!/usr/bin/env pwsh\n'
- + '$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent\n'
- + '\n'
- + '$exe=""\n'
- + 'if ($PSVersionTable.PSVersion -lt \"6.0\" -or $IsWindows) {\n'
- + ' # Fix case when both the Windows and Linux builds of Node\n'
- + ' # are installed in the same directory\n'
- + ' $exe=".exe"\n'
- + '}\n'
- if (shLongProg) {
- pwsh = pwsh
- + '$ret=0\n'
- + `if (Test-Path ${pwshLongProg}) {\n`
- + ' # Support pipeline input\n'
- + ' if ($MyInvocation.ExpectingInput) {\n'
- + ` $input | & ${pwshLongProg} ${args} ${shTarget} $args\n`
- + ' } else {\n'
- + ` & ${pwshLongProg} ${args} ${shTarget} $args\n`
- + ' }\n'
- + ' $ret=$LASTEXITCODE\n'
- + '} else {\n'
- + ' # Support pipeline input\n'
- + ' if ($MyInvocation.ExpectingInput) {\n'
- + ` $input | & ${pwshProg} ${args} ${shTarget} $args\n`
- + ' } else {\n'
- + ` & ${pwshProg} ${args} ${shTarget} $args\n`
- + ' }\n'
- + ' $ret=$LASTEXITCODE\n'
- + '}\n'
- + 'exit $ret\n'
- } else {
- pwsh = pwsh
- + '# Support pipeline input\n'
- + 'if ($MyInvocation.ExpectingInput) {\n'
- + ` $input | & ${pwshProg} ${args} ${shTarget} $args\n`
- + '} else {\n'
- + ` & ${pwshProg} ${args} ${shTarget} $args\n`
- + '}\n'
- + 'exit $LASTEXITCODE\n'
- }
- return Promise.all([
- writeFile(to + '.ps1', pwsh, 'utf8'),
- writeFile(to + '.cmd', cmd, 'utf8'),
- writeFile(to, sh, 'utf8'),
- ]).then(() => chmodShim(to))
- }
- const chmodShim = to => Promise.all([
- chmod(to, 0o755),
- chmod(to + '.cmd', 0o755),
- chmod(to + '.ps1', 0o755),
- ])
- module.exports = cmdShim
- cmdShim.ifExists = cmdShimIfExists
|