UNPKG

5.13 kBJavaScriptView Raw
1'use strict'
2
3const fs = require('fs')
4const path = require('path')
5const which = require('which')
6
7const findPrefix = require('./find-prefix')
8
9const PATH = getPATHKey()
10const SEPARATOR = getPATHSeparator()
11
12/**
13 * Get new $PATH setting with additional paths supplied by the npm.
14 *
15 * @param Object options Config options Object.
16 * @param Object options.env Environment to use. Default: process.env
17 * @param String options.wd Working directory. Default: process.cwd()
18 * @param Function fn callback function.
19 */
20
21function getPath (options, fn) {
22 options.cwd = options.cwd || process.cwd()
23 const env = options.env = options.env || process.env
24 let pathArr = getPathArr(options)
25
26 whichNpm(options, function (err, npmPath) {
27 if (err) return fn(err)
28 findPrefix(options, function (err, prefixPath) {
29 if (!err && prefixPath) {
30 // ignore err if cannot find prefix
31 pathArr.unshift(path.join(prefixPath, 'node_modules', '.bin'))
32 }
33
34 // we also unshift the bundled node-gyp-bin folder so that
35 // the bundled one will be used for installing things.
36 pathArr.unshift(path.join(path.dirname(npmPath), 'node-gyp-bin'))
37 if (env[PATH]) pathArr = pathArr.concat(env[PATH].split(SEPARATOR))
38
39 // Remove duplicated entries
40 pathArr = Array.from(new Set(pathArr))
41
42 fn(null, pathArr.join(SEPARATOR))
43 })
44 })
45}
46
47/**
48 * Async wrapper around `getPath`.
49 */
50
51function getPathAsync (options, fn) {
52 // options is optional
53 if (options instanceof Function) {
54 fn = options
55 options = {}
56 }
57 // if no fn, execute as sync
58 if (!(fn instanceof Function)) return getPathSync(options)
59 options = options || {}
60 options.isSync = false
61 return getPath(options, fn)
62}
63
64/**
65 * Sync wrapper around `getPath`.
66 */
67
68function getPathSync (options) {
69 options = options || {}
70 options.isSync = true
71 let thePath = null
72 // sync magic: if sync true, callback is executed sync
73 // therefore we can set thePath from inside it before returning
74 getPath(options, function (err, foundPath) {
75 if (err) throw err
76 thePath = foundPath
77 })
78 return thePath
79}
80
81/**
82 * Change environment to include npm path adjustments.
83 *
84 * @param Object options Config options Object.
85 * @param Object options.env Environment to use. Default: process.env
86 * @param String options.wd Working directory. Default: process.cwd()
87 * @param Function fn callback function.
88 */
89
90function setPathAsync (options, fn) {
91 // options is optional
92 if (options instanceof Function) {
93 fn = options
94 options = {}
95 }
96
97 // if no fn, execute as sync
98 if (!(fn instanceof Function)) return setPathSync(options)
99
100 getPathAsync(options, function (err, newPath) {
101 if (err) return fn(err)
102 fn(null, options.env[PATH] = newPath)
103 })
104}
105
106/**
107 * Sync version of `setPathAsync`
108 */
109function setPathSync (options) {
110 options = options || {}
111 const newPath = getPathSync(options)
112 options.env[PATH] = newPath
113 return newPath
114}
115
116/**
117 * Generate simple parts of the npm path. Basically everything that doesn't
118 * depend on potentially async operations.
119 *
120 * @return Array
121 */
122function getPathArr (options) {
123 const wd = options.cwd
124 const pathArr = []
125 const p = wd.split(path.sep + 'node_modules' + path.sep)
126 let acc = path.resolve(p.shift())
127
128 // first add the directory containing the `node` executable currently
129 // running, so that any lifecycle script that invoke 'node' will execute
130 // this same one.
131 pathArr.unshift(path.dirname(process.execPath))
132
133 p.forEach(function (pp) {
134 pathArr.unshift(path.join(acc, 'node_modules', '.bin'))
135 acc = path.join(acc, 'node_modules', pp)
136 })
137 pathArr.unshift(path.join(acc, 'node_modules', '.bin'))
138 return pathArr
139}
140
141/**
142 * Use callback-style signature but toggle sync execution if `isSync` is true.
143 * If options.npm is supplied, this will simply provide npm/bin/npm-cli.
144 */
145function whichNpm (options, fn) {
146 const npmCli = options.npm && path.join(options.npm, 'bin', 'npm-cli.js')
147
148 if (options.isSync) {
149 fn(null, fs.realpathSync(
150 npmCli || which.sync('npm')
151 ))
152 return
153 }
154
155 if (options.npm) {
156 process.nextTick(function () {
157 fn(null, npmCli)
158 })
159 return
160 }
161
162 which('npm', function (err, npmPath) {
163 if (err) return fn(err)
164 fs.realpath(npmPath, fn)
165 })
166}
167
168/**
169 * Get key to use as $PATH in environment
170 */
171
172function getPATHKey () {
173 let PATH = 'PATH'
174
175 // windows calls it's path 'Path' usually, but this is not guaranteed.
176 if (process.platform === 'win32') {
177 PATH = 'Path'
178 Object.keys(process.env).forEach(function (e) {
179 if (e.match(/^PATH$/i)) {
180 PATH = e
181 }
182 })
183 }
184 return PATH
185}
186
187/**
188 * Get $PATH separator based on environment
189 */
190function getPATHSeparator () {
191 return process.platform === 'win32' ? ';' : ':'
192}
193
194module.exports = setPathAsync
195module.exports.get = getPathAsync
196module.exports.get.sync = getPathSync
197module.exports.getSync = getPathSync
198
199module.exports.set = setPathAsync
200module.exports.set.sync = setPathSync
201module.exports.setSync = setPathSync
202
203module.exports.PATH = PATH
204module.exports.SEPARATOR = SEPARATOR