• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1var fs = require('fs')
2var path = require('path')
3
4var mkdirp = require('mkdirp')
5var rimraf = require('rimraf')
6var which = require('which')
7var test = require('tap').test
8
9var common = require('../common-tap.js')
10var isWindows = require('../../lib/utils/is-windows.js')
11
12var pkg = common.pkg
13
14var PATH
15if (isWindows) {
16  // On Windows the 'comspec' environment variable is used,
17  // so cmd.exe does not need to be on the path.
18  PATH = path.dirname(process.env.ComSpec)
19} else {
20  // On non-Windows, without the path to the shell, nothing usually works.
21  PATH = '/bin:/usr/bin'
22}
23
24var systemNode = which.sync('node', { nothrow: true, path: PATH })
25// the path to the system wide node (null if none)
26
27test('setup', function (t) {
28  fs.writeFileSync(
29    path.join(pkg, 'package.json'),
30    JSON.stringify({}, null, 2)
31  )
32  t.end()
33})
34
35test('make sure the path is correct, without directory of current node', function (t) {
36  checkPath({
37    withDirOfCurrentNode: false,
38    prependNodePathSetting: false
39  }, t)
40})
41
42test('make sure the path is correct, with directory of current node', function (t) {
43  checkPath({
44    withDirOfCurrentNode: true,
45    prependNodePathSetting: false
46  }, t)
47})
48
49test('make sure the path is correct, with directory of current node but ignored node path', function (t) {
50  checkPath({
51    withDirOfCurrentNode: true,
52    prependNodePathSetting: true
53  }, t)
54})
55
56test('make sure the path is correct, without directory of current node and automatic detection', function (t) {
57  checkPath({
58    withDirOfCurrentNode: false,
59    prependNodePathSetting: 'auto'
60  }, t)
61})
62
63test('make sure the path is correct, with directory of current node and automatic detection', function (t) {
64  checkPath({
65    withDirOfCurrentNode: true,
66    prependNodePathSetting: 'auto'
67  }, t)
68})
69
70test('make sure the path is correct, without directory of current node and warn-only detection', function (t) {
71  checkPath({
72    withDirOfCurrentNode: false,
73    prependNodePathSetting: 'warn-only'
74  }, t)
75})
76
77test('make sure the path is correct, with directory of current node and warn-only detection', function (t) {
78  checkPath({
79    withDirOfCurrentNode: true,
80    prependNodePathSetting: 'warn-only'
81  }, t)
82})
83
84test('make sure there is no warning with a symlinked node and warn-only detection', {
85  skip: isWindows && 'symlinks are weird on windows'
86}, function (t) {
87  checkPath({
88    withDirOfCurrentNode: false,
89    extraNode: true,
90    prependNodePathSetting: 'warn-only',
91    symlinkNodeInsteadOfCopying: true
92  }, t)
93})
94
95test('make sure the path is correct, with directory of current node and warn-only detection and an extra node in path', function (t) {
96  checkPath({
97    withDirOfCurrentNode: false,
98    extraNode: true,
99    prependNodePathSetting: 'warn-only'
100  }, t)
101})
102
103function checkPath (testconfig, t) {
104  var withDirOfCurrentNode = testconfig.withDirOfCurrentNode
105  var prependNodePathSetting = testconfig.prependNodePathSetting
106  var symlinkedNode = testconfig.symlinkNodeInsteadOfCopying
107  var extraNode = testconfig.extraNode
108
109  var newPATH = PATH
110  var currentNodeExecPath = process.execPath
111  if (withDirOfCurrentNode) {
112    var newNodeExeDir = path.join(pkg, 'node-bin', 'my_bundled_node')
113    mkdirp.sync(newNodeExeDir)
114    currentNodeExecPath = path.join(newNodeExeDir, path.basename(process.execPath))
115    rimraf.sync(currentNodeExecPath)
116
117    if (!symlinkedNode) {
118      fs.writeFileSync(currentNodeExecPath, fs.readFileSync(process.execPath))
119      fs.chmodSync(currentNodeExecPath, '755')
120    } else {
121      fs.symlinkSync(process.execPath, currentNodeExecPath)
122    }
123  }
124
125  if (!withDirOfCurrentNode) {
126    // Ensure that current node interpreter will be found in the PATH,
127    // so the PATH won't be prepended with its parent directory
128    newPATH = [path.dirname(process.execPath), PATH].join(process.platform === 'win32' ? ';' : ':')
129  }
130
131  common.npm(['run-script', 'env'], {
132    cwd: pkg,
133    nodeExecPath: currentNodeExecPath,
134    env: {
135      PATH: newPATH,
136      npm_config_scripts_prepend_node_path: prependNodePathSetting
137    },
138    stdio: [ 0, 'pipe', 'pipe' ]
139  }, function (er, code, stdout, stderr) {
140    if (er) throw er
141    if (!stderr.match(/^(npm WARN.*)?\n*$/)) console.error(stderr)
142    t.equal(code, 0, 'exit code')
143    var lineMatch = function (line) {
144      return /^PATH=/i.test(line)
145    }
146    // extract just the path value
147    stdout = stdout.split(/\r?\n/)
148    var observedPath = stdout.filter(lineMatch).pop().replace(/^PATH=/, '')
149    var pathSplit = process.platform === 'win32' ? ';' : ':'
150    var root = path.resolve(__dirname, '../..')
151    var actual = observedPath.split(pathSplit).map(function (p) {
152      if (p.indexOf(pkg) === 0) {
153        p = '{{PKG}}' + p.substr(pkg.length)
154      }
155      if (p.indexOf(root) === 0) {
156        p = '{{ROOT}}' + p.substr(root.length)
157      }
158      return p.replace(/\\/g, '/')
159    })
160    // spawn-wrap adds itself to the path when coverage is enabled
161    actual = actual.filter(function (p) {
162      return !p.match(/\.node-spawn-wrap/)
163    })
164
165    // get the ones we tacked on, then the system-specific requirements
166    var expectedPaths = ['{{ROOT}}/node_modules/npm-lifecycle/node-gyp-bin',
167      '{{PKG}}/node_modules/.bin']
168
169    // Check that the behaviour matches the configuration that was actually
170    // used by the child process, as the coverage tooling may set the
171    // --scripts-prepend-node-path option on its own.
172    var realPrependNodePathSetting = stdout.filter(function (line) {
173      return line.match(/npm_config_scripts_prepend_node_path=(true|auto)/)
174    }).length > 0
175
176    if (prependNodePathSetting === 'warn-only') {
177      if (symlinkedNode) {
178        t.equal(stderr, '', 'does not spit out a warning')
179      } else if (withDirOfCurrentNode) {
180        t.match(stderr, /npm WARN lifecycle/, 'spit out a warning')
181        t.match(stderr, /npm is using .*node-bin.my_bundled_node(.exe)?/, 'mention the path of the binary npm itself is using.')
182        if (extraNode) {
183          var regex = new RegExp(
184            'The node binary used for scripts is.*' +
185            process.execPath.replace(/[/\\]/g, '.'))
186          t.match(stderr, regex, 'reports the current binary vs conflicting')
187        } else if (systemNode !== null) {
188          var regexSystemNode = new RegExp(
189            'The node binary used for scripts is.*' +
190            systemNode.replace(/[/\\]/g, '.')
191          )
192          t.match(stderr, regexSystemNode, 'reports the system binary vs conflicting')
193        } else {
194          t.match(stderr, /there is no node binary in the current PATH/, 'informs user that there is no node binary in PATH')
195        }
196      } else {
197        t.same(stderr, '')
198      }
199    }
200
201    if (withDirOfCurrentNode && realPrependNodePathSetting) {
202      expectedPaths.push('{{PKG}}/node-bin/my_bundled_node')
203    }
204    var expect = expectedPaths.concat(newPATH.split(pathSplit)).map(function (p) {
205      return p.replace(/\\/g, '/')
206    })
207    t.same(actual, expect)
208    t.end()
209  })
210}
211