• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
11module.exports = cmdShim
12cmdShim.ifExists = cmdShimIfExists
13
14var fs = require("graceful-fs")
15
16var mkdir = require("mkdirp")
17  , path = require("path")
18  , toBatchSyntax = require("./lib/to-batch-syntax")
19  , shebangExpr = /^#\!\s*(?:\/usr\/bin\/env)?\s*([^ \t]+=[^ \t]+\s+)*\s*([^ \t]+)(.*)$/
20
21function cmdShimIfExists (from, to, cb) {
22  fs.stat(from, function (er) {
23    if (er) return cb()
24    cmdShim(from, to, cb)
25  })
26}
27
28// Try to unlink, but ignore errors.
29// Any problems will surface later.
30function rm (path, cb) {
31  fs.unlink(path, function(er) {
32    cb()
33  })
34}
35
36function cmdShim (from, to, cb) {
37  fs.stat(from, function (er, stat) {
38    if (er)
39      return cb(er)
40
41    cmdShim_(from, to, cb)
42  })
43}
44
45function cmdShim_ (from, to, cb) {
46  var then = times(3, next, cb)
47  rm(to, then)
48  rm(to + ".cmd", then)
49  rm(to + ".ps1", then)
50
51  function next(er) {
52    writeShim(from, to, cb)
53  }
54}
55
56function writeShim (from, to, cb) {
57  // make a cmd file and a sh script
58  // First, check if the bin is a #! of some sort.
59  // If not, then assume it's something that'll be compiled, or some other
60  // sort of script, and just call it directly.
61  mkdir(path.dirname(to), function (er) {
62    if (er)
63      return cb(er)
64    fs.readFile(from, "utf8", function (er, data) {
65      if (er) return writeShim_(from, to, null, null, null, cb)
66      var firstLine = data.trim().split(/\r*\n/)[0]
67        , shebang = firstLine.match(shebangExpr)
68      if (!shebang) return writeShim_(from, to, null, null, null, cb)
69      var vars = shebang[1] || ""
70        , prog = shebang[2]
71        , args = shebang[3] || ""
72      return writeShim_(from, to, prog, args, vars, cb)
73    })
74  })
75}
76
77
78function writeShim_ (from, to, prog, args, variables, cb) {
79  var shTarget = path.relative(path.dirname(to), from)
80    , target = shTarget.split("/").join("\\")
81    , longProg
82    , shProg = prog && prog.split("\\").join("/")
83    , shLongProg
84    , pwshProg = shProg && "\"" + shProg + "$exe\""
85    , pwshLongProg
86  shTarget = shTarget.split("\\").join("/")
87  args = args || ""
88  variables = variables || ""
89  if (!prog) {
90    prog = "\"%dp0%\\" + target + "\""
91    shProg = "\"$basedir/" + shTarget + "\""
92    pwshProg = shProg
93    args = ""
94    target = ""
95    shTarget = ""
96  } else {
97    longProg = "\"%dp0%\\" + prog + ".exe\""
98    shLongProg = "\"$basedir/" + prog + "\""
99    pwshLongProg = "\"$basedir/" + prog + "$exe\""
100    target = "\"%dp0%\\" + target + "\""
101    shTarget = "\"$basedir/" + shTarget + "\""
102  }
103
104  // @SETLOCAL
105  // @CALL :find_dp0
106  //
107  // @IF EXIST "%dp0%\node.exe" (
108  //   @SET "_prog=%dp0%\node.exe"
109  // ) ELSE (
110  //   @SET "_prog=node"
111  //   @SET PATHEXT=%PATHEXT:;.JS;=;%
112  // )
113  //
114  // "%_prog%" "%dp0%\.\node_modules\npm\bin\npm-cli.js" %*
115  // @ENDLOCAL
116  // @EXIT /b %errorlevel%
117  //
118  // :find_dp0
119  // SET dp0=%~dp0
120  // EXIT /b
121  //
122  // Subroutine trick to fix https://github.com/npm/cmd-shim/issues/10
123  var head = '@ECHO off\r\n' +
124    'SETLOCAL\r\n' +
125    'CALL :find_dp0\r\n'
126  var foot = 'ENDLOCAL\r\n' +
127    'EXIT /b %errorlevel%\r\n' +
128    ':find_dp0\r\n' +
129    'SET dp0=%~dp0\r\n' +
130    'EXIT /b\r\n'
131
132  var cmd
133  if (longProg) {
134    shLongProg = shLongProg.trim();
135    args = args.trim();
136    var variableDeclarationsAsBatch = toBatchSyntax.convertToSetCommands(variables)
137    cmd = head
138        + variableDeclarationsAsBatch
139        + "\r\n"
140        + "IF EXIST " + longProg + " (\r\n"
141        + "  SET \"_prog=" + longProg.replace(/(^")|("$)/g, '') + "\"\r\n"
142        + ") ELSE (\r\n"
143        + "  SET \"_prog=" + prog.replace(/(^")|("$)/g, '') + "\"\r\n"
144        + "  SET PATHEXT=%PATHEXT:;.JS;=;%\r\n"
145        + ")\r\n"
146        + "\r\n"
147        +  "\"%_prog%\" " + args + " " + target + " %*\r\n"
148        + foot
149  } else {
150    cmd = head + prog + " " + args + " " + target + " %*\r\n" + foot
151  }
152
153  // #!/bin/sh
154  // basedir=`dirname "$0"`
155  //
156  // case `uname` in
157  //     *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
158  // esac
159  //
160  // if [ -x "$basedir/node.exe" ]; then
161  //   "$basedir/node.exe" "$basedir/node_modules/npm/bin/npm-cli.js" "$@"
162  //   ret=$?
163  // else
164  //   node "$basedir/node_modules/npm/bin/npm-cli.js" "$@"
165  //   ret=$?
166  // fi
167  // exit $ret
168
169  var sh = "#!/bin/sh\n"
170
171  sh = sh
172      + "basedir=$(dirname \"$(echo \"$0\" | sed -e 's,\\\\,/,g')\")\n"
173      + "\n"
174      + "case `uname` in\n"
175      + "    *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w \"$basedir\"`;;\n"
176      + "esac\n"
177      + "\n"
178
179  if (shLongProg) {
180    sh = sh
181       + "if [ -x "+shLongProg+" ]; then\n"
182       + "  " + variables + shLongProg + " " + args + " " + shTarget + " \"$@\"\n"
183       + "  ret=$?\n"
184       + "else \n"
185       + "  " + variables + shProg + " " + args + " " + shTarget + " \"$@\"\n"
186       + "  ret=$?\n"
187       + "fi\n"
188       + "exit $ret\n"
189  } else {
190    sh = sh
191       + shProg + " " + args + " " + shTarget + " \"$@\"\n"
192       + "exit $?\n"
193  }
194
195  // #!/usr/bin/env pwsh
196  // $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
197  //
198  // $ret=0
199  // $exe = ""
200  // if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
201  //   # Fix case when both the Windows and Linux builds of Node
202  //   # are installed in the same directory
203  //   $exe = ".exe"
204  // }
205  // if (Test-Path "$basedir/node") {
206  //   & "$basedir/node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
207  //   $ret=$LASTEXITCODE
208  // } else {
209  //   & "node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
210  //   $ret=$LASTEXITCODE
211  // }
212  // exit $ret
213  var pwsh = "#!/usr/bin/env pwsh\n"
214           + "$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent\n"
215           + "\n"
216           + "$exe=\"\"\n"
217           + "if ($PSVersionTable.PSVersion -lt \"6.0\" -or $IsWindows) {\n"
218           + "  # Fix case when both the Windows and Linux builds of Node\n"
219           + "  # are installed in the same directory\n"
220           + "  $exe=\".exe\"\n"
221           + "}\n"
222  if (shLongProg) {
223    pwsh = pwsh
224         + "$ret=0\n"
225         + "if (Test-Path " + pwshLongProg + ") {\n"
226         + "  & " + pwshLongProg + " " + args + " " + shTarget + " $args\n"
227         + "  $ret=$LASTEXITCODE\n"
228         + "} else {\n"
229         + "  & " + pwshProg + " " + args + " " + shTarget + " $args\n"
230         + "  $ret=$LASTEXITCODE\n"
231         + "}\n"
232         + "exit $ret\n"
233  } else {
234    pwsh = pwsh
235         + "& " + pwshProg + " " + args + " " + shTarget + " $args\n"
236         + "exit $LASTEXITCODE\n"
237  }
238
239  var then = times(3, next, cb)
240  fs.writeFile(to + ".ps1", pwsh, "utf8", then)
241  fs.writeFile(to + ".cmd", cmd, "utf8", then)
242  fs.writeFile(to, sh, "utf8", then)
243  function next () {
244    chmodShim(to, cb)
245  }
246}
247
248function chmodShim (to, cb) {
249  var then = times(3, cb, cb)
250  fs.chmod(to, "0755", then)
251  fs.chmod(to + ".cmd", "0755", then)
252  fs.chmod(to + ".ps1", "0755", then)
253}
254
255function times(n, ok, cb) {
256  var errState = null
257  return function(er) {
258    if (!errState) {
259      if (er)
260        cb(errState = er)
261      else if (--n === 0)
262        ok()
263    }
264  }
265}
266