index.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. // On windows, create a .cmd file.
  2. // Read the #! in the file to see what it uses. The vast majority
  3. // of the time, this will be either:
  4. // "#!/usr/bin/env <prog> <args...>"
  5. // or:
  6. // "#!<prog> <args...>"
  7. //
  8. // Write a binroot/pkg.bin + ".cmd" file that has this line in it:
  9. // @<prog> <args...> %dp0%<target> %*
  10. const {promisify} = require('util')
  11. const fs = require('fs')
  12. const writeFile = promisify(fs.writeFile)
  13. const readFile = promisify(fs.readFile)
  14. const chmod = promisify(fs.chmod)
  15. const stat = promisify(fs.stat)
  16. const unlink = promisify(fs.unlink)
  17. const {dirname, relative} = require('path')
  18. const mkdir = require('mkdirp-infer-owner')
  19. const toBatchSyntax = require('./lib/to-batch-syntax')
  20. const shebangExpr = /^#\!\s*(?:\/usr\/bin\/env)?\s*([^ \t]+=[^ \t]+\s+)*\s*([^ \t]+)(.*)$/
  21. const cmdShimIfExists = (from, to) =>
  22. stat(from).then(() => cmdShim(from, to), () => {})
  23. // Try to unlink, but ignore errors.
  24. // Any problems will surface later.
  25. const rm = path => unlink(path).catch(() => {})
  26. const cmdShim = (from, to) =>
  27. stat(from).then(() => cmdShim_(from, to))
  28. const cmdShim_ = (from, to) => Promise.all([
  29. rm(to),
  30. rm(to + '.cmd'),
  31. rm(to + '.ps1'),
  32. ]).then(() => writeShim(from, to))
  33. const writeShim = (from, to) =>
  34. // make a cmd file and a sh script
  35. // First, check if the bin is a #! of some sort.
  36. // If not, then assume it's something that'll be compiled, or some other
  37. // sort of script, and just call it directly.
  38. mkdir(dirname(to))
  39. .then(() => readFile(from, 'utf8'))
  40. .then(data => {
  41. const firstLine = data.trim().split(/\r*\n/)[0]
  42. const shebang = firstLine.match(shebangExpr)
  43. if (!shebang) return writeShim_(from, to)
  44. const vars = shebang[1] || ''
  45. const prog = shebang[2]
  46. const args = shebang[3] || ''
  47. return writeShim_(from, to, prog, args, vars)
  48. }, er => writeShim_(from, to))
  49. const writeShim_ = (from, to, prog, args, variables) => {
  50. let shTarget = relative(dirname(to), from)
  51. let target = shTarget.split('/').join('\\')
  52. let longProg
  53. let shProg = prog && prog.split('\\').join('/')
  54. let shLongProg
  55. let pwshProg = shProg && `"${shProg}$exe"`
  56. let pwshLongProg
  57. shTarget = shTarget.split('\\').join('/')
  58. args = args || ''
  59. variables = variables || ''
  60. if (!prog) {
  61. prog = `"%dp0%\\${target}"`
  62. shProg = `"$basedir/${shTarget}"`
  63. pwshProg = shProg
  64. args = ''
  65. target = ''
  66. shTarget = ''
  67. } else {
  68. longProg = `"%dp0%\\${prog}.exe"`
  69. shLongProg = `"$basedir/${prog}"`
  70. pwshLongProg = `"$basedir/${prog}$exe"`
  71. target = `"%dp0%\\${target}"`
  72. shTarget = `"$basedir/${shTarget}"`
  73. }
  74. // Subroutine trick to fix https://github.com/npm/cmd-shim/issues/10
  75. // and https://github.com/npm/cli/issues/969
  76. const head = '@ECHO off\r\n' +
  77. 'GOTO start\r\n' +
  78. ':find_dp0\r\n' +
  79. 'SET dp0=%~dp0\r\n' +
  80. 'EXIT /b\r\n' +
  81. ':start\r\n' +
  82. 'SETLOCAL\r\n' +
  83. 'CALL :find_dp0\r\n'
  84. let cmd
  85. if (longProg) {
  86. shLongProg = shLongProg.trim();
  87. args = args.trim();
  88. const variablesBatch = toBatchSyntax.convertToSetCommands(variables)
  89. cmd = head
  90. + variablesBatch
  91. + '\r\n'
  92. + `IF EXIST ${longProg} (\r\n`
  93. + ` SET "_prog=${longProg.replace(/(^")|("$)/g, '')}"\r\n`
  94. + ') ELSE (\r\n'
  95. + ` SET "_prog=${prog.replace(/(^")|("$)/g, '')}"\r\n`
  96. + ' SET PATHEXT=%PATHEXT:;.JS;=;%\r\n'
  97. + ')\r\n'
  98. + '\r\n'
  99. // prevent "Terminate Batch Job? (Y/n)" message
  100. // https://github.com/npm/cli/issues/969#issuecomment-737496588
  101. + 'endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & '
  102. + `"%_prog%" ${args} ${target} %*\r\n`
  103. } else {
  104. cmd = `${head}${prog} ${args} ${target} %*\r\n`
  105. }
  106. // #!/bin/sh
  107. // basedir=`dirname "$0"`
  108. //
  109. // case `uname` in
  110. // *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
  111. // esac
  112. //
  113. // if [ -x "$basedir/node.exe" ]; then
  114. // exec "$basedir/node.exe" "$basedir/node_modules/npm/bin/npm-cli.js" "$@"
  115. // else
  116. // exec node "$basedir/node_modules/npm/bin/npm-cli.js" "$@"
  117. // fi
  118. let sh = "#!/bin/sh\n"
  119. sh = sh
  120. + `basedir=$(dirname "$(echo "$0" | sed -e 's,\\\\,/,g')")\n`
  121. + '\n'
  122. + 'case `uname` in\n'
  123. + ' *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;\n'
  124. + 'esac\n'
  125. + '\n'
  126. if (shLongProg) {
  127. sh = sh
  128. + `if [ -x ${shLongProg} ]; then\n`
  129. + ` exec ${variables}${shLongProg} ${args} ${shTarget} "$@"\n`
  130. + 'else \n'
  131. + ` exec ${variables}${shProg} ${args} ${shTarget} "$@"\n`
  132. + 'fi\n'
  133. } else {
  134. sh = sh
  135. + `exec ${shProg} ${args} ${shTarget} "$@"\n`
  136. }
  137. // #!/usr/bin/env pwsh
  138. // $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
  139. //
  140. // $ret=0
  141. // $exe = ""
  142. // if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
  143. // # Fix case when both the Windows and Linux builds of Node
  144. // # are installed in the same directory
  145. // $exe = ".exe"
  146. // }
  147. // if (Test-Path "$basedir/node") {
  148. // # Suport pipeline input
  149. // if ($MyInvocation.ExpectingInput) {
  150. // input | & "$basedir/node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
  151. // } else {
  152. // & "$basedir/node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
  153. // }
  154. // $ret=$LASTEXITCODE
  155. // } else {
  156. // # Support pipeline input
  157. // if ($MyInvocation.ExpectingInput) {
  158. // $input | & "node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
  159. // } else {
  160. // & "node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
  161. // }
  162. // $ret=$LASTEXITCODE
  163. // }
  164. // exit $ret
  165. let pwsh = '#!/usr/bin/env pwsh\n'
  166. + '$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent\n'
  167. + '\n'
  168. + '$exe=""\n'
  169. + 'if ($PSVersionTable.PSVersion -lt \"6.0\" -or $IsWindows) {\n'
  170. + ' # Fix case when both the Windows and Linux builds of Node\n'
  171. + ' # are installed in the same directory\n'
  172. + ' $exe=".exe"\n'
  173. + '}\n'
  174. if (shLongProg) {
  175. pwsh = pwsh
  176. + '$ret=0\n'
  177. + `if (Test-Path ${pwshLongProg}) {\n`
  178. + ' # Support pipeline input\n'
  179. + ' if ($MyInvocation.ExpectingInput) {\n'
  180. + ` $input | & ${pwshLongProg} ${args} ${shTarget} $args\n`
  181. + ' } else {\n'
  182. + ` & ${pwshLongProg} ${args} ${shTarget} $args\n`
  183. + ' }\n'
  184. + ' $ret=$LASTEXITCODE\n'
  185. + '} else {\n'
  186. + ' # Support pipeline input\n'
  187. + ' if ($MyInvocation.ExpectingInput) {\n'
  188. + ` $input | & ${pwshProg} ${args} ${shTarget} $args\n`
  189. + ' } else {\n'
  190. + ` & ${pwshProg} ${args} ${shTarget} $args\n`
  191. + ' }\n'
  192. + ' $ret=$LASTEXITCODE\n'
  193. + '}\n'
  194. + 'exit $ret\n'
  195. } else {
  196. pwsh = pwsh
  197. + '# Support pipeline input\n'
  198. + 'if ($MyInvocation.ExpectingInput) {\n'
  199. + ` $input | & ${pwshProg} ${args} ${shTarget} $args\n`
  200. + '} else {\n'
  201. + ` & ${pwshProg} ${args} ${shTarget} $args\n`
  202. + '}\n'
  203. + 'exit $LASTEXITCODE\n'
  204. }
  205. return Promise.all([
  206. writeFile(to + '.ps1', pwsh, 'utf8'),
  207. writeFile(to + '.cmd', cmd, 'utf8'),
  208. writeFile(to, sh, 'utf8'),
  209. ]).then(() => chmodShim(to))
  210. }
  211. const chmodShim = to => Promise.all([
  212. chmod(to, 0o755),
  213. chmod(to + '.cmd', 0o755),
  214. chmod(to + '.ps1', 0o755),
  215. ])
  216. module.exports = cmdShim
  217. cmdShim.ifExists = cmdShimIfExists