Skip to content

Commit d0f50b1

Browse files
committed
chore(refactor): async npm.load
Tests for cli now use the real npm PR-URL: #3451 Credit: @wraithgar Close: #3451 Reviewed-by: @nlf
1 parent 54eae30 commit d0f50b1

File tree

7 files changed

+369
-493
lines changed

7 files changed

+369
-493
lines changed

lib/cli.js

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Separated out for easier unit testing
2-
module.exports = (process) => {
2+
module.exports = async (process) => {
33
// set it here so that regardless of what happens later, we don't
44
// leak any private CLI configs to other programs
55
process.title = 'npm'
@@ -39,12 +39,8 @@ module.exports = (process) => {
3939

4040
// now actually fire up npm and run the command.
4141
// this is how to use npm programmatically:
42-
npm.load(async er => {
43-
// Any exceptions here will be picked up by the uncaughtException handler
44-
if (er)
45-
return exitHandler(er)
46-
47-
// npm --version=cli
42+
try {
43+
await npm.load()
4844
if (npm.config.get('version', 'cli')) {
4945
npm.output(npm.version)
5046
return exitHandler()
@@ -75,5 +71,7 @@ module.exports = (process) => {
7571
}
7672

7773
impl(npm.argv, exitHandler)
78-
})
74+
} catch (err) {
75+
return exitHandler(err)
76+
}
7977
}

lib/npm.js

+38-33
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const Config = require('@npmcli/config')
1111
// Patch the global fs module here at the app level
1212
require('graceful-fs').gracefulify(require('fs'))
1313

14+
// TODO make this only ever load once (or unload) in tests
1415
const procLogListener = require('./utils/proc-log-listener.js')
1516

1617
const proxyCmds = new Proxy({}, {
@@ -48,6 +49,7 @@ const _title = Symbol('_title')
4849
const npm = module.exports = new class extends EventEmitter {
4950
constructor () {
5051
super()
52+
// TODO make this only ever load once (or unload) in tests
5153
require('./utils/perf.js')
5254
this.started = Date.now()
5355
this.command = null
@@ -77,8 +79,8 @@ const npm = module.exports = new class extends EventEmitter {
7779
[_runCmd] (cmd, impl, args, cb) {
7880
if (!this.loaded) {
7981
throw new Error(
80-
'Call npm.load(cb) before using this command.\n' +
81-
'See the README.md or bin/npm-cli.js for example usage.'
82+
'Call npm.load() before using this command.\n' +
83+
'See lib/cli.js for example usage.'
8284
)
8385
}
8486

@@ -96,7 +98,7 @@ const npm = module.exports = new class extends EventEmitter {
9698
args.filter(arg => /^[\u2010-\u2015\u2212\uFE58\uFE63\uFF0D]/.test(arg))
9799
.forEach(arg => {
98100
warnedNonDashArg = true
99-
log.error('arg', 'Argument starts with non-ascii dash, this is probably invalid:', arg)
101+
this.log.error('arg', 'Argument starts with non-ascii dash, this is probably invalid:', arg)
100102
})
101103
}
102104

@@ -123,33 +125,32 @@ const npm = module.exports = new class extends EventEmitter {
123125
}
124126
}
125127

126-
// call with parsed CLI options and a callback when done loading
127-
// XXX promisify this and stop taking a callback
128128
load (cb) {
129-
if (!cb || typeof cb !== 'function')
130-
throw new TypeError('must call as: npm.load(callback)')
131-
132-
this.once('load', cb)
133-
if (this.loaded || this.loadErr) {
134-
this.emit('load', this.loadErr)
135-
return
129+
if (cb && typeof cb !== 'function')
130+
throw new TypeError('callback must be a function if provided')
131+
132+
if (!this.loadPromise) {
133+
process.emit('time', 'npm:load')
134+
this.log.pause()
135+
this.loadPromise = new Promise((resolve, reject) => {
136+
this[_load]().catch(er => er).then((er) => {
137+
this.loadErr = er
138+
if (!er && this.config.get('force'))
139+
this.log.warn('using --force', 'Recommended protections disabled.')
140+
141+
process.emit('timeEnd', 'npm:load')
142+
if (er)
143+
return reject(er)
144+
resolve()
145+
})
146+
})
136147
}
137-
if (this.loading)
138-
return
139-
140-
this.loading = true
148+
if (!cb)
149+
return this.loadPromise
141150

142-
process.emit('time', 'npm:load')
143-
this.log.pause()
144-
return this[_load]().catch(er => er).then((er) => {
145-
this.loading = false
146-
this.loadErr = er
147-
if (!er && this.config.get('force'))
148-
this.log.warn('using --force', 'Recommended protections disabled.')
149-
150-
process.emit('timeEnd', 'npm:load')
151-
this.emit('load', er)
152-
})
151+
// loadPromise is returned here for legacy purposes, old code was allowing
152+
// the mixing of callback and promise here.
153+
return this.loadPromise.then(cb, cb)
153154
}
154155

155156
get loaded () {
@@ -167,10 +168,15 @@ const npm = module.exports = new class extends EventEmitter {
167168

168169
async [_load] () {
169170
process.emit('time', 'npm:load:whichnode')
170-
const node = await which(process.argv[0]).catch(er => null)
171+
let node
172+
try {
173+
node = which.sync(process.argv[0])
174+
} catch (_) {
175+
// TODO should we throw here?
176+
}
171177
process.emit('timeEnd', 'npm:load:whichnode')
172178
if (node && node.toUpperCase() !== process.execPath.toUpperCase()) {
173-
log.verbose('node symlink', node)
179+
this.log.verbose('node symlink', node)
174180
process.execPath = node
175181
this.config.execPath = node
176182
}
@@ -198,10 +204,10 @@ const npm = module.exports = new class extends EventEmitter {
198204
process.env.COLOR = this.color ? '1' : '0'
199205

200206
process.emit('time', 'npm:load:cleanupLog')
201-
cleanUpLogFiles(this.cache, this.config.get('logs-max'), log.warn)
207+
cleanUpLogFiles(this.cache, this.config.get('logs-max'), this.log.warn)
202208
process.emit('timeEnd', 'npm:load:cleanupLog')
203209

204-
log.resume()
210+
this.log.resume()
205211

206212
process.emit('time', 'npm:load:configScope')
207213
const configScope = this.config.get('scope')
@@ -314,9 +320,8 @@ const npm = module.exports = new class extends EventEmitter {
314320
// now load everything required by the class methods
315321

316322
const log = require('npmlog')
317-
const { promisify } = require('util')
318323

319-
const which = promisify(require('which'))
324+
const which = require('which')
320325

321326
const deref = require('./utils/deref-command.js')
322327
const setupLog = require('./utils/setup-log.js')

lib/utils/perf.js

+7
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,10 @@ process.on('timeEnd', (name) => {
1414
} else
1515
log.silly('timing', "Tried to end timer that doesn't exist:", name)
1616
})
17+
18+
// for tests
19+
/* istanbul ignore next */
20+
exports.reset = () => {
21+
process.removeAllListeners('time')
22+
process.removeAllListeners('timeEnd')
23+
}

lib/utils/proc-log-listener.js

+6
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,9 @@ module.exports = () => {
1414
}
1515
})
1616
}
17+
18+
// for tests
19+
/* istanbul ignore next */
20+
module.exports.reset = () => {
21+
process.removeAllListeners('log')
22+
}

0 commit comments

Comments
 (0)