Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Karrys <luke@lukekarrys.com>2021-11-27 18:08:06 +0300
committerLuke Karrys <luke@lukekarrys.com>2021-12-02 03:43:08 +0300
commit6734ba36dd6e07a859ab4d6eb4f264d2c0022276 (patch)
treeb21a5c01d1661b4384a2251d801a590c9e14dc13
parent037f2cc8c8ed9d9a092475a5a07f2a3a88915633 (diff)
feat: streaming debug logfile
This decouples the log file writing from the terminal logging. We now open an append only file at the start of the process and stream logs to it. We still only display the log file message in timing mode or if there is an error, but the file is still written regardless. All logging now goes through `proc-log` and this is the first step to removing `npmlog`. For now `npmlog` is still used for the terminal logging but with a shim in front of it to make it easier to test and use in conjunction with `proc-log`. Ref: npm/statusboard#366 This also refactors many of the tests to always use an explicit `t.testdir` for their cache since the file is opened on each `new Npm()`. Tests are also refactored to use more of `MockNpm` with behavior to add config items and load `npm` if necessary. A new fixture `mockGlobals` was also added to make much of this more ergonomic. Ref: npm/statusboard#410 Closes npm/statusboard#411 Closes npm/statusboard#367 PR-URL: https://github.com/npm/cli/pull/4062 Credit: @lukekarrys Close: #4062 Reviewed-by: @wraithgar
-rw-r--r--.eslintrc.json14
-rw-r--r--lib/auth/legacy.js9
-rw-r--r--lib/auth/sso.js3
-rw-r--r--lib/cli.js33
-rw-r--r--lib/commands/adduser.js3
-rw-r--r--lib/commands/bin.js1
-rw-r--r--lib/commands/bugs.js2
-rw-r--r--lib/commands/cache.js4
-rw-r--r--lib/commands/ci.js5
-rw-r--r--lib/commands/config.js11
-rw-r--r--lib/commands/dedupe.js3
-rw-r--r--lib/commands/diff.js16
-rw-r--r--lib/commands/dist-tag.js3
-rw-r--r--lib/commands/docs.js3
-rw-r--r--lib/commands/doctor.js17
-rw-r--r--lib/commands/exec.js2
-rw-r--r--lib/commands/explore.js7
-rw-r--r--lib/commands/fund.js3
-rw-r--r--lib/commands/init.js16
-rw-r--r--lib/commands/install.js6
-rw-r--r--lib/commands/link.js11
-rw-r--r--lib/commands/logout.js2
-rw-r--r--lib/commands/owner.js3
-rw-r--r--lib/commands/pack.js9
-rw-r--r--lib/commands/ping.js2
-rw-r--r--lib/commands/profile.js2
-rw-r--r--lib/commands/prune.js3
-rw-r--r--lib/commands/publish.js6
-rw-r--r--lib/commands/repo.js3
-rw-r--r--lib/commands/run-script.js2
-rw-r--r--lib/commands/search.js2
-rw-r--r--lib/commands/set-script.js2
-rw-r--r--lib/commands/shrinkwrap.js3
-rw-r--r--lib/commands/star.js3
-rw-r--r--lib/commands/stars.js3
-rw-r--r--lib/commands/token.js2
-rw-r--r--lib/commands/uninstall.js3
-rw-r--r--lib/commands/unpublish.js8
-rw-r--r--lib/commands/update.js4
-rw-r--r--lib/commands/view.js4
-rw-r--r--lib/npm.js153
-rw-r--r--lib/utils/audit-error.js4
-rw-r--r--lib/utils/cleanup-log-files.js35
-rw-r--r--lib/utils/config/definitions.js9
-rw-r--r--lib/utils/deref-command.js2
-rw-r--r--lib/utils/display.js119
-rw-r--r--lib/utils/error-message.js9
-rw-r--r--lib/utils/exit-handler.js207
-rw-r--r--lib/utils/log-file.js245
-rw-r--r--lib/utils/log-shim.js59
-rw-r--r--lib/utils/proc-log-listener.js22
-rw-r--r--lib/utils/pulse-till-done.js2
-rw-r--r--lib/utils/read-user-info.js14
-rw-r--r--lib/utils/reify-output.js2
-rw-r--r--lib/utils/setup-log.js66
-rw-r--r--lib/utils/tar.js4
-rw-r--r--lib/utils/timers.js111
-rw-r--r--lib/utils/unsupported.js16
-rw-r--r--lib/utils/update-notifier.js3
-rw-r--r--lib/utils/usage.js2
-rw-r--r--lib/utils/with-chown-sync.js13
-rw-r--r--package-lock.json2
-rw-r--r--package.json2
-rw-r--r--tap-snapshots/test/lib/commands/config.js.test.cjs6
-rw-r--r--tap-snapshots/test/lib/commands/shrinkwrap.js.test.cjs70
-rw-r--r--tap-snapshots/test/lib/commands/view.js.test.cjs8
-rw-r--r--tap-snapshots/test/lib/utils/error-message.js.test.cjs83
-rw-r--r--tap-snapshots/test/lib/utils/exit-handler.js.test.cjs62
-rw-r--r--tap-snapshots/test/lib/utils/log-file.js.test.cjs68
-rw-r--r--test/fixtures/clean-snapshot.js19
-rw-r--r--test/fixtures/mock-globals.js210
-rw-r--r--test/fixtures/mock-logs.js71
-rw-r--r--test/fixtures/mock-npm.js190
-rw-r--r--test/fixtures/sandbox.js46
-rw-r--r--test/index.js4
-rw-r--r--test/lib/auth/legacy.js2
-rw-r--r--test/lib/auth/sso.js2
-rw-r--r--test/lib/cli.js182
-rw-r--r--test/lib/commands/access.js307
-rw-r--r--test/lib/commands/adduser.js9
-rw-r--r--test/lib/commands/audit.js143
-rw-r--r--test/lib/commands/birthday.js13
-rw-r--r--test/lib/commands/cache.js19
-rw-r--r--test/lib/commands/ci.js2
-rw-r--r--test/lib/commands/completion.js249
-rw-r--r--test/lib/commands/dedupe.js61
-rw-r--r--test/lib/commands/diff.js2
-rw-r--r--test/lib/commands/dist-tag.js2
-rw-r--r--test/lib/commands/doctor.js83
-rw-r--r--test/lib/commands/exec.js30
-rw-r--r--test/lib/commands/explore.js11
-rw-r--r--test/lib/commands/find-dupes.js35
-rw-r--r--test/lib/commands/get.js6
-rw-r--r--test/lib/commands/init.js32
-rw-r--r--test/lib/commands/install.js62
-rw-r--r--test/lib/commands/logout.js131
-rw-r--r--test/lib/commands/owner.js22
-rw-r--r--test/lib/commands/pack.js146
-rw-r--r--test/lib/commands/ping.js6
-rw-r--r--test/lib/commands/prefix.js5
-rw-r--r--test/lib/commands/profile.js32
-rw-r--r--test/lib/commands/prune.js26
-rw-r--r--test/lib/commands/publish.js31
-rw-r--r--test/lib/commands/repo.js91
-rw-r--r--test/lib/commands/restart.js34
-rw-r--r--test/lib/commands/root.js5
-rw-r--r--test/lib/commands/run-script.js12
-rw-r--r--test/lib/commands/set-script.js2
-rw-r--r--test/lib/commands/set.js1
-rw-r--r--test/lib/commands/shrinkwrap.js34
-rw-r--r--test/lib/commands/star.js10
-rw-r--r--test/lib/commands/stars.js12
-rw-r--r--test/lib/commands/start.js33
-rw-r--r--test/lib/commands/stop.js30
-rw-r--r--test/lib/commands/test.js32
-rw-r--r--test/lib/commands/token.js24
-rw-r--r--test/lib/commands/unpublish.js9
-rw-r--r--test/lib/commands/update.js28
-rw-r--r--test/lib/commands/version.js504
-rw-r--r--test/lib/commands/view.js18
-rw-r--r--test/lib/commands/whoami.js20
-rw-r--r--test/lib/fixtures/mock-globals.js321
-rw-r--r--test/lib/load-all-commands.js13
-rw-r--r--test/lib/load-all.js39
-rw-r--r--test/lib/npm.js528
-rw-r--r--test/lib/utils/audit-error.js9
-rw-r--r--test/lib/utils/cleanup-log-files.js79
-rw-r--r--test/lib/utils/config/definitions.js107
-rw-r--r--test/lib/utils/did-you-mean.js6
-rw-r--r--test/lib/utils/display.js85
-rw-r--r--test/lib/utils/error-message.js264
-rw-r--r--test/lib/utils/exit-handler.js618
-rw-r--r--test/lib/utils/is-windows-bash.js34
-rw-r--r--test/lib/utils/log-file.js333
-rw-r--r--test/lib/utils/log-shim.js100
-rw-r--r--test/lib/utils/npm-usage.js6
-rw-r--r--test/lib/utils/proc-log-listener.js41
-rw-r--r--test/lib/utils/pulse-till-done.js19
-rw-r--r--test/lib/utils/read-user-info.js30
-rw-r--r--test/lib/utils/reify-output.js7
-rw-r--r--test/lib/utils/setup-log.js296
-rw-r--r--test/lib/utils/tar.js52
-rw-r--r--test/lib/utils/timers.js82
-rw-r--r--test/lib/utils/unsupported.js52
-rw-r--r--test/lib/utils/update-notifier.js70
145 files changed, 4631 insertions, 3286 deletions
diff --git a/.eslintrc.json b/.eslintrc.json
index b39431d2c..2968a2ea3 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,3 +1,15 @@
{
- "extends": ["@npmcli"]
+ "extends": ["@npmcli"],
+ "overrides": [{
+ "files": "test/**",
+ "rules": {
+ "no-extend-native": "off",
+ "no-global-assign": "off"
+ }
+ }, {
+ "files": ["lib/**"],
+ "rules": {
+ "no-console": "warn"
+ }
+ }]
}
diff --git a/lib/auth/legacy.js b/lib/auth/legacy.js
index 2da82e361..7929ccc64 100644
--- a/lib/auth/legacy.js
+++ b/lib/auth/legacy.js
@@ -1,15 +1,12 @@
-const log = require('npmlog')
const profile = require('npm-profile')
-
+const log = require('../utils/log-shim')
const openUrl = require('../utils/open-url.js')
const read = require('../utils/read-user-info.js')
const loginPrompter = async (creds) => {
- const opts = { log: log }
-
- creds.username = await read.username('Username:', creds.username, opts)
+ creds.username = await read.username('Username:', creds.username)
creds.password = await read.password('Password:', creds.password)
- creds.email = await read.email('Email: (this IS public) ', creds.email, opts)
+ creds.email = await read.email('Email: (this IS public) ', creds.email)
return creds
}
diff --git a/lib/auth/sso.js b/lib/auth/sso.js
index 6fcfc30e5..795eb8972 100644
--- a/lib/auth/sso.js
+++ b/lib/auth/sso.js
@@ -7,10 +7,9 @@
// CLI, we can remove this, and fold the lib/auth/legacy.js back into
// lib/adduser.js
-const log = require('npmlog')
const profile = require('npm-profile')
const npmFetch = require('npm-registry-fetch')
-
+const log = require('../utils/log-shim')
const openUrl = require('../utils/open-url.js')
const otplease = require('../utils/otplease.js')
diff --git a/lib/cli.js b/lib/cli.js
index 9dcd9d04d..3d0c32d4b 100644
--- a/lib/cli.js
+++ b/lib/cli.js
@@ -4,20 +4,23 @@ module.exports = async process => {
// leak any private CLI configs to other programs
process.title = 'npm'
- const { checkForBrokenNode, checkForUnsupportedNode } = require('../lib/utils/unsupported.js')
-
+ // We used to differentiate between known broken and unsupported
+ // versions of node and attempt to only log unsupported but still run.
+ // After we dropped node 10 support, we can use new features
+ // (like static, private, etc) which will only give vague syntax errors,
+ // so now both broken and unsupported use console, but only broken
+ // will process.exit. It is important to now perform *both* of these
+ // checks as early as possible so the user gets the error message.
+ const { checkForBrokenNode, checkForUnsupportedNode } = require('./utils/unsupported.js')
checkForBrokenNode()
-
- const log = require('npmlog')
- // pause it here so it can unpause when we've loaded the configs
- // and know what loglevel we should be printing.
- log.pause()
-
checkForUnsupportedNode()
- const Npm = require('../lib/npm.js')
+ const exitHandler = require('./utils/exit-handler.js')
+ process.on('uncaughtException', exitHandler)
+ process.on('unhandledRejection', exitHandler)
+
+ const Npm = require('./npm.js')
const npm = new Npm()
- const exitHandler = require('../lib/utils/exit-handler.js')
exitHandler.setNpm(npm)
// if npm is called as "npmg" or "npm_g", then
@@ -26,16 +29,14 @@ module.exports = async process => {
process.argv.splice(1, 1, 'npm', '-g')
}
- const replaceInfo = require('../lib/utils/replace-info.js')
+ const log = require('./utils/log-shim.js')
+ const replaceInfo = require('./utils/replace-info.js')
log.verbose('cli', replaceInfo(process.argv))
log.info('using', 'npm@%s', npm.version)
log.info('using', 'node@%s', process.version)
- process.on('uncaughtException', exitHandler)
- process.on('unhandledRejection', exitHandler)
-
- const updateNotifier = require('../lib/utils/update-notifier.js')
+ const updateNotifier = require('./utils/update-notifier.js')
let cmd
// now actually fire up npm and run the command.
@@ -63,7 +64,7 @@ module.exports = async process => {
}
await npm.exec(cmd, npm.argv)
- exitHandler()
+ return exitHandler()
} catch (err) {
if (err.code === 'EUNKNOWNCOMMAND') {
const didYouMean = require('./utils/did-you-mean.js')
diff --git a/lib/commands/adduser.js b/lib/commands/adduser.js
index 6cd6d3001..1cf70fffb 100644
--- a/lib/commands/adduser.js
+++ b/lib/commands/adduser.js
@@ -1,4 +1,4 @@
-const log = require('npmlog')
+const log = require('../utils/log-shim.js')
const replaceInfo = require('../utils/replace-info.js')
const BaseCommand = require('../base-command.js')
const authTypes = {
@@ -31,6 +31,7 @@ class AddUser extends BaseCommand {
creds,
registry,
scope,
+ log,
})
await this.updateConfig({
diff --git a/lib/commands/bin.js b/lib/commands/bin.js
index 8f5ae0cc5..bb700d45a 100644
--- a/lib/commands/bin.js
+++ b/lib/commands/bin.js
@@ -10,6 +10,7 @@ class Bin extends BaseCommand {
const b = this.npm.bin
this.npm.output(b)
if (this.npm.config.get('global') && !envPath.includes(b)) {
+ // XXX: does this need to be console?
console.error('(not in PATH env variable)')
}
}
diff --git a/lib/commands/bugs.js b/lib/commands/bugs.js
index 8ca8188cc..5dfd1eb91 100644
--- a/lib/commands/bugs.js
+++ b/lib/commands/bugs.js
@@ -1,5 +1,5 @@
-const log = require('npmlog')
const pacote = require('pacote')
+const log = require('../utils/log-shim')
const openUrl = require('../utils/open-url.js')
const hostedFromMani = require('../utils/hosted-git-info-from-manifest.js')
const BaseCommand = require('../base-command.js')
diff --git a/lib/commands/cache.js b/lib/commands/cache.js
index b1c045bbf..ecb34cb89 100644
--- a/lib/commands/cache.js
+++ b/lib/commands/cache.js
@@ -1,6 +1,5 @@
const cacache = require('cacache')
const { promisify } = require('util')
-const log = require('npmlog')
const pacote = require('pacote')
const path = require('path')
const rimraf = promisify(require('rimraf'))
@@ -9,6 +8,7 @@ const BaseCommand = require('../base-command.js')
const npa = require('npm-package-arg')
const jsonParse = require('json-parse-even-better-errors')
const localeCompare = require('@isaacs/string-locale-compare')('en')
+const log = require('../utils/log-shim')
const searchCachePackage = async (path, spec, cacheKeys) => {
const parsed = npa(spec)
@@ -141,7 +141,7 @@ class Cache extends BaseCommand {
try {
entry = await cacache.get(cachePath, key)
} catch (err) {
- this.npm.log.warn(`Not Found: ${key}`)
+ log.warn(`Not Found: ${key}`)
break
}
this.npm.output(`Deleted: ${key}`)
diff --git a/lib/commands/ci.js b/lib/commands/ci.js
index e928a01d1..2c2f8da86 100644
--- a/lib/commands/ci.js
+++ b/lib/commands/ci.js
@@ -5,8 +5,7 @@ const reifyFinish = require('../utils/reify-finish.js')
const runScript = require('@npmcli/run-script')
const fs = require('fs')
const readdir = util.promisify(fs.readdir)
-
-const log = require('npmlog')
+const log = require('../utils/log-shim.js')
const removeNodeModules = async where => {
const rimrafOpts = { glob: false }
@@ -39,7 +38,7 @@ class CI extends ArboristWorkspaceCmd {
const opts = {
...this.npm.flatOptions,
path: where,
- log: this.npm.log,
+ log,
save: false, // npm ci should never modify the lockfile or package.json
workspaces: this.workspaceNames,
}
diff --git a/lib/commands/config.js b/lib/commands/config.js
index 0cdcd576f..eb1d570c6 100644
--- a/lib/commands/config.js
+++ b/lib/commands/config.js
@@ -11,6 +11,7 @@ const { spawn } = require('child_process')
const { EOL } = require('os')
const ini = require('ini')
const localeCompare = require('@isaacs/string-locale-compare')('en')
+const log = require('../utils/log-shim.js')
// take an array of `[key, value, k2=v2, k3, v3, ...]` and turn into
// { key: value, k2: v2, k3: v3 }
@@ -87,12 +88,12 @@ class Config extends BaseCommand {
}
async execWorkspaces (args, filters) {
- this.npm.log.warn('config', 'This command does not support workspaces.')
+ log.warn('config', 'This command does not support workspaces.')
return this.exec(args)
}
async exec ([action, ...args]) {
- this.npm.log.disableProgress()
+ log.disableProgress()
try {
switch (action) {
case 'set':
@@ -117,7 +118,7 @@ class Config extends BaseCommand {
throw this.usageError()
}
} finally {
- this.npm.log.enableProgress()
+ log.enableProgress()
}
}
@@ -128,10 +129,10 @@ class Config extends BaseCommand {
const where = this.npm.flatOptions.location
for (const [key, val] of Object.entries(keyValues(args))) {
- this.npm.log.info('config', 'set %j %j', key, val)
+ log.info('config', 'set %j %j', key, val)
this.npm.config.set(key, val || '', where)
if (!this.npm.config.validate(where)) {
- this.npm.log.warn('config', 'omitting invalid config values')
+ log.warn('config', 'omitting invalid config values')
}
}
diff --git a/lib/commands/dedupe.js b/lib/commands/dedupe.js
index e1eafbe3b..cc4b119d0 100644
--- a/lib/commands/dedupe.js
+++ b/lib/commands/dedupe.js
@@ -1,6 +1,7 @@
// dedupe duplicated packages, or find them in the tree
const Arborist = require('@npmcli/arborist')
const reifyFinish = require('../utils/reify-finish.js')
+const log = require('../utils/log-shim.js')
const ArboristWorkspaceCmd = require('../arborist-cmd.js')
@@ -32,7 +33,7 @@ class Dedupe extends ArboristWorkspaceCmd {
const where = this.npm.prefix
const opts = {
...this.npm.flatOptions,
- log: this.npm.log,
+ log,
path: where,
dryRun,
workspaces: this.workspaceNames,
diff --git a/lib/commands/diff.js b/lib/commands/diff.js
index 3134f502e..d737a58dc 100644
--- a/lib/commands/diff.js
+++ b/lib/commands/diff.js
@@ -1,13 +1,11 @@
const { resolve } = require('path')
-
const semver = require('semver')
const libnpmdiff = require('libnpmdiff')
const npa = require('npm-package-arg')
const Arborist = require('@npmcli/arborist')
-const npmlog = require('npmlog')
const pacote = require('pacote')
const pickManifest = require('npm-pick-manifest')
-
+const log = require('../utils/log-shim')
const readPackageName = require('../utils/read-package-name.js')
const BaseCommand = require('../base-command.js')
@@ -57,7 +55,7 @@ class Diff extends BaseCommand {
}
const [a, b] = await this.retrieveSpecs(specs)
- npmlog.info('diff', { src: a, dst: b })
+ log.info('diff', { src: a, dst: b })
const res = await libnpmdiff([a, b], {
...this.npm.flatOptions,
@@ -83,7 +81,7 @@ class Diff extends BaseCommand {
try {
name = await readPackageName(this.prefix)
} catch (e) {
- npmlog.verbose('diff', 'could not read project dir package.json')
+ log.verbose('diff', 'could not read project dir package.json')
}
if (!name) {
@@ -116,7 +114,7 @@ class Diff extends BaseCommand {
try {
pkgName = await readPackageName(this.prefix)
} catch (e) {
- npmlog.verbose('diff', 'could not read project dir package.json')
+ log.verbose('diff', 'could not read project dir package.json')
noPackageJson = true
}
@@ -154,7 +152,7 @@ class Diff extends BaseCommand {
actualTree.inventory.query('name', spec.name)
.values().next().value
} catch (e) {
- npmlog.verbose('diff', 'failed to load actual install tree')
+ log.verbose('diff', 'failed to load actual install tree')
}
if (!node || !node.name || !node.package || !node.package.version) {
@@ -227,7 +225,7 @@ class Diff extends BaseCommand {
try {
pkgName = await readPackageName(this.prefix)
} catch (e) {
- npmlog.verbose('diff', 'could not read project dir package.json')
+ log.verbose('diff', 'could not read project dir package.json')
}
if (!pkgName) {
@@ -261,7 +259,7 @@ class Diff extends BaseCommand {
const arb = new Arborist(opts)
actualTree = await arb.loadActual(opts)
} catch (e) {
- npmlog.verbose('diff', 'failed to load actual install tree')
+ log.verbose('diff', 'failed to load actual install tree')
}
return specs.map(i => {
diff --git a/lib/commands/dist-tag.js b/lib/commands/dist-tag.js
index fa79b293c..bf2dffe91 100644
--- a/lib/commands/dist-tag.js
+++ b/lib/commands/dist-tag.js
@@ -1,8 +1,7 @@
-const log = require('npmlog')
const npa = require('npm-package-arg')
const regFetch = require('npm-registry-fetch')
const semver = require('semver')
-
+const log = require('../utils/log-shim')
const otplease = require('../utils/otplease.js')
const readPackageName = require('../utils/read-package-name.js')
const BaseCommand = require('../base-command.js')
diff --git a/lib/commands/docs.js b/lib/commands/docs.js
index 9aba24205..19cd73564 100644
--- a/lib/commands/docs.js
+++ b/lib/commands/docs.js
@@ -1,8 +1,7 @@
-const log = require('npmlog')
const pacote = require('pacote')
const openUrl = require('../utils/open-url.js')
const hostedFromMani = require('../utils/hosted-git-info-from-manifest.js')
-
+const log = require('../utils/log-shim')
const BaseCommand = require('../base-command.js')
class Docs extends BaseCommand {
static description = 'Open documentation for a package in a web browser'
diff --git a/lib/commands/doctor.js b/lib/commands/doctor.js
index 6b8878b6f..47a522eb6 100644
--- a/lib/commands/doctor.js
+++ b/lib/commands/doctor.js
@@ -8,6 +8,7 @@ const pacote = require('pacote')
const { resolve } = require('path')
const semver = require('semver')
const { promisify } = require('util')
+const log = require('../utils/log-shim.js')
const ansiTrim = require('../utils/ansi-trim.js')
const isWindows = require('../utils/is-windows.js')
const ping = require('../utils/ping.js')
@@ -42,7 +43,7 @@ class Doctor extends BaseCommand {
static params = ['registry']
async exec (args) {
- this.npm.log.info('Running checkup')
+ log.info('Running checkup')
// each message is [title, ok, message]
const messages = []
@@ -124,7 +125,7 @@ class Doctor extends BaseCommand {
stringLength: s => ansiTrim(s).length,
}
- const silent = this.npm.log.levels[this.npm.log.level] > this.npm.log.levels.error
+ const silent = log.levels[log.level] > log.levels.error
if (!silent) {
this.npm.output(table(outTable, tableOpts))
if (!allOk) {
@@ -137,7 +138,7 @@ class Doctor extends BaseCommand {
}
async checkPing () {
- const tracker = this.npm.log.newItem('checkPing', 1)
+ const tracker = log.newItem('checkPing', 1)
tracker.info('checkPing', 'Pinging registry')
try {
await ping(this.npm.flatOptions)
@@ -154,7 +155,7 @@ class Doctor extends BaseCommand {
}
async getLatestNpmVersion () {
- const tracker = this.npm.log.newItem('getLatestNpmVersion', 1)
+ const tracker = log.newItem('getLatestNpmVersion', 1)
tracker.info('getLatestNpmVersion', 'Getting npm package information')
try {
const latest = (await pacote.manifest('npm@latest', this.npm.flatOptions)).version
@@ -173,7 +174,7 @@ class Doctor extends BaseCommand {
const current = process.version
const currentRange = `^${current}`
const url = 'https://nodejs.org/dist/index.json'
- const tracker = this.npm.log.newItem('getLatestNodejsVersion', 1)
+ const tracker = log.newItem('getLatestNodejsVersion', 1)
tracker.info('getLatestNodejsVersion', 'Getting Node.js release information')
try {
const res = await fetch(url, { method: 'GET', ...this.npm.flatOptions })
@@ -207,7 +208,7 @@ class Doctor extends BaseCommand {
let ok = true
- const tracker = this.npm.log.newItem(root, 1)
+ const tracker = log.newItem(root, 1)
try {
const uid = process.getuid()
@@ -269,7 +270,7 @@ class Doctor extends BaseCommand {
}
async getGitPath () {
- const tracker = this.npm.log.newItem('getGitPath', 1)
+ const tracker = log.newItem('getGitPath', 1)
tracker.info('getGitPath', 'Finding git in your PATH')
try {
return await which('git').catch(er => {
@@ -282,7 +283,7 @@ class Doctor extends BaseCommand {
}
async verifyCachedFiles () {
- const tracker = this.npm.log.newItem('verifyCachedFiles', 1)
+ const tracker = log.newItem('verifyCachedFiles', 1)
tracker.info('verifyCachedFiles', 'Verifying the npm cache')
try {
const stats = await cacache.verify(this.npm.flatOptions.cache)
diff --git a/lib/commands/exec.js b/lib/commands/exec.js
index 515ac910f..61a6d9659 100644
--- a/lib/commands/exec.js
+++ b/lib/commands/exec.js
@@ -1,6 +1,7 @@
const libexec = require('libnpmexec')
const BaseCommand = require('../base-command.js')
const getLocationMsg = require('../exec/get-workspace-location-msg.js')
+const log = require('../utils/log-shim')
// it's like this:
//
@@ -59,7 +60,6 @@ class Exec extends BaseCommand {
const {
flatOptions,
localBin,
- log,
globalBin,
} = this.npm
const output = (...outputArgs) => this.npm.output(...outputArgs)
diff --git a/lib/commands/explore.js b/lib/commands/explore.js
index f94fff01c..90e6af69f 100644
--- a/lib/commands/explore.js
+++ b/lib/commands/explore.js
@@ -4,6 +4,7 @@
const rpj = require('read-package-json-fast')
const runScript = require('@npmcli/run-script')
const { join, resolve, relative } = require('path')
+const log = require('../utils/log-shim.js')
const completion = require('../utils/completion/installed-shallow.js')
const BaseCommand = require('../base-command.js')
@@ -37,7 +38,7 @@ class Explore extends BaseCommand {
// handle all the escaping and PATH setup stuff.
const pkg = await rpj(resolve(path, 'package.json')).catch(er => {
- this.npm.log.error('explore', `It doesn't look like ${pkgname} is installed.`)
+ log.error('explore', `It doesn't look like ${pkgname} is installed.`)
throw er
})
@@ -50,7 +51,7 @@ class Explore extends BaseCommand {
if (!args.length) {
this.npm.output(`\nExploring ${path}\nType 'exit' or ^D when finished\n`)
}
- this.npm.log.disableProgress()
+ log.disableProgress()
try {
return await runScript({
...this.npm.flatOptions,
@@ -71,7 +72,7 @@ class Explore extends BaseCommand {
}
})
} finally {
- this.npm.log.enableProgress()
+ log.enableProgress()
}
}
}
diff --git a/lib/commands/fund.js b/lib/commands/fund.js
index 81c6d9a1b..47a51c33a 100644
--- a/lib/commands/fund.js
+++ b/lib/commands/fund.js
@@ -5,6 +5,7 @@ const pacote = require('pacote')
const semver = require('semver')
const npa = require('npm-package-arg')
const { depth } = require('treeverse')
+const log = require('../utils/log-shim.js')
const { readTree: getFundingInfo, normalizeFunding, isValidFunding } = require('libnpmfund')
const completion = require('../utils/completion/installed-deep.js')
@@ -68,7 +69,7 @@ class Fund extends ArboristWorkspaceCmd {
// TODO: add !workspacesEnabled option handling to libnpmfund
const fundingInfo = getFundingInfo(tree, {
...this.flatOptions,
- log: this.npm.log,
+ log,
workspaces: this.workspaceNames,
})
diff --git a/lib/commands/init.js b/lib/commands/init.js
index eaca2716e..7e8a8f7a5 100644
--- a/lib/commands/init.js
+++ b/lib/commands/init.js
@@ -7,6 +7,7 @@ const rpj = require('read-package-json-fast')
const libexec = require('libnpmexec')
const mapWorkspaces = require('@npmcli/map-workspaces')
const PackageJson = require('@npmcli/package-json')
+const log = require('../utils/log-shim.js')
const getLocationMsg = require('../exec/get-workspace-location-msg.js')
const BaseCommand = require('../base-command.js')
@@ -94,7 +95,6 @@ class Init extends BaseCommand {
const {
flatOptions,
localBin,
- log,
globalBin,
} = this.npm
// this function is definitely called. But because of coverage map stuff
@@ -125,8 +125,8 @@ class Init extends BaseCommand {
}
async template (path = process.cwd()) {
- this.npm.log.pause()
- this.npm.log.disableProgress()
+ log.pause()
+ log.disableProgress()
const initFile = this.npm.config.get('init-module')
if (!this.npm.config.get('yes') && !this.npm.config.get('force')) {
@@ -147,17 +147,17 @@ class Init extends BaseCommand {
// XXX promisify init-package-json
await new Promise((res, rej) => {
initJson(path, initFile, this.npm.config, (er, data) => {
- this.npm.log.resume()
- this.npm.log.enableProgress()
- this.npm.log.silly('package data', data)
+ log.resume()
+ log.enableProgress()
+ log.silly('package data', data)
if (er && er.message === 'canceled') {
- this.npm.log.warn('init', 'canceled')
+ log.warn('init', 'canceled')
return res()
}
if (er) {
rej(er)
} else {
- this.npm.log.info('init', 'written successfully')
+ log.info('init', 'written successfully')
res(data)
}
})
diff --git a/lib/commands/install.js b/lib/commands/install.js
index 02ccb5724..a92a5edc5 100644
--- a/lib/commands/install.js
+++ b/lib/commands/install.js
@@ -3,7 +3,7 @@ const fs = require('fs')
const util = require('util')
const readdir = util.promisify(fs.readdir)
const reifyFinish = require('../utils/reify-finish.js')
-const log = require('npmlog')
+const log = require('../utils/log-shim.js')
const { resolve, join } = require('path')
const Arborist = require('@npmcli/arborist')
const runScript = require('@npmcli/run-script')
@@ -118,7 +118,7 @@ class Install extends ArboristWorkspaceCmd {
checks.checkEngine(npmManifest, npmManifest.version, process.version)
} catch (e) {
if (forced) {
- this.npm.log.warn(
+ log.warn(
'install',
/* eslint-disable-next-line max-len */
`Forcing global npm install with incompatible version ${npmManifest.version} into node ${process.version}`
@@ -147,7 +147,7 @@ class Install extends ArboristWorkspaceCmd {
const opts = {
...this.npm.flatOptions,
- log: this.npm.log,
+ log,
auditLevel: null,
path: where,
add: args,
diff --git a/lib/commands/link.js b/lib/commands/link.js
index 8755af6f6..e8e2c6b34 100644
--- a/lib/commands/link.js
+++ b/lib/commands/link.js
@@ -7,6 +7,7 @@ const Arborist = require('@npmcli/arborist')
const npa = require('npm-package-arg')
const rpj = require('read-package-json-fast')
const semver = require('semver')
+const log = require('../utils/log-shim.js')
const reifyFinish = require('../utils/reify-finish.js')
@@ -68,7 +69,7 @@ class Link extends ArboristWorkspaceCmd {
const globalOpts = {
...this.npm.flatOptions,
path: globalTop,
- log: this.npm.log,
+ log,
global: true,
prune: false,
}
@@ -117,7 +118,7 @@ class Link extends ArboristWorkspaceCmd {
const localArb = new Arborist({
...this.npm.flatOptions,
prune: false,
- log: this.npm.log,
+ log,
path: this.npm.prefix,
save,
})
@@ -125,7 +126,7 @@ class Link extends ArboristWorkspaceCmd {
...this.npm.flatOptions,
prune: false,
path: this.npm.prefix,
- log: this.npm.log,
+ log,
add: names.map(l => `file:${resolve(globalTop, 'node_modules', l)}`),
save,
workspaces: this.workspaceNames,
@@ -142,12 +143,12 @@ class Link extends ArboristWorkspaceCmd {
const arb = new Arborist({
...this.npm.flatOptions,
path: globalTop,
- log: this.npm.log,
+ log,
global: true,
})
await arb.reify({
add,
- log: this.npm.log,
+ log,
})
await reifyFinish(this.npm, arb)
}
diff --git a/lib/commands/logout.js b/lib/commands/logout.js
index e17b2b879..4e6bab985 100644
--- a/lib/commands/logout.js
+++ b/lib/commands/logout.js
@@ -1,6 +1,6 @@
-const log = require('npmlog')
const getAuth = require('npm-registry-fetch/auth.js')
const npmFetch = require('npm-registry-fetch')
+const log = require('../utils/log-shim')
const BaseCommand = require('../base-command.js')
class Logout extends BaseCommand {
diff --git a/lib/commands/owner.js b/lib/commands/owner.js
index 8f0b1f1ef..c027ad646 100644
--- a/lib/commands/owner.js
+++ b/lib/commands/owner.js
@@ -1,8 +1,7 @@
-const log = require('npmlog')
const npa = require('npm-package-arg')
const npmFetch = require('npm-registry-fetch')
const pacote = require('pacote')
-
+const log = require('../utils/log-shim')
const otplease = require('../utils/otplease.js')
const readLocalPkgName = require('../utils/read-package-name.js')
const BaseCommand = require('../base-command.js')
diff --git a/lib/commands/pack.js b/lib/commands/pack.js
index d84dde86e..0719fa3b8 100644
--- a/lib/commands/pack.js
+++ b/lib/commands/pack.js
@@ -1,14 +1,11 @@
const util = require('util')
-const log = require('npmlog')
const pacote = require('pacote')
const libpack = require('libnpmpack')
const npa = require('npm-package-arg')
const path = require('path')
-
+const log = require('../utils/log-shim')
const { getContents, logTar } = require('../utils/tar.js')
-
const writeFile = util.promisify(require('fs').writeFile)
-
const BaseCommand = require('../base-command.js')
class Pack extends BaseCommand {
@@ -70,7 +67,7 @@ class Pack extends BaseCommand {
}
for (const tar of tarballs) {
- logTar(tar, { log, unicode })
+ logTar(tar, { unicode })
this.npm.output(tar.filename.replace(/^@/, '').replace(/\//, '-'))
}
}
@@ -82,7 +79,7 @@ class Pack extends BaseCommand {
const useWorkspaces = args.length === 0 || args.includes('.')
if (!useWorkspaces) {
- this.npm.log.warn('Ignoring workspaces for specified package(s)')
+ log.warn('Ignoring workspaces for specified package(s)')
return this.exec(args)
}
diff --git a/lib/commands/ping.js b/lib/commands/ping.js
index a049d2412..993e029d4 100644
--- a/lib/commands/ping.js
+++ b/lib/commands/ping.js
@@ -1,4 +1,4 @@
-const log = require('npmlog')
+const log = require('../utils/log-shim')
const pingUtil = require('../utils/ping.js')
const BaseCommand = require('../base-command.js')
diff --git a/lib/commands/profile.js b/lib/commands/profile.js
index 0939013cc..e1102696e 100644
--- a/lib/commands/profile.js
+++ b/lib/commands/profile.js
@@ -1,7 +1,7 @@
const inspect = require('util').inspect
const { URL } = require('url')
const ansistyles = require('ansistyles')
-const log = require('npmlog')
+const log = require('../utils/log-shim.js')
const npmProfile = require('npm-profile')
const qrcodeTerminal = require('qrcode-terminal')
const Table = require('cli-table3')
diff --git a/lib/commands/prune.js b/lib/commands/prune.js
index 403575e02..5831df628 100644
--- a/lib/commands/prune.js
+++ b/lib/commands/prune.js
@@ -1,5 +1,6 @@
// prune extraneous packages
const Arborist = require('@npmcli/arborist')
+const log = require('../utils/log-shim.js')
const reifyFinish = require('../utils/reify-finish.js')
const ArboristWorkspaceCmd = require('../arborist-cmd.js')
@@ -14,7 +15,7 @@ class Prune extends ArboristWorkspaceCmd {
const opts = {
...this.npm.flatOptions,
path: where,
- log: this.npm.log,
+ log,
workspaces: this.workspaceNames,
}
const arb = new Arborist(opts)
diff --git a/lib/commands/publish.js b/lib/commands/publish.js
index 88ddcae7b..ad538668b 100644
--- a/lib/commands/publish.js
+++ b/lib/commands/publish.js
@@ -1,5 +1,5 @@
const util = require('util')
-const log = require('npmlog')
+const log = require('../utils/log-shim.js')
const semver = require('semver')
const pack = require('libnpmpack')
const libpub = require('libnpmpublish').publish
@@ -94,10 +94,10 @@ class Publish extends BaseCommand {
flatten(manifest.publishConfig, opts)
}
- // note that logTar calls npmlog.notice(), so if we ARE in silent mode,
+ // note that logTar calls log.notice(), so if we ARE in silent mode,
// this will do nothing, but we still want it in the debuglog if it fails.
if (!json) {
- logTar(pkgContents, { log, unicode })
+ logTar(pkgContents, { unicode })
}
if (!dryRun) {
diff --git a/lib/commands/repo.js b/lib/commands/repo.js
index cc68e8565..8ac4178f2 100644
--- a/lib/commands/repo.js
+++ b/lib/commands/repo.js
@@ -1,7 +1,6 @@
-const log = require('npmlog')
const pacote = require('pacote')
const { URL } = require('url')
-
+const log = require('../utils/log-shim')
const hostedFromMani = require('../utils/hosted-git-info-from-manifest.js')
const openUrl = require('../utils/open-url.js')
diff --git a/lib/commands/run-script.js b/lib/commands/run-script.js
index 37140c8c5..cd877e0b3 100644
--- a/lib/commands/run-script.js
+++ b/lib/commands/run-script.js
@@ -3,7 +3,7 @@ const chalk = require('chalk')
const runScript = require('@npmcli/run-script')
const { isServerPackage } = runScript
const rpj = require('read-package-json-fast')
-const log = require('npmlog')
+const log = require('../utils/log-shim.js')
const didYouMean = require('../utils/did-you-mean.js')
const isWindowsShell = require('../utils/is-windows-shell.js')
diff --git a/lib/commands/search.js b/lib/commands/search.js
index ff533ebbd..bdeeffe81 100644
--- a/lib/commands/search.js
+++ b/lib/commands/search.js
@@ -1,7 +1,7 @@
const Minipass = require('minipass')
const Pipeline = require('minipass-pipeline')
const libSearch = require('libnpmsearch')
-const log = require('npmlog')
+const log = require('../utils/log-shim.js')
const formatPackageStream = require('../search/format-package-stream.js')
const packageFilter = require('../search/package-filter.js')
diff --git a/lib/commands/set-script.js b/lib/commands/set-script.js
index 58fd2726d..7c73ff01b 100644
--- a/lib/commands/set-script.js
+++ b/lib/commands/set-script.js
@@ -1,7 +1,7 @@
const { resolve } = require('path')
-const log = require('npmlog')
const rpj = require('read-package-json-fast')
const PackageJson = require('@npmcli/package-json')
+const log = require('../utils/log-shim')
const BaseCommand = require('../base-command.js')
class SetScript extends BaseCommand {
diff --git a/lib/commands/shrinkwrap.js b/lib/commands/shrinkwrap.js
index dfb3c8e38..05e3f6d27 100644
--- a/lib/commands/shrinkwrap.js
+++ b/lib/commands/shrinkwrap.js
@@ -1,8 +1,7 @@
const { resolve, basename } = require('path')
const { unlink } = require('fs').promises
const Arborist = require('@npmcli/arborist')
-const log = require('npmlog')
-
+const log = require('../utils/log-shim')
const BaseCommand = require('../base-command.js')
class Shrinkwrap extends BaseCommand {
static description = 'Lock down dependency versions for publication'
diff --git a/lib/commands/star.js b/lib/commands/star.js
index 1bbd25efd..ec1160589 100644
--- a/lib/commands/star.js
+++ b/lib/commands/star.js
@@ -1,7 +1,6 @@
const fetch = require('npm-registry-fetch')
-const log = require('npmlog')
const npa = require('npm-package-arg')
-
+const log = require('../utils/log-shim')
const getIdentity = require('../utils/get-identity')
const BaseCommand = require('../base-command.js')
diff --git a/lib/commands/stars.js b/lib/commands/stars.js
index 1260655d0..f45ec846d 100644
--- a/lib/commands/stars.js
+++ b/lib/commands/stars.js
@@ -1,6 +1,5 @@
-const log = require('npmlog')
const fetch = require('npm-registry-fetch')
-
+const log = require('../utils/log-shim')
const getIdentity = require('../utils/get-identity.js')
const BaseCommand = require('../base-command.js')
diff --git a/lib/commands/token.js b/lib/commands/token.js
index db2374203..df80f1afe 100644
--- a/lib/commands/token.js
+++ b/lib/commands/token.js
@@ -1,7 +1,7 @@
const Table = require('cli-table3')
const ansistyles = require('ansistyles')
const { v4: isCidrV4, v6: isCidrV6 } = require('is-cidr')
-const log = require('npmlog')
+const log = require('../utils/log-shim.js')
const profile = require('npm-profile')
const otplease = require('../utils/otplease.js')
diff --git a/lib/commands/uninstall.js b/lib/commands/uninstall.js
index dba45e127..b40c59bda 100644
--- a/lib/commands/uninstall.js
+++ b/lib/commands/uninstall.js
@@ -1,4 +1,5 @@
const { resolve } = require('path')
+const log = require('../utils/log-shim.js')
const Arborist = require('@npmcli/arborist')
const rpj = require('read-package-json-fast')
@@ -48,7 +49,7 @@ class Uninstall extends ArboristWorkspaceCmd {
const opts = {
...this.npm.flatOptions,
path,
- log: this.npm.log,
+ log,
rm: args,
workspaces: this.workspaceNames,
}
diff --git a/lib/commands/unpublish.js b/lib/commands/unpublish.js
index 3636dc58a..578890025 100644
--- a/lib/commands/unpublish.js
+++ b/lib/commands/unpublish.js
@@ -5,7 +5,7 @@ const libaccess = require('libnpmaccess')
const npmFetch = require('npm-registry-fetch')
const libunpub = require('libnpmpublish').unpublish
const readJson = util.promisify(require('read-package-json'))
-
+const log = require('../utils/log-shim')
const otplease = require('../utils/otplease.js')
const getIdentity = require('../utils/get-identity.js')
@@ -66,8 +66,8 @@ class Unpublish extends BaseCommand {
let pkgName
let pkgVersion
- this.npm.log.silly('unpublish', 'args[0]', args[0])
- this.npm.log.silly('unpublish', 'spec', spec)
+ log.silly('unpublish', 'args[0]', args[0])
+ log.silly('unpublish', 'spec', spec)
if ((!spec || !spec.rawSpec) && !force) {
throw this.usageError(
@@ -92,7 +92,7 @@ class Unpublish extends BaseCommand {
}
}
- this.npm.log.verbose('unpublish', manifest)
+ log.verbose('unpublish', manifest)
const { name, version, publishConfig } = manifest
const pkgJsonSpec = npa.resolve(name, version)
diff --git a/lib/commands/update.js b/lib/commands/update.js
index 4bb74990b..a8bbc4c96 100644
--- a/lib/commands/update.js
+++ b/lib/commands/update.js
@@ -1,7 +1,7 @@
const path = require('path')
const Arborist = require('@npmcli/arborist')
-const log = require('npmlog')
+const log = require('../utils/log-shim.js')
const reifyFinish = require('../utils/reify-finish.js')
const completion = require('../utils/completion/installed-deep.js')
@@ -47,7 +47,7 @@ class Update extends ArboristWorkspaceCmd {
const arb = new Arborist({
...this.npm.flatOptions,
- log: this.npm.log,
+ log,
path: where,
workspaces: this.workspaceNames,
})
diff --git a/lib/commands/view.js b/lib/commands/view.js
index 105ebc16d..4f7464ddd 100644
--- a/lib/commands/view.js
+++ b/lib/commands/view.js
@@ -4,7 +4,7 @@ const color = require('ansicolors')
const columns = require('cli-columns')
const fs = require('fs')
const jsonParse = require('json-parse-even-better-errors')
-const log = require('npmlog')
+const log = require('../utils/log-shim.js')
const npa = require('npm-package-arg')
const { resolve } = require('path')
const formatBytes = require('../utils/format-bytes.js')
@@ -139,7 +139,7 @@ class View extends BaseCommand {
const local = /^\.@/.test(pkg) || pkg === '.'
if (!local) {
- this.npm.log.warn('Ignoring workspaces for specified package(s)')
+ log.warn('Ignoring workspaces for specified package(s)')
return this.exec([pkg, ...args])
}
let wholePackument = false
diff --git a/lib/npm.js b/lib/npm.js
index 0096e0ac5..4d22b531a 100644
--- a/lib/npm.js
+++ b/lib/npm.js
@@ -1,34 +1,10 @@
const EventEmitter = require('events')
const { resolve, dirname } = require('path')
const Config = require('@npmcli/config')
-const log = require('npmlog')
// Patch the global fs module here at the app level
require('graceful-fs').gracefulify(require('fs'))
-// TODO make this only ever load once (or unload) in tests
-const procLogListener = require('./utils/proc-log-listener.js')
-
-// Timers in progress
-const timers = new Map()
-// Finished timers
-const timings = {}
-
-const processOnTimeHandler = name => {
- timers.set(name, Date.now())
-}
-
-const processOnTimeEndHandler = name => {
- if (timers.has(name)) {
- const ms = Date.now() - timers.get(name)
- log.timing(name, `Completed in ${ms}ms`)
- timings[name] = ms
- timers.delete(name)
- } else {
- log.silly('timing', "Tried to end timer that doesn't exist:", name)
- }
-}
-
const { definitions, flatten, shorthands } = require('./utils/config/index.js')
const { shellouts } = require('./utils/cmd-list.js')
const usage = require('./utils/npm-usage.js')
@@ -36,8 +12,11 @@ const usage = require('./utils/npm-usage.js')
const which = require('which')
const deref = require('./utils/deref-command.js')
-const setupLog = require('./utils/setup-log.js')
-const cleanUpLogFiles = require('./utils/cleanup-log-files.js')
+const LogFile = require('./utils/log-file.js')
+const Timers = require('./utils/timers.js')
+const Display = require('./utils/display.js')
+const log = require('./utils/log-shim')
+const replaceInfo = require('./utils/replace-info.js')
let warnedNonDashArg = false
const _load = Symbol('_load')
@@ -50,21 +29,30 @@ class Npm extends EventEmitter {
return pkg.version
}
+ #unloaded = false
+ #timers = null
+ #logFile = null
+ #display = null
+
constructor () {
super()
- this.started = Date.now()
this.command = null
- this.timings = timings
- this.timers = timers
- process.on('time', processOnTimeHandler)
- process.on('timeEnd', processOnTimeEndHandler)
- procLogListener()
- process.emit('time', 'npm')
+ this.#logFile = new LogFile()
+ this.#display = new Display()
+ this.#timers = new Timers({
+ start: 'npm',
+ listener: (name, ms) => {
+ const args = ['timing', name, `Completed in ${ms}ms`]
+ this.#logFile.log(...args)
+ this.#display.log(...args)
+ },
+ })
this.config = new Config({
npmPath: dirname(__dirname),
definitions,
flatten,
shorthands,
+ log,
})
this[_title] = process.title
this.updateNotification = null
@@ -117,7 +105,7 @@ class Npm extends EventEmitter {
.filter(arg => /^[\u2010-\u2015\u2212\uFE58\uFE63\uFF0D]/.test(arg))
.forEach(arg => {
warnedNonDashArg = true
- this.log.error(
+ log.error(
'arg',
'Argument starts with non-ascii dash, this is probably invalid:',
arg
@@ -164,14 +152,13 @@ class Npm extends EventEmitter {
async load () {
if (!this.loadPromise) {
process.emit('time', 'npm:load')
- this.log.pause()
this.loadPromise = new Promise((resolve, reject) => {
this[_load]()
.catch(er => er)
.then(er => {
this.loadErr = er
if (!er && this.config.get('force')) {
- this.log.warn('using --force', 'Recommended protections disabled.')
+ log.warn('using --force', 'Recommended protections disabled.')
}
process.emit('timeEnd', 'npm:load')
@@ -189,6 +176,34 @@ class Npm extends EventEmitter {
return this.config.loaded
}
+ // This gets called at the end of the exit handler and
+ // during any tests to cleanup all of our listeners
+ // Everything in here should be synchronous
+ unload () {
+ // Track if we've already unloaded so we dont
+ // write multiple timing files. This is only an
+ // issue in tests right now since we unload
+ // in both tap teardowns and the exit handler
+ if (this.#unloaded) {
+ return
+ }
+ this.#timers.off()
+ this.#display.off()
+ this.#logFile.off()
+ if (this.loaded && this.config.get('timing')) {
+ this.#timers.writeFile({
+ command: process.argv.slice(2),
+ // We used to only ever report a single log file
+ // so to be backwards compatible report the last logfile
+ // XXX: remove this in npm 9 or just keep it forever
+ logfile: this.logFiles[this.logFiles.length - 1],
+ logfiles: this.logFiles,
+ version: this.version,
+ })
+ }
+ this.#unloaded = true
+ }
+
get title () {
return this[_title]
}
@@ -203,12 +218,12 @@ class Npm extends EventEmitter {
let node
try {
node = which.sync(process.argv[0])
- } catch (_) {
+ } catch {
// TODO should we throw here?
}
process.emit('timeEnd', 'npm:load:whichnode')
if (node && node.toUpperCase() !== process.execPath.toUpperCase()) {
- this.log.verbose('node symlink', node)
+ log.verbose('node symlink', node)
process.execPath = node
this.config.execPath = node
}
@@ -228,19 +243,35 @@ class Npm extends EventEmitter {
const tokrev = deref(this.argv[0]) === 'token' && this.argv[1] === 'revoke'
this.title = tokrev
? 'npm token revoke' + (this.argv[2] ? ' ***' : '')
- : ['npm', ...this.argv].join(' ')
+ : replaceInfo(['npm', ...this.argv].join(' '))
process.emit('timeEnd', 'npm:load:setTitle')
- process.emit('time', 'npm:load:setupLog')
- setupLog(this.config)
- process.emit('timeEnd', 'npm:load:setupLog')
+ process.emit('time', 'npm:load:display')
+ this.#display.load({
+ // Use logColor since that is based on stderr
+ color: this.logColor,
+ progress: this.flatOptions.progress,
+ timing: this.config.get('timing'),
+ loglevel: this.config.get('loglevel'),
+ unicode: this.config.get('unicode'),
+ heading: this.config.get('heading'),
+ })
+ process.emit('timeEnd', 'npm:load:display')
process.env.COLOR = this.color ? '1' : '0'
- process.emit('time', 'npm:load:cleanupLog')
- cleanUpLogFiles(this.cache, this.config.get('logs-max'), this.log.warn)
- process.emit('timeEnd', 'npm:load:cleanupLog')
+ process.emit('time', 'npm:load:logFile')
+ this.#logFile.load({
+ dir: resolve(this.cache, '_logs'),
+ logsMax: this.config.get('logs-max'),
+ })
+ log.verbose('logfile', this.#logFile.files[0])
+ process.emit('timeEnd', 'npm:load:logFile')
- this.log.resume()
+ process.emit('time', 'npm:load:timers')
+ this.#timers.load({
+ dir: this.cache,
+ })
+ process.emit('timeEnd', 'npm:load:timers')
process.emit('time', 'npm:load:configScope')
const configScope = this.config.get('scope')
@@ -258,18 +289,35 @@ class Npm extends EventEmitter {
return flat
}
+ // color and logColor are a special derived values that takes into
+ // consideration not only the config, but whether or not we are operating
+ // in a tty with the associated output (stdout/stderr)
get color () {
- // This is a special derived value that takes into consideration not only
- // the config, but whether or not we are operating in a tty.
return this.flatOptions.color
}
+ get logColor () {
+ return this.flatOptions.logColor
+ }
+
get lockfileVersion () {
return 2
}
- get log () {
- return log
+ get unfinishedTimers () {
+ return this.#timers.unfinished
+ }
+
+ get finishedTimers () {
+ return this.#timers.finished
+ }
+
+ get started () {
+ return this.#timers.started
+ }
+
+ get logFiles () {
+ return this.#logFile.files
}
get cache () {
@@ -347,9 +395,10 @@ class Npm extends EventEmitter {
// output to stdout in a progress bar compatible way
output (...msg) {
- this.log.clearProgress()
+ log.clearProgress()
+ // eslint-disable-next-line no-console
console.log(...msg)
- this.log.showProgress()
+ log.showProgress()
}
}
module.exports = Npm
diff --git a/lib/utils/audit-error.js b/lib/utils/audit-error.js
index b4ab26fd0..7feccc739 100644
--- a/lib/utils/audit-error.js
+++ b/lib/utils/audit-error.js
@@ -1,3 +1,5 @@
+const log = require('./log-shim')
+
// print an error or just nothing if the audit report has an error
// this is called by the audit command, and by the reify-output util
// prints a JSON version of the error if it's --json
@@ -15,7 +17,7 @@ const auditError = (npm, report) => {
const { error } = report
// ok, we care about it, then
- npm.log.warn('audit', error.message)
+ log.warn('audit', error.message)
const { body: errBody } = error
const body = Buffer.isBuffer(errBody) ? errBody.toString() : errBody
if (npm.flatOptions.json) {
diff --git a/lib/utils/cleanup-log-files.js b/lib/utils/cleanup-log-files.js
deleted file mode 100644
index 8fb0fa155..000000000
--- a/lib/utils/cleanup-log-files.js
+++ /dev/null
@@ -1,35 +0,0 @@
-// module to clean out the old log files in cache/_logs
-// this is a best-effort attempt. if a rm fails, we just
-// log a message about it and move on. We do return a
-// Promise that succeeds when we've tried to delete everything,
-// just for the benefit of testing this function properly.
-
-const { resolve } = require('path')
-const rimraf = require('rimraf')
-const glob = require('glob')
-module.exports = (cache, max, warn) => {
- return new Promise(done => {
- glob(resolve(cache, '_logs', '*-debug.log'), (er, files) => {
- if (er) {
- return done()
- }
-
- let pending = files.length - max
- if (pending <= 0) {
- return done()
- }
-
- for (let i = 0; i < files.length - max; i++) {
- rimraf(files[i], er => {
- if (er) {
- warn('log', 'failed to remove log file', files[i])
- }
-
- if (--pending === 0) {
- done()
- }
- })
- }
- })
- })
-}
diff --git a/lib/utils/config/definitions.js b/lib/utils/config/definitions.js
index e94136286..181406918 100644
--- a/lib/utils/config/definitions.js
+++ b/lib/utils/config/definitions.js
@@ -472,7 +472,10 @@ define('color', {
flatten (key, obj, flatOptions) {
flatOptions.color = !obj.color ? false
: obj.color === 'always' ? true
- : process.stdout.isTTY
+ : !!process.stdout.isTTY
+ flatOptions.logColor = !obj.color ? false
+ : obj.color === 'always' ? true
+ : !!process.stderr.isTTY
},
})
@@ -1533,6 +1536,10 @@ define('progress', {
Set to \`false\` to suppress the progress bar.
`,
+ flatten (key, obj, flatOptions) {
+ flatOptions.progress = !obj.progress ? false
+ : !!process.stderr.isTTY && process.env.TERM !== 'dumb'
+ },
})
define('proxy', {
diff --git a/lib/utils/deref-command.js b/lib/utils/deref-command.js
index dd89fb5a4..0a3c8c90b 100644
--- a/lib/utils/deref-command.js
+++ b/lib/utils/deref-command.js
@@ -1,6 +1,6 @@
// de-reference abbreviations and shorthands into canonical command name
-const { aliases, cmdList, plumbing } = require('../utils/cmd-list.js')
+const { aliases, cmdList, plumbing } = require('./cmd-list.js')
const aliasNames = Object.keys(aliases)
const fullList = cmdList.concat(aliasNames).filter(c => !plumbing.includes(c))
const abbrev = require('abbrev')
diff --git a/lib/utils/display.js b/lib/utils/display.js
new file mode 100644
index 000000000..aae51e880
--- /dev/null
+++ b/lib/utils/display.js
@@ -0,0 +1,119 @@
+const { inspect } = require('util')
+const npmlog = require('npmlog')
+const log = require('./log-shim.js')
+const { explain } = require('./explain-eresolve.js')
+
+const _logHandler = Symbol('logHandler')
+const _eresolveWarn = Symbol('eresolveWarn')
+const _log = Symbol('log')
+const _npmlog = Symbol('npmlog')
+
+class Display {
+ constructor () {
+ // pause by default until config is loaded
+ this.on()
+ log.pause()
+ }
+
+ on () {
+ process.on('log', this[_logHandler])
+ }
+
+ off () {
+ process.off('log', this[_logHandler])
+ // Unbalanced calls to enable/disable progress
+ // will leave change listeners on the tracker
+ // This pretty much only happens in tests but
+ // this removes the event emitter listener warnings
+ log.tracker.removeAllListeners()
+ }
+
+ load (config) {
+ const {
+ color,
+ timing,
+ loglevel,
+ unicode,
+ progress,
+ heading = 'npm',
+ } = config
+
+ // XXX: decouple timing from loglevel
+ if (timing && loglevel === 'notice') {
+ log.level = 'timing'
+ } else {
+ log.level = loglevel
+ }
+
+ log.heading = heading
+
+ if (color) {
+ log.enableColor()
+ } else {
+ log.disableColor()
+ }
+
+ if (unicode) {
+ log.enableUnicode()
+ } else {
+ log.disableUnicode()
+ }
+
+ // if it's more than error, don't show progress
+ const silent = log.levels[log.level] > log.levels.error
+ if (progress && !silent) {
+ log.enableProgress()
+ } else {
+ log.disableProgress()
+ }
+
+ // Resume displaying logs now that we have config
+ log.resume()
+ }
+
+ log (...args) {
+ this[_logHandler](...args)
+ }
+
+ [_logHandler] = (level, ...args) => {
+ try {
+ this[_log](level, ...args)
+ } catch (ex) {
+ try {
+ // if it crashed once, it might again!
+ this[_npmlog]('verbose', `attempt to log ${inspect(args)} crashed`, ex)
+ } catch (ex2) {
+ // eslint-disable-next-line no-console
+ console.error(`attempt to log ${inspect(args)} crashed`, ex, ex2)
+ }
+ }
+ }
+
+ [_log] (...args) {
+ return this[_eresolveWarn](...args) || this[_npmlog](...args)
+ }
+
+ // Explicitly call these on npmlog and not log shim
+ // This is the final place we should call npmlog before removing it.
+ [_npmlog] (level, ...args) {
+ npmlog[level](...args)
+ }
+
+ // Also (and this is a really inexcusable kludge), we patch the
+ // log.warn() method so that when we see a peerDep override
+ // explanation from Arborist, we can replace the object with a
+ // highly abbreviated explanation of what's being overridden.
+ [_eresolveWarn] (level, heading, message, expl) {
+ if (level === 'warn' &&
+ heading === 'ERESOLVE' &&
+ expl && typeof expl === 'object'
+ ) {
+ this[_npmlog](level, heading, message)
+ this[_npmlog](level, '', explain(expl, log.useColor(), 2))
+ // Return true to short circuit other log in chain
+ return true
+ }
+ }
+}
+
+module.exports = Display
diff --git a/lib/utils/error-message.js b/lib/utils/error-message.js
index 48ad4676f..4d584346d 100644
--- a/lib/utils/error-message.js
+++ b/lib/utils/error-message.js
@@ -1,9 +1,9 @@
const { format } = require('util')
const { resolve } = require('path')
const nameValidator = require('validate-npm-package-name')
-const npmlog = require('npmlog')
const replaceInfo = require('./replace-info.js')
const { report } = require('./explain-eresolve.js')
+const log = require('./log-shim')
module.exports = (er, npm) => {
const short = []
@@ -20,7 +20,10 @@ module.exports = (er, npm) => {
case 'ERESOLVE':
short.push(['ERESOLVE', er.message])
detail.push(['', ''])
- detail.push(['', report(er, npm.color, resolve(npm.cache, 'eresolve-report.txt'))])
+ // XXX(display): error messages are logged so we use the logColor since that is based
+ // on stderr. This should be handled solely by the display layer so it could also be
+ // printed to stdout if necessary.
+ detail.push(['', report(er, !!npm.logColor, resolve(npm.cache, 'eresolve-report.txt'))])
break
case 'ENOLOCK': {
@@ -61,7 +64,7 @@ module.exports = (er, npm) => {
if (!isWindows && (isCachePath || isCacheDest)) {
// user probably doesn't need this, but still add it to the debug log
- npmlog.verbose(er.stack)
+ log.verbose(er.stack)
short.push([
'',
[
diff --git a/lib/utils/exit-handler.js b/lib/utils/exit-handler.js
index 5b2811468..324346624 100644
--- a/lib/utils/exit-handler.js
+++ b/lib/utils/exit-handler.js
@@ -1,119 +1,108 @@
const os = require('os')
-const path = require('path')
-const writeFileAtomic = require('write-file-atomic')
-const mkdirp = require('mkdirp-infer-owner')
-const fs = require('graceful-fs')
+const log = require('./log-shim.js')
const errorMessage = require('./error-message.js')
const replaceInfo = require('./replace-info.js')
-let exitHandlerCalled = false
-let logFileName
-let npm // set by the cli
-let wroteLogFile = false
-
-const getLogFile = () => {
- // we call this multiple times, so we need to treat it as a singleton because
- // the date is part of the name
- if (!logFileName) {
- logFileName = path.resolve(
- npm.config.get('cache'),
- '_logs',
- new Date().toISOString().replace(/[.:]/g, '_') + '-debug.log'
- )
- }
+const messageText = msg => msg.map(line => line.slice(1).join(' ')).join('\n')
- return logFileName
-}
+let npm = null // set by the cli
+let exitHandlerCalled = false
+let showLogFileMessage = false
process.on('exit', code => {
+ log.disableProgress()
+
// process.emit is synchronous, so the timeEnd handler will run before the
// unfinished timer check below
process.emit('timeEnd', 'npm')
- npm.log.disableProgress()
- for (const [name, timers] of npm.timers) {
- npm.log.verbose('unfinished npm timer', name, timers)
- }
- if (npm.config.loaded && npm.config.get('timing')) {
- try {
- const file = path.resolve(npm.config.get('cache'), '_timing.json')
- const dir = path.dirname(npm.config.get('cache'))
- mkdirp.sync(dir)
-
- fs.appendFileSync(
- file,
- JSON.stringify({
- command: process.argv.slice(2),
- logfile: getLogFile(),
- version: npm.version,
- ...npm.timings,
- }) + '\n'
- )
-
- const st = fs.lstatSync(path.dirname(npm.config.get('cache')))
- fs.chownSync(dir, st.uid, st.gid)
- fs.chownSync(file, st.uid, st.gid)
- } catch (ex) {
- // ignore
+ const hasNpm = !!npm
+ const hasLoadedNpm = hasNpm && npm.config.loaded
+
+ // Unfinished timers can be read before config load
+ if (hasNpm) {
+ for (const [name, timer] of npm.unfinishedTimers) {
+ log.verbose('unfinished npm timer', name, timer)
}
}
if (!code) {
- npm.log.info('ok')
+ log.info('ok')
} else {
- npm.log.verbose('code', code)
+ log.verbose('code', code)
}
if (!exitHandlerCalled) {
process.exitCode = code || 1
- npm.log.error('', 'Exit handler never called!')
+ log.error('', 'Exit handler never called!')
console.error('')
- npm.log.error('', 'This is an error with npm itself. Please report this error at:')
- npm.log.error('', ' <https://github.com/npm/cli/issues>')
- // TODO this doesn't have an npm.config.loaded guard
- writeLogFile()
+ log.error('', 'This is an error with npm itself. Please report this error at:')
+ log.error('', ' <https://github.com/npm/cli/issues>')
+ showLogFileMessage = true
}
- // In timing mode we always write the log file
- if (npm.config.loaded && npm.config.get('timing') && !wroteLogFile) {
- writeLogFile()
+
+ // In timing mode we always show the log file message
+ if (hasLoadedNpm && npm.config.get('timing')) {
+ showLogFileMessage = true
}
- if (wroteLogFile) {
+
+ // npm must be loaded to know where the log file was written
+ if (showLogFileMessage && hasLoadedNpm) {
// just a line break
- if (npm.log.levels[npm.log.level] <= npm.log.levels.error) {
+ if (log.levels[log.level] <= log.levels.error) {
console.error('')
}
- npm.log.error(
+ log.error(
'',
- ['A complete log of this run can be found in:', ' ' + getLogFile()].join('\n')
+ [
+ 'A complete log of this run can be found in:',
+ ...npm.logFiles.map(f => ' ' + f),
+ ].join('\n')
)
}
+ // This removes any listeners npm setup and writes files if necessary
+ // This is mostly used for tests to avoid max listener warnings
+ if (hasLoadedNpm) {
+ npm.unload()
+ }
+
// these are needed for the tests to have a clean slate in each test case
exitHandlerCalled = false
- wroteLogFile = false
+ showLogFileMessage = false
})
const exitHandler = err => {
- npm.log.disableProgress()
- if (!npm.config.loaded) {
+ exitHandlerCalled = true
+
+ log.disableProgress()
+
+ const hasNpm = !!npm
+ const hasLoadedNpm = hasNpm && npm.config.loaded
+
+ if (!hasNpm) {
+ err = err || new Error('Exit prior to setting npm in exit handler')
+ console.error(err.stack || err.message)
+ return process.exit(1)
+ }
+
+ if (!hasLoadedNpm) {
err = err || new Error('Exit prior to config file resolving.')
console.error(err.stack || err.message)
}
// only show the notification if it finished.
if (typeof npm.updateNotification === 'string') {
- const { level } = npm.log
- npm.log.level = 'notice'
- npm.log.notice('', npm.updateNotification)
- npm.log.level = level
+ const { level } = log
+ log.level = 'notice'
+ log.notice('', npm.updateNotification)
+ log.level = level
}
- exitHandlerCalled = true
-
let exitCode
- let noLog
+ let noLogMessage
if (err) {
exitCode = 1
@@ -125,13 +114,13 @@ const exitHandler = err => {
const quietShellout = isShellout && typeof err.code === 'number' && err.code
if (quietShellout) {
exitCode = err.code
- noLog = true
+ noLogMessage = true
} else if (typeof err === 'string') {
- noLog = true
- npm.log.error('', err)
+ log.error('', err)
+ noLogMessage = true
} else if (!(err instanceof Error)) {
- noLog = true
- npm.log.error('weird error', err)
+ log.error('weird error', err)
+ noLogMessage = true
} else {
if (!err.code) {
const matchErrorCode = err.message.match(/^(?:Error: )?(E[A-Z]+)/)
@@ -141,31 +130,30 @@ const exitHandler = err => {
for (const k of ['type', 'stack', 'statusCode', 'pkgid']) {
const v = err[k]
if (v) {
- npm.log.verbose(k, replaceInfo(v))
+ log.verbose(k, replaceInfo(v))
}
}
- npm.log.verbose('cwd', process.cwd())
-
const args = replaceInfo(process.argv)
- npm.log.verbose('', os.type() + ' ' + os.release())
- npm.log.verbose('argv', args.map(JSON.stringify).join(' '))
- npm.log.verbose('node', process.version)
- npm.log.verbose('npm ', 'v' + npm.version)
+ log.verbose('cwd', process.cwd())
+ log.verbose('', os.type() + ' ' + os.release())
+ log.verbose('argv', args.map(JSON.stringify).join(' '))
+ log.verbose('node', process.version)
+ log.verbose('npm ', 'v' + npm.version)
for (const k of ['code', 'syscall', 'file', 'path', 'dest', 'errno']) {
const v = err[k]
if (v) {
- npm.log.error(k, v)
+ log.error(k, v)
}
}
const msg = errorMessage(err, npm)
for (const errline of [...msg.summary, ...msg.detail]) {
- npm.log.error(...errline)
+ log.error(...errline)
}
- if (npm.config.loaded && npm.config.get('json')) {
+ if (hasLoadedNpm && npm.config.get('json')) {
const error = {
error: {
code: err.code,
@@ -183,17 +171,12 @@ const exitHandler = err => {
}
}
}
- npm.log.verbose('exit', exitCode || 0)
- if (npm.log.level === 'silent') {
- noLog = true
- }
+ log.verbose('exit', exitCode || 0)
- // noLog is true if there was an error, including if config wasn't loaded, so
- // this doesn't need a config.loaded guard
- if (exitCode && !noLog) {
- writeLogFile()
- }
+ showLogFileMessage = log.level === 'silent' || noLogMessage
+ ? false
+ : !!exitCode
// explicitly call process.exit now so we don't hang on things like the
// update notifier, also flush stdout beforehand because process.exit doesn't
@@ -201,42 +184,6 @@ const exitHandler = err => {
process.stdout.write('', () => process.exit(exitCode))
}
-const messageText = msg => msg.map(line => line.slice(1).join(' ')).join('\n')
-
-const writeLogFile = () => {
- try {
- let logOutput = ''
- npm.log.record.forEach(m => {
- const p = [m.id, m.level]
- if (m.prefix) {
- p.push(m.prefix)
- }
- const pref = p.join(' ')
-
- m.message
- .trim()
- .split(/\r?\n/)
- .map(line => (pref + ' ' + line).trim())
- .forEach(line => {
- logOutput += line + os.EOL
- })
- })
-
- const file = getLogFile()
- const dir = path.dirname(file)
- mkdirp.sync(dir)
- writeFileAtomic.sync(file, logOutput)
-
- const st = fs.lstatSync(path.dirname(npm.config.get('cache')))
- fs.chownSync(dir, st.uid, st.gid)
- fs.chownSync(file, st.uid, st.gid)
-
- // truncate once it's been written.
- npm.log.record.length = 0
- wroteLogFile = true
- } catch (ex) {}
-}
-
module.exports = exitHandler
module.exports.setNpm = n => {
npm = n
diff --git a/lib/utils/log-file.js b/lib/utils/log-file.js
new file mode 100644
index 000000000..b37fd23e0
--- /dev/null
+++ b/lib/utils/log-file.js
@@ -0,0 +1,245 @@
+const os = require('os')
+const path = require('path')
+const { format, promisify } = require('util')
+const rimraf = promisify(require('rimraf'))
+const glob = promisify(require('glob'))
+const MiniPass = require('minipass')
+const fsMiniPass = require('fs-minipass')
+const log = require('./log-shim')
+const withChownSync = require('./with-chown-sync')
+
+const _logHandler = Symbol('logHandler')
+const _formatLogItem = Symbol('formatLogItem')
+const _getLogFilePath = Symbol('getLogFilePath')
+const _openLogFile = Symbol('openLogFile')
+const _cleanLogs = Symbol('cleanlogs')
+const _endStream = Symbol('endStream')
+const _isBuffered = Symbol('isBuffered')
+
+class LogFiles {
+ // If we write multiple log files we want them all to have the same
+ // identifier for sorting and matching purposes
+ #logId = null
+
+ // Default to a plain minipass stream so we can buffer
+ // initial writes before we know the cache location
+ #logStream = null
+
+ // We cap log files at a certain number of log events per file.
+ // Note that each log event can write more than one line to the
+ // file. Then we rotate log files once this number of events is reached
+ #MAX_LOGS_PER_FILE = null
+
+ // Now that we write logs continuously we need to have a backstop
+ // here for infinite loops that still log. This is also partially handled
+ // by the config.get('max-files') option, but this is a failsafe to
+ // prevent runaway log file creation
+ #MAX_LOG_FILES_PER_PROCESS = null
+
+ #fileLogCount = 0
+ #totalLogCount = 0
+ #dir = null
+ #logsMax = null
+ #files = []
+
+ constructor ({
+ maxLogsPerFile = 50_000,
+ maxFilesPerProcess = 5,
+ } = {}) {
+ this.#logId = LogFiles.logId(new Date())
+ this.#MAX_LOGS_PER_FILE = maxLogsPerFile
+ this.#MAX_LOG_FILES_PER_PROCESS = maxFilesPerProcess
+ this.on()
+ }
+
+ static logId (d) {
+ return d.toISOString().replace(/[.:]/g, '_')
+ }
+
+ static fileName (prefix, suffix) {
+ return `${prefix}-debug-${suffix}.log`
+ }
+
+ static format (count, level, title, ...args) {
+ let prefix = `${count} ${level}`
+ if (title) {
+ prefix += ` ${title}`
+ }
+
+ return format(...args)
+ .split(/\r?\n/)
+ .reduce((lines, line) =>
+ lines += prefix + (line ? ' ' : '') + line + os.EOL,
+ ''
+ )
+ }
+
+ on () {
+ this.#logStream = new MiniPass()
+ process.on('log', this[_logHandler])
+ }
+
+ off () {
+ process.off('log', this[_logHandler])
+ this[_endStream]()
+ }
+
+ load ({ dir, logsMax } = {}) {
+ this.#dir = dir
+ this.#logsMax = logsMax
+
+ // Log stream has already ended
+ if (!this.#logStream) {
+ return
+ }
+ // Pipe our initial stream to our new file stream and
+ // set that as the new log logstream for future writes
+ const initialFile = this[_openLogFile]()
+ if (initialFile) {
+ this.#logStream = this.#logStream.pipe(initialFile)
+ }
+
+ // Kickoff cleaning process. This is async but it wont delete
+ // our next log file since it deletes oldest first. Return the
+ // result so it can be awaited in tests
+ return this[_cleanLogs]()
+ }
+
+ log (...args) {
+ this[_logHandler](...args)
+ }
+
+ get files () {
+ return this.#files
+ }
+
+ get [_isBuffered] () {
+ return this.#logStream instanceof MiniPass
+ }
+
+ [_endStream] (output) {
+ if (this.#logStream) {
+ this.#logStream.end(output)
+ this.#logStream = null
+ }
+ }
+
+ [_logHandler] = (level, ...args) => {
+ // Ignore pause and resume events since we
+ // write everything to the log file
+ if (level === 'pause' || level === 'resume') {
+ return
+ }
+
+ // If the stream is ended then do nothing
+ if (!this.#logStream) {
+ return
+ }
+
+ const logOutput = this[_formatLogItem](level, ...args)
+
+ if (this[_isBuffered]) {
+ // Cant do anything but buffer the output if we dont
+ // have a file stream yet
+ this.#logStream.write(logOutput)
+ return
+ }
+
+ // Open a new log file if we've written too many logs to this one
+ if (this.#fileLogCount >= this.#MAX_LOGS_PER_FILE) {
+ // Write last chunk to the file and close it
+ this[_endStream](logOutput)
+ if (this.#files.length >= this.#MAX_LOG_FILES_PER_PROCESS) {
+ // but if its way too many then we just stop listening
+ this.off()
+ } else {
+ // otherwise we are ready for a new file for the next event
+ this.#logStream = this[_openLogFile]()
+ }
+ } else {
+ this.#logStream.write(logOutput)
+ }
+ }
+
+ [_formatLogItem] (...args) {
+ this.#fileLogCount += 1
+ return LogFiles.format(this.#totalLogCount++, ...args)
+ }
+
+ [_getLogFilePath] (prefix, suffix) {
+ return path.resolve(this.#dir, LogFiles.fileName(prefix, suffix))
+ }
+
+ [_openLogFile] () {
+ // Count in filename will be 0 indexed
+ const count = this.#files.length
+
+ // Pad with zeros so that our log files are always sorted properly
+ // We never want to write files ending in `-9.log` and `-10.log` because
+ // log file cleaning is done by deleting the oldest so in this example
+ // `-10.log` would be deleted next
+ const countDigits = this.#MAX_LOG_FILES_PER_PROCESS.toString().length
+
+ try {
+ const logStream = withChownSync(
+ this[_getLogFilePath](this.#logId, count.toString().padStart(countDigits, '0')),
+ // Some effort was made to make the async, but we need to write logs
+ // during process.on('exit') which has to be synchronous. So in order
+ // to never drop log messages, it is easiest to make it sync all the time
+ // and this was measured to be about 1.5% slower for 40k lines of output
+ (f) => new fsMiniPass.WriteStreamSync(f, { flags: 'a' })
+ )
+ if (count > 0) {
+ // Reset file log count if we are opening
+ // after our first file
+ this.#fileLogCount = 0
+ }
+ this.#files.push(logStream.path)
+ return logStream
+ } catch (e) {
+ // XXX: do something here for errors?
+ // log to display only?
+ return null
+ }
+ }
+
+ async [_cleanLogs] () {
+ // module to clean out the old log files
+ // this is a best-effort attempt. if a rm fails, we just
+ // log a message about it and move on. We do return a
+ // Promise that succeeds when we've tried to delete everything,
+ // just for the benefit of testing this function properly.
+
+ if (typeof this.#logsMax !== 'number') {
+ return
+ }
+
+ // Add 1 to account for the current log file and make
+ // minimum config 0 so current log file is never deleted
+ // XXX: we should make a separate documented option to
+ // disable log file writing
+ const max = Math.max(this.#logsMax, 0) + 1
+ try {
+ const files = await glob(this[_getLogFilePath]('*', '*'))
+ const toDelete = files.length - max
+
+ if (toDelete <= 0) {
+ return
+ }
+
+ log.silly('logfile', `start cleaning logs, removing ${toDelete} files`)
+
+ for (const file of files.slice(0, toDelete)) {
+ try {
+ await rimraf(file)
+ } catch (e) {
+ log.warn('logfile', 'error removing log file', file, e)
+ }
+ }
+ } catch (e) {
+ log.warn('logfile', 'error cleaning log files', e)
+ }
+ }
+}
+
+module.exports = LogFiles
diff --git a/lib/utils/log-shim.js b/lib/utils/log-shim.js
new file mode 100644
index 000000000..9d5a36d96
--- /dev/null
+++ b/lib/utils/log-shim.js
@@ -0,0 +1,59 @@
+const NPMLOG = require('npmlog')
+const PROCLOG = require('proc-log')
+
+// Sets getter and optionally a setter
+// otherwise setting should throw
+const accessors = (obj, set) => (k) => ({
+ get: () => obj[k],
+ set: set ? (v) => (obj[k] = v) : () => {
+ throw new Error(`Cant set ${k}`)
+ },
+})
+
+// Set the value to a bound function on the object
+const value = (obj) => (k) => ({
+ value: (...args) => obj[k].apply(obj, args),
+})
+
+const properties = {
+ // npmlog getters/setters
+ level: accessors(NPMLOG, true),
+ heading: accessors(NPMLOG, true),
+ levels: accessors(NPMLOG),
+ gauge: accessors(NPMLOG),
+ stream: accessors(NPMLOG),
+ tracker: accessors(NPMLOG),
+ progressEnabled: accessors(NPMLOG),
+ // npmlog methods
+ useColor: value(NPMLOG),
+ enableColor: value(NPMLOG),
+ disableColor: value(NPMLOG),
+ enableUnicode: value(NPMLOG),
+ disableUnicode: value(NPMLOG),
+ enableProgress: value(NPMLOG),
+ disableProgress: value(NPMLOG),
+ clearProgress: value(NPMLOG),
+ showProgress: value(NPMLOG),
+ newItem: value(NPMLOG),
+ newGroup: value(NPMLOG),
+ // proclog methods
+ notice: value(PROCLOG),
+ error: value(PROCLOG),
+ warn: value(PROCLOG),
+ info: value(PROCLOG),
+ verbose: value(PROCLOG),
+ http: value(PROCLOG),
+ silly: value(PROCLOG),
+ pause: value(PROCLOG),
+ resume: value(PROCLOG),
+}
+
+const descriptors = Object.entries(properties).reduce((acc, [k, v]) => {
+ acc[k] = { enumerable: true, ...v(k) }
+ return acc
+}, {})
+
+// Create an object with the allowed properties rom npm log and all
+// the logging methods from proc log
+// XXX: this should go away and requires of this should be replaced with proc-log + new display
+module.exports = Object.freeze(Object.defineProperties({}, descriptors))
diff --git a/lib/utils/proc-log-listener.js b/lib/utils/proc-log-listener.js
deleted file mode 100644
index 2cfe94ecb..000000000
--- a/lib/utils/proc-log-listener.js
+++ /dev/null
@@ -1,22 +0,0 @@
-const log = require('npmlog')
-const { inspect } = require('util')
-module.exports = () => {
- process.on('log', (level, ...args) => {
- try {
- log[level](...args)
- } catch (ex) {
- try {
- // if it crashed once, it might again!
- log.verbose(`attempt to log ${inspect([level, ...args])} crashed`, ex)
- } catch (ex2) {
- console.error(`attempt to log ${inspect([level, ...args])} crashed`, ex)
- }
- }
- })
-}
-
-// for tests
-/* istanbul ignore next */
-module.exports.reset = () => {
- process.removeAllListeners('log')
-}
diff --git a/lib/utils/pulse-till-done.js b/lib/utils/pulse-till-done.js
index a88b8aacd..222941414 100644
--- a/lib/utils/pulse-till-done.js
+++ b/lib/utils/pulse-till-done.js
@@ -1,4 +1,4 @@
-const log = require('npmlog')
+const log = require('./log-shim.js')
let pulseTimer = null
const withPromise = async (promise) => {
diff --git a/lib/utils/read-user-info.js b/lib/utils/read-user-info.js
index 993aa886f..ac24396c6 100644
--- a/lib/utils/read-user-info.js
+++ b/lib/utils/read-user-info.js
@@ -1,7 +1,7 @@
const { promisify } = require('util')
const readAsync = promisify(require('read'))
const userValidate = require('npm-user-validate')
-const log = require('npmlog')
+const log = require('./log-shim.js')
exports.otp = readOTP
exports.password = readPassword
@@ -40,30 +40,30 @@ function readPassword (msg = passwordPrompt, password, isRetry) {
.then((password) => readPassword(msg, password, true))
}
-function readUsername (msg = usernamePrompt, username, opts = {}, isRetry) {
+function readUsername (msg = usernamePrompt, username, isRetry) {
if (isRetry && username) {
const error = userValidate.username(username)
if (error) {
- opts.log && opts.log.warn(error.message)
+ log.warn(error.message)
} else {
return Promise.resolve(username.trim())
}
}
return read({ prompt: msg, default: username || '' })
- .then((username) => readUsername(msg, username, opts, true))
+ .then((username) => readUsername(msg, username, true))
}
-function readEmail (msg = emailPrompt, email, opts = {}, isRetry) {
+function readEmail (msg = emailPrompt, email, isRetry) {
if (isRetry && email) {
const error = userValidate.email(email)
if (error) {
- opts.log && opts.log.warn(error.message)
+ log.warn(error.message)
} else {
return email.trim()
}
}
return read({ prompt: msg, default: email || '' })
- .then((username) => readEmail(msg, username, opts, true))
+ .then((username) => readEmail(msg, username, true))
}
diff --git a/lib/utils/reify-output.js b/lib/utils/reify-output.js
index 7741b7220..b4114c1b2 100644
--- a/lib/utils/reify-output.js
+++ b/lib/utils/reify-output.js
@@ -9,7 +9,7 @@
// found 37 vulnerabilities (5 low, 7 moderate, 25 high)
// run `npm audit fix` to fix them, or `npm audit` for details
-const log = require('npmlog')
+const log = require('./log-shim.js')
const { depth } = require('treeverse')
const ms = require('ms')
const auditReport = require('npm-audit-report')
diff --git a/lib/utils/setup-log.js b/lib/utils/setup-log.js
deleted file mode 100644
index 05ca38c82..000000000
--- a/lib/utils/setup-log.js
+++ /dev/null
@@ -1,66 +0,0 @@
-// module to set the appropriate log settings based on configs
-// returns a boolean to say whether we should enable color on
-// stdout or not.
-//
-// Also (and this is a really inexcusable kludge), we patch the
-// log.warn() method so that when we see a peerDep override
-// explanation from Arborist, we can replace the object with a
-// highly abbreviated explanation of what's being overridden.
-const log = require('npmlog')
-const { explain } = require('./explain-eresolve.js')
-
-module.exports = (config) => {
- const color = config.get('color')
-
- const { warn } = log
-
- const stdoutTTY = process.stdout.isTTY
- const stderrTTY = process.stderr.isTTY
- const dumbTerm = process.env.TERM === 'dumb'
- const stderrNotDumb = stderrTTY && !dumbTerm
- // this logic is duplicated in the config 'color' flattener
- const enableColorStderr = color === 'always' ? true
- : color === false ? false
- : stderrTTY
-
- const enableColorStdout = color === 'always' ? true
- : color === false ? false
- : stdoutTTY
-
- log.warn = (heading, ...args) => {
- if (heading === 'ERESOLVE' && args[1] && typeof args[1] === 'object') {
- warn(heading, args[0])
- return warn('', explain(args[1], enableColorStdout, 2))
- }
- return warn(heading, ...args)
- }
-
- if (config.get('timing') && config.get('loglevel') === 'notice') {
- log.level = 'timing'
- } else {
- log.level = config.get('loglevel')
- }
-
- log.heading = config.get('heading') || 'npm'
-
- if (enableColorStderr) {
- log.enableColor()
- } else {
- log.disableColor()
- }
-
- if (config.get('unicode')) {
- log.enableUnicode()
- } else {
- log.disableUnicode()
- }
-
- // if it's more than error, don't show progress
- const quiet = log.levels[log.level] > log.levels.error
-
- if (config.get('progress') && stderrNotDumb && !quiet) {
- log.enableProgress()
- } else {
- log.disableProgress()
- }
-}
diff --git a/lib/utils/tar.js b/lib/utils/tar.js
index 26e7a98df..2f2773c6d 100644
--- a/lib/utils/tar.js
+++ b/lib/utils/tar.js
@@ -1,6 +1,6 @@
const tar = require('tar')
const ssri = require('ssri')
-const npmlog = require('npmlog')
+const log = require('./log-shim')
const formatBytes = require('./format-bytes.js')
const columnify = require('columnify')
const localeCompare = require('@isaacs/string-locale-compare')('en', {
@@ -9,7 +9,7 @@ const localeCompare = require('@isaacs/string-locale-compare')('en', {
})
const logTar = (tarball, opts = {}) => {
- const { unicode = false, log = npmlog } = opts
+ const { unicode = false } = opts
log.notice('')
log.notice('', `${unicode ? '📦 ' : 'package:'} ${tarball.name}@${tarball.version}`)
log.notice('=== Tarball Contents ===')
diff --git a/lib/utils/timers.js b/lib/utils/timers.js
new file mode 100644
index 000000000..acff29eb0
--- /dev/null
+++ b/lib/utils/timers.js
@@ -0,0 +1,111 @@
+const EE = require('events')
+const path = require('path')
+const fs = require('graceful-fs')
+const log = require('./log-shim')
+const withChownSync = require('./with-chown-sync.js')
+
+const _timeListener = Symbol('timeListener')
+const _timeEndListener = Symbol('timeEndListener')
+const _init = Symbol('init')
+
+// This is an event emiiter but on/off
+// only listen on a single internal event that gets
+// emitted whenever a timer ends
+class Timers extends EE {
+ #unfinished = new Map()
+ #finished = {}
+ #onTimeEnd = Symbol('onTimeEnd')
+ #dir = null
+ #initialListener = null
+ #initialTimer = null
+
+ constructor ({ listener = null, start = 'npm' } = {}) {
+ super()
+ this.#initialListener = listener
+ this.#initialTimer = start
+ this[_init]()
+ }
+
+ get unfinished () {
+ return this.#unfinished
+ }
+
+ get finished () {
+ return this.#finished
+ }
+
+ [_init] () {
+ this.on()
+ if (this.#initialListener) {
+ this.on(this.#initialListener)
+ }
+ process.emit('time', this.#initialTimer)
+ this.started = this.#unfinished.get(this.#initialTimer)
+ }
+
+ on (listener) {
+ if (listener) {
+ super.on(this.#onTimeEnd, listener)
+ } else {
+ process.on('time', this[_timeListener])
+ process.on('timeEnd', this[_timeEndListener])
+ }
+ }
+
+ off (listener) {
+ if (listener) {
+ super.off(this.#onTimeEnd, listener)
+ } else {
+ this.removeAllListeners(this.#onTimeEnd)
+ process.off('time', this[_timeListener])
+ process.off('timeEnd', this[_timeEndListener])
+ }
+ }
+
+ load ({ dir }) {
+ this.#dir = dir
+ }
+
+ writeFile (fileData) {
+ try {
+ const globalStart = this.started
+ const globalEnd = this.#finished.npm || Date.now()
+ const content = {
+ ...fileData,
+ ...this.#finished,
+ // add any unfinished timers with their relative start/end
+ unfinished: [...this.#unfinished.entries()].reduce((acc, [name, start]) => {
+ acc[name] = [start - globalStart, globalEnd - globalStart]
+ return acc
+ }, {}),
+ }
+ withChownSync(
+ path.resolve(this.#dir, '_timing.json'),
+ (f) =>
+ // we append line delimited json to this file...forever
+ // XXX: should we also write a process specific timing file?
+ // with similar rules to the debug log (max files, etc)
+ fs.appendFileSync(f, JSON.stringify(content) + '\n')
+ )
+ } catch (e) {
+ log.warn('timing', 'could not write timing file', e)
+ }
+ }
+
+ [_timeListener] = (name) => {
+ this.#unfinished.set(name, Date.now())
+ }
+
+ [_timeEndListener] = (name) => {
+ if (this.#unfinished.has(name)) {
+ const ms = Date.now() - this.#unfinished.get(name)
+ this.#finished[name] = ms
+ this.#unfinished.delete(name)
+ this.emit(this.#onTimeEnd, name, ms)
+ } else {
+ log.silly('timing', "Tried to end timer that doesn't exist:", name)
+ }
+ }
+}
+
+module.exports = Timers
diff --git a/lib/utils/unsupported.js b/lib/utils/unsupported.js
index 5f6a341a8..75aad5e78 100644
--- a/lib/utils/unsupported.js
+++ b/lib/utils/unsupported.js
@@ -1,7 +1,14 @@
+/* eslint-disable no-console */
const semver = require('semver')
const supported = require('../../package.json').engines.node
const knownBroken = '<6.2.0 || 9 <9.3.0'
+// Keep this file compatible with all practical versions of node
+// so we dont get syntax errors when trying to give the users
+// a nice error message. Don't use our log handler because
+// if we encounter a syntax error early on, that will never
+// get displayed to the user.
+
const checkVersion = exports.checkVersion = version => {
const versionNoPrerelease = version.replace(/-.*$/, '')
return {
@@ -24,10 +31,9 @@ exports.checkForBrokenNode = () => {
exports.checkForUnsupportedNode = () => {
const nodejs = checkVersion(process.version)
if (nodejs.unsupported) {
- const log = require('npmlog')
- log.warn('npm', 'npm does not support Node.js ' + process.version)
- log.warn('npm', 'You should probably upgrade to a newer version of node as we')
- log.warn('npm', "can't make any promises that npm will work with this version.")
- log.warn('npm', 'You can find the latest version at https://nodejs.org/')
+ console.error('npm does not support Node.js ' + process.version)
+ console.error('You should probably upgrade to a newer version of node as we')
+ console.error("can't make any promises that npm will work with this version.")
+ console.error('You can find the latest version at https://nodejs.org/')
}
}
diff --git a/lib/utils/update-notifier.js b/lib/utils/update-notifier.js
index 2b45d54c8..44b6a5433 100644
--- a/lib/utils/update-notifier.js
+++ b/lib/utils/update-notifier.js
@@ -10,6 +10,7 @@ const { promisify } = require('util')
const stat = promisify(require('fs').stat)
const writeFile = promisify(require('fs').writeFile)
const { resolve } = require('path')
+const log = require('./log-shim.js')
const isGlobalNpmUpdate = npm => {
return npm.flatOptions.global &&
@@ -61,7 +62,7 @@ const updateNotifier = async (npm, spec = 'latest') => {
// if they're currently using a prerelease, nudge to the next prerelease
// otherwise, nudge to latest.
- const useColor = npm.log.useColor()
+ const useColor = log.useColor()
const mani = await pacote.manifest(`npm@${spec}`, {
// always prefer latest, even if doing --tag=whatever on the cmd
diff --git a/lib/utils/usage.js b/lib/utils/usage.js
index e23e50c51..39eaa45e4 100644
--- a/lib/utils/usage.js
+++ b/lib/utils/usage.js
@@ -1,4 +1,4 @@
-const aliases = require('../utils/cmd-list').aliases
+const aliases = require('./cmd-list').aliases
module.exports = function usage (cmd, txt, opt) {
const post = Object.keys(aliases).reduce(function (p, c) {
diff --git a/lib/utils/with-chown-sync.js b/lib/utils/with-chown-sync.js
new file mode 100644
index 000000000..481b5696d
--- /dev/null
+++ b/lib/utils/with-chown-sync.js
@@ -0,0 +1,13 @@
+const mkdirp = require('mkdirp-infer-owner')
+const fs = require('graceful-fs')
+const path = require('path')
+
+module.exports = (file, method) => {
+ const dir = path.dirname(file)
+ mkdirp.sync(dir)
+ const result = method(file)
+ const st = fs.lstatSync(dir)
+ fs.chownSync(dir, st.uid, st.gid)
+ fs.chownSync(file, st.uid, st.gid)
+ return result
+}
diff --git a/package-lock.json b/package-lock.json
index 646d14988..06a1c95ed 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -63,6 +63,7 @@
"opener",
"pacote",
"parse-conflict-json",
+ "proc-log",
"qrcode-terminal",
"read",
"read-package-json",
@@ -140,6 +141,7 @@
"opener": "^1.5.2",
"pacote": "^12.0.2",
"parse-conflict-json": "^1.1.1",
+ "proc-log": "^1.0.0",
"qrcode-terminal": "^0.12.0",
"read": "~1.0.7",
"read-package-json": "^4.1.1",
diff --git a/package.json b/package.json
index 118ffe623..9c0cf0142 100644
--- a/package.json
+++ b/package.json
@@ -109,6 +109,7 @@
"opener": "^1.5.2",
"pacote": "^12.0.2",
"parse-conflict-json": "^1.1.1",
+ "proc-log": "^1.0.0",
"qrcode-terminal": "^0.12.0",
"read": "~1.0.7",
"read-package-json": "^4.1.1",
@@ -181,6 +182,7 @@
"opener",
"pacote",
"parse-conflict-json",
+ "proc-log",
"qrcode-terminal",
"read",
"read-package-json",
diff --git a/tap-snapshots/test/lib/commands/config.js.test.cjs b/tap-snapshots/test/lib/commands/config.js.test.cjs
index 814f6de7c..da7a89bae 100644
--- a/tap-snapshots/test/lib/commands/config.js.test.cjs
+++ b/tap-snapshots/test/lib/commands/config.js.test.cjs
@@ -9,6 +9,7 @@ exports[`test/lib/commands/config.js TAP config list --json > output matches sna
{
"prefix": "{LOCALPREFIX}",
"userconfig": "{HOME}/.npmrc",
+ "cache": "{NPMDIR}/test/lib/commands/tap-testdir-config-config-list---json-sandbox/cache",
"json": true,
"projectloaded": "yes",
"userloaded": "yes",
@@ -24,7 +25,6 @@ exports[`test/lib/commands/config.js TAP config list --json > output matches sna
"bin-links": true,
"browser": null,
"ca": null,
- "cache": "{CACHE}",
"cache-max": null,
"cache-min": 0,
"cafile": null,
@@ -175,7 +175,7 @@ before = null
bin-links = true
browser = null
ca = null
-cache = "{CACHE}"
+; cache = "{CACHE}" ; overridden by cli
cache-max = null
cache-min = 0
cafile = null
@@ -324,6 +324,7 @@ projectloaded = "yes"
; "cli" config from command line options
+cache = "{NPMDIR}/test/lib/commands/tap-testdir-config-config-list---long-sandbox/cache"
long = true
prefix = "{LOCALPREFIX}"
userconfig = "{HOME}/.npmrc"
@@ -332,6 +333,7 @@ userconfig = "{HOME}/.npmrc"
exports[`test/lib/commands/config.js TAP config list > output matches snapshot 1`] = `
; "cli" config from command line options
+cache = "{NPMDIR}/test/lib/commands/tap-testdir-config-config-list-sandbox/cache"
prefix = "{LOCALPREFIX}"
userconfig = "{HOME}/.npmrc"
diff --git a/tap-snapshots/test/lib/commands/shrinkwrap.js.test.cjs b/tap-snapshots/test/lib/commands/shrinkwrap.js.test.cjs
index a0d579577..ddc80a935 100644
--- a/tap-snapshots/test/lib/commands/shrinkwrap.js.test.cjs
+++ b/tap-snapshots/test/lib/commands/shrinkwrap.js.test.cjs
@@ -16,7 +16,7 @@ exports[`test/lib/commands/shrinkwrap.js TAP with hidden lockfile ancient > must
},
"config": {},
"shrinkwrap": {
- "name": "tap-testdir-shrinkwrap-with-hidden-lockfile-ancient",
+ "name": "root",
"lockfileVersion": 1,
"requires": true
},
@@ -36,10 +36,10 @@ exports[`test/lib/commands/shrinkwrap.js TAP with hidden lockfile ancient upgrad
}
},
"config": {
- "lockfileVersion": 3
+ "lockfile-version": 3
},
"shrinkwrap": {
- "name": "tap-testdir-shrinkwrap-with-hidden-lockfile-ancient-upgrade",
+ "name": "root",
"lockfileVersion": 3,
"requires": true,
"packages": {}
@@ -61,7 +61,7 @@ exports[`test/lib/commands/shrinkwrap.js TAP with hidden lockfile existing > mus
},
"config": {},
"shrinkwrap": {
- "name": "tap-testdir-shrinkwrap-with-hidden-lockfile-existing",
+ "name": "root",
"lockfileVersion": 2,
"requires": true,
"packages": {}
@@ -82,10 +82,10 @@ exports[`test/lib/commands/shrinkwrap.js TAP with hidden lockfile existing downg
}
},
"config": {
- "lockfileVersion": 1
+ "lockfile-version": 1
},
"shrinkwrap": {
- "name": "tap-testdir-shrinkwrap-with-hidden-lockfile-existing-downgrade",
+ "name": "root",
"lockfileVersion": 1,
"requires": true
},
@@ -105,10 +105,10 @@ exports[`test/lib/commands/shrinkwrap.js TAP with hidden lockfile existing upgra
}
},
"config": {
- "lockfileVersion": 3
+ "lockfile-version": 3
},
"shrinkwrap": {
- "name": "tap-testdir-shrinkwrap-with-hidden-lockfile-existing-upgrade",
+ "name": "root",
"lockfileVersion": 3,
"requires": true,
"packages": {}
@@ -124,7 +124,7 @@ exports[`test/lib/commands/shrinkwrap.js TAP with nothing ancient > must match s
"localPrefix": {},
"config": {},
"shrinkwrap": {
- "name": "tap-testdir-shrinkwrap-with-nothing-ancient",
+ "name": "root",
"lockfileVersion": 2,
"requires": true,
"packages": {}
@@ -139,10 +139,10 @@ exports[`test/lib/commands/shrinkwrap.js TAP with nothing ancient upgrade > must
{
"localPrefix": {},
"config": {
- "lockfileVersion": 3
+ "lockfile-version": 3
},
"shrinkwrap": {
- "name": "tap-testdir-shrinkwrap-with-nothing-ancient-upgrade",
+ "name": "root",
"lockfileVersion": 3,
"requires": true,
"packages": {}
@@ -162,12 +162,12 @@ exports[`test/lib/commands/shrinkwrap.js TAP with npm-shrinkwrap.json ancient >
},
"config": {},
"shrinkwrap": {
- "name": "tap-testdir-shrinkwrap-with-npm-shrinkwrap.json-ancient",
+ "name": "root",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "name": "tap-testdir-shrinkwrap-with-npm-shrinkwrap.json-ancient"
+ "name": "root"
}
}
},
@@ -185,15 +185,15 @@ exports[`test/lib/commands/shrinkwrap.js TAP with npm-shrinkwrap.json ancient up
}
},
"config": {
- "lockfileVersion": 3
+ "lockfile-version": 3
},
"shrinkwrap": {
- "name": "tap-testdir-shrinkwrap-with-npm-shrinkwrap.json-ancient-upgrade",
+ "name": "root",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "tap-testdir-shrinkwrap-with-npm-shrinkwrap.json-ancient-upgrade"
+ "name": "root"
}
}
},
@@ -212,12 +212,12 @@ exports[`test/lib/commands/shrinkwrap.js TAP with npm-shrinkwrap.json existing >
},
"config": {},
"shrinkwrap": {
- "name": "tap-testdir-shrinkwrap-with-npm-shrinkwrap.json-existing",
+ "name": "root",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "name": "tap-testdir-shrinkwrap-with-npm-shrinkwrap.json-existing"
+ "name": "root"
}
}
},
@@ -235,10 +235,10 @@ exports[`test/lib/commands/shrinkwrap.js TAP with npm-shrinkwrap.json existing d
}
},
"config": {
- "lockfileVersion": 1
+ "lockfile-version": 1
},
"shrinkwrap": {
- "name": "tap-testdir-shrinkwrap-with-npm-shrinkwrap.json-existing-downgrade",
+ "name": "root",
"lockfileVersion": 1,
"requires": true
},
@@ -256,15 +256,15 @@ exports[`test/lib/commands/shrinkwrap.js TAP with npm-shrinkwrap.json existing u
}
},
"config": {
- "lockfileVersion": 3
+ "lockfile-version": 3
},
"shrinkwrap": {
- "name": "tap-testdir-shrinkwrap-with-npm-shrinkwrap.json-existing-upgrade",
+ "name": "root",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "tap-testdir-shrinkwrap-with-npm-shrinkwrap.json-existing-upgrade"
+ "name": "root"
}
}
},
@@ -283,12 +283,12 @@ exports[`test/lib/commands/shrinkwrap.js TAP with package-lock.json ancient > mu
},
"config": {},
"shrinkwrap": {
- "name": "tap-testdir-shrinkwrap-with-package-lock.json-ancient",
+ "name": "root",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "name": "tap-testdir-shrinkwrap-with-package-lock.json-ancient"
+ "name": "root"
}
}
},
@@ -306,15 +306,15 @@ exports[`test/lib/commands/shrinkwrap.js TAP with package-lock.json ancient upgr
}
},
"config": {
- "lockfileVersion": 3
+ "lockfile-version": 3
},
"shrinkwrap": {
- "name": "tap-testdir-shrinkwrap-with-package-lock.json-ancient-upgrade",
+ "name": "root",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "tap-testdir-shrinkwrap-with-package-lock.json-ancient-upgrade"
+ "name": "root"
}
}
},
@@ -333,12 +333,12 @@ exports[`test/lib/commands/shrinkwrap.js TAP with package-lock.json existing > m
},
"config": {},
"shrinkwrap": {
- "name": "tap-testdir-shrinkwrap-with-package-lock.json-existing",
+ "name": "root",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "name": "tap-testdir-shrinkwrap-with-package-lock.json-existing"
+ "name": "root"
}
}
},
@@ -356,10 +356,10 @@ exports[`test/lib/commands/shrinkwrap.js TAP with package-lock.json existing dow
}
},
"config": {
- "lockfileVersion": 1
+ "lockfile-version": 1
},
"shrinkwrap": {
- "name": "tap-testdir-shrinkwrap-with-package-lock.json-existing-downgrade",
+ "name": "root",
"lockfileVersion": 1,
"requires": true
},
@@ -377,15 +377,15 @@ exports[`test/lib/commands/shrinkwrap.js TAP with package-lock.json existing upg
}
},
"config": {
- "lockfileVersion": 3
+ "lockfile-version": 3
},
"shrinkwrap": {
- "name": "tap-testdir-shrinkwrap-with-package-lock.json-existing-upgrade",
+ "name": "root",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "tap-testdir-shrinkwrap-with-package-lock.json-existing-upgrade"
+ "name": "root"
}
}
},
diff --git a/tap-snapshots/test/lib/commands/view.js.test.cjs b/tap-snapshots/test/lib/commands/view.js.test.cjs
index 10d38cb3f..72d09b44e 100644
--- a/tap-snapshots/test/lib/commands/view.js.test.cjs
+++ b/tap-snapshots/test/lib/commands/view.js.test.cjs
@@ -82,7 +82,7 @@ dist
dist-tags:
latest: 1.0.0
-published yesterday
+published {TIME} ago
`
exports[`test/lib/commands/view.js TAP should log info of package in current working dir specific version > must match snapshot 1`] = `
@@ -99,7 +99,7 @@ dist
dist-tags:
latest: 1.0.0
-published yesterday
+published {TIME} ago
`
exports[`test/lib/commands/view.js TAP should log package info package from git > must match snapshot 1`] = `
@@ -302,7 +302,7 @@ dist
dist-tags:
latest: 1.0.0
-published yesterday
+published {TIME} ago
`
exports[`test/lib/commands/view.js TAP should log package info package with semver range > must match snapshot 1`] = `
@@ -319,7 +319,7 @@ dist
dist-tags:
latest: 1.0.0
-published yesterday
+published {TIME} ago
blue@1.0.1 | Proprietary | deps: none | versions: 2
diff --git a/tap-snapshots/test/lib/utils/error-message.js.test.cjs b/tap-snapshots/test/lib/utils/error-message.js.test.cjs
index e4efb0eb9..3b82e3c05 100644
--- a/tap-snapshots/test/lib/utils/error-message.js.test.cjs
+++ b/tap-snapshots/test/lib/utils/error-message.js.test.cjs
@@ -255,7 +255,7 @@ Object {
"summary": Array [
Array [
"notsup",
- "Unsupported platform for lodash@1.0.0: wanted {\\"os\\":\\"!yours,mine\\",\\"arch\\":\\"x867,x5309\\"} (current: {\\"os\\":\\"posix\\",\\"arch\\":\\"x64\\"})",
+ "Unsupported platform for lodash@1.0.0: wanted {/"os/":/"!yours,mine/",/"arch/":/"x867,x5309/"} (current: {/"os/":/"posix/",/"arch/":/"x64/"})",
],
],
}
@@ -277,7 +277,7 @@ Object {
"summary": Array [
Array [
"notsup",
- "Unsupported platform for lodash@1.0.0: wanted {\\"os\\":\\"!yours\\",\\"arch\\":\\"x420\\"} (current: {\\"os\\":\\"posix\\",\\"arch\\":\\"x64\\"})",
+ "Unsupported platform for lodash@1.0.0: wanted {/"os/":/"!yours/",/"arch/":/"x420/"} (current: {/"os/":/"posix/",/"arch/":/"x64/"})",
],
],
}
@@ -394,7 +394,7 @@ Object {
"",
Error: whoopsie {
"code": "EACCES",
- "dest": "/some/cache/dir/dest",
+ "dest": "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-false-loaded-false-cachePath-false-cacheDest-true-/cache/dest",
"path": "/not/cache/dir/path",
},
],
@@ -428,7 +428,7 @@ Object {
Error: whoopsie {
"code": "EACCES",
"dest": "/not/cache/dir/dest",
- "path": "/some/cache/dir/path",
+ "path": "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-false-loaded-false-cachePath-true-cacheDest-false-/cache/path",
},
],
],
@@ -460,8 +460,8 @@ Object {
"",
Error: whoopsie {
"code": "EACCES",
- "dest": "/some/cache/dir/dest",
- "path": "/some/cache/dir/path",
+ "dest": "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-false-loaded-false-cachePath-true-cacheDest-true-/cache/dest",
+ "path": "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-false-loaded-false-cachePath-true-cacheDest-true-/cache/path",
},
],
],
@@ -502,7 +502,12 @@ Object {
`
exports[`test/lib/utils/error-message.js TAP eacces/eperm {"windows":false,"loaded":true,"cachePath":false,"cacheDest":false} > must match snapshot 2`] = `
-Array []
+Array [
+ Array [
+ "logfile",
+ "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-false-loaded-true-cachePath-false-cacheDest-false-/cache/_logs/{DATE}-debug-0.log",
+ ],
+]
`
exports[`test/lib/utils/error-message.js TAP eacces/eperm {"windows":false,"loaded":true,"cachePath":false,"cacheDest":true} > must match snapshot 1`] = `
@@ -517,7 +522,7 @@ Object {
previous versions of npm which has since been addressed.
To permanently fix this problem, please run:
- sudo chown -R 867:5309 "/some/cache/dir"
+ sudo chown -R 867:5309 "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-false-loaded-true-cachePath-false-cacheDest-true-/cache"
),
],
],
@@ -527,6 +532,10 @@ Object {
exports[`test/lib/utils/error-message.js TAP eacces/eperm {"windows":false,"loaded":true,"cachePath":false,"cacheDest":true} > must match snapshot 2`] = `
Array [
Array [
+ "logfile",
+ "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-false-loaded-true-cachePath-false-cacheDest-true-/cache/_logs/{DATE}-debug-0.log",
+ ],
+ Array [
"dummy stack trace",
],
]
@@ -544,7 +553,7 @@ Object {
previous versions of npm which has since been addressed.
To permanently fix this problem, please run:
- sudo chown -R 867:5309 "/some/cache/dir"
+ sudo chown -R 867:5309 "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-false-loaded-true-cachePath-true-cacheDest-false-/cache"
),
],
],
@@ -554,6 +563,10 @@ Object {
exports[`test/lib/utils/error-message.js TAP eacces/eperm {"windows":false,"loaded":true,"cachePath":true,"cacheDest":false} > must match snapshot 2`] = `
Array [
Array [
+ "logfile",
+ "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-false-loaded-true-cachePath-true-cacheDest-false-/cache/_logs/{DATE}-debug-0.log",
+ ],
+ Array [
"dummy stack trace",
],
]
@@ -571,7 +584,7 @@ Object {
previous versions of npm which has since been addressed.
To permanently fix this problem, please run:
- sudo chown -R 867:5309 "/some/cache/dir"
+ sudo chown -R 867:5309 "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-false-loaded-true-cachePath-true-cacheDest-true-/cache"
),
],
],
@@ -581,6 +594,10 @@ Object {
exports[`test/lib/utils/error-message.js TAP eacces/eperm {"windows":false,"loaded":true,"cachePath":true,"cacheDest":true} > must match snapshot 2`] = `
Array [
Array [
+ "logfile",
+ "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-false-loaded-true-cachePath-true-cacheDest-true-/cache/_logs/{DATE}-debug-0.log",
+ ],
+ Array [
"dummy stack trace",
],
]
@@ -642,7 +659,7 @@ Object {
"",
Error: whoopsie {
"code": "EACCES",
- "dest": "/some/cache/dir/dest",
+ "dest": "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-true-loaded-false-cachePath-false-cacheDest-true-/cache/dest",
"path": "/not/cache/dir/path",
},
],
@@ -677,7 +694,7 @@ Object {
Error: whoopsie {
"code": "EACCES",
"dest": "/not/cache/dir/dest",
- "path": "/some/cache/dir/path",
+ "path": "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-true-loaded-false-cachePath-true-cacheDest-false-/cache/path",
},
],
],
@@ -710,8 +727,8 @@ Object {
"",
Error: whoopsie {
"code": "EACCES",
- "dest": "/some/cache/dir/dest",
- "path": "/some/cache/dir/path",
+ "dest": "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-true-loaded-false-cachePath-true-cacheDest-true-/cache/dest",
+ "path": "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-true-loaded-false-cachePath-true-cacheDest-true-/cache/path",
},
],
],
@@ -753,7 +770,12 @@ Object {
`
exports[`test/lib/utils/error-message.js TAP eacces/eperm {"windows":true,"loaded":true,"cachePath":false,"cacheDest":false} > must match snapshot 2`] = `
-Array []
+Array [
+ Array [
+ "logfile",
+ "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-true-loaded-true-cachePath-false-cacheDest-false-/cache/_logs/{DATE}-debug-0.log",
+ ],
+]
`
exports[`test/lib/utils/error-message.js TAP eacces/eperm {"windows":true,"loaded":true,"cachePath":false,"cacheDest":true} > must match snapshot 1`] = `
@@ -778,7 +800,7 @@ Object {
"",
Error: whoopsie {
"code": "EACCES",
- "dest": "/some/cache/dir/dest",
+ "dest": "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-true-loaded-true-cachePath-false-cacheDest-true-/cache/dest",
"path": "/not/cache/dir/path",
},
],
@@ -787,7 +809,12 @@ Object {
`
exports[`test/lib/utils/error-message.js TAP eacces/eperm {"windows":true,"loaded":true,"cachePath":false,"cacheDest":true} > must match snapshot 2`] = `
-Array []
+Array [
+ Array [
+ "logfile",
+ "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-true-loaded-true-cachePath-false-cacheDest-true-/cache/_logs/{DATE}-debug-0.log",
+ ],
+]
`
exports[`test/lib/utils/error-message.js TAP eacces/eperm {"windows":true,"loaded":true,"cachePath":true,"cacheDest":false} > must match snapshot 1`] = `
@@ -813,7 +840,7 @@ Object {
Error: whoopsie {
"code": "EACCES",
"dest": "/not/cache/dir/dest",
- "path": "/some/cache/dir/path",
+ "path": "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-true-loaded-true-cachePath-true-cacheDest-false-/cache/path",
},
],
],
@@ -821,7 +848,12 @@ Object {
`
exports[`test/lib/utils/error-message.js TAP eacces/eperm {"windows":true,"loaded":true,"cachePath":true,"cacheDest":false} > must match snapshot 2`] = `
-Array []
+Array [
+ Array [
+ "logfile",
+ "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-true-loaded-true-cachePath-true-cacheDest-false-/cache/_logs/{DATE}-debug-0.log",
+ ],
+]
`
exports[`test/lib/utils/error-message.js TAP eacces/eperm {"windows":true,"loaded":true,"cachePath":true,"cacheDest":true} > must match snapshot 1`] = `
@@ -846,8 +878,8 @@ Object {
"",
Error: whoopsie {
"code": "EACCES",
- "dest": "/some/cache/dir/dest",
- "path": "/some/cache/dir/path",
+ "dest": "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-true-loaded-true-cachePath-true-cacheDest-true-/cache/dest",
+ "path": "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-true-loaded-true-cachePath-true-cacheDest-true-/cache/path",
},
],
],
@@ -855,7 +887,12 @@ Object {
`
exports[`test/lib/utils/error-message.js TAP eacces/eperm {"windows":true,"loaded":true,"cachePath":true,"cacheDest":true} > must match snapshot 2`] = `
-Array []
+Array [
+ Array [
+ "logfile",
+ "{CWD}/test/lib/utils/tap-testdir-error-message-eacces-eperm--windows-true-loaded-true-cachePath-true-cacheDest-true-/cache/_logs/{DATE}-debug-0.log",
+ ],
+]
`
exports[`test/lib/utils/error-message.js TAP enoent without a file > must match snapshot 1`] = `
@@ -863,7 +900,7 @@ Object {
"detail": Array [
Array [
"enoent",
- "This is related to npm not being able to find a file.\\n",
+ "This is related to npm not being able to find a file./n",
],
],
"summary": Array [
diff --git a/tap-snapshots/test/lib/utils/exit-handler.js.test.cjs b/tap-snapshots/test/lib/utils/exit-handler.js.test.cjs
index eb383c104..523aabca2 100644
--- a/tap-snapshots/test/lib/utils/exit-handler.js.test.cjs
+++ b/tap-snapshots/test/lib/utils/exit-handler.js.test.cjs
@@ -5,16 +5,56 @@
* Make sure to inspect the output below. Do not ignore changes!
*/
'use strict'
-exports[`test/lib/utils/exit-handler.js TAP handles unknown error > should have expected log contents for unknown error 1`] = `
-24 verbose stack Error: ERROR
-25 verbose cwd {CWD}
-26 verbose Foo 1.0.0
-27 verbose argv "/node" "{CWD}/test/lib/utils/exit-handler.js"
-28 verbose node v1.0.0
-29 verbose npm v1.0.0
-30 error code ERROR
-31 error ERR ERROR
-32 error ERR ERROR
-33 verbose exit 1
+exports[`test/lib/utils/exit-handler.js TAP handles unknown error with logs and debug file > debug file contents 1`] = `
+0 timing npm:load:whichnode Completed in {TIME}ms
+15 timing config:load Completed in {TIME}ms
+16 timing npm:load:configload Completed in {TIME}ms
+17 timing npm:load:setTitle Completed in {TIME}ms
+19 timing npm:load:display Completed in {TIME}ms
+20 verbose logfile {CWD}/test/lib/utils/tap-testdir-exit-handler-handles-unknown-error-with-logs-and-debug-file/cache/_logs/{DATE}-debug-0.log
+21 timing npm:load:logFile Completed in {TIME}ms
+22 timing npm:load:timers Completed in {TIME}ms
+23 timing npm:load:configScope Completed in {TIME}ms
+24 timing npm:load Completed in {TIME}ms
+25 verbose stack Error: Unknown error
+26 verbose cwd {CWD}
+27 verbose Foo 1.0.0
+28 verbose argv "/node" "{CWD}/test/lib/utils/exit-handler.js"
+29 verbose node v1.0.0
+30 verbose npm v1.0.0
+31 error code ECODE
+32 error ERR SUMMARY Unknown error
+33 error ERR DETAIL Unknown error
+34 verbose exit 1
+35 timing npm Completed in {TIME}ms
+36 verbose code 1
+37 error A complete log of this run can be found in:
+37 error {CWD}/test/lib/utils/tap-testdir-exit-handler-handles-unknown-error-with-logs-and-debug-file/cache/_logs/{DATE}-debug-0.log
+`
+exports[`test/lib/utils/exit-handler.js TAP handles unknown error with logs and debug file > logs 1`] = `
+timing npm:load:whichnode Completed in {TIME}ms
+timing config:load Completed in {TIME}ms
+timing npm:load:configload Completed in {TIME}ms
+timing npm:load:setTitle Completed in {TIME}ms
+timing npm:load:display Completed in {TIME}ms
+verbose logfile {CWD}/test/lib/utils/tap-testdir-exit-handler-handles-unknown-error-with-logs-and-debug-file/cache/_logs/{DATE}-debug-0.log
+timing npm:load:logFile Completed in {TIME}ms
+timing npm:load:timers Completed in {TIME}ms
+timing npm:load:configScope Completed in {TIME}ms
+timing npm:load Completed in {TIME}ms
+verbose stack Error: Unknown error
+verbose cwd {CWD}
+verbose Foo 1.0.0
+verbose argv "/node" "{CWD}/test/lib/utils/exit-handler.js"
+verbose node v1.0.0
+verbose npm v1.0.0
+error code ECODE
+error ERR SUMMARY Unknown error
+error ERR DETAIL Unknown error
+verbose exit 1
+timing npm Completed in {TIME}ms
+verbose code 1
+error A complete log of this run can be found in:
+ {CWD}/test/lib/utils/tap-testdir-exit-handler-handles-unknown-error-with-logs-and-debug-file/cache/_logs/{DATE}-debug-0.log
`
diff --git a/tap-snapshots/test/lib/utils/log-file.js.test.cjs b/tap-snapshots/test/lib/utils/log-file.js.test.cjs
new file mode 100644
index 000000000..ecce9eafc
--- /dev/null
+++ b/tap-snapshots/test/lib/utils/log-file.js.test.cjs
@@ -0,0 +1,68 @@
+/* IMPORTANT
+ * This snapshot file is auto-generated, but designed for humans.
+ * It should be checked into source control and tracked carefully.
+ * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
+ * Make sure to inspect the output below. Do not ignore changes!
+ */
+'use strict'
+exports[`test/lib/utils/log-file.js TAP snapshot > must match snapshot 1`] = `
+0 error no prefix
+1 error prefix with prefix
+2 error prefix 1 2 3
+3 verbose { obj: { with: { many: [Object] } } }
+4 verbose {"obj":{"with":{"many":{"props":1}}}}
+5 verbose {
+5 verbose "obj": {
+5 verbose "with": {
+5 verbose "many": {
+5 verbose "props": 1
+5 verbose }
+5 verbose }
+5 verbose }
+5 verbose }
+6 verbose [ 'test', 'with', 'an', 'array' ]
+7 verbose ["test","with","an","array"]
+8 verbose [
+8 verbose "test",
+8 verbose "with",
+8 verbose "an",
+8 verbose "array"
+8 verbose ]
+9 verbose [ 'test', [ 'with', [ 'an', [Array] ] ] ]
+10 verbose ["test",["with",["an",["array"]]]]
+11 verbose [
+11 verbose "test",
+11 verbose [
+11 verbose "with",
+11 verbose [
+11 verbose "an",
+11 verbose [
+11 verbose "array"
+11 verbose ]
+11 verbose ]
+11 verbose ]
+11 verbose ]
+12 error pre has many errors Error: message
+12 error pre at stack trace line 0
+12 error pre at stack trace line 1
+12 error pre at stack trace line 2
+12 error pre at stack trace line 3
+12 error pre at stack trace line 4
+12 error pre at stack trace line 5
+12 error pre at stack trace line 6
+12 error pre at stack trace line 7
+12 error pre at stack trace line 8
+12 error pre at stack trace line 9 Error: message2
+12 error pre at stack trace line 0
+12 error pre at stack trace line 1
+12 error pre at stack trace line 2
+12 error pre at stack trace line 3
+12 error pre at stack trace line 4
+12 error pre at stack trace line 5
+12 error pre at stack trace line 6
+12 error pre at stack trace line 7
+12 error pre at stack trace line 8
+12 error pre at stack trace line 9
+13 error nostack [Error: message]
+
+`
diff --git a/test/fixtures/clean-snapshot.js b/test/fixtures/clean-snapshot.js
new file mode 100644
index 000000000..037155eea
--- /dev/null
+++ b/test/fixtures/clean-snapshot.js
@@ -0,0 +1,19 @@
+// XXX: this also cleans quoted " in json snapshots
+// ideally this could be avoided but its easier to just
+// run this command inside cleanSnapshot
+const normalizePath = (str) => str
+ .replace(/\r\n/g, '\n') // normalize line endings (for ini)
+ .replace(/[A-z]:\\/g, '\\') // turn windows roots to posix ones
+ .replace(/\\+/g, '/') // replace \ with /
+
+const cleanCwd = (path) => normalizePath(path)
+ .replace(new RegExp(normalizePath(process.cwd()), 'g'), '{CWD}')
+
+const cleanDate = (str) =>
+ str.replace(/\d{4}-\d{2}-\d{2}T\d{2}[_:]\d{2}[_:]\d{2}[_:]\d{3}Z/g, '{DATE}')
+
+module.exports = {
+ normalizePath,
+ cleanCwd,
+ cleanDate,
+}
diff --git a/test/fixtures/mock-globals.js b/test/fixtures/mock-globals.js
new file mode 100644
index 000000000..29da2a48b
--- /dev/null
+++ b/test/fixtures/mock-globals.js
@@ -0,0 +1,210 @@
+// An initial implementation for a feature that will hopefully exist in tap
+// https://github.com/tapjs/node-tap/issues/789
+// This file is only used in tests but it is still tested itself.
+// Hopefully it can be removed for a feature in tap in the future
+
+const sep = '.'
+const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k)
+const opd = (o, k) => Object.getOwnPropertyDescriptor(o, k)
+const po = (o) => Object.getPrototypeOf(o)
+const pojo = (o) => Object.prototype.toString.call(o) === '[object Object]'
+const last = (arr) => arr[arr.length - 1]
+const splitLast = (str) => str.split(new RegExp(`\\${sep}(?=[^${sep}]+$)`))
+const dupes = (arr) => arr.filter((k, i) => arr.indexOf(k) !== i)
+const dupesStartsWith = (arr) => arr.filter((k1) => arr.some((k2) => k2.startsWith(k1 + sep)))
+
+// A weird getter that can look up keys on nested objects but also
+// match keys with dots in their names, eg { 'process.env': { TERM: 'a' } }
+// can be looked up with the key 'process.env.TERM'
+const get = (obj, key, childKey = '') => {
+ if (has(obj, key)) {
+ return childKey ? get(obj[key], childKey) : obj[key]
+ } else if (key.includes(sep)) {
+ const [parentKey, prefix] = splitLast(key)
+ return get(
+ obj,
+ parentKey,
+ prefix + (childKey && sep + childKey)
+ )
+ }
+}
+
+// Map an object to an array of nested keys separated by dots
+// { a: 1, b: { c: 2, d: [1] } } => ['a', 'b.c', 'b.d']
+const getKeys = (values, p = '', acc = []) =>
+ Object.entries(values).reduce((memo, [k, value]) => {
+ const key = p ? [p, k].join(sep) : k
+ return pojo(value) ? getKeys(value, key, memo) : memo.concat(key)
+ }, acc)
+
+// Walk prototype chain to get first available descriptor. This is necessary
+// to get the current property descriptor for things like `process.on`.
+// Since `opd(process, 'on') === undefined` but if you
+// walk up the prototype chain you get the original descriptor
+// `opd(po(po(process)), 'on') === { value, ... }`
+const protoDescriptor = (obj, key) => {
+ let descriptor
+ // i always wanted to assign variables in a while loop's condition
+ // i thought it would feel better than this
+ while (!(descriptor = opd(obj, key))) {
+ if (!(obj = po(obj))) {
+ break
+ }
+ }
+ return descriptor
+}
+
+// Path can be different cases across platform so get the original case
+// of the path before anything is changed
+// XXX: other special cases to handle?
+const specialCaseKeys = (() => {
+ const originalKeys = {
+ PATH: process.env.PATH ? 'PATH' : process.env.Path ? 'Path' : 'path',
+ }
+ return (key) => {
+ switch (key.toLowerCase()) {
+ case 'process.env.path':
+ return originalKeys.PATH
+ }
+ }
+})()
+
+const _setGlobal = Symbol('setGlobal')
+const _nextDescriptor = Symbol('nextDescriptor')
+
+class DescriptorStack {
+ #stack = []
+ #global = null
+ #valueKey = null
+ #defaultDescriptor = { configurable: true, writable: true, enumerable: true }
+ #delete = () => ({ DELETE: true })
+ #isDelete = (o) => o && o.DELETE === true
+
+ constructor (key) {
+ const keys = splitLast(key)
+ this.#global = keys.length === 1 ? global : get(global, keys[0])
+ this.#valueKey = specialCaseKeys(key) || last(keys)
+ // If the global object doesnt return a descriptor for the key
+ // then we mark it for deletion on teardown
+ this.#stack = [
+ protoDescriptor(this.#global, this.#valueKey) || this.#delete(),
+ ]
+ }
+
+ add (value) {
+ // This must be a unique object so we can find it later via indexOf
+ // That's why delete/nextDescriptor create new objects
+ const nextDescriptor = this[_nextDescriptor](value)
+ this.#stack.push(this[_setGlobal](nextDescriptor))
+
+ return () => {
+ const index = this.#stack.indexOf(nextDescriptor)
+ // If the stack doesnt contain the descriptor anymore
+ // than do nothing. This keeps the reset function indempotent
+ if (index > -1) {
+ // Resetting removes a descriptor from the stack
+ this.#stack.splice(index, 1)
+ // But we always reset to what is now the most recent in case
+ // resets are being called manually out of order
+ this[_setGlobal](last(this.#stack))
+ }
+ }
+ }
+
+ reset () {
+ // Everything could be reset manually so only
+ // teardown if we have an initial descriptor left
+ // and then delete the rest of the stack
+ if (this.#stack.length) {
+ this[_setGlobal](this.#stack[0])
+ this.#stack.length = 0
+ }
+ }
+
+ [_setGlobal] (d) {
+ if (this.#isDelete(d)) {
+ delete this.#global[this.#valueKey]
+ } else {
+ Object.defineProperty(this.#global, this.#valueKey, d)
+ }
+ return d
+ }
+
+ [_nextDescriptor] (value) {
+ if (value === undefined) {
+ return this.#delete()
+ }
+ const d = last(this.#stack)
+ return {
+ // If the previous descriptor was one to delete the property
+ // then use the default descriptor as the base
+ ...(this.#isDelete(d) ? this.#defaultDescriptor : d),
+ ...(d && d.get ? { get: () => value } : { value }),
+ }
+ }
+}
+
+class MockGlobals {
+ #descriptors = {}
+
+ register (globals, { replace = false } = {}) {
+ // Replace means dont merge in object values but replace them instead
+ // so we only get top level keys instead of walking the obj
+ const keys = replace ? Object.keys(globals) : getKeys(globals)
+
+ // An error state where due to object mode there are multiple global
+ // values to be set with the same key
+ const duplicates = dupes(keys)
+ if (duplicates.length) {
+ throw new Error(`mockGlobals was called with duplicate keys: ${duplicates}`)
+ }
+
+ // Another error where when in replace mode overlapping keys are set like
+ // process and process.stdout which would cause unexpected behavior
+ const overlapping = dupesStartsWith(keys)
+ if (overlapping.length) {
+ const message = overlapping
+ .map((k) => `${k} -> ${keys.filter((kk) => kk.startsWith(k + sep))}`)
+ throw new Error(`mockGlobals was called with overlapping keys: ${message}`)
+ }
+
+ // Set each property passed in and return fns to reset them
+ // Return an object with each path as a key for manually resetting in each test
+ return keys.reduce((acc, key) => {
+ const desc = this.#descriptors[key] || (this.#descriptors[key] = new DescriptorStack(key))
+ acc[key] = desc.add(get(globals, key))
+ return acc
+ }, {})
+ }
+
+ teardown (key) {
+ if (!key) {
+ Object.values(this.#descriptors).forEach((d) => d.reset())
+ return
+ }
+ this.#descriptors[key].reset()
+ }
+}
+
+// Each test has one instance of MockGlobals so it can be called multiple times per test
+// Its a weak map so that it can be garbage collected along with the tap tests without
+// needing to explicitly call cache.delete
+const cache = new WeakMap()
+
+module.exports = (t, globals, options) => {
+ let instance = cache.get(t)
+ if (!instance) {
+ instance = cache.set(t, new MockGlobals()).get(t)
+ // Teardown only needs to be initialized once. The instance
+ // will keep track of its own state during the test
+ t.teardown(() => instance.teardown())
+ }
+
+ return {
+ // Reset contains only the functions to reset the globals
+ // set by this function call
+ reset: instance.register(globals, options),
+ // Teardown will reset across all calls tied to this test
+ teardown: () => instance.teardown(),
+ }
+}
diff --git a/test/fixtures/mock-logs.js b/test/fixtures/mock-logs.js
new file mode 100644
index 000000000..80037c6ff
--- /dev/null
+++ b/test/fixtures/mock-logs.js
@@ -0,0 +1,71 @@
+
+const NPMLOG = require('npmlog')
+const { LEVELS } = require('proc-log')
+
+const merge = (...objs) => objs.reduce((acc, obj) => ({ ...acc, ...obj }))
+
+const mockLogs = (otherMocks = {}) => {
+ // Return mocks as an array with getters for each level
+ // that return an array of logged properties with the
+ // level removed. This is for convenience throughout tests
+ const logs = Object.defineProperties(
+ [],
+ ['timing', ...LEVELS].reduce((acc, level) => {
+ acc[level] = {
+ get () {
+ return this
+ .filter(([l]) => level === l)
+ .map(([l, ...args]) => args)
+ },
+ }
+ return acc
+ }, {})
+ )
+
+ // This returns an object with mocked versions of all necessary
+ // logging modules. It mocks them with methods that add logs
+ // to an array which it also returns. The reason it also returns
+ // the mocks is that in tests the same instance of these mocks
+ // should be passed to multiple calls to t.mock.
+ // XXX: this is messy and fragile and should be removed in favor
+ // of some other way to collect and filter logs across all tests
+ const logMocks = {
+ 'proc-log': merge(
+ { LEVELS },
+ LEVELS.reduce((acc, l) => {
+ acc[l] = (...args) => {
+ // Re-emit log item for since the log file listens on these
+ process.emit('log', l, ...args)
+ // Dont add pause/resume events to the logs. Those aren't displayed
+ // and emitting them is tested in the display layer
+ if (l !== 'pause' && l !== 'resume') {
+ logs.push([l, ...args])
+ }
+ }
+ return acc
+ }, {}),
+ otherMocks['proc-log']
+ ),
+ // Object.assign is important here because we need to assign
+ // mocked properties directly to npmlog and then mock with that
+ // object. This is necessary so tests can still directly set
+ // `log.level = 'silent'` anywhere in the test and have that
+ // that reflected in the npmlog singleton.
+ // XXX: remove with npmlog
+ npmlog: Object.assign(NPMLOG, merge(
+ // no-op all npmlog methods by default so tests
+ // dont output anything to the terminal
+ Object.keys(NPMLOG.levels).reduce((acc, k) => {
+ acc[k] = () => {}
+ return acc
+ }, {}),
+ // except collect timing logs
+ { timing: (...args) => logs.push(['timing', ...args]) },
+ otherMocks.npmlog
+ )),
+ }
+
+ return { logs, logMocks }
+}
+
+module.exports = mockLogs
diff --git a/test/fixtures/mock-npm.js b/test/fixtures/mock-npm.js
index a51ec3e5b..751885531 100644
--- a/test/fixtures/mock-npm.js
+++ b/test/fixtures/mock-npm.js
@@ -1,71 +1,126 @@
-const npmlog = require('npmlog')
-const procLog = require('../../lib/utils/proc-log-listener.js')
-procLog.reset()
-
-// In theory we shouldn't have to do this if all the tests were tearing down
-// their listeners properly, we're still getting warnings even though
-// perfStop() and procLog.reset() is in the teardown script. This silences the
-// warnings for now
-require('events').defaultMaxListeners = Infinity
-
-const realLog = {}
-for (const level in npmlog.levels) {
- realLog[level] = npmlog[level]
-}
-
-const { title, execPath } = process
+const os = require('os')
+const fs = require('fs').promises
+const path = require('path')
+const mockLogs = require('./mock-logs')
+const mockGlobals = require('./mock-globals')
+const log = require('../../lib/utils/log-shim')
-// Eventually this should default to having a prefix of an empty testdir, and
-// awaiting npm.load() unless told not to (for npm tests for example). Ideally
-// the prefix of an empty dir is inferred rather than explicitly set
const RealMockNpm = (t, otherMocks = {}) => {
- const mock = {}
- mock.logs = []
- mock.outputs = []
- mock.joinedOutput = () => {
- return mock.outputs.map(o => o.join(' ')).join('\n')
+ const mock = {
+ ...mockLogs(otherMocks),
+ outputs: [],
+ joinedOutput: () => mock.outputs.map(o => o.join(' ')).join('\n'),
}
- mock.filteredLogs = title => mock.logs.filter(([t]) => t === title).map(([, , msg]) => msg)
- const Npm = t.mock('../../lib/npm.js', otherMocks)
- class MockNpm extends Npm {
- constructor () {
- super()
- for (const level in npmlog.levels) {
- npmlog[level] = (...msg) => {
- mock.logs.push([level, ...msg])
-
- const l = npmlog.level
- npmlog.level = 'silent'
- realLog[level](...msg)
- npmlog.level = l
- }
- }
- // npm.js tests need this restored to actually test this function!
- mock.npmOutput = this.output
- this.output = (...msg) => mock.outputs.push(msg)
+
+ const Npm = t.mock('../../lib/npm.js', {
+ ...otherMocks,
+ ...mock.logMocks,
+ })
+
+ mock.Npm = class MockNpm extends Npm {
+ // lib/npm.js tests needs this to actually test the function!
+ originalOutput (...args) {
+ super.output(...args)
+ }
+
+ output (...args) {
+ mock.outputs.push(args)
}
}
- mock.Npm = MockNpm
- t.afterEach(() => {
- mock.outputs.length = 0
- mock.logs.length = 0
+
+ return mock
+}
+
+// Resolve some options to a function call with supplied args
+const result = (fn, ...args) => typeof fn === 'function' ? fn(...args) : fn
+
+const LoadMockNpm = async (t, {
+ init = true,
+ load = init,
+ testdir = {},
+ config = {},
+ mocks = {},
+ globals = null,
+} = {}) => {
+ // Mock some globals with their original values so they get torn down
+ // back to the original at the end of the test since they are manipulated
+ // by npm itself
+ mockGlobals(t, {
+ process: {
+ title: process.title,
+ execPath: process.execPath,
+ env: {
+ npm_command: process.env.npm_command,
+ COLOR: process.env.COLOR,
+ },
+ },
})
- t.teardown(() => {
- process.removeAllListeners('time')
- process.removeAllListeners('timeEnd')
- npmlog.record.length = 0
- for (const level in npmlog.levels) {
- npmlog[level] = realLog[level]
- }
- procLog.reset()
- process.title = title
- process.execPath = execPath
- delete process.env.npm_command
- delete process.env.COLOR
+ const { Npm, ...rest } = RealMockNpm(t, mocks)
+
+ if (!init && load) {
+ throw new Error('cant `load` without `init`')
+ }
+
+ const _level = log.level
+ t.teardown(() => log.level = _level)
+
+ if (config.loglevel) {
+ // Set log level as early as possible since it is set
+ // on the npmlog singleton and shared across everything
+ log.level = config.loglevel
+ }
+
+ const dir = t.testdir({ root: testdir, cache: {} })
+ const prefix = path.join(dir, 'root')
+ const cache = path.join(dir, 'cache')
+
+ // Set cache to testdir via env var so it is available when load is run
+ // XXX: remove this for a solution where cache argv is passed in
+ mockGlobals(t, {
+ 'process.env.npm_config_cache': cache,
})
- return mock
+ if (globals) {
+ mockGlobals(t, result(globals, { prefix, cache }))
+ }
+
+ const npm = init ? new Npm() : null
+ t.teardown(() => npm && npm.unload())
+
+ if (load) {
+ await npm.load()
+ for (const [k, v] of Object.entries(result(config, { npm, prefix, cache }))) {
+ npm.config.set(k, v)
+ }
+ if (config.loglevel) {
+ // Set global loglevel *again* since it possibly got reset during load
+ // XXX: remove with npmlog
+ log.level = config.loglevel
+ }
+ npm.prefix = prefix
+ npm.cache = cache
+ }
+
+ return {
+ ...rest,
+ Npm,
+ npm,
+ prefix,
+ cache,
+ debugFile: async () => {
+ const readFiles = npm.logFiles.map(f => fs.readFile(f))
+ const logFiles = await Promise.all(readFiles)
+ return logFiles
+ .flatMap((d) => d.toString().trim().split(os.EOL))
+ .filter(Boolean)
+ .join('\n')
+ },
+ timingFile: async () => {
+ const data = await fs.readFile(path.resolve(cache, '_timing.json'), 'utf8')
+ return JSON.parse(data) // XXX: this fails if multiple timings are written
+ },
+ }
}
const realConfig = require('../../lib/utils/config')
@@ -96,21 +151,6 @@ class MockNpm {
set: (k, v) => config[k] = v,
list: [{ ...realConfig.defaults, ...config }],
}
- if (!this.log) {
- this.log = {
- clearProgress: () => {},
- disableProgress: () => {},
- enableProgress: () => {},
- http: () => {},
- info: () => {},
- levels: [],
- notice: () => {},
- pause: () => {},
- silly: () => {},
- verbose: () => {},
- warn: () => {},
- }
- }
}
output (...msg) {
@@ -127,5 +167,5 @@ const FakeMockNpm = (base = {}) => {
module.exports = {
fake: FakeMockNpm,
- real: RealMockNpm,
+ load: LoadMockNpm,
}
diff --git a/test/fixtures/sandbox.js b/test/fixtures/sandbox.js
index b012790fb..701d9cea7 100644
--- a/test/fixtures/sandbox.js
+++ b/test/fixtures/sandbox.js
@@ -4,15 +4,12 @@ const { homedir, tmpdir } = require('os')
const { dirname, join } = require('path')
const { promisify } = require('util')
const mkdirp = require('mkdirp-infer-owner')
-const npmlog = require('npmlog')
const rimraf = promisify(require('rimraf'))
+const mockLogs = require('./mock-logs')
const chain = new Map()
const sandboxes = new Map()
-// Disable lint errors for assigning to process global
-/* global process:writable */
-
// keep a reference to the real process
const _process = process
@@ -34,19 +31,6 @@ createHook({
},
}).enable()
-for (const level in npmlog.levels) {
- npmlog[`_${level}`] = npmlog[level]
- npmlog[level] = (...args) => {
- process._logs = process._logs || {}
- process._logs[level] = process._logs[level] || []
- process._logs[level].push(args)
- const _level = npmlog.level
- npmlog.level = 'silent'
- npmlog[`_${level}`](...args)
- npmlog.level = _level
- }
-}
-
const _data = Symbol('sandbox.data')
const _dirs = Symbol('sandbox.dirs')
const _test = Symbol('sandbox.test')
@@ -57,6 +41,7 @@ const _output = Symbol('sandbox.output')
const _proxy = Symbol('sandbox.proxy')
const _get = Symbol('sandbox.proxy.get')
const _set = Symbol('sandbox.proxy.set')
+const _logs = Symbol('sandbox.logs')
// these config keys can be redacted widely
const redactedDefaults = [
@@ -92,6 +77,7 @@ class Sandbox extends EventEmitter {
global: options.global || join(tempDir, 'global'),
home: options.home || join(tempDir, 'home'),
project: options.project || join(tempDir, 'project'),
+ cache: options.cache || join(tempDir, 'cache'),
}
this[_proxy] = new Proxy(_process, {
@@ -111,7 +97,7 @@ class Sandbox extends EventEmitter {
}
get logs () {
- return this[_proxy]._logs
+ return this[_logs]
}
get global () {
@@ -126,6 +112,10 @@ class Sandbox extends EventEmitter {
return this[_dirs].project
}
+ get cache () {
+ return this[_dirs].cache
+ }
+
get process () {
return this[_proxy]
}
@@ -205,7 +195,9 @@ class Sandbox extends EventEmitter {
if (this[_parent]) {
sandboxes.delete(this[_parent])
}
-
+ if (this[_npm]) {
+ this[_npm].unload()
+ }
return rimraf(this[_dirs].temp).catch(() => null)
}
@@ -275,11 +267,17 @@ class Sandbox extends EventEmitter {
'--prefix', this.project,
'--userconfig', join(this.home, '.npmrc'),
'--globalconfig', join(this.global, 'npmrc'),
+ '--cache', this.cache,
command,
...argv,
]
- const Npm = this[_test].mock('../../lib/npm.js', this[_mocks])
+ const mockedLogs = mockLogs(this[_mocks])
+ this[_logs] = mockedLogs.logs
+ const Npm = this[_test].mock('../../lib/npm.js', {
+ ...this[_mocks],
+ ...mockedLogs.logMocks,
+ })
this[_npm] = new Npm()
this[_npm].output = (...args) => this[_output].push(args)
await this[_npm].load()
@@ -321,11 +319,17 @@ class Sandbox extends EventEmitter {
'--prefix', this.project,
'--userconfig', join(this.home, '.npmrc'),
'--globalconfig', join(this.global, 'npmrc'),
+ '--cache', this.cache,
command,
...argv,
]
- const Npm = this[_test].mock('../../lib/npm.js', this[_mocks])
+ const mockedLogs = mockLogs(this[_mocks])
+ this[_logs] = mockedLogs.logs
+ const Npm = this[_test].mock('../../lib/npm.js', {
+ ...this[_mocks],
+ ...mockedLogs.logMocks,
+ })
this[_npm] = new Npm()
this[_npm].output = (...args) => this[_output].push(args)
await this[_npm].load()
diff --git a/test/index.js b/test/index.js
index 26db16e1f..081c89cee 100644
--- a/test/index.js
+++ b/test/index.js
@@ -1,16 +1,18 @@
const t = require('tap')
const index = require.resolve('../index.js')
const packageIndex = require.resolve('../')
+
t.equal(index, packageIndex, 'index is main package require() export')
t.throws(() => require(index), {
message: 'The programmatic API was removed in npm v8.0.0',
})
t.test('loading as main module will load the cli', t => {
+ const cwd = t.testdir()
const { spawn } = require('child_process')
const LS = require('../lib/commands/ls.js')
const ls = new LS({})
- const p = spawn(process.execPath, [index, 'ls', '-h'])
+ const p = spawn(process.execPath, [index, 'ls', '-h', '--cache', cwd])
const out = []
p.stdout.on('data', c => out.push(c))
p.on('close', (code, signal) => {
diff --git a/test/lib/auth/legacy.js b/test/lib/auth/legacy.js
index 7b61e9f6e..0c23f8ba6 100644
--- a/test/lib/auth/legacy.js
+++ b/test/lib/auth/legacy.js
@@ -6,7 +6,7 @@ const token = '24528a24f240'
const profile = {}
const read = {}
const legacy = t.mock('../../../lib/auth/legacy.js', {
- npmlog: {
+ 'proc-log': {
info: (...msgs) => {
log += msgs.join(' ')
},
diff --git a/test/lib/auth/sso.js b/test/lib/auth/sso.js
index d59220559..473c8cc24 100644
--- a/test/lib/auth/sso.js
+++ b/test/lib/auth/sso.js
@@ -11,7 +11,7 @@ const SSO_URL = 'https://registry.npmjs.org/{SSO_URL}'
const profile = {}
const npmFetch = {}
const sso = t.mock('../../../lib/auth/sso.js', {
- npmlog: {
+ 'proc-log': {
info: (...msgs) => {
log += msgs.join(' ') + '\n'
},
diff --git a/test/lib/cli.js b/test/lib/cli.js
index d762943b4..f02c57d8c 100644
--- a/test/lib/cli.js
+++ b/test/lib/cli.js
@@ -1,176 +1,153 @@
const t = require('tap')
-const { real: mockNpm } = require('../fixtures/mock-npm.js')
-
-const unsupportedMock = {
- checkForBrokenNode: () => {},
- checkForUnsupportedNode: () => {},
-}
-
-let exitHandlerCalled = null
-let exitHandlerNpm = null
-let exitHandlerCb
-const exitHandlerMock = (...args) => {
- exitHandlerCalled = args
- if (exitHandlerCb) {
- exitHandlerCb()
+const mockGlobals = require('../fixtures/mock-globals.js')
+const { load: loadMockNpm } = require('../fixtures/mock-npm.js')
+
+const cliMock = async (t, mocks) => {
+ let exitHandlerArgs = null
+ let npm = null
+ const exitHandlerMock = (...args) => {
+ exitHandlerArgs = args
+ npm.unload()
}
-}
-exitHandlerMock.setNpm = npm => {
- exitHandlerNpm = npm
-}
-
-const logs = []
-const npmlogMock = {
- pause: () => logs.push('pause'),
- verbose: (...msg) => logs.push(['verbose', ...msg]),
- info: (...msg) => logs.push(['info', ...msg]),
-}
+ exitHandlerMock.setNpm = _npm => npm = _npm
-const cliMock = Npm =>
- t.mock('../../lib/cli.js', {
+ const { Npm, outputs, logMocks, logs } = await loadMockNpm(t, { mocks, init: false })
+ const cli = t.mock('../../lib/cli.js', {
'../../lib/npm.js': Npm,
'../../lib/utils/update-notifier.js': async () => null,
- '../../lib/utils/unsupported.js': unsupportedMock,
+ '../../lib/utils/unsupported.js': {
+ checkForBrokenNode: () => {},
+ checkForUnsupportedNode: () => {},
+ },
'../../lib/utils/exit-handler.js': exitHandlerMock,
- npmlog: npmlogMock,
+ ...logMocks,
})
-const processMock = proc => {
- const mocked = {
- ...process,
- on: () => {},
- ...proc,
+ return {
+ Npm,
+ cli,
+ outputs,
+ exitHandlerCalled: () => exitHandlerArgs,
+ exitHandlerNpm: () => npm,
+ logs,
}
- // nopt looks at process directly
- process.argv = mocked.argv
- return mocked
}
-const { argv } = process
-
t.afterEach(() => {
- logs.length = 0
- process.argv = argv
- exitHandlerCalled = null
- exitHandlerNpm = null
+ delete process.exitCode
})
t.test('print the version, and treat npm_g as npm -g', async t => {
- const proc = processMock({
- argv: ['node', 'npm_g', '-v'],
- version: process.version,
+ mockGlobals(t, {
+ 'process.argv': ['node', 'npm_g', '-v'],
})
- const { Npm, outputs } = mockNpm(t)
- const cli = cliMock(Npm)
- await cli(proc)
+ const { logs, cli, Npm, outputs, exitHandlerCalled } = await cliMock(t)
+ await cli(process)
- t.strictSame(proc.argv, ['node', 'npm', '-g', '-v'], 'npm process.argv was rewritten')
t.strictSame(process.argv, ['node', 'npm', '-g', '-v'], 'system process.argv was rewritten')
- t.strictSame(logs, [
- 'pause',
- ['verbose', 'cli', proc.argv],
- ['info', 'using', 'npm@%s', Npm.version],
- ['info', 'using', 'node@%s', process.version],
+ t.strictSame(logs.verbose.filter(([p]) => p !== 'logfile'), [
+ ['cli', process.argv],
+ ])
+ t.strictSame(logs.info, [
+ ['using', 'npm@%s', Npm.version],
+ ['using', 'node@%s', process.version],
])
t.strictSame(outputs, [[Npm.version]])
- t.strictSame(exitHandlerCalled, [])
+ t.strictSame(exitHandlerCalled(), [])
})
t.test('calling with --versions calls npm version with no args', async t => {
- t.plan(5)
- const proc = processMock({
- argv: ['node', 'npm', 'install', 'or', 'whatever', '--versions'],
+ t.plan(6)
+ mockGlobals(t, {
+ 'process.argv': ['node', 'npm', 'install', 'or', 'whatever', '--versions'],
})
- const { Npm, outputs } = mockNpm(t, {
+ const { logs, cli, Npm, outputs, exitHandlerCalled } = await cliMock(t, {
'../../lib/commands/version.js': class Version {
async exec (args) {
t.strictSame(args, [])
}
},
})
- const cli = cliMock(Npm)
- await cli(proc)
- t.equal(proc.title, 'npm')
- t.strictSame(logs, [
- 'pause',
- ['verbose', 'cli', proc.argv],
- ['info', 'using', 'npm@%s', Npm.version],
- ['info', 'using', 'node@%s', process.version],
+
+ await cli(process)
+ t.equal(process.title, 'npm install or whatever')
+ t.strictSame(logs.verbose.filter(([p]) => p !== 'logfile'), [
+ ['cli', process.argv],
+ ])
+ t.strictSame(logs.info, [
+ ['using', 'npm@%s', Npm.version],
+ ['using', 'node@%s', process.version],
])
t.strictSame(outputs, [])
- t.strictSame(exitHandlerCalled, [])
+ t.strictSame(exitHandlerCalled(), [])
})
t.test('logged argv is sanitized', async t => {
- const proc = processMock({
- argv: [
+ mockGlobals(t, {
+ 'process.argv': [
'node',
'npm',
'version',
'https://username:password@npmjs.org/test_url_with_a_password',
],
})
- const { Npm } = mockNpm(t, {
+ const { logs, cli, Npm } = await cliMock(t, {
'../../lib/commands/version.js': class Version {
async exec (args) {}
},
})
- const cli = cliMock(Npm)
-
- await cli(proc)
- t.equal(proc.title, 'npm')
- t.strictSame(logs, [
- 'pause',
+ await cli(process)
+ t.ok(process.title.startsWith('npm version https://username:***@npmjs.org'))
+ t.strictSame(logs.verbose.filter(([p]) => p !== 'logfile'), [
[
- 'verbose',
'cli',
['node', 'npm', 'version', 'https://username:***@npmjs.org/test_url_with_a_password'],
],
- ['info', 'using', 'npm@%s', Npm.version],
- ['info', 'using', 'node@%s', process.version],
+ ])
+ t.strictSame(logs.info, [
+ ['using', 'npm@%s', Npm.version],
+ ['using', 'node@%s', process.version],
])
})
t.test('print usage if no params provided', async t => {
- const proc = processMock({
- argv: ['node', 'npm'],
+ mockGlobals(t, {
+ 'process.argv': ['node', 'npm'],
})
- const { Npm, outputs } = mockNpm(t)
- const cli = cliMock(Npm)
- await cli(proc)
+ const { cli, outputs, exitHandlerCalled, exitHandlerNpm } = await cliMock(t)
+ await cli(process)
t.match(outputs[0][0], 'Usage:', 'outputs npm usage')
- t.match(exitHandlerCalled, [], 'should call exitHandler with no args')
- t.ok(exitHandlerNpm, 'exitHandler npm is set')
- t.match(proc.exitCode, 1)
+ t.match(exitHandlerCalled(), [], 'should call exitHandler with no args')
+ t.ok(exitHandlerNpm(), 'exitHandler npm is set')
+ t.match(process.exitCode, 1)
})
t.test('print usage if non-command param provided', async t => {
- const proc = processMock({
- argv: ['node', 'npm', 'tset'],
+ mockGlobals(t, {
+ 'process.argv': ['node', 'npm', 'tset'],
})
- const { Npm, outputs } = mockNpm(t)
- const cli = cliMock(Npm)
- await cli(proc)
+ const { cli, outputs, exitHandlerCalled, exitHandlerNpm } = await cliMock(t)
+ await cli(process)
t.match(outputs[0][0], 'Unknown command: "tset"')
t.match(outputs[0][0], 'Did you mean this?')
- t.match(exitHandlerCalled, [], 'should call exitHandler with no args')
- t.ok(exitHandlerNpm, 'exitHandler npm is set')
- t.match(proc.exitCode, 1)
+ t.match(exitHandlerCalled(), [], 'should call exitHandler with no args')
+ t.ok(exitHandlerNpm(), 'exitHandler npm is set')
+ t.match(process.exitCode, 1)
})
t.test('load error calls error handler', async t => {
- const proc = processMock({
- argv: ['node', 'npm', 'asdf'],
+ mockGlobals(t, {
+ 'process.argv': ['node', 'npm', 'asdf'],
})
const err = new Error('test load error')
- const { Npm } = mockNpm(t, {
+ const { cli, exitHandlerCalled } = await cliMock(t, {
'../../lib/utils/config/index.js': {
definitions: null,
flatten: null,
@@ -182,7 +159,6 @@ t.test('load error calls error handler', async t => {
}
},
})
- const cli = cliMock(Npm)
- await cli(proc)
- t.strictSame(exitHandlerCalled, [err])
+ await cli(process)
+ t.strictSame(exitHandlerCalled(), [err])
})
diff --git a/test/lib/commands/access.js b/test/lib/commands/access.js
index fdf132aff..298897e4f 100644
--- a/test/lib/commands/access.js
+++ b/test/lib/commands/access.js
@@ -1,18 +1,9 @@
const t = require('tap')
-const { real: mockNpm } = require('../../fixtures/mock-npm.js')
-
-const { Npm } = mockNpm(t)
-const npm = new Npm()
-
-const prefix = t.testdir({})
-
-t.before(async () => {
- await npm.load()
- npm.prefix = prefix
-})
+const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
t.test('completion', async t => {
+ const { npm } = await loadMockNpm(t)
const access = await npm.cmd('access')
const testComp = (argv, expect) => {
const res = access.completion({ conf: { argv: { remain: argv } } })
@@ -42,6 +33,7 @@ t.test('completion', async t => {
})
t.test('subcommand required', async t => {
+ const { npm } = await loadMockNpm(t)
const access = await npm.cmd('access')
await t.rejects(
npm.exec('access', []),
@@ -50,6 +42,7 @@ t.test('subcommand required', async t => {
})
t.test('unrecognized subcommand', async t => {
+ const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('access', ['blerg']),
/Usage: blerg is not a recognized subcommand/,
@@ -58,6 +51,7 @@ t.test('unrecognized subcommand', async t => {
})
t.test('edit', async t => {
+ const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('access', ['edit', '@scoped/another']),
/edit subcommand is not implemented yet/,
@@ -66,15 +60,13 @@ t.test('edit', async t => {
})
t.test('access public on unscoped package', async t => {
- t.teardown(() => {
- npm.prefix = prefix
- })
- const testdir = t.testdir({
- 'package.json': JSON.stringify({
- name: 'npm-access-public-pkg',
- }),
+ const { npm } = await loadMockNpm(t, {
+ testdir: {
+ 'package.json': JSON.stringify({
+ name: 'npm-access-public-pkg',
+ }),
+ },
})
- npm.prefix = testdir
await t.rejects(
npm.exec('access', ['public']),
/Usage: This command is only available for scoped packages/,
@@ -84,30 +76,30 @@ t.test('access public on unscoped package', async t => {
t.test('access public on scoped package', async t => {
t.plan(2)
- const { Npm } = mockNpm(t, {
- libnpmaccess: {
- public: (pkg, { registry }) => {
- t.equal(pkg, name, 'should use pkg name ref')
- t.equal(
- registry,
- 'https://registry.npmjs.org/',
- 'should forward correct options'
- )
- return true
+ const name = '@scoped/npm-access-public-pkg'
+ const { npm } = await loadMockNpm(t, {
+ mocks: {
+ libnpmaccess: {
+ public: (pkg, { registry }) => {
+ t.equal(pkg, name, 'should use pkg name ref')
+ t.equal(
+ registry,
+ 'https://registry.npmjs.org/',
+ 'should forward correct options'
+ )
+ return true
+ },
},
},
+ testdir: {
+ 'package.json': JSON.stringify({ name }),
+ },
})
- const npm = new Npm()
- await npm.load()
- const name = '@scoped/npm-access-public-pkg'
- const testdir = t.testdir({
- 'package.json': JSON.stringify({ name }),
- })
- npm.prefix = testdir
await npm.exec('access', ['public'])
})
t.test('access public on missing package.json', async t => {
+ const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('access', ['public']),
/no package name passed to command and no package.json found/,
@@ -116,14 +108,12 @@ t.test('access public on missing package.json', async t => {
})
t.test('access public on invalid package.json', async t => {
- t.teardown(() => {
- npm.prefix = prefix
- })
- const testdir = t.testdir({
- 'package.json': '{\n',
- node_modules: {},
+ const { npm } = await loadMockNpm(t, {
+ testdir: {
+ 'package.json': '{\n',
+ node_modules: {},
+ },
})
- npm.prefix = testdir
await t.rejects(
npm.exec('access', ['public']),
{ code: 'EJSONPARSE' },
@@ -132,15 +122,13 @@ t.test('access public on invalid package.json', async t => {
})
t.test('access restricted on unscoped package', async t => {
- t.teardown(() => {
- npm.prefix = prefix
- })
- const testdir = t.testdir({
- 'package.json': JSON.stringify({
- name: 'npm-access-restricted-pkg',
- }),
+ const { npm } = await loadMockNpm(t, {
+ testdir: {
+ 'package.json': JSON.stringify({
+ name: 'npm-access-restricted-pkg',
+ }),
+ },
})
- npm.prefix = testdir
await t.rejects(
npm.exec('access', ['public']),
/Usage: This command is only available for scoped packages/,
@@ -150,30 +138,30 @@ t.test('access restricted on unscoped package', async t => {
t.test('access restricted on scoped package', async t => {
t.plan(2)
- const { Npm } = mockNpm(t, {
- libnpmaccess: {
- restricted: (pkg, { registry }) => {
- t.equal(pkg, name, 'should use pkg name ref')
- t.equal(
- registry,
- 'https://registry.npmjs.org/',
- 'should forward correct options'
- )
- return true
+ const name = '@scoped/npm-access-restricted-pkg'
+ const { npm } = await loadMockNpm(t, {
+ mocks: {
+ libnpmaccess: {
+ restricted: (pkg, { registry }) => {
+ t.equal(pkg, name, 'should use pkg name ref')
+ t.equal(
+ registry,
+ 'https://registry.npmjs.org/',
+ 'should forward correct options'
+ )
+ return true
+ },
},
},
+ testdir: {
+ 'package.json': JSON.stringify({ name }),
+ },
})
- const npm = new Npm()
- await npm.load()
- const name = '@scoped/npm-access-restricted-pkg'
- const testdir = t.testdir({
- 'package.json': JSON.stringify({ name }),
- })
- npm.prefix = testdir
await npm.exec('access', ['restricted'])
})
t.test('access restricted on missing package.json', async t => {
+ const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('access', ['restricted']),
/no package name passed to command and no package.json found/,
@@ -182,14 +170,12 @@ t.test('access restricted on missing package.json', async t => {
})
t.test('access restricted on invalid package.json', async t => {
- t.teardown(() => {
- npm.prefix = prefix
- })
- const testdir = t.testdir({
- 'package.json': '{\n',
- node_modules: {},
+ const { npm } = await loadMockNpm(t, {
+ testdir: {
+ 'package.json': '{\n',
+ node_modules: {},
+ },
})
- npm.prefix = testdir
await t.rejects(
npm.exec('access', ['restricted']),
{ code: 'EJSONPARSE' },
@@ -199,17 +185,18 @@ t.test('access restricted on invalid package.json', async t => {
t.test('access grant read-only', async t => {
t.plan(3)
- const { Npm } = mockNpm(t, {
- libnpmaccess: {
- grant: (spec, team, permissions) => {
- t.equal(spec, '@scoped/another', 'should use expected spec')
- t.equal(team, 'myorg:myteam', 'should use expected team')
- t.equal(permissions, 'read-only', 'should forward permissions')
- return true
+ const { npm } = await loadMockNpm(t, {
+ mocks: {
+ libnpmaccess: {
+ grant: (spec, team, permissions) => {
+ t.equal(spec, '@scoped/another', 'should use expected spec')
+ t.equal(team, 'myorg:myteam', 'should use expected team')
+ t.equal(permissions, 'read-only', 'should forward permissions')
+ return true
+ },
},
},
})
- const npm = new Npm()
await npm.exec('access', [
'grant',
'read-only',
@@ -220,17 +207,18 @@ t.test('access grant read-only', async t => {
t.test('access grant read-write', async t => {
t.plan(3)
- const { Npm } = mockNpm(t, {
- libnpmaccess: {
- grant: (spec, team, permissions) => {
- t.equal(spec, '@scoped/another', 'should use expected spec')
- t.equal(team, 'myorg:myteam', 'should use expected team')
- t.equal(permissions, 'read-write', 'should forward permissions')
- return true
+ const { npm } = await loadMockNpm(t, {
+ mocks: {
+ libnpmaccess: {
+ grant: (spec, team, permissions) => {
+ t.equal(spec, '@scoped/another', 'should use expected spec')
+ t.equal(team, 'myorg:myteam', 'should use expected team')
+ t.equal(permissions, 'read-write', 'should forward permissions')
+ return true
+ },
},
},
})
- const npm = new Npm()
await npm.exec('access', [
'grant',
'read-write',
@@ -241,24 +229,23 @@ t.test('access grant read-write', async t => {
t.test('access grant current cwd', async t => {
t.plan(3)
- const testdir = t.testdir({
- 'package.json': JSON.stringify({
- name: 'yargs',
- }),
- })
- const { Npm } = mockNpm(t, {
- libnpmaccess: {
- grant: (spec, team, permissions) => {
- t.equal(spec, 'yargs', 'should use expected spec')
- t.equal(team, 'myorg:myteam', 'should use expected team')
- t.equal(permissions, 'read-write', 'should forward permissions')
- return true
+ const { npm } = await loadMockNpm(t, {
+ mocks: {
+ libnpmaccess: {
+ grant: (spec, team, permissions) => {
+ t.equal(spec, 'yargs', 'should use expected spec')
+ t.equal(team, 'myorg:myteam', 'should use expected team')
+ t.equal(permissions, 'read-write', 'should forward permissions')
+ return true
+ },
},
},
+ testdir: {
+ 'package.json': JSON.stringify({
+ name: 'yargs',
+ }),
+ },
})
- const npm = new Npm()
- await npm.load()
- npm.prefix = testdir
await npm.exec('access', [
'grant',
'read-write',
@@ -267,6 +254,7 @@ t.test('access grant current cwd', async t => {
})
t.test('access grant others', async t => {
+ const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('access', [
'grant',
@@ -280,6 +268,7 @@ t.test('access grant others', async t => {
})
t.test('access grant missing team args', async t => {
+ const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('access', [
'grant',
@@ -293,6 +282,7 @@ t.test('access grant missing team args', async t => {
})
t.test('access grant malformed team arg', async t => {
+ const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('access', [
'grant',
@@ -307,36 +297,37 @@ t.test('access grant malformed team arg', async t => {
t.test('access 2fa-required/2fa-not-required', async t => {
t.plan(2)
- const { Npm } = mockNpm(t, {
- libnpmaccess: {
- tfaRequired: (spec) => {
- t.equal(spec, '@scope/pkg', 'should use expected spec')
- return true
- },
- tfaNotRequired: (spec) => {
- t.equal(spec, 'unscoped-pkg', 'should use expected spec')
- return true
+ const { npm } = await loadMockNpm(t, {
+ mocks: {
+ libnpmaccess: {
+ tfaRequired: (spec) => {
+ t.equal(spec, '@scope/pkg', 'should use expected spec')
+ return true
+ },
+ tfaNotRequired: (spec) => {
+ t.equal(spec, 'unscoped-pkg', 'should use expected spec')
+ return true
+ },
},
},
})
- const npm = new Npm()
-
await npm.exec('access', ['2fa-required', '@scope/pkg'])
await npm.exec('access', ['2fa-not-required', 'unscoped-pkg'])
})
t.test('access revoke', async t => {
t.plan(2)
- const { Npm } = mockNpm(t, {
- libnpmaccess: {
- revoke: (spec, team) => {
- t.equal(spec, '@scoped/another', 'should use expected spec')
- t.equal(team, 'myorg:myteam', 'should use expected team')
- return true
+ const { npm } = await loadMockNpm(t, {
+ mocks: {
+ libnpmaccess: {
+ revoke: (spec, team) => {
+ t.equal(spec, '@scoped/another', 'should use expected spec')
+ t.equal(team, 'myorg:myteam', 'should use expected team')
+ return true
+ },
},
},
})
- const npm = new Npm()
await npm.exec('access', [
'revoke',
'myorg:myteam',
@@ -345,6 +336,7 @@ t.test('access revoke', async t => {
})
t.test('access revoke missing team args', async t => {
+ const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('access', [
'revoke',
@@ -357,6 +349,7 @@ t.test('access revoke missing team args', async t => {
})
t.test('access revoke malformed team arg', async t => {
+ const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('access', [
'revoke',
@@ -370,30 +363,32 @@ t.test('access revoke malformed team arg', async t => {
t.test('npm access ls-packages with no team', async t => {
t.plan(1)
- const { Npm } = mockNpm(t, {
- libnpmaccess: {
- lsPackages: (entity) => {
- t.equal(entity, 'foo', 'should use expected entity')
- return {}
+ const { npm } = await loadMockNpm(t, {
+ mocks: {
+ libnpmaccess: {
+ lsPackages: (entity) => {
+ t.equal(entity, 'foo', 'should use expected entity')
+ return {}
+ },
},
+ '../../lib/utils/get-identity.js': () => Promise.resolve('foo'),
},
- '../../lib/utils/get-identity.js': () => Promise.resolve('foo'),
})
- const npm = new Npm()
await npm.exec('access', ['ls-packages'])
})
t.test('access ls-packages on team', async t => {
t.plan(1)
- const { Npm } = mockNpm(t, {
- libnpmaccess: {
- lsPackages: (entity) => {
- t.equal(entity, 'myorg:myteam', 'should use expected entity')
- return {}
+ const { npm } = await loadMockNpm(t, {
+ mocks: {
+ libnpmaccess: {
+ lsPackages: (entity) => {
+ t.equal(entity, 'myorg:myteam', 'should use expected entity')
+ return {}
+ },
},
},
})
- const npm = new Npm()
await npm.exec('access', [
'ls-packages',
'myorg:myteam',
@@ -402,36 +397,36 @@ t.test('access ls-packages on team', async t => {
t.test('access ls-collaborators on current', async t => {
t.plan(1)
- const testdir = t.testdir({
- 'package.json': JSON.stringify({
- name: 'yargs',
- }),
- })
- const { Npm } = mockNpm(t, {
- libnpmaccess: {
- lsCollaborators: (spec) => {
- t.equal(spec, 'yargs', 'should use expected spec')
- return {}
+ const { npm } = await loadMockNpm(t, {
+ mocks: {
+ libnpmaccess: {
+ lsCollaborators: (spec) => {
+ t.equal(spec, 'yargs', 'should use expected spec')
+ return {}
+ },
},
},
+ testdir: {
+ 'package.json': JSON.stringify({
+ name: 'yargs',
+ }),
+ },
})
- const npm = new Npm()
- await npm.load()
- npm.prefix = testdir
await npm.exec('access', ['ls-collaborators'])
})
t.test('access ls-collaborators on spec', async t => {
t.plan(1)
- const { Npm } = mockNpm(t, {
- libnpmaccess: {
- lsCollaborators: (spec) => {
- t.equal(spec, 'yargs', 'should use expected spec')
- return {}
+ const { npm } = await loadMockNpm(t, {
+ mocks: {
+ libnpmaccess: {
+ lsCollaborators: (spec) => {
+ t.equal(spec, 'yargs', 'should use expected spec')
+ return {}
+ },
},
},
})
- const npm = new Npm()
await npm.exec('access', [
'ls-collaborators',
'yargs',
diff --git a/test/lib/commands/adduser.js b/test/lib/commands/adduser.js
index 71d79ea93..8a9358f9a 100644
--- a/test/lib/commands/adduser.js
+++ b/test/lib/commands/adduser.js
@@ -20,6 +20,13 @@ const authDummy = (npm, options) => {
throw new Error('did not pass full flatOptions to auth function')
}
+ if (!options.log) {
+ // A quick to test to make sure a log gets passed to auth
+ // XXX: should be refactored with change to real mock npm
+ // https://github.com/npm/statusboard/issues/411
+ throw new Error('pass log to auth')
+ }
+
return Promise.resolve({
message: 'success',
newCreds: {
@@ -71,6 +78,8 @@ const AddUser = t.mock('../../../lib/commands/adduser.js', {
npmlog: {
clearProgress: () => null,
disableProgress: () => null,
+ },
+ 'proc-log': {
notice: (_, msg) => {
registryOutput = msg
},
diff --git a/test/lib/commands/audit.js b/test/lib/commands/audit.js
index 3c87c76a8..05f268d6b 100644
--- a/test/lib/commands/audit.js
+++ b/test/lib/commands/audit.js
@@ -1,5 +1,5 @@
const t = require('tap')
-const { real: mockNpm } = require('../../fixtures/mock-npm')
+const { load: _loadMockNpm } = require('../../fixtures/mock-npm')
t.test('should audit using Arborist', async t => {
let ARB_ARGS = null
@@ -8,36 +8,35 @@ t.test('should audit using Arborist', async t => {
let AUDIT_REPORT_CALLED = false
let ARB_OBJ = null
- const { Npm, outputs } = mockNpm(t, {
- 'npm-audit-report': () => {
- AUDIT_REPORT_CALLED = true
- return {
- report: 'there are vulnerabilities',
- exitCode: 0,
- }
- },
- '@npmcli/arborist': function (args) {
- ARB_ARGS = args
- ARB_OBJ = this
- this.audit = () => {
- AUDIT_CALLED = true
- this.auditReport = {}
- }
- },
- '../../lib/utils/reify-finish.js': (npm, arb) => {
- if (arb !== ARB_OBJ) {
- throw new Error('got wrong object passed to reify-output')
- }
+ const loadMockNpm = (t) => _loadMockNpm(t, {
+ mocks: {
+ 'npm-audit-report': () => {
+ AUDIT_REPORT_CALLED = true
+ return {
+ report: 'there are vulnerabilities',
+ exitCode: 0,
+ }
+ },
+ '@npmcli/arborist': function (args) {
+ ARB_ARGS = args
+ ARB_OBJ = this
+ this.audit = () => {
+ AUDIT_CALLED = true
+ this.auditReport = {}
+ }
+ },
+ '../../lib/utils/reify-finish.js': (npm, arb) => {
+ if (arb !== ARB_OBJ) {
+ throw new Error('got wrong object passed to reify-output')
+ }
- REIFY_FINISH_CALLED = true
+ REIFY_FINISH_CALLED = true
+ },
},
})
- const npm = new Npm()
- await npm.load()
- npm.prefix = t.testdir()
-
t.test('audit', async t => {
+ const { npm, outputs } = await loadMockNpm(t)
await npm.exec('audit', [])
t.match(ARB_ARGS, { audit: true, path: npm.prefix })
t.equal(AUDIT_CALLED, true, 'called audit')
@@ -46,6 +45,7 @@ t.test('should audit using Arborist', async t => {
})
t.test('audit fix', async t => {
+ const { npm } = await loadMockNpm(t)
await npm.exec('audit', ['fix'])
t.equal(REIFY_FINISH_CALLED, true, 'called reify output')
})
@@ -53,69 +53,67 @@ t.test('should audit using Arborist', async t => {
t.test('should audit - json', async t => {
t.plan(1)
- const { Npm } = mockNpm(t, {
- 'npm-audit-report': (_, opts) => {
- t.match(opts.reporter, 'json')
- return {
- report: 'there are vulnerabilities',
- exitCode: 0,
- }
+ const { npm } = await _loadMockNpm(t, {
+ mocks: {
+ 'npm-audit-report': (_, opts) => {
+ t.match(opts.reporter, 'json')
+ return {
+ report: 'there are vulnerabilities',
+ exitCode: 0,
+ }
+ },
+ '@npmcli/arborist': function () {
+ this.audit = () => {
+ this.auditReport = {}
+ }
+ },
+ '../../lib/utils/reify-output.js': () => {},
},
- '@npmcli/arborist': function () {
- this.audit = () => {
- this.auditReport = {}
- }
+ config: {
+ json: true,
},
- '../../lib/utils/reify-output.js': () => {},
})
- const npm = new Npm()
- await npm.load()
- npm.prefix = t.testdir()
- npm.config.set('json', true)
await npm.exec('audit', [])
})
t.test('report endpoint error', async t => {
- const { Npm, outputs, filteredLogs } = mockNpm(t, {
- 'npm-audit-report': () => {
- throw new Error('should not call audit report when there are errors')
- },
- '@npmcli/arborist': function () {
- this.audit = () => {
- this.auditReport = {
- error: {
- message: 'hello, this didnt work',
- method: 'POST',
- uri: 'https://example.com/',
- headers: {
- head: ['ers'],
+ const loadMockNpm = (t, options) => _loadMockNpm(t, {
+ mocks: {
+ 'npm-audit-report': () => {
+ throw new Error('should not call audit report when there are errors')
+ },
+ '@npmcli/arborist': function () {
+ this.audit = () => {
+ this.auditReport = {
+ error: {
+ message: 'hello, this didnt work',
+ method: 'POST',
+ uri: 'https://example.com/',
+ headers: {
+ head: ['ers'],
+ },
+ statusCode: 420,
+ body: 'this is a string',
},
- statusCode: 420,
- body: 'this is a string',
- // body: json ? { nope: 'lol' } : Buffer.from('i had a vuln but i eated it lol'),
- },
+ }
}
- }
+ },
+ '../../lib/utils/reify-output.js': () => {},
},
- '../../lib/utils/reify-output.js': () => {},
+ ...options,
})
- const npm = new Npm()
- await npm.load()
- npm.prefix = t.testdir()
- // npm.config.set('json', )
+
t.test('json=false', async t => {
+ const { npm, outputs, logs } = await loadMockNpm(t, { config: { json: false } })
await t.rejects(npm.exec('audit', []), 'audit endpoint returned an error')
- t.match(filteredLogs('warn'), ['hello, this didnt work'])
+ t.match(logs.warn, [['audit', 'hello, this didnt work']])
t.strictSame(outputs, [['this is a string']])
})
t.test('json=true', async t => {
- t.teardown(() => {
- npm.config.set('json', false)
- })
- npm.config.set('json', true)
+ const { npm, outputs, logs } = await loadMockNpm(t, { config: { json: true } })
await t.rejects(npm.exec('audit', []), 'audit endpoint returned an error')
- t.match(filteredLogs('warn'), ['hello, this didnt work'])
+ t.match(logs.warn, [['audit', 'hello, this didnt work']])
t.strictSame(outputs, [[
'{\n' +
' "message": "hello, this didnt work",\n' +
@@ -135,8 +133,7 @@ t.test('report endpoint error', async t => {
})
t.test('completion', async t => {
- const { Npm } = mockNpm(t)
- const npm = new Npm()
+ const { npm } = await _loadMockNpm(t)
const audit = await npm.cmd('audit')
t.test('fix', async t => {
await t.resolveMatch(
diff --git a/test/lib/commands/birthday.js b/test/lib/commands/birthday.js
index 8c95dd57b..9156d3df0 100644
--- a/test/lib/commands/birthday.js
+++ b/test/lib/commands/birthday.js
@@ -1,14 +1,15 @@
const t = require('tap')
-const { real: mockNpm } = require('../../fixtures/mock-npm')
+const { load: loadMockNpm } = require('../../fixtures/mock-npm')
t.test('birthday', async t => {
t.plan(2)
- const { Npm } = mockNpm(t, {
- libnpmexec: ({ args, yes }) => {
- t.ok(yes)
- t.match(args, ['@npmcli/npm-birthday'])
+ const { npm } = await loadMockNpm(t, {
+ mocks: {
+ libnpmexec: ({ args, yes }) => {
+ t.ok(yes)
+ t.match(args, ['@npmcli/npm-birthday'])
+ },
},
})
- const npm = new Npm()
await npm.exec('birthday', [])
})
diff --git a/test/lib/commands/cache.js b/test/lib/commands/cache.js
index 70a8ba1b2..fc92facff 100644
--- a/test/lib/commands/cache.js
+++ b/test/lib/commands/cache.js
@@ -12,11 +12,6 @@ const rimraf = (path, cb) => {
}
let logOutput = []
-const npmlog = {
- silly: (...args) => {
- logOutput.push(['silly', ...args])
- },
-}
let tarballStreamSpec = ''
let tarballStreamOpts = {}
@@ -141,9 +136,16 @@ const cacache = {
const Cache = t.mock('../../../lib/commands/cache.js', {
cacache,
- npmlog,
pacote,
rimraf,
+ 'proc-log': {
+ silly: (...args) => {
+ logOutput.push(['silly', ...args])
+ },
+ warn: (...args) => {
+ logOutput.push(['warn', ...args])
+ },
+ },
})
const npm = mockNpm({
@@ -153,11 +155,6 @@ const npm = mockNpm({
output: (msg) => {
outputOutput.push(msg)
},
- log: {
- warn: (...args) => {
- logOutput.push(['warn', ...args])
- },
- },
})
const cache = new Cache(npm)
diff --git a/test/lib/commands/ci.js b/test/lib/commands/ci.js
index 1091f9125..537d0784f 100644
--- a/test/lib/commands/ci.js
+++ b/test/lib/commands/ci.js
@@ -159,7 +159,7 @@ t.test('should throw if package-lock.json or npm-shrinkwrap missing', async t =>
const CI = t.mock('../../../lib/commands/ci.js', {
'@npmcli/run-script': opts => {},
'../../../lib/utils/reify-finish.js': async () => {},
- npmlog: {
+ 'proc-log': {
verbose: () => {
t.ok(true, 'log fn called')
},
diff --git a/test/lib/commands/completion.js b/test/lib/commands/completion.js
index 51212f06d..dd571baf7 100644
--- a/test/lib/commands/completion.js
+++ b/test/lib/commands/completion.js
@@ -6,189 +6,153 @@ const completionScript = fs
.readFileSync(path.resolve(__dirname, '../../../lib/utils/completion.sh'), { encoding: 'utf8' })
.replace(/^#!.*?\n/, '')
-const { real: mockNpm } = require('../../fixtures/mock-npm')
-
-const { Npm, outputs } = mockNpm(t, {
- '../../lib/utils/is-windows-shell.js': false,
-})
-const npm = new Npm()
+const { load: _loadMockNpm } = require('../../fixtures/mock-npm')
+const mockGlobals = require('../../fixtures/mock-globals')
+
+const loadMockCompletion = async (t, o = {}) => {
+ const { globals, windows, ...options } = o
+ let resetGlobals = {}
+ if (globals) {
+ resetGlobals = mockGlobals(t, globals).reset
+ }
+ const res = await _loadMockNpm(t, {
+ mocks: {
+ '../../lib/utils/is-windows-shell.js': !!windows,
+ ...options.mocks,
+ },
+ ...options,
+ })
+ const completion = await res.npm.cmd('completion')
+ return {
+ resetGlobals,
+ completion,
+ ...res,
+ }
+}
+
+const loadMockCompletionComp = async (t, word, line) =>
+ loadMockCompletion(t, {
+ globals: {
+ 'process.env.COMP_CWORD': word,
+ 'process.env.COMP_LINE': line,
+ 'process.env.COMP_POINT': line.length,
+ },
+ })
t.test('completion', async t => {
- const completion = await npm.cmd('completion')
t.test('completion completion', async t => {
- const home = process.env.HOME
- t.teardown(() => {
- process.env.HOME = home
- })
-
- process.env.HOME = t.testdir({
- '.bashrc': '',
- '.zshrc': '',
+ const { outputs, completion, prefix } = await loadMockCompletion(t, {
+ testdir: {
+ '.bashrc': 'aaa',
+ '.zshrc': 'aaa',
+ },
})
+ mockGlobals(t, { 'process.env.HOME': prefix })
await completion.completion({ w: 2 })
t.matchSnapshot(outputs, 'both shells')
})
t.test('completion completion no known shells', async t => {
- const home = process.env.HOME
- t.teardown(() => {
- process.env.HOME = home
- })
-
- process.env.HOME = t.testdir()
+ const { outputs, completion, prefix } = await loadMockCompletion(t)
+ mockGlobals(t, { 'process.env.HOME': prefix })
await completion.completion({ w: 2 })
t.matchSnapshot(outputs, 'no responses')
})
t.test('completion completion wrong word count', async t => {
+ const { outputs, completion } = await loadMockCompletion(t)
+
await completion.completion({ w: 3 })
t.matchSnapshot(outputs, 'no responses')
})
t.test('dump script when completion is not being attempted', async t => {
- const _write = process.stdout.write
- const _on = process.stdout.on
- t.teardown(() => {
- process.stdout.write = _write
- process.stdout.on = _on
+ let errorHandler, data
+ const { completion, resetGlobals } = await loadMockCompletion(t, {
+ globals: {
+ 'process.stdout.on': (event, handler) => {
+ errorHandler = handler
+ resetGlobals['process.stdout.on']()
+ },
+ 'process.stdout.write': (chunk, callback) => {
+ data = chunk
+ process.nextTick(() => {
+ callback()
+ errorHandler({ errno: 'EPIPE' })
+ })
+ resetGlobals['process.stdout.write']()
+ },
+ },
})
- let errorHandler
- process.stdout.on = (event, handler) => {
- errorHandler = handler
- process.stdout.on = _on
- }
-
- let data
- process.stdout.write = (chunk, callback) => {
- data = chunk
- process.stdout.write = _write
- process.nextTick(() => {
- callback()
- errorHandler({ errno: 'EPIPE' })
- })
- }
-
await completion.exec({})
-
t.equal(data, completionScript, 'wrote the completion script')
})
t.test('dump script exits correctly when EPIPE is emitted on stdout', async t => {
- const _write = process.stdout.write
- const _on = process.stdout.on
- t.teardown(() => {
- process.stdout.write = _write
- process.stdout.on = _on
+ let errorHandler, data
+ const { completion, resetGlobals } = await loadMockCompletion(t, {
+ globals: {
+ 'process.stdout.on': (event, handler) => {
+ if (event === 'error') {
+ errorHandler = handler
+ }
+ resetGlobals['process.stdout.on']()
+ },
+ 'process.stdout.write': (chunk, callback) => {
+ data = chunk
+ process.nextTick(() => {
+ errorHandler({ errno: 'EPIPE' })
+ callback()
+ })
+ resetGlobals['process.stdout.write']()
+ },
+ },
})
- let errorHandler
- process.stdout.on = (event, handler) => {
- errorHandler = handler
- process.stdout.on = _on
- }
-
- let data
- process.stdout.write = (chunk, callback) => {
- data = chunk
- process.stdout.write = _write
- process.nextTick(() => {
- errorHandler({ errno: 'EPIPE' })
- callback()
- })
- }
-
await completion.exec({})
t.equal(data, completionScript, 'wrote the completion script')
})
t.test('single command name', async t => {
- process.env.COMP_CWORD = 1
- process.env.COMP_LINE = 'npm conf'
- process.env.COMP_POINT = process.env.COMP_LINE.length
-
- t.teardown(() => {
- delete process.env.COMP_CWORD
- delete process.env.COMP_LINE
- delete process.env.COMP_POINT
- })
+ const { outputs, completion } = await loadMockCompletionComp(t, 1, 'npm conf')
await completion.exec(['npm', 'conf'])
t.matchSnapshot(outputs, 'single command name')
})
t.test('multiple command names', async t => {
- process.env.COMP_CWORD = 1
- process.env.COMP_LINE = 'npm a'
- process.env.COMP_POINT = process.env.COMP_LINE.length
-
- t.teardown(() => {
- delete process.env.COMP_CWORD
- delete process.env.COMP_LINE
- delete process.env.COMP_POINT
- })
+ const { outputs, completion } = await loadMockCompletionComp(t, 1, 'npm a')
await completion.exec(['npm', 'a'])
t.matchSnapshot(outputs, 'multiple command names')
})
t.test('completion of invalid command name does nothing', async t => {
- process.env.COMP_CWORD = 1
- process.env.COMP_LINE = 'npm compute'
- process.env.COMP_POINT = process.env.COMP_LINE.length
-
- t.teardown(() => {
- delete process.env.COMP_CWORD
- delete process.env.COMP_LINE
- delete process.env.COMP_POINT
- })
+ const { outputs, completion } = await loadMockCompletionComp(t, 1, 'npm compute')
await completion.exec(['npm', 'compute'])
t.matchSnapshot(outputs, 'no results')
})
t.test('subcommand completion', async t => {
- process.env.COMP_CWORD = 2
- process.env.COMP_LINE = 'npm access '
- process.env.COMP_POINT = process.env.COMP_LINE.length
-
- t.teardown(() => {
- delete process.env.COMP_CWORD
- delete process.env.COMP_LINE
- delete process.env.COMP_POINT
- })
+ const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm access ')
await completion.exec(['npm', 'access', ''])
t.matchSnapshot(outputs, 'subcommands')
})
t.test('filtered subcommands', async t => {
- process.env.COMP_CWORD = 2
- process.env.COMP_LINE = 'npm access p'
- process.env.COMP_POINT = process.env.COMP_LINE.length
-
- t.teardown(() => {
- delete process.env.COMP_CWORD
- delete process.env.COMP_LINE
- delete process.env.COMP_POINT
- })
+ const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm access p')
await completion.exec(['npm', 'access', 'p'])
t.matchSnapshot(outputs, 'filtered subcommands')
})
t.test('commands with no completion', async t => {
- process.env.COMP_CWORD = 2
- process.env.COMP_LINE = 'npm adduser '
- process.env.COMP_POINT = process.env.COMP_LINE.length
-
- t.teardown(() => {
- delete process.env.COMP_CWORD
- delete process.env.COMP_LINE
- delete process.env.COMP_POINT
- })
+ const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm adduser ')
// quotes around adduser are to ensure coverage when unescaping commands
await completion.exec(['npm', "'adduser'", ''])
@@ -196,63 +160,28 @@ t.test('completion', async t => {
})
t.test('flags', async t => {
- process.env.COMP_CWORD = 2
- process.env.COMP_LINE = 'npm install --v'
- process.env.COMP_POINT = process.env.COMP_LINE.length
-
- t.teardown(() => {
- delete process.env.COMP_CWORD
- delete process.env.COMP_LINE
- delete process.env.COMP_POINT
- })
+ const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm install --v')
await completion.exec(['npm', 'install', '--v'])
-
t.matchSnapshot(outputs, 'flags')
})
t.test('--no- flags', async t => {
- process.env.COMP_CWORD = 2
- process.env.COMP_LINE = 'npm install --no-v'
- process.env.COMP_POINT = process.env.COMP_LINE.length
-
- t.teardown(() => {
- delete process.env.COMP_CWORD
- delete process.env.COMP_LINE
- delete process.env.COMP_POINT
- })
+ const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm install --no-v')
await completion.exec(['npm', 'install', '--no-v'])
-
t.matchSnapshot(outputs, 'flags')
})
t.test('double dashes escape from flag completion', async t => {
- process.env.COMP_CWORD = 2
- process.env.COMP_LINE = 'npm -- install --'
- process.env.COMP_POINT = process.env.COMP_LINE.length
-
- t.teardown(() => {
- delete process.env.COMP_CWORD
- delete process.env.COMP_LINE
- delete process.env.COMP_POINT
- })
+ const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm -- install --')
await completion.exec(['npm', '--', 'install', '--'])
-
t.matchSnapshot(outputs, 'full command list')
})
t.test('completion cannot complete options that take a value in mid-command', async t => {
- process.env.COMP_CWORD = 2
- process.env.COMP_LINE = 'npm --registry install'
- process.env.COMP_POINT = process.env.COMP_LINE.length
-
- t.teardown(() => {
- delete process.env.COMP_CWORD
- delete process.env.COMP_LINE
- delete process.env.COMP_POINT
- })
+ const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm --registry install')
await completion.exec(['npm', '--registry', 'install'])
t.matchSnapshot(outputs, 'does not try to complete option arguments in the middle of a command')
@@ -260,11 +189,7 @@ t.test('completion', async t => {
})
t.test('windows without bash', async t => {
- const { Npm, outputs } = mockNpm(t, {
- '../../lib/utils/is-windows-shell.js': true,
- })
- const npm = new Npm()
- const completion = await npm.cmd('completion')
+ const { outputs, completion } = await loadMockCompletion(t, { windows: true })
await t.rejects(
completion.exec({}),
{ code: 'ENOTSUP', message: /completion supported only in MINGW/ },
diff --git a/test/lib/commands/dedupe.js b/test/lib/commands/dedupe.js
index 8fc0be061..2e2fae238 100644
--- a/test/lib/commands/dedupe.js
+++ b/test/lib/commands/dedupe.js
@@ -1,11 +1,12 @@
const t = require('tap')
-const { real: mockNpm } = require('../../fixtures/mock-npm')
+const { load: loadMockNpm } = require('../../fixtures/mock-npm')
t.test('should throw in global mode', async (t) => {
- const { Npm } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
- npm.config.set('global', true)
+ const { npm } = await loadMockNpm(t, {
+ config: {
+ global: true,
+ },
+ })
t.rejects(
npm.exec('dedupe', []),
{ code: 'EDEDUPEGLOBAL' },
@@ -15,39 +16,41 @@ t.test('should throw in global mode', async (t) => {
t.test('should remove dupes using Arborist', async (t) => {
t.plan(5)
- const { Npm } = mockNpm(t, {
- '@npmcli/arborist': function (args) {
- t.ok(args, 'gets options object')
- t.ok(args.path, 'gets path option')
- t.ok(args.dryRun, 'gets dryRun from user')
- this.dedupe = () => {
- t.ok(true, 'dedupe is called')
- }
+ const { npm } = await loadMockNpm(t, {
+ mocks: {
+ '@npmcli/arborist': function (args) {
+ t.ok(args, 'gets options object')
+ t.ok(args.path, 'gets path option')
+ t.ok(args.dryRun, 'gets dryRun from user')
+ this.dedupe = () => {
+ t.ok(true, 'dedupe is called')
+ }
+ },
+ '../../lib/utils/reify-finish.js': (npm, arb) => {
+ t.ok(arb, 'gets arborist tree')
+ },
},
- '../../lib/utils/reify-finish.js': (npm, arb) => {
- t.ok(arb, 'gets arborist tree')
+ config: {
+ 'dry-run': 'true',
},
})
- const npm = new Npm()
- await npm.load()
- npm.config.set('prefix', 'foo')
- npm.config.set('dry-run', 'true')
await npm.exec('dedupe', [])
})
t.test('should remove dupes using Arborist - no arguments', async (t) => {
t.plan(1)
- const { Npm } = mockNpm(t, {
- '@npmcli/arborist': function (args) {
- t.ok(args.dryRun, 'gets dryRun from config')
- this.dedupe = () => {}
+ const { npm } = await loadMockNpm(t, {
+ mocks: {
+ '@npmcli/arborist': function (args) {
+ t.ok(args.dryRun, 'gets dryRun from config')
+ this.dedupe = () => {}
+ },
+ '../../lib/utils/reify-output.js': () => {},
+ '../../lib/utils/reify-finish.js': () => {},
+ },
+ config: {
+ 'dry-run': true,
},
- '../../lib/utils/reify-output.js': () => {},
- '../../lib/utils/reify-finish.js': () => {},
})
- const npm = new Npm()
- await npm.load()
- npm.config.set('prefix', 'foo')
- npm.config.set('dry-run', true)
await npm.exec('dedupe', [])
})
diff --git a/test/lib/commands/diff.js b/test/lib/commands/diff.js
index 811936fe6..ed0702e37 100644
--- a/test/lib/commands/diff.js
+++ b/test/lib/commands/diff.js
@@ -31,7 +31,7 @@ const npm = mockNpm({
})
const mocks = {
- npmlog: { info: noop, verbose: noop },
+ 'proc-log': { info: noop, verbose: noop },
libnpmdiff: (...args) => libnpmdiff(...args),
'npm-registry-fetch': async () => ({}),
'../../../lib/utils/usage.js': () => 'usage instructions',
diff --git a/test/lib/commands/dist-tag.js b/test/lib/commands/dist-tag.js
index 6b45dc116..756a09d7d 100644
--- a/test/lib/commands/dist-tag.js
+++ b/test/lib/commands/dist-tag.js
@@ -61,7 +61,7 @@ const logger = (...msgs) => {
}
const DistTag = t.mock('../../../lib/commands/dist-tag.js', {
- npmlog: {
+ 'proc-log': {
error: logger,
info: logger,
verbose: logger,
diff --git a/test/lib/commands/doctor.js b/test/lib/commands/doctor.js
index e3ad5cc72..51b6111a0 100644
--- a/test/lib/commands/doctor.js
+++ b/test/lib/commands/doctor.js
@@ -50,13 +50,13 @@ const logs = {
info: [],
}
-const clearLogs = (obj = logs) => {
+const clearLogs = () => {
output.length = 0
- for (const key in obj) {
- if (Array.isArray(obj[key])) {
- obj[key].length = 0
+ for (const key in logs) {
+ if (Array.isArray(logs[key])) {
+ logs[key].length = 0
} else {
- delete obj[key]
+ delete logs[key]
}
}
}
@@ -65,13 +65,41 @@ const npm = {
flatOptions: {
registry: 'https://registry.npmjs.org/',
},
- log: {
+ version: '7.1.0',
+ output: data => {
+ output.push(data)
+ },
+}
+
+let latestNpm = npm.version
+const pacote = {
+ manifest: async () => {
+ return { version: latestNpm }
+ },
+}
+
+let verifyResponse = { verifiedCount: 1, verifiedContent: 1 }
+const cacache = {
+ verify: async () => {
+ return verifyResponse
+ },
+}
+
+const mocks = {
+ '../../../lib/utils/is-windows.js': false,
+ '../../../lib/utils/ping.js': ping,
+ cacache,
+ pacote,
+ 'make-fetch-happen': fetch,
+ which,
+ 'proc-log': {
info: msg => {
logs.info.push(msg)
},
+ },
+ npmlog: {
newItem: name => {
logs[name] = {}
-
return {
info: (_, msg) => {
if (!logs[name].info) {
@@ -109,33 +137,11 @@ const npm = {
error: 0,
},
},
- version: '7.1.0',
- output: data => {
- output.push(data)
- },
-}
-let latestNpm = npm.version
-const pacote = {
- manifest: async () => {
- return { version: latestNpm }
- },
-}
-
-let verifyResponse = { verifiedCount: 1, verifiedContent: 1 }
-const cacache = {
- verify: async () => {
- return verifyResponse
- },
}
const Doctor = t.mock('../../../lib/commands/doctor.js', {
- '../../../lib/utils/is-windows.js': false,
- '../../../lib/utils/ping.js': ping,
- cacache,
- pacote,
- 'make-fetch-happen': fetch,
- which,
+ ...mocks,
})
const doctor = new Doctor(npm)
@@ -205,7 +211,7 @@ t.test('node versions', t => {
npm.globalDir = dir
npm.localBin = dir
npm.globalBin = dir
- npm.log.level = 'info'
+ mocks.npmlog.level = 'info'
st.teardown(() => {
delete npm.cache
@@ -214,7 +220,7 @@ t.test('node versions', t => {
delete npm.globalDir
delete npm.localBin
delete npm.globalBin
- npm.log.level = 'error'
+ mocks.npmlog.level = 'error'
clearLogs()
})
@@ -293,12 +299,8 @@ t.test('node versions', t => {
vt.test('npm doctor skips some tests in windows', async st => {
const WinDoctor = t.mock('../../../lib/commands/doctor.js', {
+ ...mocks,
'../../../lib/utils/is-windows.js': true,
- '../../../lib/utils/ping.js': ping,
- cacache,
- pacote,
- 'make-fetch-happen': fetch,
- which,
})
const winDoctor = new WinDoctor(npm)
@@ -592,12 +594,7 @@ t.test('node versions', t => {
}
const Doctor = t.mock('../../../lib/commands/doctor.js', {
- '../../../lib/utils/is-windows.js': false,
- '../../../lib/utils/ping.js': ping,
- cacache,
- pacote,
- 'make-fetch-happen': fetch,
- which,
+ ...mocks,
fs,
})
const doctor = new Doctor(npm)
diff --git a/test/lib/commands/exec.js b/test/lib/commands/exec.js
index 4ab26568f..3c75c1d8d 100644
--- a/test/lib/commands/exec.js
+++ b/test/lib/commands/exec.js
@@ -44,17 +44,6 @@ const npm = mockNpm({
localPrefix: 'local-prefix',
localBin: 'local-bin',
globalBin: 'global-bin',
- log: {
- disableProgress: () => {
- PROGRESS_ENABLED = false
- },
- enableProgress: () => {
- PROGRESS_ENABLED = true
- },
- warn: (...args) => {
- LOG_WARN.push(args)
- },
- },
})
const RUN_SCRIPTS = []
@@ -87,6 +76,23 @@ const PATH = require('../../../lib/utils/path.js')
let CI_NAME = 'travis-ci'
+const log = {
+ 'proc-log': {
+ warn: (...args) => {
+ LOG_WARN.push(args)
+ },
+ },
+ npmlog: {
+ disableProgress: () => {
+ PROGRESS_ENABLED = false
+ },
+ enableProgress: () => {
+ PROGRESS_ENABLED = true
+ },
+ clearProgress: () => {},
+ },
+}
+
const mocks = {
libnpmexec: t.mock('libnpmexec', {
'@npmcli/arborist': Arborist,
@@ -95,7 +101,9 @@ const mocks = {
pacote,
read,
'mkdirp-infer-owner': mkdirp,
+ ...log,
}),
+ ...log,
}
const Exec = t.mock('../../../lib/commands/exec.js', mocks)
const exec = new Exec(npm)
diff --git a/test/lib/commands/explore.js b/test/lib/commands/explore.js
index b2e7be213..d1355d767 100644
--- a/test/lib/commands/explore.js
+++ b/test/lib/commands/explore.js
@@ -51,14 +51,17 @@ const getExplore = (windows) => {
path: require('path')[windows ? 'win32' : 'posix'],
'read-package-json-fast': mockRPJ,
'@npmcli/run-script': mockRunScript,
- })
- const npm = {
- dir: windows ? 'c:\\npm\\dir' : '/npm/dir',
- log: {
+ 'proc-log': {
error: (...msg) => logs.push(msg),
+ warn: () => {},
+ },
+ npmlog: {
disableProgress: () => {},
enableProgress: () => {},
},
+ })
+ const npm = {
+ dir: windows ? 'c:\\npm\\dir' : '/npm/dir',
flatOptions: {
shell: 'shell-command',
},
diff --git a/test/lib/commands/find-dupes.js b/test/lib/commands/find-dupes.js
index c1b9c71df..06bd097b6 100644
--- a/test/lib/commands/find-dupes.js
+++ b/test/lib/commands/find-dupes.js
@@ -1,27 +1,28 @@
const t = require('tap')
-const { real: mockNpm } = require('../../fixtures/mock-npm')
+const { load: loadMockNpm } = require('../../fixtures/mock-npm')
t.test('should run dedupe in dryRun mode', async (t) => {
t.plan(5)
- const { Npm } = mockNpm(t, {
- '@npmcli/arborist': function (args) {
- t.ok(args, 'gets options object')
- t.ok(args.path, 'gets path option')
- t.ok(args.dryRun, 'is called in dryRun mode')
- this.dedupe = () => {
- t.ok(true, 'dedupe is called')
- }
+ const { npm } = await loadMockNpm(t, {
+ mocks: {
+ '@npmcli/arborist': function (args) {
+ t.ok(args, 'gets options object')
+ t.ok(args.path, 'gets path option')
+ t.ok(args.dryRun, 'is called in dryRun mode')
+ this.dedupe = () => {
+ t.ok(true, 'dedupe is called')
+ }
+ },
+ '../../lib/utils/reify-finish.js': (npm, arb) => {
+ t.ok(arb, 'gets arborist tree')
+ },
},
- '../../lib/utils/reify-finish.js': (npm, arb) => {
- t.ok(arb, 'gets arborist tree')
+ config: {
+ // explicitly set to false so we can be 100% sure it's always true when it
+ // hits arborist
+ 'dry-run': false,
},
})
- const npm = new Npm()
- await npm.load()
- // explicitly set to false so we can be 100% sure it's always true when it
- // hits arborist
- npm.config.set('dry-run', false)
- npm.config.set('prefix', 'foo')
await npm.exec('find-dupes', [])
})
diff --git a/test/lib/commands/get.js b/test/lib/commands/get.js
index ba9e770e3..597cccc3f 100644
--- a/test/lib/commands/get.js
+++ b/test/lib/commands/get.js
@@ -1,12 +1,10 @@
const t = require('tap')
-const { real: mockNpm } = require('../../fixtures/mock-npm')
+const { load: loadMockNpm } = require('../../fixtures/mock-npm')
t.test('should retrieve values from config', async t => {
- const { joinedOutput, Npm } = mockNpm(t)
- const npm = new Npm()
+ const { joinedOutput, npm } = await loadMockNpm(t)
const name = 'editor'
const value = 'vigor'
- await npm.load()
npm.config.set(name, value)
await npm.exec('get', [name])
t.equal(
diff --git a/test/lib/commands/init.js b/test/lib/commands/init.js
index 74b33168a..215ebc581 100644
--- a/test/lib/commands/init.js
+++ b/test/lib/commands/init.js
@@ -3,14 +3,6 @@ const fs = require('fs')
const { resolve } = require('path')
const { fake: mockNpm } = require('../../fixtures/mock-npm')
-const npmLog = {
- disableProgress: () => null,
- enableProgress: () => null,
- info: () => null,
- pause: () => null,
- resume: () => null,
- silly: () => null,
-}
const config = {
cache: 'bad-cache-dir',
'init-module': '~/.npm-init.js',
@@ -23,10 +15,19 @@ const flatOptions = {
const npm = mockNpm({
flatOptions,
config,
- log: npmLog,
})
const mocks = {
'../../../lib/utils/usage.js': () => 'usage instructions',
+ npmlog: {
+ disableProgress: () => null,
+ enableProgress: () => null,
+ },
+ 'proc-log': {
+ info: () => null,
+ pause: () => null,
+ resume: () => null,
+ silly: () => null,
+ },
}
const Init = t.mock('../../../lib/commands/init.js', mocks)
const init = new Init(npm)
@@ -37,7 +38,6 @@ const noop = () => {}
t.afterEach(() => {
config.yes = true
config.package = undefined
- npm.log = npmLog
process.chdir(_cwd)
console.log = _consolelog
})
@@ -251,13 +251,15 @@ t.test('npm init cancel', async t => {
'init-package-json': (dir, initFile, config, cb) => cb(
new Error('canceled')
),
+ 'proc-log': {
+ ...mocks['proc-log'],
+ warn: (title, msg) => {
+ t.equal(title, 'init', 'should have init title')
+ t.equal(msg, 'canceled', 'should log canceled')
+ },
+ },
})
const init = new Init(npm)
- npm.log = { ...npm.log }
- npm.log.warn = (title, msg) => {
- t.equal(title, 'init', 'should have init title')
- t.equal(msg, 'canceled', 'should log canceled')
- }
process.chdir(npm.localPrefix)
await init.exec([])
diff --git a/test/lib/commands/install.js b/test/lib/commands/install.js
index 994684596..d5db3af67 100644
--- a/test/lib/commands/install.js
+++ b/test/lib/commands/install.js
@@ -1,7 +1,10 @@
const t = require('tap')
const path = require('path')
-const { real: mockNpm } = require('../../fixtures/mock-npm')
+const { load: _loadMockNpm } = require('../../fixtures/mock-npm')
+
+// Make less churn in the test to pass in mocks only signature
+const loadMockNpm = (t, mocks) => _loadMockNpm(t, { mocks })
t.test('with args, dev=true', async t => {
const SCRIPTS = []
@@ -9,7 +12,7 @@ t.test('with args, dev=true', async t => {
let REIFY_CALLED = false
let ARB_OBJ = null
- const { Npm, filteredLogs } = mockNpm(t, {
+ const { npm, logs } = await loadMockNpm(t, {
'@npmcli/run-script': ({ event }) => {
SCRIPTS.push(event)
},
@@ -27,8 +30,6 @@ t.test('with args, dev=true', async t => {
},
})
- const npm = new Npm()
- await npm.load()
// This is here because CI calls tests with `--ignore-scripts`, which config
// picks up from argv
npm.config.set('ignore-scripts', false)
@@ -41,8 +42,8 @@ t.test('with args, dev=true', async t => {
await npm.exec('install', ['fizzbuzz'])
t.match(
- filteredLogs('warn'),
- ['Usage of the `--dev` option is deprecated. Use `--include=dev` instead.']
+ logs.warn,
+ [['install', 'Usage of the `--dev` option is deprecated. Use `--include=dev` instead.']]
)
t.match(
ARB_ARGS,
@@ -59,7 +60,7 @@ t.test('without args', async t => {
let REIFY_CALLED = false
let ARB_OBJ = null
- const { Npm } = mockNpm(t, {
+ const { npm } = await loadMockNpm(t, {
'@npmcli/run-script': ({ event }) => {
SCRIPTS.push(event)
},
@@ -77,8 +78,6 @@ t.test('without args', async t => {
},
})
- const npm = new Npm()
- await npm.load()
npm.prefix = path.resolve(t.testdir({}))
npm.config.set('ignore-scripts', false)
await npm.exec('install', [])
@@ -98,7 +97,7 @@ t.test('without args', async t => {
t.test('should ignore scripts with --ignore-scripts', async t => {
const SCRIPTS = []
let REIFY_CALLED = false
- const { Npm } = mockNpm(t, {
+ const { npm } = await loadMockNpm(t, {
'../../lib/utils/reify-finish.js': async () => {},
'@npmcli/run-script': ({ event }) => {
SCRIPTS.push(event)
@@ -109,8 +108,6 @@ t.test('should ignore scripts with --ignore-scripts', async t => {
}
},
})
- const npm = new Npm()
- await npm.load()
npm.config.set('ignore-scripts', true)
npm.prefix = path.resolve(t.testdir({}))
await npm.exec('install', [])
@@ -122,7 +119,7 @@ t.test('should install globally using Arborist', async t => {
const SCRIPTS = []
let ARB_ARGS = null
let REIFY_CALLED
- const { Npm } = mockNpm(t, {
+ const { npm } = await loadMockNpm(t, {
'@npmcli/run-script': ({ event }) => {
SCRIPTS.push(event)
},
@@ -134,8 +131,6 @@ t.test('should install globally using Arborist', async t => {
}
},
})
- const npm = new Npm()
- await npm.load()
npm.config.set('global', true)
npm.globalPrefix = path.resolve(t.testdir({}))
await npm.exec('install', [])
@@ -148,7 +143,7 @@ t.test('should install globally using Arborist', async t => {
})
t.test('npm i -g npm engines check success', async t => {
- const { Npm } = mockNpm(t, {
+ const { npm } = await loadMockNpm(t, {
'../../lib/utils/reify-finish.js': async () => {},
'@npmcli/arborist': function () {
this.reify = () => {}
@@ -164,8 +159,6 @@ t.test('npm i -g npm engines check success', async t => {
},
},
})
- const npm = new Npm()
- await npm.load()
npm.globalDir = t.testdir({})
npm.config.set('global', true)
await npm.exec('install', ['npm'])
@@ -173,7 +166,7 @@ t.test('npm i -g npm engines check success', async t => {
})
t.test('npm i -g npm engines check failure', async t => {
- const { Npm } = mockNpm(t, {
+ const { npm } = await loadMockNpm(t, {
pacote: {
manifest: () => {
return {
@@ -186,8 +179,6 @@ t.test('npm i -g npm engines check failure', async t => {
},
},
})
- const npm = new Npm()
- await npm.load()
npm.globalDir = t.testdir({})
npm.config.set('global', true)
await t.rejects(
@@ -208,7 +199,7 @@ t.test('npm i -g npm engines check failure', async t => {
})
t.test('npm i -g npm engines check failure forced override', async t => {
- const { Npm } = mockNpm(t, {
+ const { npm } = await loadMockNpm(t, {
'../../lib/utils/reify-finish.js': async () => {},
'@npmcli/arborist': function () {
this.reify = () => {}
@@ -225,8 +216,6 @@ t.test('npm i -g npm engines check failure forced override', async t => {
},
},
})
- const npm = new Npm()
- await npm.load()
npm.globalDir = t.testdir({})
npm.config.set('global', true)
npm.config.set('force', true)
@@ -235,7 +224,7 @@ t.test('npm i -g npm engines check failure forced override', async t => {
})
t.test('npm i -g npm@version engines check failure', async t => {
- const { Npm } = mockNpm(t, {
+ const { npm } = await loadMockNpm(t, {
pacote: {
manifest: () => {
return {
@@ -248,8 +237,6 @@ t.test('npm i -g npm@version engines check failure', async t => {
},
},
})
- const npm = new Npm()
- await npm.load()
npm.globalDir = t.testdir({})
npm.config.set('global', true)
await t.rejects(
@@ -283,8 +270,7 @@ t.test('completion', async t => {
})
t.test('completion to folder - has a match', async t => {
- const { Npm } = mockNpm(t)
- const npm = new Npm()
+ const { npm } = await _loadMockNpm(t, { load: false })
const install = await npm.cmd('install')
process.chdir(testdir)
const res = await install.completion({ partialWord: './ar' })
@@ -292,16 +278,14 @@ t.test('completion', async t => {
})
t.test('completion to folder - invalid dir', async t => {
- const { Npm } = mockNpm(t)
- const npm = new Npm()
+ const { npm } = await _loadMockNpm(t, { load: false })
const install = await npm.cmd('install')
const res = await install.completion({ partialWord: '/does/not/exist' })
t.strictSame(res, [], 'invalid dir: no matching')
})
t.test('completion to folder - no matches', async t => {
- const { Npm } = mockNpm(t)
- const npm = new Npm()
+ const { npm } = await _loadMockNpm(t, { load: false })
const install = await npm.cmd('install')
process.chdir(testdir)
const res = await install.completion({ partialWord: './pa' })
@@ -309,8 +293,7 @@ t.test('completion', async t => {
})
t.test('completion to folder - match is not a package', async t => {
- const { Npm } = mockNpm(t)
- const npm = new Npm()
+ const { npm } = await _loadMockNpm(t, { load: false })
const install = await npm.cmd('install')
process.chdir(testdir)
const res = await install.completion({ partialWord: './othe' })
@@ -318,8 +301,7 @@ t.test('completion', async t => {
})
t.test('completion to url', async t => {
- const { Npm } = mockNpm(t)
- const npm = new Npm()
+ const { npm } = await _loadMockNpm(t, { load: false })
const install = await npm.cmd('install')
process.chdir(testdir)
const res = await install.completion({ partialWord: 'http://path/to/url' })
@@ -327,8 +309,7 @@ t.test('completion', async t => {
})
t.test('no /', async t => {
- const { Npm } = mockNpm(t)
- const npm = new Npm()
+ const { npm } = await _loadMockNpm(t, { load: false })
const install = await npm.cmd('install')
process.chdir(testdir)
const res = await install.completion({ partialWord: 'toto' })
@@ -336,8 +317,7 @@ t.test('completion', async t => {
})
t.test('only /', async t => {
- const { Npm } = mockNpm(t)
- const npm = new Npm()
+ const { npm } = await _loadMockNpm(t, { load: false })
const install = await npm.cmd('install')
process.chdir(testdir)
const res = await install.completion({ partialWord: '/' })
diff --git a/test/lib/commands/logout.js b/test/lib/commands/logout.js
index 39ef86c84..ee01e7500 100644
--- a/test/lib/commands/logout.js
+++ b/test/lib/commands/logout.js
@@ -10,45 +10,31 @@ const flatOptions = {
scope: '',
}
const npm = mockNpm({ config, flatOptions })
-
-const npmlog = {}
-
let result = null
-const npmFetch = (url, opts) => {
- result = { url, opts }
-}
-const mocks = {
- npmlog,
- 'npm-registry-fetch': npmFetch,
+const mockLogout = (otherMocks) => {
+ const Logout = t.mock('../../../lib/commands/logout.js', {
+ 'npm-registry-fetch': (url, opts) => {
+ result = { url, opts }
+ },
+ ...otherMocks,
+ })
+ return new Logout(npm)
}
-const Logout = t.mock('../../../lib/commands/logout.js', mocks)
-const logout = new Logout(npm)
+t.afterEach(() => {
+ delete flatOptions.token
+ result = null
+ config.clearCredentialsByURI = null
+ config.delete = null
+ config.save = null
+})
t.test('token logout', async t => {
- t.teardown(() => {
- delete flatOptions.token
- result = null
- mocks['npm-registry-fetch'] = null
- config.clearCredentialsByURI = null
- config.delete = null
- config.save = null
- npmlog.verbose = null
- })
t.plan(5)
flatOptions['//registry.npmjs.org/:_authToken'] = '@foo/'
- npmlog.verbose = (title, msg) => {
- t.equal(title, 'logout', 'should have correcct log prefix')
- t.equal(
- msg,
- 'clearing token for https://registry.npmjs.org/',
- 'should log message with correct registry'
- )
- }
-
npm.config.clearCredentialsByURI = registry => {
t.equal(
registry,
@@ -61,6 +47,19 @@ t.test('token logout', async t => {
t.equal(type, 'user', 'should save to user config')
}
+ const logout = mockLogout({
+ 'proc-log': {
+ verbose: (title, msg) => {
+ t.equal(title, 'logout', 'should have correcct log prefix')
+ t.equal(
+ msg,
+ 'clearing token for https://registry.npmjs.org/',
+ 'should log message with correct registry'
+ )
+ },
+ },
+ })
+
await logout.exec([])
t.same(
@@ -87,12 +86,11 @@ t.test('token scoped logout', async t => {
delete config['@myscope:registry']
delete flatOptions.scope
result = null
- mocks['npm-registry-fetch'] = null
config.clearCredentialsByURI = null
config.delete = null
config.save = null
- npmlog.verbose = null
})
+
t.plan(7)
flatOptions['//diff-registry.npmjs.com/:_authToken'] = '@bar/'
@@ -102,15 +100,6 @@ t.test('token scoped logout', async t => {
flatOptions.scope = '@myscope'
flatOptions['@myscope:registry'] = 'https://diff-registry.npmjs.com/'
- npmlog.verbose = (title, msg) => {
- t.equal(title, 'logout', 'should have correcct log prefix')
- t.equal(
- msg,
- 'clearing token for https://diff-registry.npmjs.com/',
- 'should log message with correct registry'
- )
- }
-
npm.config.clearCredentialsByURI = registry => {
t.equal(
registry,
@@ -128,6 +117,19 @@ t.test('token scoped logout', async t => {
t.equal(type, 'user', 'should save to user config')
}
+ const logout = mockLogout({
+ 'proc-log': {
+ verbose: (title, msg) => {
+ t.equal(title, 'logout', 'should have correcct log prefix')
+ t.equal(
+ msg,
+ 'clearing token for https://diff-registry.npmjs.com/',
+ 'should log message with correct registry'
+ )
+ },
+ },
+ })
+
await logout.exec([])
t.same(
@@ -154,29 +156,34 @@ t.test('user/pass logout', async t => {
delete flatOptions['//registry.npmjs.org/:_password']
npm.config.clearCredentialsByURI = null
npm.config.save = null
- npmlog.verbose = null
})
t.plan(2)
flatOptions['//registry.npmjs.org/:username'] = 'foo'
flatOptions['//registry.npmjs.org/:_password'] = 'bar'
- npmlog.verbose = (title, msg) => {
- t.equal(title, 'logout', 'should have correct log prefix')
- t.equal(
- msg,
- 'clearing user credentials for https://registry.npmjs.org/',
- 'should log message with correct registry'
- )
- }
-
npm.config.clearCredentialsByURI = () => null
npm.config.save = () => null
+ const logout = mockLogout({
+ 'proc-log': {
+ verbose: (title, msg) => {
+ t.equal(title, 'logout', 'should have correct log prefix')
+ t.equal(
+ msg,
+ 'clearing user credentials for https://registry.npmjs.org/',
+ 'should log message with correct registry'
+ )
+ },
+ },
+ })
+
await logout.exec([])
})
t.test('missing credentials', async t => {
+ const logout = mockLogout()
+
await t.rejects(
logout.exec([]),
{
@@ -191,11 +198,9 @@ t.test('ignore invalid scoped registry config', async t => {
t.teardown(() => {
delete flatOptions.token
result = null
- mocks['npm-registry-fetch'] = null
config.clearCredentialsByURI = null
config.delete = null
config.save = null
- npmlog.verbose = null
})
t.plan(4)
@@ -203,15 +208,6 @@ t.test('ignore invalid scoped registry config', async t => {
config.scope = '@myscope'
flatOptions['@myscope:registry'] = ''
- npmlog.verbose = (title, msg) => {
- t.equal(title, 'logout', 'should have correcct log prefix')
- t.equal(
- msg,
- 'clearing token for https://registry.npmjs.org/',
- 'should log message with correct registry'
- )
- }
-
npm.config.clearCredentialsByURI = registry => {
t.equal(
registry,
@@ -223,6 +219,19 @@ t.test('ignore invalid scoped registry config', async t => {
npm.config.delete = () => null
npm.config.save = () => null
+ const logout = mockLogout({
+ 'proc-log': {
+ verbose: (title, msg) => {
+ t.equal(title, 'logout', 'should have correcct log prefix')
+ t.equal(
+ msg,
+ 'clearing token for https://registry.npmjs.org/',
+ 'should log message with correct registry'
+ )
+ },
+ },
+ })
+
await logout.exec([])
t.same(
diff --git a/test/lib/commands/owner.js b/test/lib/commands/owner.js
index 8645b349f..b5d4d1584 100644
--- a/test/lib/commands/owner.js
+++ b/test/lib/commands/owner.js
@@ -14,11 +14,11 @@ const npm = mockNpm({
})
const npmFetch = { json: noop }
-const npmlog = { error: noop, info: noop, verbose: noop }
+const log = { error: noop, info: noop, verbose: noop }
const pacote = { packument: noop }
const mocks = {
- npmlog,
+ 'proc-log': log,
'npm-registry-fetch': npmFetch,
pacote,
'../../../lib/utils/otplease.js': async (opts, fn) => fn({ otp: '123456', opts }),
@@ -97,7 +97,7 @@ t.test('owner ls no args no cwd package', async t => {
result = ''
t.teardown(() => {
result = ''
- npmlog.error = noop
+ log.error = noop
})
await t.rejects(
@@ -114,14 +114,14 @@ t.test('owner ls fails to retrieve packument', async t => {
pacote.packument = () => {
throw new Error('ERR')
}
- npmlog.error = (title, msg, pkgName) => {
+ log.error = (title, msg, pkgName) => {
t.equal(title, 'owner ls', 'should list npm owner ls title')
t.equal(msg, "Couldn't get owner data", 'should use expected msg')
t.equal(pkgName, '@npmcli/map-workspaces', 'should use pkg name')
}
t.teardown(() => {
result = ''
- npmlog.error = noop
+ log.error = noop
pacote.packument = noop
})
@@ -276,7 +276,7 @@ t.test('owner add <user> <pkg> already an owner', async t => {
t.plan(2)
result = ''
- npmlog.info = (title, msg) => {
+ log.info = (title, msg) => {
t.equal(title, 'owner add', 'should use expected title')
t.equal(
msg,
@@ -304,7 +304,7 @@ t.test('owner add <user> <pkg> already an owner', async t => {
}
t.teardown(() => {
result = ''
- npmlog.info = noop
+ log.info = noop
npmFetch.json = noop
pacote.packument = noop
})
@@ -385,7 +385,7 @@ t.test('owner add <user> <pkg> fails to retrieve user info', async t => {
t.plan(3)
result = ''
- npmlog.error = (title, msg) => {
+ log.error = (title, msg) => {
t.equal(title, 'owner mutate', 'should use expected title')
t.equal(msg, 'Error getting user data for foo')
}
@@ -406,7 +406,7 @@ t.test('owner add <user> <pkg> fails to retrieve user info', async t => {
})
t.teardown(() => {
result = ''
- npmlog.error = noop
+ log.error = noop
npmFetch.json = noop
pacote.packument = noop
})
@@ -552,7 +552,7 @@ t.test('owner rm <user> <pkg> not a current owner', async t => {
t.plan(2)
result = ''
- npmlog.info = (title, msg) => {
+ log.info = (title, msg) => {
t.equal(title, 'owner rm', 'should log expected title')
t.equal(msg, 'Not a package owner: foo', 'should log.info not a package owner msg')
}
@@ -578,7 +578,7 @@ t.test('owner rm <user> <pkg> not a current owner', async t => {
}
t.teardown(() => {
result = ''
- npmlog.info = noop
+ log.info = noop
npmFetch.json = noop
pacote.packument = noop
})
diff --git a/test/lib/commands/pack.js b/test/lib/commands/pack.js
index bc8877208..21057e207 100644
--- a/test/lib/commands/pack.js
+++ b/test/lib/commands/pack.js
@@ -1,5 +1,5 @@
const t = require('tap')
-const { real: mockNpm } = require('../../fixtures/mock-npm')
+const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const path = require('path')
const fs = require('fs')
@@ -9,33 +9,31 @@ t.afterEach(t => {
})
t.test('should pack current directory with no arguments', async t => {
- const { Npm, outputs, filteredLogs } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
- npm.prefix = t.testdir({
- 'package.json': JSON.stringify({
- name: 'test-package',
- version: '1.0.0',
- }),
+ const { npm, outputs, logs } = await loadMockNpm(t, {
+ testdir: {
+ 'package.json': JSON.stringify({
+ name: 'test-package',
+ version: '1.0.0',
+ }),
+ },
})
process.chdir(npm.prefix)
await npm.exec('pack', [])
const filename = 'test-package-1.0.0.tgz'
t.strictSame(outputs, [[filename]])
- t.matchSnapshot(filteredLogs('notice'), 'logs pack contents')
+ t.matchSnapshot(logs.notice.map(([, m]) => m), 'logs pack contents')
t.ok(fs.statSync(path.resolve(npm.prefix, filename)))
})
t.test('follows pack-destination config', async t => {
- const { Npm, outputs } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
- npm.prefix = t.testdir({
- 'package.json': JSON.stringify({
- name: 'test-package',
- version: '1.0.0',
- }),
- 'tar-destination': {},
+ const { npm, outputs } = await loadMockNpm(t, {
+ testdir: {
+ 'package.json': JSON.stringify({
+ name: 'test-package',
+ version: '1.0.0',
+ }),
+ 'tar-destination': {},
+ },
})
process.chdir(npm.prefix)
npm.config.set('pack-destination', path.join(npm.prefix, 'tar-destination'))
@@ -46,14 +44,13 @@ t.test('follows pack-destination config', async t => {
})
t.test('should pack given directory for scoped package', async t => {
- const { Npm, outputs } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
- npm.prefix = t.testdir({
- 'package.json': JSON.stringify({
- name: '@npm/test-package',
- version: '1.0.0',
- }),
+ const { npm, outputs } = await loadMockNpm(t, {
+ testdir: {
+ 'package.json': JSON.stringify({
+ name: '@npm/test-package',
+ version: '1.0.0',
+ }),
+ },
})
process.chdir(npm.prefix)
await npm.exec('pack', [])
@@ -63,49 +60,46 @@ t.test('should pack given directory for scoped package', async t => {
})
t.test('should log output as valid json', async t => {
- const { Npm, outputs, filteredLogs } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
- npm.prefix = t.testdir({
- 'package.json': JSON.stringify({
- name: 'test-package',
- version: '1.0.0',
- }),
+ const { npm, outputs, logs } = await loadMockNpm(t, {
+ testdir: {
+ 'package.json': JSON.stringify({
+ name: 'test-package',
+ version: '1.0.0',
+ }),
+ },
})
process.chdir(npm.prefix)
npm.config.set('json', true)
await npm.exec('pack', [])
const filename = 'test-package-1.0.0.tgz'
t.matchSnapshot(outputs.map(JSON.parse), 'outputs as json')
- t.matchSnapshot(filteredLogs('notice'), 'logs pack contents')
+ t.matchSnapshot(logs.notice.map(([, m]) => m), 'logs pack contents')
t.ok(fs.statSync(path.resolve(npm.prefix, filename)))
})
t.test('dry run', async t => {
- const { Npm, outputs, filteredLogs } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
- npm.prefix = t.testdir({
- 'package.json': JSON.stringify({
- name: 'test-package',
- version: '1.0.0',
- }),
+ const { npm, outputs, logs } = await loadMockNpm(t, {
+ testdir: {
+ 'package.json': JSON.stringify({
+ name: 'test-package',
+ version: '1.0.0',
+ }),
+ },
})
npm.config.set('dry-run', true)
process.chdir(npm.prefix)
await npm.exec('pack', [])
const filename = 'test-package-1.0.0.tgz'
t.strictSame(outputs, [[filename]])
- t.matchSnapshot(filteredLogs('notice'), 'logs pack contents')
+ t.matchSnapshot(logs.notice.map(([, m]) => m), 'logs pack contents')
t.throws(() => fs.statSync(path.resolve(npm.prefix, filename)))
})
t.test('invalid packument', async t => {
- const { Npm, outputs } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
- npm.prefix = t.testdir({
- 'package.json': '{}',
+ const { npm, outputs } = await loadMockNpm(t, {
+ testdir: {
+ 'package.json': '{}',
+ },
})
process.chdir(npm.prefix)
await t.rejects(
@@ -116,52 +110,58 @@ t.test('invalid packument', async t => {
})
t.test('workspaces', async t => {
- const { Npm, outputs } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
- npm.prefix = t.testdir({
- 'package.json': JSON.stringify(
- {
- name: 'workspaces-test',
- version: '1.0.0',
- workspaces: ['workspace-a', 'workspace-b'],
+ const loadWorkspaces = (t) => loadMockNpm(t, {
+ testdir: {
+ 'package.json': JSON.stringify(
+ {
+ name: 'workspaces-test',
+ version: '1.0.0',
+ workspaces: ['workspace-a', 'workspace-b'],
+ },
+ null,
+ 2
+ ),
+ 'workspace-a': {
+ 'package.json': JSON.stringify({
+ name: 'workspace-a',
+ version: '1.0.0',
+ }),
+ },
+ 'workspace-b': {
+ 'package.json': JSON.stringify({
+ name: 'workspace-b',
+ version: '1.0.0',
+ }),
},
- null,
- 2
- ),
- 'workspace-a': {
- 'package.json': JSON.stringify({
- name: 'workspace-a',
- version: '1.0.0',
- }),
},
- 'workspace-b': {
- 'package.json': JSON.stringify({
- name: 'workspace-b',
- version: '1.0.0',
- }),
+ config: {
+ workspaces: true,
},
})
- npm.config.set('workspaces', true)
+
t.test('all workspaces', async t => {
+ const { npm, outputs } = await loadWorkspaces(t)
process.chdir(npm.prefix)
await npm.exec('pack', [])
t.strictSame(outputs, [['workspace-a-1.0.0.tgz'], ['workspace-b-1.0.0.tgz']])
})
t.test('all workspaces, `.` first arg', async t => {
+ const { npm, outputs } = await loadWorkspaces(t)
process.chdir(npm.prefix)
await npm.exec('pack', ['.'])
t.strictSame(outputs, [['workspace-a-1.0.0.tgz'], ['workspace-b-1.0.0.tgz']])
})
t.test('one workspace', async t => {
+ const { npm, outputs } = await loadWorkspaces(t)
process.chdir(npm.prefix)
await npm.exec('pack', ['workspace-a'])
t.strictSame(outputs, [['workspace-a-1.0.0.tgz']])
})
t.test('specific package', async t => {
+ const { npm, outputs } = await loadWorkspaces(t)
process.chdir(npm.prefix)
await npm.exec('pack', [npm.prefix])
t.strictSame(outputs, [['workspaces-test-1.0.0.tgz']])
diff --git a/test/lib/commands/ping.js b/test/lib/commands/ping.js
index 7011c709b..f808e0ac3 100644
--- a/test/lib/commands/ping.js
+++ b/test/lib/commands/ping.js
@@ -11,7 +11,7 @@ t.test('pings', async t => {
t.equal(spec.registry, registry, 'passes flatOptions')
return {}
},
- npmlog: {
+ 'proc-log': {
notice: (type, spec) => {
++noticeCalls
if (noticeCalls === 1) {
@@ -45,7 +45,7 @@ t.test('pings and logs details', async t => {
t.equal(spec.registry, registry, 'passes flatOptions')
return details
},
- npmlog: {
+ 'proc-log': {
notice: (type, spec) => {
++noticeCalls
if (noticeCalls === 1) {
@@ -83,7 +83,7 @@ t.test('pings and returns json', async t => {
t.equal(spec.registry, registry, 'passes flatOptions')
return details
},
- npmlog: {
+ 'proc-log': {
notice: (type, spec) => {
++noticeCalls
if (noticeCalls === 1) {
diff --git a/test/lib/commands/prefix.js b/test/lib/commands/prefix.js
index 6f059e73a..e8295cf6a 100644
--- a/test/lib/commands/prefix.js
+++ b/test/lib/commands/prefix.js
@@ -1,9 +1,8 @@
const t = require('tap')
-const { real: mockNpm } = require('../../fixtures/mock-npm')
+const { load: loadMockNpm } = require('../../fixtures/mock-npm')
t.test('prefix', async t => {
- const { joinedOutput, Npm } = mockNpm(t)
- const npm = new Npm()
+ const { joinedOutput, npm } = await loadMockNpm(t, { load: false })
await npm.exec('prefix', [])
t.equal(
joinedOutput(),
diff --git a/test/lib/commands/profile.js b/test/lib/commands/profile.js
index 6554ca89e..0f16c1db1 100644
--- a/test/lib/commands/profile.js
+++ b/test/lib/commands/profile.js
@@ -22,6 +22,8 @@ const mocks = {
ansistyles: { bright: a => a },
npmlog: {
gauge: { show () {} },
+ },
+ 'proc-log': {
info () {},
notice () {},
warn () {},
@@ -489,23 +491,23 @@ t.test('profile set <key> <value>', t => {
},
}
- const npmlog = {
- gauge: {
- show () {},
- },
- warn (title, msg) {
- t.equal(title, 'profile', 'should use expected profile')
- t.equal(
- msg,
- 'Passwords do not match, please try again.',
- 'should log password mismatch message'
- )
- },
- }
-
const Profile = t.mock('../../../lib/commands/profile.js', {
...mocks,
- npmlog,
+ npmlog: {
+ gauge: {
+ show () {},
+ },
+ },
+ 'proc-log': {
+ warn (title, msg) {
+ t.equal(title, 'profile', 'should use expected profile')
+ t.equal(
+ msg,
+ 'Passwords do not match, please try again.',
+ 'should log password mismatch message'
+ )
+ },
+ },
'npm-profile': npmProfile,
'../../../lib/utils/read-user-info.js': readUserInfo,
})
diff --git a/test/lib/commands/prune.js b/test/lib/commands/prune.js
index 49d5ab9be..a7f56547b 100644
--- a/test/lib/commands/prune.js
+++ b/test/lib/commands/prune.js
@@ -1,20 +1,22 @@
const t = require('tap')
-const { real: mockNpm } = require('../../fixtures/mock-npm')
+const { load: loadMockNpm } = require('../../fixtures/mock-npm')
t.test('should prune using Arborist', async (t) => {
t.plan(4)
- const { Npm } = mockNpm(t, {
- '@npmcli/arborist': function (args) {
- t.ok(args, 'gets options object')
- t.ok(args.path, 'gets path option')
- this.prune = () => {
- t.ok(true, 'prune is called')
- }
- },
- '../../lib/utils/reify-finish.js': (arb) => {
- t.ok(arb, 'gets arborist tree')
+ const { npm } = await loadMockNpm(t, {
+ load: false,
+ mocks: {
+ '@npmcli/arborist': function (args) {
+ t.ok(args, 'gets options object')
+ t.ok(args.path, 'gets path option')
+ this.prune = () => {
+ t.ok(true, 'prune is called')
+ }
+ },
+ '../../lib/utils/reify-finish.js': (arb) => {
+ t.ok(arb, 'gets arborist tree')
+ },
},
})
- const npm = new Npm()
await npm.exec('prune', [])
})
diff --git a/test/lib/commands/publish.js b/test/lib/commands/publish.js
index 5f4fb4010..1178cd6ee 100644
--- a/test/lib/commands/publish.js
+++ b/test/lib/commands/publish.js
@@ -1,13 +1,15 @@
const t = require('tap')
const { fake: mockNpm } = require('../../fixtures/mock-npm')
const fs = require('fs')
+const log = require('../../../lib/utils/log-shim')
// The way we set loglevel is kind of convoluted, and there is no way to affect
// it from these tests, which only interact with lib/publish.js, which assumes
// that the code that is requiring and calling lib/publish.js has already
// taken care of the loglevel
-const log = require('npmlog')
-log.level = 'silent'
+const _level = log.level
+t.beforeEach(() => (log.level = 'silent'))
+t.teardown(() => (log.level = _level))
t.cleanSnapshot = data => {
return data.replace(/^ *"gitHead": .*$\n/gm, '')
@@ -19,8 +21,6 @@ const defaults = Object.entries(definitions).reduce((defaults, [key, def]) => {
return defaults
}, {})
-t.afterEach(() => (log.level = 'silent'))
-
t.test(
/* eslint-disable-next-line max-len */
'should publish with libnpmpublish, passing through flatOptions and respecting publishConfig.registry',
@@ -147,7 +147,7 @@ t.test('if loglevel=info and json, should not output package contents', async t
id: 'someid',
}),
logTar: () => {
- t.pass('logTar is called')
+ t.fail('logTar is not called in json mode')
},
},
libnpmpublish: {
@@ -188,7 +188,6 @@ t.test(
),
})
- log.level = 'silent'
const Publish = t.mock('../../../lib/commands/publish.js', {
'../../../lib/utils/tar.js': {
getContents: () => ({
@@ -681,9 +680,12 @@ t.test('private workspaces', async t => {
}
t.test('with color', async t => {
+ t.plan(4)
+
+ log.level = 'info'
const Publish = t.mock('../../../lib/commands/publish.js', {
...mocks,
- npmlog: {
+ 'proc-log': {
notice () {},
verbose () {},
warn (title, msg) {
@@ -707,9 +709,12 @@ t.test('private workspaces', async t => {
})
t.test('colorless', async t => {
+ t.plan(4)
+
+ log.level = 'info'
const Publish = t.mock('../../../lib/commands/publish.js', {
...mocks,
- npmlog: {
+ 'proc-log': {
notice () {},
verbose () {},
warn (title, msg) {
@@ -730,6 +735,8 @@ t.test('private workspaces', async t => {
})
t.test('unexpected error', async t => {
+ t.plan(1)
+
const Publish = t.mock('../../../lib/commands/publish.js', {
...mocks,
libnpmpublish: {
@@ -741,7 +748,7 @@ t.test('private workspaces', async t => {
publishes.push(manifest)
},
},
- npmlog: {
+ 'proc-log': {
notice () {},
verbose () {},
},
@@ -755,6 +762,8 @@ t.test('private workspaces', async t => {
})
t.test('runs correct lifecycle scripts', async t => {
+ t.plan(5)
+
const testDir = t.testdir({
'package.json': JSON.stringify(
{
@@ -773,6 +782,7 @@ t.test('runs correct lifecycle scripts', async t => {
})
const scripts = []
+ log.level = 'info'
const Publish = t.mock('../../../lib/commands/publish.js', {
'@npmcli/run-script': args => {
scripts.push(args)
@@ -810,6 +820,8 @@ t.test('runs correct lifecycle scripts', async t => {
})
t.test('does not run scripts on --ignore-scripts', async t => {
+ t.plan(4)
+
const testDir = t.testdir({
'package.json': JSON.stringify(
{
@@ -821,6 +833,7 @@ t.test('does not run scripts on --ignore-scripts', async t => {
),
})
+ log.level = 'info'
const Publish = t.mock('../../../lib/commands/publish.js', {
'@npmcli/run-script': () => {
t.fail('should not call run-script')
diff --git a/test/lib/commands/repo.js b/test/lib/commands/repo.js
index 4e61047b4..93eb6d031 100644
--- a/test/lib/commands/repo.js
+++ b/test/lib/commands/repo.js
@@ -1,8 +1,8 @@
const t = require('tap')
-const { real: mockNpm } = require('../../fixtures/mock-npm.js')
-const { join, sep } = require('path')
+const { load: _loadMockNpm } = require('../../fixtures/mock-npm.js')
+const { sep } = require('path')
-const pkgDirs = t.testdir({
+const fixture = {
'package.json': JSON.stringify({
name: 'thispkg',
version: '1.2.3',
@@ -149,35 +149,36 @@ const pkgDirs = t.testdir({
},
}),
},
- workspaces: {
+}
+
+const workspaceFixture = {
+ 'package.json': JSON.stringify({
+ name: 'workspaces-test',
+ version: '1.2.3-test',
+ workspaces: ['workspace-a', 'workspace-b', 'workspace-c'],
+ repository: 'https://github.com/npm/workspaces-test',
+ }),
+ 'workspace-a': {
'package.json': JSON.stringify({
- name: 'workspaces-test',
- version: '1.2.3-test',
- workspaces: ['workspace-a', 'workspace-b', 'workspace-c'],
- repository: 'https://github.com/npm/workspaces-test',
+ name: 'workspace-a',
+ version: '1.2.3-a',
+ repository: 'http://repo.workspace-a/',
}),
- 'workspace-a': {
- 'package.json': JSON.stringify({
- name: 'workspace-a',
- version: '1.2.3-a',
- repository: 'http://repo.workspace-a/',
- }),
- },
- 'workspace-b': {
- 'package.json': JSON.stringify({
- name: 'workspace-b',
- version: '1.2.3-n',
- repository: 'https://github.com/npm/workspace-b',
- }),
- },
- 'workspace-c': JSON.stringify({
- 'package.json': {
- name: 'workspace-n',
- version: '1.2.3-n',
- },
+ },
+ 'workspace-b': {
+ 'package.json': JSON.stringify({
+ name: 'workspace-b',
+ version: '1.2.3-n',
+ repository: 'https://github.com/npm/workspace-b',
}),
},
-})
+ 'workspace-c': JSON.stringify({
+ 'package.json': {
+ name: 'workspace-n',
+ version: '1.2.3-n',
+ },
+ }),
+}
// keep a tally of which urls got opened
let opened = {}
@@ -185,20 +186,18 @@ const openUrl = async (npm, url, errMsg) => {
opened[url] = opened[url] || 0
opened[url]++
}
-
-const { Npm } = mockNpm(t, {
- '../../lib/utils/open-url.js': openUrl,
-})
-const npm = new Npm()
-
-t.before(async () => {
- await npm.load()
-})
-
t.afterEach(() => opened = {})
-t.test('open repo urls', t => {
- npm.localPrefix = pkgDirs
+const loadMockNpm = async (t, prefix) => {
+ const res = await _loadMockNpm(t, {
+ mocks: { '../../lib/utils/open-url.js': openUrl },
+ testdir: prefix,
+ })
+ return res
+}
+
+t.test('open repo urls', async t => {
+ const { npm } = await loadMockNpm(t, fixture)
const expect = {
hostedgit: 'https://github.com/foo/hostedgit',
hostedgitat: 'https://github.com/foo/hostedgitat',
@@ -239,8 +238,9 @@ t.test('open repo urls', t => {
})
})
-t.test('fail if cannot figure out repo url', t => {
- npm.localPrefix = pkgDirs
+t.test('fail if cannot figure out repo url', async t => {
+ const { npm } = await loadMockNpm(t, fixture)
+
const cases = [
'norepo',
'repoobbj-nourl',
@@ -261,13 +261,13 @@ t.test('fail if cannot figure out repo url', t => {
})
t.test('open default package if none specified', async t => {
- npm.localPrefix = pkgDirs
+ const { npm } = await loadMockNpm(t, fixture)
await npm.exec('repo', [])
t.equal(opened['https://example.com/thispkg'], 1, 'opened expected url', { opened })
})
-t.test('workspaces', t => {
- npm.localPrefix = join(pkgDirs, 'workspaces')
+t.test('workspaces', async t => {
+ const { npm } = await loadMockNpm(t, workspaceFixture)
t.afterEach(() => {
npm.config.set('workspaces', null)
@@ -311,5 +311,4 @@ t.test('workspaces', t => {
)
t.match({}, opened, 'opened no repo urls')
})
- t.end()
})
diff --git a/test/lib/commands/restart.js b/test/lib/commands/restart.js
index 608de0331..7730f1a30 100644
--- a/test/lib/commands/restart.js
+++ b/test/lib/commands/restart.js
@@ -1,6 +1,6 @@
const t = require('tap')
const spawk = require('spawk')
-const { real: mockNpm } = require('../../fixtures/mock-npm')
+const { load: loadMockNpm } = require('../../fixtures/mock-npm')
spawk.preventUnmatched()
t.teardown(() => {
@@ -12,24 +12,24 @@ t.teardown(() => {
// pretty specific internals of runScript
const makeSpawnArgs = require('@npmcli/run-script/lib/make-spawn-args.js')
-t.test('should run stop script from package.json', async t => {
- const prefix = t.testdir({
- 'package.json': JSON.stringify({
- name: 'x',
- version: '1.2.3',
- scripts: {
- restart: 'node ./test-restart.js',
- },
- }),
+t.test('should run restart script from package.json', async t => {
+ const { npm } = await loadMockNpm(t, {
+ testdir: {
+ 'package.json': JSON.stringify({
+ name: 'x',
+ version: '1.2.3',
+ scripts: {
+ restart: 'node ./test-restart.js',
+ },
+ }),
+ },
+ config: {
+ loglevel: 'silent',
+ },
})
- const { Npm } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
- npm.log.level = 'silent'
- npm.localPrefix = prefix
- const [scriptShell] = makeSpawnArgs({ path: prefix })
+ const [scriptShell] = makeSpawnArgs({ path: npm.prefix })
const script = spawk.spawn(scriptShell, (args) => {
- t.ok(args.includes('node ./test-restart.js "foo"'), 'ran stop script with extra args')
+ t.ok(args.includes('node ./test-restart.js "foo"'), 'ran restart script with extra args')
return true
})
await npm.exec('restart', ['foo'])
diff --git a/test/lib/commands/root.js b/test/lib/commands/root.js
index 9871ddb25..a886b30c3 100644
--- a/test/lib/commands/root.js
+++ b/test/lib/commands/root.js
@@ -1,9 +1,8 @@
const t = require('tap')
-const { real: mockNpm } = require('../../fixtures/mock-npm')
+const { load: loadMockNpm } = require('../../fixtures/mock-npm')
t.test('prefix', async (t) => {
- const { joinedOutput, Npm } = mockNpm(t)
- const npm = new Npm()
+ const { joinedOutput, npm } = await loadMockNpm(t, { load: false })
await npm.exec('root', [])
t.equal(
joinedOutput(),
diff --git a/test/lib/commands/run-script.js b/test/lib/commands/run-script.js
index e421c655e..ea0227cda 100644
--- a/test/lib/commands/run-script.js
+++ b/test/lib/commands/run-script.js
@@ -31,13 +31,16 @@ const output = []
const npmlog = {
disableProgress: () => null,
level: 'warn',
+}
+
+const log = {
error: () => null,
}
t.afterEach(() => {
npm.color = false
npmlog.level = 'warn'
- npmlog.error = () => null
+ log.error = () => null
output.length = 0
RUN_SCRIPTS.length = 0
config['if-present'] = false
@@ -56,6 +59,7 @@ const getRS = windows => {
}
),
npmlog,
+ 'proc-log': log,
'../../../lib/utils/is-windows-shell.js': windows,
})
return new RunScript(npm)
@@ -758,7 +762,7 @@ t.test('workspaces', t => {
t.test('missing scripts in all workspaces', async t => {
const LOG = []
- npmlog.error = err => {
+ log.error = err => {
LOG.push(String(err))
}
await t.rejects(
@@ -805,7 +809,7 @@ t.test('workspaces', t => {
t.test('missing scripts in some workspaces', async t => {
const LOG = []
- npmlog.error = err => {
+ log.error = err => {
LOG.push(String(err))
}
await runScript.execWorkspaces(['test'], ['a', 'b', 'c', 'd'])
@@ -857,6 +861,7 @@ t.test('workspaces', t => {
throw new Error('err')
},
npmlog,
+ 'proc-log': log,
'../../../lib/utils/is-windows-shell.js': false,
})
const runScript = new RunScript(npm)
@@ -875,6 +880,7 @@ t.test('workspaces', t => {
RUN_SCRIPTS.push(opts)
},
npmlog,
+ 'proc-log': log,
'../../../lib/utils/is-windows-shell.js': false,
})
const runScript = new RunScript(npm)
diff --git a/test/lib/commands/set-script.js b/test/lib/commands/set-script.js
index 592a2431c..2c4fe57d6 100644
--- a/test/lib/commands/set-script.js
+++ b/test/lib/commands/set-script.js
@@ -10,7 +10,7 @@ const npm = mockNpm(flatOptions)
const ERROR_OUTPUT = []
const WARN_OUTPUT = []
const SetScript = t.mock('../../../lib/commands/set-script.js', {
- npmlog: {
+ 'proc-log': {
error: (...args) => {
ERROR_OUTPUT.push(args)
},
diff --git a/test/lib/commands/set.js b/test/lib/commands/set.js
index a57ea1a54..feeb90157 100644
--- a/test/lib/commands/set.js
+++ b/test/lib/commands/set.js
@@ -2,6 +2,7 @@ const t = require('tap')
// can't run this until npm set can save to project level npmrc
t.skip('npm set', async t => {
+ // XXX: convert to loadMockNpm
const { real: mockNpm } = require('../../fixtures/mock-npm')
const { joinedOutput, Npm } = mockNpm(t)
const npm = new Npm()
diff --git a/test/lib/commands/shrinkwrap.js b/test/lib/commands/shrinkwrap.js
index db4021abd..2b9e46c70 100644
--- a/test/lib/commands/shrinkwrap.js
+++ b/test/lib/commands/shrinkwrap.js
@@ -1,7 +1,7 @@
const t = require('tap')
const fs = require('fs')
const { resolve } = require('path')
-const { real: mockNpm } = require('../../fixtures/mock-npm')
+const { load: loadMockNpm } = require('../../fixtures/mock-npm')
// Attempt to parse json values in snapshots before
// stringifying to remove escaped values like \\"
@@ -13,7 +13,7 @@ t.formatSnapshot = obj =>
(k, v) => {
try {
return JSON.parse(v)
- } catch (_) {}
+ } catch {}
return v
},
2
@@ -23,33 +23,25 @@ t.formatSnapshot = obj =>
// and make some assertions that should always be true. Sets
// the results on t.context for use in child tests
const shrinkwrap = async (t, testdir = {}, config = {}, mocks = {}) => {
- const { Npm, filteredLogs } = mockNpm(t, mocks)
- const npm = new Npm()
- await npm.load()
-
- npm.localPrefix = t.testdir(testdir)
- if (config.lockfileVersion) {
- npm.config.set('lockfile-version', config.lockfileVersion)
- }
- if (config.global) {
- npm.config.set('global', config.global)
- }
+ const { npm, logs } = await loadMockNpm(t, {
+ mocks,
+ config,
+ testdir,
+ })
await npm.exec('shrinkwrap', [])
- const newFile = resolve(npm.localPrefix, 'npm-shrinkwrap.json')
- const oldFile = resolve(npm.localPrefix, 'package-lock.json')
- const notices = filteredLogs('notice')
- const warnings = filteredLogs('warn')
+ const newFile = resolve(npm.prefix, 'npm-shrinkwrap.json')
+ const oldFile = resolve(npm.prefix, 'package-lock.json')
t.notOk(fs.existsSync(oldFile), 'package-lock is always deleted')
- t.same(warnings, [], 'no warnings')
+ t.same(logs.warn, [], 'no warnings')
t.teardown(() => delete t.context)
t.context = {
localPrefix: testdir,
config,
shrinkwrap: JSON.parse(fs.readFileSync(newFile)),
- logs: notices,
+ logs: logs.notice.map(([, m]) => m),
}
}
@@ -58,8 +50,8 @@ const shrinkwrap = async (t, testdir = {}, config = {}, mocks = {}) => {
const shrinkwrapMatrix = async (t, file, assertions) => {
const ancient = JSON.stringify({ lockfileVersion: 1 })
const existing = JSON.stringify({ lockfileVersion: 2 })
- const upgrade = { lockfileVersion: 3 }
- const downgrade = { lockfileVersion: 1 }
+ const upgrade = { 'lockfile-version': 3 }
+ const downgrade = { 'lockfile-version': 1 }
let ancientDir = {}
let existingDir = null
diff --git a/test/lib/commands/star.js b/test/lib/commands/star.js
index 13838bb10..9a4903642 100644
--- a/test/lib/commands/star.js
+++ b/test/lib/commands/star.js
@@ -15,9 +15,9 @@ const npm = mockNpm({
},
})
const npmFetch = { json: noop }
-const npmlog = { error: noop, info: noop, verbose: noop }
+const log = { error: noop, info: noop, verbose: noop }
const mocks = {
- npmlog,
+ 'proc-log': log,
'npm-registry-fetch': npmFetch,
'../../../lib/utils/get-identity.js': async () => 'foo',
'../../../lib/utils/usage.js': () => 'usage instructions',
@@ -29,7 +29,7 @@ const star = new Star(npm)
t.afterEach(() => {
config.unicode = false
config['star.unstar'] = false
- npmlog.info = noop
+ log.info = noop
result = ''
})
@@ -53,7 +53,7 @@ t.test('star a package', async t => {
: {}
),
})
- npmlog.info = (title, msg, id) => {
+ log.info = (title, msg, id) => {
t.equal(title, 'star', 'should use expected title')
t.equal(msg, 'starring', 'should use expected msg')
t.equal(id, pkgName, 'should use expected id')
@@ -78,7 +78,7 @@ t.test('unstar a package', async t => {
: { foo: true }
),
})
- npmlog.info = (title, msg, id) => {
+ log.info = (title, msg, id) => {
t.equal(title, 'unstar', 'should use expected title')
t.equal(msg, 'unstarring', 'should use expected msg')
t.equal(id, pkgName, 'should use expected id')
diff --git a/test/lib/commands/stars.js b/test/lib/commands/stars.js
index 4ed643858..959739653 100644
--- a/test/lib/commands/stars.js
+++ b/test/lib/commands/stars.js
@@ -11,9 +11,9 @@ const npm = {
},
}
const npmFetch = { json: noop }
-const npmlog = { warn: noop }
+const log = { warn: noop }
const mocks = {
- npmlog,
+ 'proc-log': log,
'npm-registry-fetch': npmFetch,
'../../../lib/utils/get-identity.js': async () => 'foo',
'../../../lib/utils/usage.js': () => 'usage instructions',
@@ -24,7 +24,7 @@ const stars = new Stars(npm)
t.afterEach(() => {
npm.config = { get () {} }
- npmlog.warn = noop
+ log.warn = noop
result = ''
})
@@ -81,7 +81,7 @@ t.test('unauthorized request', async t => {
)
}
- npmlog.warn = (title, msg) => {
+ log.warn = (title, msg) => {
t.equal(title, 'stars', 'should use expected title')
t.equal(
msg,
@@ -108,7 +108,7 @@ t.test('unexpected error', async t => {
throw new Error('ERROR')
}
- npmlog.warn = (title, msg) => {
+ log.warn = (title, msg) => {
throw new Error('Should not output extra warning msgs')
}
@@ -123,7 +123,7 @@ t.test('no pkg starred', async t => {
t.plan(2)
npmFetch.json = async (uri, opts) => ({ rows: [] })
- npmlog.warn = (title, msg) => {
+ log.warn = (title, msg) => {
t.equal(title, 'stars', 'should use expected title')
t.equal(
msg,
diff --git a/test/lib/commands/start.js b/test/lib/commands/start.js
index 1f26f38ea..4f7dc366d 100644
--- a/test/lib/commands/start.js
+++ b/test/lib/commands/start.js
@@ -1,6 +1,6 @@
const t = require('tap')
const spawk = require('spawk')
-const { real: mockNpm } = require('../../fixtures/mock-npm')
+const { load: loadMockNpm } = require('../../fixtures/mock-npm')
spawk.preventUnmatched()
t.teardown(() => {
@@ -12,22 +12,23 @@ t.teardown(() => {
// pretty specific internals of runScript
const makeSpawnArgs = require('@npmcli/run-script/lib/make-spawn-args.js')
-t.test('should run stop script from package.json', async t => {
- const prefix = t.testdir({
- 'package.json': JSON.stringify({
- name: 'x',
- version: '1.2.3',
- scripts: {
- start: 'node ./test-start.js',
- },
- }),
+t.test('should run start script from package.json', async t => {
+ t.plan(2)
+ const { npm } = await loadMockNpm(t, {
+ testdir: {
+ 'package.json': JSON.stringify({
+ name: 'x',
+ version: '1.2.3',
+ scripts: {
+ start: 'node ./test-start.js',
+ },
+ }),
+ },
+ config: {
+ loglevel: 'silent',
+ },
})
- const { Npm } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
- npm.log.level = 'silent'
- npm.localPrefix = prefix
- const [scriptShell] = makeSpawnArgs({ path: prefix })
+ const [scriptShell] = makeSpawnArgs({ path: npm.prefix })
const script = spawk.spawn(scriptShell, (args) => {
t.ok(args.includes('node ./test-start.js "foo"'), 'ran start script with extra args')
return true
diff --git a/test/lib/commands/stop.js b/test/lib/commands/stop.js
index 4f189449b..53d057b71 100644
--- a/test/lib/commands/stop.js
+++ b/test/lib/commands/stop.js
@@ -1,6 +1,6 @@
const t = require('tap')
const spawk = require('spawk')
-const { real: mockNpm } = require('../../fixtures/mock-npm')
+const { load: loadMockNpm } = require('../../fixtures/mock-npm')
spawk.preventUnmatched()
t.teardown(() => {
@@ -13,21 +13,21 @@ t.teardown(() => {
const makeSpawnArgs = require('@npmcli/run-script/lib/make-spawn-args.js')
t.test('should run stop script from package.json', async t => {
- const prefix = t.testdir({
- 'package.json': JSON.stringify({
- name: 'x',
- version: '1.2.3',
- scripts: {
- stop: 'node ./test-stop.js',
- },
- }),
+ const { npm } = await loadMockNpm(t, {
+ testdir: {
+ 'package.json': JSON.stringify({
+ name: 'x',
+ version: '1.2.3',
+ scripts: {
+ stop: 'node ./test-stop.js',
+ },
+ }),
+ },
+ config: {
+ loglevel: 'silent',
+ },
})
- const { Npm } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
- npm.log.level = 'silent'
- npm.localPrefix = prefix
- const [scriptShell] = makeSpawnArgs({ path: prefix })
+ const [scriptShell] = makeSpawnArgs({ path: npm.prefix })
const script = spawk.spawn(scriptShell, (args) => {
t.ok(args.includes('node ./test-stop.js "foo"'), 'ran stop script with extra args')
return true
diff --git a/test/lib/commands/test.js b/test/lib/commands/test.js
index 4e5ce289b..a3dbd3ff4 100644
--- a/test/lib/commands/test.js
+++ b/test/lib/commands/test.js
@@ -1,6 +1,6 @@
const t = require('tap')
const spawk = require('spawk')
-const { real: mockNpm } = require('../../fixtures/mock-npm')
+const { load: loadMockNpm } = require('../../fixtures/mock-npm')
spawk.preventUnmatched()
t.teardown(() => {
@@ -12,22 +12,22 @@ t.teardown(() => {
// pretty specific internals of runScript
const makeSpawnArgs = require('@npmcli/run-script/lib/make-spawn-args.js')
-t.test('should run stop script from package.json', async t => {
- const prefix = t.testdir({
- 'package.json': JSON.stringify({
- name: 'x',
- version: '1.2.3',
- scripts: {
- test: 'node ./test-test.js',
- },
- }),
+t.test('should run test script from package.json', async t => {
+ const { npm } = await loadMockNpm(t, {
+ testdir: {
+ 'package.json': JSON.stringify({
+ name: 'x',
+ version: '1.2.3',
+ scripts: {
+ test: 'node ./test-test.js',
+ },
+ }),
+ },
+ config: {
+ loglevel: 'silent',
+ },
})
- const { Npm } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
- npm.log.level = 'silent'
- npm.localPrefix = prefix
- const [scriptShell] = makeSpawnArgs({ path: prefix })
+ const [scriptShell] = makeSpawnArgs({ path: npm.prefix })
const script = spawk.spawn(scriptShell, (args) => {
t.ok(args.includes('node ./test-test.js "foo"'), 'ran test script with extra args')
return true
diff --git a/test/lib/commands/token.js b/test/lib/commands/token.js
index 6d0dc9d7e..65a094a0b 100644
--- a/test/lib/commands/token.js
+++ b/test/lib/commands/token.js
@@ -3,25 +3,24 @@ const t = require('tap')
const mocks = {
profile: {},
output: () => {},
- log: {},
readUserInfo: {},
}
const npm = {
output: (...args) => mocks.output(...args),
}
-const Token = t.mock('../../../lib/commands/token.js', {
+const mockToken = (otherMocks) => t.mock('../../../lib/commands/token.js', {
'../../../lib/utils/otplease.js': (opts, fn) => {
return Promise.resolve().then(() => fn(opts))
},
'../../../lib/utils/read-user-info.js': mocks.readUserInfo,
'npm-profile': mocks.profile,
- npmlog: mocks.log,
+ ...otherMocks,
})
-const token = new Token(npm)
+const tokenWithMocks = (options = {}) => {
+ const { log, ...mockRequests } = options
-const tokenWithMocks = mockRequests => {
for (const mod in mockRequests) {
if (mod === 'npm') {
mockRequests.npm = { ...npm, ...mockRequests.npm }
@@ -50,13 +49,24 @@ const tokenWithMocks = mockRequests => {
}
}
- const token = new Token(mockRequests.npm || npm)
+ const MockedToken = mockToken(log ? {
+ 'proc-log': {
+ info: log.info,
+ },
+ npmlog: {
+ gauge: log.gauge,
+ newItem: log.newItem,
+ },
+ } : {})
+ const token = new MockedToken(mockRequests.npm || npm)
return [token, reset]
}
t.test('completion', t => {
t.plan(5)
+ const [token] = tokenWithMocks()
+
const testComp = (argv, expect) => {
t.resolveMatch(token.completion({ conf: { argv: { remain: argv } } }), expect, argv.join(' '))
}
@@ -74,7 +84,7 @@ t.test('completion', t => {
t.test('token foobar', async t => {
t.plan(2)
- const [, reset] = tokenWithMocks({
+ const [token, reset] = tokenWithMocks({
log: {
gauge: {
show: name => {
diff --git a/test/lib/commands/unpublish.js b/test/lib/commands/unpublish.js
index 6ac206753..1424adf5c 100644
--- a/test/lib/commands/unpublish.js
+++ b/test/lib/commands/unpublish.js
@@ -17,7 +17,6 @@ const testDir = t.testdir({
const npm = mockNpm({
localPrefix: testDir,
- log: { silly () {}, verbose () {} },
config,
output: (...msg) => {
result += msg.join('\n')
@@ -30,10 +29,10 @@ const mocks = {
'npm-registry-fetch': { json: noop },
'../../../lib/utils/otplease.js': async (opts, fn) => fn(opts),
'../../../lib/utils/get-identity.js': async () => 'foo',
+ 'proc-log': { silly () {}, verbose () {} },
}
t.afterEach(() => {
- npm.log = { silly () {}, verbose () {} }
npm.localPrefix = testDir
result = ''
config['dry-run'] = false
@@ -44,7 +43,7 @@ t.afterEach(() => {
t.test('no args --force', async t => {
config.force = true
- npm.log = {
+ const log = {
silly (title) {
t.equal(title, 'unpublish', 'should silly log args')
},
@@ -74,6 +73,7 @@ t.test('no args --force', async t => {
const Unpublish = t.mock('../../../lib/commands/unpublish.js', {
...mocks,
libnpmpublish,
+ 'proc-log': log,
})
const unpublish = new Unpublish(npm)
@@ -147,7 +147,7 @@ t.test('too many args', async t => {
})
t.test('unpublish <pkg>@version', async t => {
- npm.log = {
+ const log = {
silly (title, key, value) {
t.equal(title, 'unpublish', 'should silly log args')
if (key === 'spec') {
@@ -172,6 +172,7 @@ t.test('unpublish <pkg>@version', async t => {
const Unpublish = t.mock('../../../lib/commands/unpublish.js', {
...mocks,
libnpmpublish,
+ 'proc-log': log,
})
const unpublish = new Unpublish(npm)
diff --git a/test/lib/commands/update.js b/test/lib/commands/update.js
index 6ca6dbc87..aecb2c32b 100644
--- a/test/lib/commands/update.js
+++ b/test/lib/commands/update.js
@@ -9,12 +9,10 @@ const config = {
const noop = () => null
const npm = mockNpm({
globalDir: '',
- log: noop,
config,
prefix: '',
})
const mocks = {
- npmlog: { warn () {} },
'@npmcli/arborist': class {
reify () {}
},
@@ -29,22 +27,23 @@ t.afterEach(() => {
})
t.test('no args', async t => {
- t.plan(3)
+ t.plan(4)
npm.prefix = '/project/a'
class Arborist {
constructor (args) {
+ const { log, ...rest } = args
t.same(
- args,
+ rest,
{
...npm.flatOptions,
path: npm.prefix,
- log: noop,
workspaces: null,
},
'should call arborist contructor with expected args'
)
+ t.match(log, {}, 'log is passed in')
}
reify ({ update }) {
@@ -65,22 +64,23 @@ t.test('no args', async t => {
})
t.test('with args', async t => {
- t.plan(3)
+ t.plan(4)
npm.prefix = '/project/a'
class Arborist {
constructor (args) {
+ const { log, ...rest } = args
t.same(
- args,
+ rest,
{
...npm.flatOptions,
path: npm.prefix,
- log: noop,
workspaces: null,
},
'should call arborist contructor with expected args'
)
+ t.match(log, {}, 'log is passed in')
}
reify ({ update }) {
@@ -108,7 +108,7 @@ t.test('update --depth=<number>', async t => {
const Update = t.mock('../../../lib/commands/update.js', {
...mocks,
- npmlog: {
+ 'proc-log': {
warn: (title, msg) => {
t.equal(title, 'update', 'should print expected title')
t.match(
@@ -125,7 +125,7 @@ t.test('update --depth=<number>', async t => {
})
t.test('update --global', async t => {
- t.plan(2)
+ t.plan(3)
const normalizePath = p => p.replace(/\\+/g, '/')
const redactCwd = (path) => normalizePath(path)
@@ -137,13 +137,15 @@ t.test('update --global', async t => {
class Arborist {
constructor (args) {
- const { path, ...opts } = args
+ const { path, log, ...rest } = args
t.same(
- opts,
- { ...npm.flatOptions, log: noop, workspaces: undefined },
+ rest,
+ { ...npm.flatOptions, workspaces: undefined },
'should call arborist contructor with expected options'
)
+ t.match(log, {}, 'log is passed in')
+
t.equal(
redactCwd(path),
'{CWD}/global/lib',
diff --git a/test/lib/commands/version.js b/test/lib/commands/version.js
index 6603b5810..980353897 100644
--- a/test/lib/commands/version.js
+++ b/test/lib/commands/version.js
@@ -1,5 +1,6 @@
const t = require('tap')
const { fake: mockNpm } = require('../../fixtures/mock-npm')
+const mockGlobals = require('../../fixtures/mock-globals.js')
let result = []
@@ -26,294 +27,301 @@ const mocks = {
const Version = t.mock('../../../lib/commands/version.js', mocks)
const version = new Version(npm)
-const _processVersions = process.versions
t.afterEach(() => {
config.json = false
npm.prefix = ''
- process.versions = _processVersions
result = []
})
-t.test('no args', async t => {
- const prefix = t.testdir({
- 'package.json': JSON.stringify({
- name: 'test-version-no-args',
- version: '3.2.1',
- }),
- })
- npm.prefix = prefix
- Object.defineProperty(process, 'versions', { value: { node: '1.0.0' } })
-
- await version.exec([])
-
- t.same(
- result,
- [
- {
- 'test-version-no-args': '3.2.1',
- node: '1.0.0',
- npm: '1.0.0',
- },
- ],
- 'should output expected values for various versions in npm'
- )
-})
-
-t.test('too many args', async t => {
- await t.rejects(
- version.exec(['foo', 'bar']),
- /npm version/,
- 'should throw usage instructions error'
- )
-})
-
-t.test('completion', async t => {
- const testComp = async (argv, expect) => {
- const res = await version.completion({ conf: { argv: { remain: argv } } })
- t.strictSame(res, expect, argv.join(' '))
- }
-
- await testComp(
- ['npm', 'version'],
- ['major', 'minor', 'patch', 'premajor', 'preminor', 'prepatch', 'prerelease', 'from-git']
- )
- await testComp(['npm', 'version', 'major'], [])
-
- t.end()
-})
-
-t.test('failure reading package.json', async t => {
- const prefix = t.testdir({})
- npm.prefix = prefix
-
- await version.exec([])
-
- t.same(
- result,
- [
- {
- npm: '1.0.0',
- node: '1.0.0',
- },
- ],
- 'should not have package name on returning object'
- )
-})
-
-t.test('--json option', async t => {
- const prefix = t.testdir({})
- config.json = true
- npm.prefix = prefix
- Object.defineProperty(process, 'versions', { value: {} })
-
- await version.exec([])
- t.same(result, ['{\n "npm": "1.0.0"\n}'], 'should return json stringified result')
-})
+t.test('node@1', t => {
+ mockGlobals(t, { 'process.versions': { node: '1.0.0' } }, { replace: true })
-t.test('with one arg', async t => {
- const Version = t.mock('../../../lib/commands/version.js', {
- ...mocks,
- libnpmversion: (arg, opts) => {
- t.equal(arg, 'major', 'should forward expected value')
- t.same(
- opts,
- {
- path: '',
- },
- 'should forward expected options'
- )
- return '4.0.0'
- },
- })
- const version = new Version(npm)
-
- await version.exec(['major'])
- t.same(result, ['v4.0.0'], 'outputs the new version prefixed by the tagVersionPrefix')
-})
+ t.test('no args', async t => {
+ const prefix = t.testdir({
+ 'package.json': JSON.stringify({
+ name: 'test-version-no-args',
+ version: '3.2.1',
+ }),
+ })
+ npm.prefix = prefix
-t.test('workspaces', async t => {
- t.teardown(() => {
- npm.localPrefix = ''
- npm.prefix = ''
- })
+ await version.exec([])
- t.test('no args, all workspaces', async t => {
- const testDir = t.testdir({
- 'package.json': JSON.stringify(
- {
- name: 'workspaces-test',
- version: '1.0.0',
- workspaces: ['workspace-a', 'workspace-b'],
- },
- null,
- 2
- ),
- 'workspace-a': {
- 'package.json': JSON.stringify({
- name: 'workspace-a',
- version: '1.0.0',
- }),
- },
- 'workspace-b': {
- 'package.json': JSON.stringify({
- name: 'workspace-b',
- version: '1.0.0',
- }),
- },
- })
- npm.localPrefix = testDir
- npm.prefix = testDir
- const version = new Version(npm)
- await version.execWorkspaces([], [])
t.same(
result,
[
{
- 'workspaces-test': '1.0.0',
- 'workspace-a': '1.0.0',
- 'workspace-b': '1.0.0',
+ 'test-version-no-args': '3.2.1',
+ node: '1.0.0',
npm: '1.0.0',
},
],
- 'outputs includes main package and workspace versions'
+ 'should output expected values for various versions in npm'
)
})
- t.test('no args, single workspaces', async t => {
- const testDir = t.testdir({
- 'package.json': JSON.stringify(
- {
- name: 'workspaces-test',
- version: '1.0.0',
- workspaces: ['workspace-a', 'workspace-b'],
- },
- null,
- 2
- ),
- 'workspace-a': {
- 'package.json': JSON.stringify({
- name: 'workspace-a',
- version: '1.0.0',
- }),
- },
- 'workspace-b': {
- 'package.json': JSON.stringify({
- name: 'workspace-b',
- version: '1.0.0',
- }),
- },
- })
- npm.localPrefix = testDir
- npm.prefix = testDir
- const version = new Version(npm)
- await version.execWorkspaces([], ['workspace-a'])
- t.same(
- result,
- [
- {
- 'workspaces-test': '1.0.0',
- 'workspace-a': '1.0.0',
- npm: '1.0.0',
- },
- ],
- 'outputs includes main package and requested workspace versions'
+ t.test('too many args', async t => {
+ await t.rejects(
+ version.exec(['foo', 'bar']),
+ /npm version/,
+ 'should throw usage instructions error'
)
})
- t.test('no args, all workspaces, workspace with missing name or version', async t => {
- const testDir = t.testdir({
- 'package.json': JSON.stringify(
- {
- name: 'workspaces-test',
- version: '1.0.0',
- workspaces: ['workspace-a', 'workspace-b', 'workspace-c'],
- },
- null,
- 2
- ),
- 'workspace-a': {
- 'package.json': JSON.stringify({
- name: 'workspace-a',
- version: '1.0.0',
- }),
- },
- 'workspace-b': {
- 'package.json': JSON.stringify({
- name: 'workspace-b',
- }),
- },
- 'workspace-c': {
- 'package.json': JSON.stringify({
- version: '1.0.0',
- }),
- },
- })
- npm.localPrefix = testDir
- npm.prefix = testDir
- const version = new Version(npm)
- await version.execWorkspaces([], [])
+ t.test('completion', async t => {
+ const testComp = async (argv, expect) => {
+ const res = await version.completion({ conf: { argv: { remain: argv } } })
+ t.strictSame(res, expect, argv.join(' '))
+ }
+
+ await testComp(
+ ['npm', 'version'],
+ ['major', 'minor', 'patch', 'premajor', 'preminor', 'prepatch', 'prerelease', 'from-git']
+ )
+ await testComp(['npm', 'version', 'major'], [])
+
+ t.end()
+ })
+
+ t.test('failure reading package.json', async t => {
+ const prefix = t.testdir({})
+ npm.prefix = prefix
+
+ await version.exec([])
+
t.same(
result,
[
{
- 'workspaces-test': '1.0.0',
- 'workspace-a': '1.0.0',
npm: '1.0.0',
+ node: '1.0.0',
},
],
- 'outputs includes main package and valid workspace versions'
+ 'should not have package name on returning object'
)
})
+ t.end()
+})
- t.test('with one arg, all workspaces', async t => {
- const libNpmVersionArgs = []
- const testDir = t.testdir({
- 'package.json': JSON.stringify(
- {
- name: 'workspaces-test',
- version: '1.0.0',
- workspaces: ['workspace-a', 'workspace-b'],
- },
- null,
- 2
- ),
- 'workspace-a': {
- 'package.json': JSON.stringify({
- name: 'workspace-a',
- version: '1.0.0',
- }),
- },
- 'workspace-b': {
- 'package.json': JSON.stringify({
- name: 'workspace-b',
- version: '1.0.0',
- }),
- },
- })
+t.test('empty versions', t => {
+ mockGlobals(t, { 'process.versions': {} }, { replace: true })
+
+ t.test('--json option', async t => {
+ const prefix = t.testdir({})
+ config.json = true
+ npm.prefix = prefix
+
+ await version.exec([])
+ t.same(result, ['{\n "npm": "1.0.0"\n}'], 'should return json stringified result')
+ })
+
+ t.test('with one arg', async t => {
const Version = t.mock('../../../lib/commands/version.js', {
...mocks,
libnpmversion: (arg, opts) => {
- libNpmVersionArgs.push([arg, opts])
- return '2.0.0'
+ t.equal(arg, 'major', 'should forward expected value')
+ t.same(
+ opts,
+ {
+ path: '',
+ },
+ 'should forward expected options'
+ )
+ return '4.0.0'
},
})
- npm.localPrefix = testDir
- npm.prefix = testDir
const version = new Version(npm)
- await version.execWorkspaces(['major'], [])
- t.same(
- result,
- ['workspace-a', 'v2.0.0', 'workspace-b', 'v2.0.0'],
- 'outputs the new version for only the workspaces prefixed by the tagVersionPrefix'
- )
+ await version.exec(['major'])
+ t.same(result, ['v4.0.0'], 'outputs the new version prefixed by the tagVersionPrefix')
})
- t.test('too many args', async t => {
- await t.rejects(
- version.execWorkspaces(['foo', 'bar'], []),
- /npm version/,
- 'should throw usage instructions error'
- )
+ t.test('workspaces', async t => {
+ t.teardown(() => {
+ npm.localPrefix = ''
+ npm.prefix = ''
+ })
+
+ t.test('no args, all workspaces', async t => {
+ const testDir = t.testdir({
+ 'package.json': JSON.stringify(
+ {
+ name: 'workspaces-test',
+ version: '1.0.0',
+ workspaces: ['workspace-a', 'workspace-b'],
+ },
+ null,
+ 2
+ ),
+ 'workspace-a': {
+ 'package.json': JSON.stringify({
+ name: 'workspace-a',
+ version: '1.0.0',
+ }),
+ },
+ 'workspace-b': {
+ 'package.json': JSON.stringify({
+ name: 'workspace-b',
+ version: '1.0.0',
+ }),
+ },
+ })
+ npm.localPrefix = testDir
+ npm.prefix = testDir
+ const version = new Version(npm)
+ await version.execWorkspaces([], [])
+ t.same(
+ result,
+ [
+ {
+ 'workspaces-test': '1.0.0',
+ 'workspace-a': '1.0.0',
+ 'workspace-b': '1.0.0',
+ npm: '1.0.0',
+ },
+ ],
+ 'outputs includes main package and workspace versions'
+ )
+ })
+
+ t.test('no args, single workspaces', async t => {
+ const testDir = t.testdir({
+ 'package.json': JSON.stringify(
+ {
+ name: 'workspaces-test',
+ version: '1.0.0',
+ workspaces: ['workspace-a', 'workspace-b'],
+ },
+ null,
+ 2
+ ),
+ 'workspace-a': {
+ 'package.json': JSON.stringify({
+ name: 'workspace-a',
+ version: '1.0.0',
+ }),
+ },
+ 'workspace-b': {
+ 'package.json': JSON.stringify({
+ name: 'workspace-b',
+ version: '1.0.0',
+ }),
+ },
+ })
+ npm.localPrefix = testDir
+ npm.prefix = testDir
+ const version = new Version(npm)
+ await version.execWorkspaces([], ['workspace-a'])
+ t.same(
+ result,
+ [
+ {
+ 'workspaces-test': '1.0.0',
+ 'workspace-a': '1.0.0',
+ npm: '1.0.0',
+ },
+ ],
+ 'outputs includes main package and requested workspace versions'
+ )
+ })
+
+ t.test('no args, all workspaces, workspace with missing name or version', async t => {
+ const testDir = t.testdir({
+ 'package.json': JSON.stringify(
+ {
+ name: 'workspaces-test',
+ version: '1.0.0',
+ workspaces: ['workspace-a', 'workspace-b', 'workspace-c'],
+ },
+ null,
+ 2
+ ),
+ 'workspace-a': {
+ 'package.json': JSON.stringify({
+ name: 'workspace-a',
+ version: '1.0.0',
+ }),
+ },
+ 'workspace-b': {
+ 'package.json': JSON.stringify({
+ name: 'workspace-b',
+ }),
+ },
+ 'workspace-c': {
+ 'package.json': JSON.stringify({
+ version: '1.0.0',
+ }),
+ },
+ })
+ npm.localPrefix = testDir
+ npm.prefix = testDir
+ const version = new Version(npm)
+ await version.execWorkspaces([], [])
+ t.same(
+ result,
+ [
+ {
+ 'workspaces-test': '1.0.0',
+ 'workspace-a': '1.0.0',
+ npm: '1.0.0',
+ },
+ ],
+ 'outputs includes main package and valid workspace versions'
+ )
+ })
+
+ t.test('with one arg, all workspaces', async t => {
+ const libNpmVersionArgs = []
+ const testDir = t.testdir({
+ 'package.json': JSON.stringify(
+ {
+ name: 'workspaces-test',
+ version: '1.0.0',
+ workspaces: ['workspace-a', 'workspace-b'],
+ },
+ null,
+ 2
+ ),
+ 'workspace-a': {
+ 'package.json': JSON.stringify({
+ name: 'workspace-a',
+ version: '1.0.0',
+ }),
+ },
+ 'workspace-b': {
+ 'package.json': JSON.stringify({
+ name: 'workspace-b',
+ version: '1.0.0',
+ }),
+ },
+ })
+ const Version = t.mock('../../../lib/commands/version.js', {
+ ...mocks,
+ libnpmversion: (arg, opts) => {
+ libNpmVersionArgs.push([arg, opts])
+ return '2.0.0'
+ },
+ })
+ npm.localPrefix = testDir
+ npm.prefix = testDir
+ const version = new Version(npm)
+
+ await version.execWorkspaces(['major'], [])
+ t.same(
+ result,
+ ['workspace-a', 'v2.0.0', 'workspace-b', 'v2.0.0'],
+ 'outputs the new version for only the workspaces prefixed by the tagVersionPrefix'
+ )
+ })
+
+ t.test('too many args', async t => {
+ await t.rejects(
+ version.execWorkspaces(['foo', 'bar'], []),
+ /npm version/,
+ 'should throw usage instructions error'
+ )
+ })
})
+
+ t.end()
})
diff --git a/test/lib/commands/view.js b/test/lib/commands/view.js
index 728787ec4..035490a79 100644
--- a/test/lib/commands/view.js
+++ b/test/lib/commands/view.js
@@ -1,6 +1,7 @@
const t = require('tap')
-t.cleanSnapshot = str => str.replace(/published .*? ago/g, 'published {TIME} ago')
+t.cleanSnapshot = str => str
+ .replace(/(published ).*?( ago)/g, '$1{TIME}$2')
// run the same as tap does when running directly with node
process.stdout.columns = undefined
@@ -17,8 +18,8 @@ const cleanLogs = () => {
console.log = fn
}
-// 25 hours ago
-const yesterday = new Date(Date.now() - 1000 * 60 * 60 * 25)
+// 3 days. its never yesterday and never a week ago
+const yesterday = new Date(Date.now() - 1000 * 60 * 60 * 24 * 3)
const packument = (nv, opts) => {
if (!opts.fullMetadata) {
@@ -564,6 +565,12 @@ t.test('workspaces', async t => {
pacote: {
packument,
},
+ 'proc-log': {
+ warn: (msg) => {
+ warnMsg = msg
+ },
+ silly: () => {},
+ },
})
const config = {
unicode: false,
@@ -571,11 +578,6 @@ t.test('workspaces', async t => {
}
let warnMsg
const npm = mockNpm({
- log: {
- warn: (msg) => {
- warnMsg = msg
- },
- },
config,
localPrefix: testDir,
})
diff --git a/test/lib/commands/whoami.js b/test/lib/commands/whoami.js
index dc6144ec1..66c3f0c6b 100644
--- a/test/lib/commands/whoami.js
+++ b/test/lib/commands/whoami.js
@@ -1,26 +1,24 @@
const t = require('tap')
-const { real: mockNpm } = require('../../fixtures/mock-npm')
+const { load: _loadMockNpm } = require('../../fixtures/mock-npm')
const username = 'foo'
-const { joinedOutput, Npm } = mockNpm(t, {
- '../../lib/utils/get-identity.js': () => Promise.resolve(username),
-})
-const npm = new Npm()
-
-t.before(async () => {
- await npm.load()
+const loadMockNpm = (t, options) => _loadMockNpm(t, {
+ mocks: {
+ '../../lib/utils/get-identity.js': () => Promise.resolve(username),
+ },
+ ...options,
})
t.test('npm whoami', async (t) => {
+ const { npm, joinedOutput } = await loadMockNpm(t)
await npm.exec('whoami', [])
t.equal(joinedOutput(), username, 'should print username')
})
t.test('npm whoami --json', async (t) => {
- t.teardown(() => {
- npm.config.set('json', false)
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ config: { json: true },
})
- npm.config.set('json', true)
await npm.exec('whoami', [])
t.equal(JSON.parse(joinedOutput()), username, 'should print username')
})
diff --git a/test/lib/fixtures/mock-globals.js b/test/lib/fixtures/mock-globals.js
new file mode 100644
index 000000000..02566e575
--- /dev/null
+++ b/test/lib/fixtures/mock-globals.js
@@ -0,0 +1,321 @@
+const t = require('tap')
+const mockGlobals = require('../../fixtures/mock-globals')
+
+const originals = {
+ platform: process.platform,
+ error: console.error,
+ stderrOn: process.stderr.on,
+ stderrWrite: process.stderr.write,
+ shell: process.env.SHELL,
+ home: process.env.HOME,
+ argv: process.argv,
+ env: process.env,
+ setInterval,
+}
+
+t.test('console', async t => {
+ await t.test('mocks', async (t) => {
+ const errors = []
+ mockGlobals(t, {
+ 'console.error': (...args) => errors.push(...args),
+ })
+
+ console.error(1)
+ console.error(2)
+ console.error(3)
+ t.strictSame(errors, [1, 2, 3], 'i got my errors')
+ })
+
+ t.equal(console.error, originals.error)
+})
+
+t.test('platform', async (t) => {
+ t.equal(process.platform, originals.platform)
+
+ await t.test('posix', async (t) => {
+ mockGlobals(t, { 'process.platform': 'posix' })
+ t.equal(process.platform, 'posix')
+
+ await t.test('win32 --> woo', async (t) => {
+ mockGlobals(t, { 'process.platform': 'win32' })
+ t.equal(process.platform, 'win32')
+
+ mockGlobals(t, { 'process.platform': 'woo' })
+ t.equal(process.platform, 'woo')
+ })
+
+ t.equal(process.platform, 'posix')
+ })
+
+ t.equal(process.platform, originals.platform)
+})
+
+t.test('manual reset', async t => {
+ let errorHandler, data
+
+ const { reset } = mockGlobals(t, {
+ 'process.stderr.on': (__, handler) => {
+ errorHandler = handler
+ reset['process.stderr.on']()
+ },
+ 'process.stderr.write': (chunk, callback) => {
+ data = chunk
+ process.nextTick(() => {
+ errorHandler({ errno: 'EPIPE' })
+ callback()
+ })
+ reset['process.stderr.write']()
+ },
+ })
+
+ await new Promise((res, rej) => {
+ process.stderr.on('error', er => er.errno === 'EPIPE' ? res() : rej(er))
+ process.stderr.write('hey', res)
+ })
+
+ t.equal(process.stderr.on, originals.stderrOn)
+ t.equal(process.stderr.write, originals.stderrWrite)
+ t.equal(data, 'hey', 'handles EPIPE errors')
+ t.ok(errorHandler)
+})
+
+t.test('reset called multiple times', async (t) => {
+ await t.test('single reset', async t => {
+ const { reset } = mockGlobals(t, { 'process.platform': 'z' })
+ t.equal(process.platform, 'z')
+
+ reset['process.platform']()
+ t.equal(process.platform, originals.platform)
+
+ reset['process.platform']()
+ reset['process.platform']()
+ reset['process.platform']()
+ t.equal(process.platform, originals.platform)
+ })
+
+ t.equal(process.platform, originals.platform)
+})
+
+t.test('object mode', async t => {
+ await t.test('mocks', async t => {
+ const home = t.testdir()
+
+ mockGlobals(t, {
+ process: {
+ stderr: {
+ on: '1',
+ },
+ env: {
+ HOME: home,
+ },
+ },
+ })
+
+ t.equal(process.stderr.on, '1')
+ t.equal(process.env.HOME, home)
+ })
+
+ t.equal(process.env.HOME, originals.home)
+ t.equal(process.stderr.write, originals.stderrWrite)
+})
+
+t.test('mixed object/string mode', async t => {
+ await t.test('mocks', async t => {
+ const home = t.testdir()
+
+ mockGlobals(t, {
+ 'process.env': {
+ HOME: home,
+ TEST: '1',
+ },
+ })
+
+ t.equal(process.env.HOME, home)
+ t.equal(process.env.TEST, '1')
+ })
+
+ t.equal(process.env.HOME, originals.home)
+ t.equal(process.env.TEST, undefined)
+})
+
+t.test('conflicting mixed object/string mode', async t => {
+ await t.test('same key', async t => {
+ t.throws(
+ () => mockGlobals(t, {
+ process: {
+ env: {
+ HOME: '1',
+ TEST: '1',
+ NODE_ENV: '1',
+ },
+ stderr: {
+ write: '1',
+ },
+ },
+ 'process.env.HOME': '1',
+ 'process.stderr.write': '1',
+ }),
+ /process.env.HOME,process.stderr.write/
+ )
+ })
+
+ await t.test('partial overwrite with replace', async t => {
+ t.throws(
+ () => mockGlobals(t, {
+ process: {
+ env: {
+ HOME: '1',
+ TEST: '1',
+ NODE_ENV: '1',
+ },
+ stderr: {
+ write: '1',
+ },
+ },
+ 'process.env.HOME': '1',
+ 'process.stderr.write': '1',
+ }, { replace: true }),
+ /process -> process.env.HOME,process.stderr.write/
+ )
+ })
+})
+
+t.test('falsy values', async t => {
+ await t.test('undefined deletes', async t => {
+ mockGlobals(t, { 'process.platform': undefined })
+ t.notOk(Object.prototype.hasOwnProperty.call(process, 'platform'))
+ t.equal(process.platform, undefined)
+ })
+
+ await t.test('null', async t => {
+ mockGlobals(t, { 'process.platform': null })
+ t.ok(Object.prototype.hasOwnProperty.call(process, 'platform'))
+ t.equal(process.platform, null)
+ })
+
+ t.equal(process.platform, originals.platform)
+})
+
+t.test('date', async t => {
+ await t.test('mocks', async t => {
+ mockGlobals(t, {
+ 'Date.now': () => 100,
+ 'Date.prototype.toISOString': () => 'DDD',
+ })
+ t.equal(Date.now(), 100)
+ t.equal(new Date().toISOString(), 'DDD')
+ })
+
+ t.ok(Date.now() > 100)
+ t.ok(new Date().toISOString().includes('T'))
+})
+
+t.test('argv', async t => {
+ await t.test('argv', async t => {
+ mockGlobals(t, { 'process.argv': ['node', 'woo'] })
+ t.strictSame(process.argv, ['node', 'woo'])
+ })
+
+ t.strictSame(process.argv, originals.argv)
+})
+
+t.test('replace', async (t) => {
+ await t.test('env', async t => {
+ mockGlobals(t, { 'process.env': { HOME: '1' } }, { replace: true })
+ t.strictSame(process.env, { HOME: '1' })
+ t.equal(Object.keys(process.env).length, 1)
+ })
+
+ await t.test('setInterval', async t => {
+ mockGlobals(t, { setInterval: 0 }, { replace: true })
+ t.strictSame(setInterval, 0)
+ })
+
+ t.strictSame(setInterval, originals.setInterval)
+ t.strictSame(process.env, originals.env)
+})
+
+t.test('multiple mocks and resets', async (t) => {
+ const initial = 'a'
+ const platforms = ['b', 'c', 'd', 'e', 'f', 'g']
+
+ await t.test('first in, first out', async t => {
+ mockGlobals(t, { 'process.platform': initial })
+ t.equal(process.platform, initial)
+
+ await t.test('platforms', async (t) => {
+ const resets = platforms.map((platform) => {
+ const { reset } = mockGlobals(t, { 'process.platform': platform })
+ t.equal(process.platform, platform)
+ return reset['process.platform']
+ }).reverse()
+
+ ;[...platforms.reverse()].forEach((platform, index) => {
+ const reset = resets[index]
+ const nextPlatform = index === platforms.length - 1 ? initial : platforms[index + 1]
+ t.equal(process.platform, platform)
+ reset()
+ t.equal(process.platform, nextPlatform, 'first reset')
+ reset()
+ reset()
+ t.equal(process.platform, nextPlatform, 'multiple resets are indempotent')
+ })
+ })
+
+ t.equal(process.platform, initial)
+ })
+
+ await t.test('last in,first out', async t => {
+ mockGlobals(t, { 'process.platform': initial })
+ t.equal(process.platform, initial)
+
+ await t.test('platforms', async (t) => {
+ const resets = platforms.map((platform) => {
+ const { reset } = mockGlobals(t, { 'process.platform': platform })
+ t.equal(process.platform, platform)
+ return reset['process.platform']
+ })
+
+ resets.forEach((reset, index) => {
+ // Calling a reset out of order removes it from the stack
+ // but does not change the descriptor so it should still be the
+ // last in descriptor until there are none left
+ const lastPlatform = platforms[platforms.length - 1]
+ const nextPlatform = index === platforms.length - 1 ? initial : lastPlatform
+ t.equal(process.platform, lastPlatform)
+ reset()
+ t.equal(process.platform, nextPlatform, 'multiple resets are indempotent')
+ reset()
+ reset()
+ t.equal(process.platform, nextPlatform, 'multiple resets are indempotent')
+ })
+ })
+
+ t.equal(process.platform, initial)
+ })
+
+ t.test('reset all', async (t) => {
+ const { teardown } = mockGlobals(t, { 'process.platform': initial })
+
+ await t.test('platforms', async (t) => {
+ const resets = platforms.map((p) => {
+ const { teardown, reset } = mockGlobals(t, { 'process.platform': p })
+ t.equal(process.platform, p)
+ return [
+ reset['process.platform'],
+ teardown,
+ ]
+ })
+
+ resets.forEach(r => r[1]())
+ t.equal(process.platform, initial, 'teardown goes to initial value')
+
+ resets.forEach((r) => r[0]())
+ t.equal(process.platform, initial, 'calling resets after teardown does nothing')
+ })
+
+ t.equal(process.platform, initial)
+ teardown()
+ t.equal(process.platform, originals.platform)
+ })
+})
diff --git a/test/lib/load-all-commands.js b/test/lib/load-all-commands.js
index f813e50b2..248c81a30 100644
--- a/test/lib/load-all-commands.js
+++ b/test/lib/load-all-commands.js
@@ -4,21 +4,16 @@
// renders also ensures that any params we've defined in our commands work.
const t = require('tap')
const util = require('util')
-const { real: mockNpm } = require('../fixtures/mock-npm.js')
+const { load: loadMockNpm } = require('../fixtures/mock-npm.js')
const { cmdList } = require('../../lib/utils/cmd-list.js')
-const { Npm, outputs } = mockNpm(t)
-const npm = new Npm()
-
t.test('load each command', async t => {
- t.afterEach(() => {
- outputs.length = 0
- })
t.plan(cmdList.length)
- await npm.load()
- npm.config.set('usage', true) // This makes npm.exec output the usage
for (const cmd of cmdList.sort((a, b) => a.localeCompare(b, 'en'))) {
t.test(cmd, async t => {
+ const { npm, outputs } = await loadMockNpm(t, {
+ config: { usage: true },
+ })
const impl = await npm.cmd(cmd)
if (impl.completion) {
t.type(impl.completion, 'function', 'completion, if present, is a function')
diff --git a/test/lib/load-all.js b/test/lib/load-all.js
index fb45331ba..e5d7b558c 100644
--- a/test/lib/load-all.js
+++ b/test/lib/load-all.js
@@ -1,34 +1,31 @@
const t = require('tap')
const glob = require('glob')
const { resolve } = require('path')
-const { real: mockNpm } = require('../fixtures/mock-npm')
+const { load: loadMockNpm } = require('../fixtures/mock-npm')
const full = process.env.npm_lifecycle_event === 'check-coverage'
if (!full) {
t.pass('nothing to do here, not checking for full coverage')
} else {
- const { Npm } = mockNpm(t)
- const npm = new Npm()
+ t.test('load all', async (t) => {
+ const { npm } = await loadMockNpm(t, { })
- t.teardown(() => {
- const exitHandler = require('../../lib/utils/exit-handler.js')
- exitHandler.setNpm(npm)
- exitHandler()
- })
-
- t.before(async t => {
- await npm.load()
- })
+ t.teardown(() => {
+ const exitHandler = require('../../lib/utils/exit-handler.js')
+ exitHandler.setNpm(npm)
+ exitHandler()
+ })
- t.test('load all the files', t => {
- // just load all the files so we measure coverage for the missing tests
- const dir = resolve(__dirname, '../../lib')
- for (const f of glob.sync(`${dir}/**/*.js`)) {
- require(f)
- t.pass('loaded ' + f)
- }
- t.pass('loaded all files')
- t.end()
+ t.test('load all the files', t => {
+ // just load all the files so we measure coverage for the missing tests
+ const dir = resolve(__dirname, '../../lib')
+ for (const f of glob.sync(`${dir}/**/*.js`)) {
+ require(f)
+ t.pass('loaded ' + f)
+ }
+ t.pass('loaded all files')
+ t.end()
+ })
})
}
diff --git a/test/lib/npm.js b/test/lib/npm.js
index 1ccd26e37..2a0c5a89d 100644
--- a/test/lib/npm.js
+++ b/test/lib/npm.js
@@ -1,7 +1,8 @@
const t = require('tap')
+const { resolve, dirname } = require('path')
-const npmlog = require('npmlog')
-const { real: mockNpm } = require('../fixtures/mock-npm.js')
+const { load: loadMockNpm } = require('../fixtures/mock-npm.js')
+const mockGlobals = require('../fixtures/mock-globals')
// delete this so that we don't have configs from the fact that it
// is being run by 'npm test'
@@ -15,7 +16,7 @@ for (const env of Object.keys(process.env).filter(e => /^npm_/.test(e))) {
// if this test is just run directly, which is also acceptable.
if (event === 'test') {
t.ok(
- ['test', 'run-script'].some(i => i === event),
+ ['test', 'run-script'].some(i => i === process.env[env]),
'should match "npm test" or "npm run test"'
)
} else {
@@ -25,41 +26,14 @@ for (const env of Object.keys(process.env).filter(e => /^npm_/.test(e))) {
delete process.env[env]
}
-const { resolve, dirname } = require('path')
-
-const actualPlatform = process.platform
-const beWindows = () => {
- Object.defineProperty(process, 'platform', {
- value: 'win32',
- configurable: true,
- })
-}
-const bePosix = () => {
- Object.defineProperty(process, 'platform', {
- value: 'posix',
- configurable: true,
- })
-}
-const argv = [...process.argv]
-
-t.afterEach(() => {
+t.afterEach(async (t) => {
for (const env of Object.keys(process.env).filter(e => /^npm_/.test(e))) {
delete process.env[env]
}
- process.env.npm_config_cache = CACHE
- process.argv = argv
- Object.defineProperty(process, 'platform', {
- value: actualPlatform,
- configurable: true,
- })
})
-const CACHE = t.testdir()
-process.env.npm_config_cache = CACHE
-
t.test('not yet loaded', async t => {
- const { Npm, logs } = mockNpm(t)
- const npm = new Npm()
+ const { npm, logs } = await loadMockNpm(t, { load: false })
t.match(npm, {
started: Number,
command: null,
@@ -79,8 +53,7 @@ t.test('not yet loaded', async t => {
t.test('npm.load', async t => {
t.test('load error', async t => {
- const { Npm } = mockNpm(t)
- const npm = new Npm()
+ const { npm } = await loadMockNpm(t, { load: false })
const loadError = new Error('load error')
npm.config.load = async () => {
throw loadError
@@ -103,32 +76,28 @@ t.test('npm.load', async t => {
})
t.test('basic loading', async t => {
- const { Npm, logs } = mockNpm(t)
- const npm = new Npm()
- const dir = t.testdir({
- node_modules: {},
+ const { npm, logs, prefix: dir, cache } = await loadMockNpm(t, {
+ testdir: { node_modules: {} },
})
- await npm.load()
+
t.equal(npm.loaded, true)
t.equal(npm.config.loaded, true)
t.equal(npm.config.get('force'), false)
t.ok(npm.usage, 'has usage')
- npm.config.set('prefix', dir)
t.match(npm, {
flatOptions: {},
})
- t.match(logs, [
- ['timing', 'npm:load', /Completed in [0-9.]+ms/],
+ t.match(logs.timing.filter(([p]) => p === 'npm:load'), [
+ ['npm:load', /Completed in [0-9.]+ms/],
])
- bePosix()
- t.equal(resolve(npm.cache), resolve(CACHE), 'cache is cache')
+ mockGlobals(t, { process: { platform: 'posix' } })
+ t.equal(resolve(npm.cache), resolve(cache), 'cache is cache')
const newCache = t.testdir()
npm.cache = newCache
t.equal(npm.config.get('cache'), newCache, 'cache setter sets config')
t.equal(npm.cache, newCache, 'cache getter gets new config')
- t.equal(npm.log, npmlog, 'npmlog getter')
t.equal(npm.lockfileVersion, 2, 'lockfileVersion getter')
t.equal(npm.prefix, npm.localPrefix, 'prefix is local prefix')
t.not(npm.prefix, npm.globalPrefix, 'prefix is not global prefix')
@@ -160,10 +129,9 @@ t.test('npm.load', async t => {
t.equal(npm.bin, npm.globalBin, 'bin is global bin after prefix setter')
t.not(npm.bin, npm.localBin, 'bin is not local bin after prefix setter')
- beWindows()
+ mockGlobals(t, { process: { platform: 'win32' } })
t.equal(npm.bin, npm.globalBin, 'bin is global bin in windows mode')
t.equal(npm.dir, npm.globalDir, 'dir is global dir in windows mode')
- bePosix()
const tmp = npm.tmp
t.match(tmp, String, 'npm.tmp is a string')
@@ -171,13 +139,12 @@ t.test('npm.load', async t => {
})
t.test('forceful loading', async t => {
- process.argv = [...process.argv, '--force', '--color', 'always']
- const { Npm, logs } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
- t.match(logs.filter(l => l[0] !== 'timing'), [
+ mockGlobals(t, {
+ 'process.argv': [...process.argv, '--force', '--color', 'always'],
+ })
+ const { logs } = await loadMockNpm(t)
+ t.match(logs.warn, [
[
- 'warn',
'using --force',
'Recommended protections disabled.',
],
@@ -185,54 +152,42 @@ t.test('npm.load', async t => {
})
t.test('node is a symlink', async t => {
- const node = actualPlatform === 'win32' ? 'node.exe' : 'node'
- const dir = t.testdir({
- '.npmrc': 'foo = bar',
- bin: t.fixture('symlink', dirname(process.execPath)),
+ const node = process.platform === 'win32' ? 'node.exe' : 'node'
+ mockGlobals(t, {
+ 'process.argv': [
+ node,
+ process.argv[1],
+ '--usage',
+ '--scope=foo',
+ 'token',
+ 'revoke',
+ 'blergggg',
+ ],
})
-
- const PATH = process.env.PATH || process.env.Path
- process.env.PATH = resolve(dir, 'bin')
- process.argv = [
- node,
- process.argv[1],
- '--prefix', dir,
- '--userconfig', `${dir}/.npmrc`,
- '--usage',
- '--scope=foo',
- 'token',
- 'revoke',
- 'blergggg',
- ]
-
- t.teardown(() => {
- process.env.PATH = PATH
+ const { npm, logs, outputs, prefix } = await loadMockNpm(t, {
+ testdir: {
+ bin: t.fixture('symlink', dirname(process.execPath)),
+ },
+ globals: ({ prefix }) => ({
+ 'process.env.PATH': resolve(prefix, 'bin'),
+ }),
})
- const { Npm, logs, outputs } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
t.equal(npm.config.get('scope'), '@foo', 'added the @ sign to scope')
- t.match(logs.filter(l => l[0] !== 'timing' || !/^config:/.test(l[1])), [
- [
- 'timing',
- 'npm:load:whichnode',
- /Completed in [0-9.]+ms/,
- ],
- [
- 'verbose',
- 'node symlink',
- resolve(dir, 'bin', node),
- ],
- [
- 'timing',
- 'npm:load',
- /Completed in [0-9.]+ms/,
- ],
+ t.match([
+ ...logs.timing.filter(([p]) => p === 'npm:load:whichnode'),
+ ...logs.verbose,
+ ...logs.timing.filter(([p]) => p === 'npm:load'),
+ ], [
+ ['npm:load:whichnode', /Completed in [0-9.]+ms/],
+ ['node symlink', resolve(prefix, 'bin', node)],
+ ['logfile', /.*-debug-0.log/],
+ ['npm:load', /Completed in [0-9.]+ms/],
])
- t.equal(process.execPath, resolve(dir, 'bin', node))
+ t.equal(process.execPath, resolve(prefix, 'bin', node))
outputs.length = 0
+ logs.length = 0
await npm.exec('ll', [])
t.equal(npm.command, 'll', 'command set to first npm command')
@@ -271,33 +226,34 @@ t.test('npm.load', async t => {
})
t.test('--no-workspaces with --workspace', async t => {
- const dir = t.testdir({
- packages: {
- a: {
- 'package.json': JSON.stringify({
- name: 'a',
- version: '1.0.0',
- scripts: { test: 'echo test a' },
- }),
+ mockGlobals(t, {
+ 'process.argv': [
+ process.execPath,
+ process.argv[1],
+ '--color', 'false',
+ '--workspaces', 'false',
+ '--workspace', 'a',
+ ],
+ })
+ const { npm } = await loadMockNpm(t, {
+ load: false,
+ testdir: {
+ packages: {
+ a: {
+ 'package.json': JSON.stringify({
+ name: 'a',
+ version: '1.0.0',
+ scripts: { test: 'echo test a' },
+ }),
+ },
},
+ 'package.json': JSON.stringify({
+ name: 'root',
+ version: '1.0.0',
+ workspaces: ['./packages/*'],
+ }),
},
- 'package.json': JSON.stringify({
- name: 'root',
- version: '1.0.0',
- workspaces: ['./packages/*'],
- }),
})
- process.argv = [
- process.execPath,
- process.argv[1],
- '--userconfig', resolve(dir, '.npmrc'),
- '--color', 'false',
- '--workspaces', 'false',
- '--workspace', 'a',
- ]
- const { Npm } = mockNpm(t)
- const npm = new Npm()
- npm.localPrefix = dir
await t.rejects(
npm.exec('run', []),
/Can not use --no-workspaces and --workspace at the same time/
@@ -305,47 +261,40 @@ t.test('npm.load', async t => {
})
t.test('workspace-aware configs and commands', async t => {
- const dir = t.testdir({
- packages: {
- a: {
- 'package.json': JSON.stringify({
- name: 'a',
- version: '1.0.0',
- scripts: { test: 'echo test a' },
- }),
- },
- b: {
- 'package.json': JSON.stringify({
- name: 'b',
- version: '1.0.0',
- scripts: { test: 'echo test b' },
- }),
+ mockGlobals(t, {
+ 'process.argv': [
+ process.execPath,
+ process.argv[1],
+ '--color', 'false',
+ '--workspaces', 'true',
+ ],
+ })
+ const { npm, outputs } = await loadMockNpm(t, {
+ testdir: {
+ packages: {
+ a: {
+ 'package.json': JSON.stringify({
+ name: 'a',
+ version: '1.0.0',
+ scripts: { test: 'echo test a' },
+ }),
+ },
+ b: {
+ 'package.json': JSON.stringify({
+ name: 'b',
+ version: '1.0.0',
+ scripts: { test: 'echo test b' },
+ }),
+ },
},
+ 'package.json': JSON.stringify({
+ name: 'root',
+ version: '1.0.0',
+ workspaces: ['./packages/*'],
+ }),
},
- 'package.json': JSON.stringify({
- name: 'root',
- version: '1.0.0',
- workspaces: ['./packages/*'],
- }),
- '.npmrc': '',
})
- process.argv = [
- process.execPath,
- process.argv[1],
- '--userconfig',
- resolve(dir, '.npmrc'),
- '--color',
- 'false',
- '--workspaces',
- 'true',
- ]
-
- const { Npm, outputs } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
- npm.localPrefix = dir
-
// verify that calling the command with a short name still sets
// the npm.command property to the full canonical name of the cmd.
npm.command = null
@@ -368,44 +317,42 @@ t.test('npm.load', async t => {
})
t.test('workspaces in global mode', async t => {
- const dir = t.testdir({
- packages: {
- a: {
- 'package.json': JSON.stringify({
- name: 'a',
- version: '1.0.0',
- scripts: { test: 'echo test a' },
- }),
- },
- b: {
- 'package.json': JSON.stringify({
- name: 'b',
- version: '1.0.0',
- scripts: { test: 'echo test b' },
- }),
+ mockGlobals(t, {
+ 'process.argv': [
+ process.execPath,
+ process.argv[1],
+ '--color',
+ 'false',
+ '--workspaces',
+ '--global',
+ 'true',
+ ],
+ })
+ const { npm } = await loadMockNpm(t, {
+ testdir: {
+ packages: {
+ a: {
+ 'package.json': JSON.stringify({
+ name: 'a',
+ version: '1.0.0',
+ scripts: { test: 'echo test a' },
+ }),
+ },
+ b: {
+ 'package.json': JSON.stringify({
+ name: 'b',
+ version: '1.0.0',
+ scripts: { test: 'echo test b' },
+ }),
+ },
},
+ 'package.json': JSON.stringify({
+ name: 'root',
+ version: '1.0.0',
+ workspaces: ['./packages/*'],
+ }),
},
- 'package.json': JSON.stringify({
- name: 'root',
- version: '1.0.0',
- workspaces: ['./packages/*'],
- }),
})
- process.argv = [
- process.execPath,
- process.argv[1],
- '--userconfig',
- resolve(dir, '.npmrc'),
- '--color',
- 'false',
- '--workspaces',
- '--global',
- 'true',
- ]
- const { Npm } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
- npm.localPrefix = dir
// verify that calling the command with a short name still sets
// the npm.command property to the full canonical name of the cmd.
npm.command = null
@@ -418,109 +365,156 @@ t.test('npm.load', async t => {
t.test('set process.title', async t => {
t.test('basic title setting', async t => {
- process.argv = [
- process.execPath,
- process.argv[1],
- '--usage',
- '--scope=foo',
- 'ls',
- ]
- const { Npm } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
+ mockGlobals(t, {
+ 'process.argv': [
+ process.execPath,
+ process.argv[1],
+ '--usage',
+ '--scope=foo',
+ 'ls',
+ ],
+ })
+ const { npm } = await loadMockNpm(t)
t.equal(npm.title, 'npm ls')
t.equal(process.title, 'npm ls')
})
t.test('do not expose token being revoked', async t => {
- process.argv = [
- process.execPath,
- process.argv[1],
- '--usage',
- '--scope=foo',
- 'token',
- 'revoke',
- 'deadbeefcafebad',
- ]
- const { Npm } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
+ mockGlobals(t, {
+ 'process.argv': [
+ process.execPath,
+ process.argv[1],
+ '--usage',
+ '--scope=foo',
+ 'token',
+ 'revoke',
+ 'deadbeefcafebad',
+ ],
+ })
+ const { npm } = await loadMockNpm(t)
t.equal(npm.title, 'npm token revoke ***')
t.equal(process.title, 'npm token revoke ***')
})
t.test('do show *** unless a token is actually being revoked', async t => {
- process.argv = [
- process.execPath,
- process.argv[1],
- '--usage',
- '--scope=foo',
- 'token',
- 'revoke',
- ]
- const { Npm } = mockNpm(t)
- const npm = new Npm()
- await npm.load()
+ mockGlobals(t, {
+ 'process.argv': [
+ process.execPath,
+ process.argv[1],
+ '--usage',
+ '--scope=foo',
+ 'token',
+ 'revoke',
+ ],
+ })
+ const { npm } = await loadMockNpm(t)
t.equal(npm.title, 'npm token revoke')
t.equal(process.title, 'npm token revoke')
})
})
-t.test('timings', t => {
- const { Npm, logs } = mockNpm(t)
- const npm = new Npm()
- process.emit('time', 'foo')
- process.emit('time', 'bar')
- t.match(npm.timers.get('foo'), Number, 'foo timer is a number')
- t.match(npm.timers.get('bar'), Number, 'foo timer is a number')
- process.emit('timeEnd', 'foo')
- process.emit('timeEnd', 'bar')
- process.emit('timeEnd', 'baz')
- t.match(logs, [
- ['timing', 'foo', /Completed in [0-9]+ms/],
- ['timing', 'bar', /Completed in [0-9]+ms/],
- [
- 'silly',
+t.test('debug-log', async t => {
+ const { npm, debugFile } = await loadMockNpm(t, { load: false })
+
+ const log1 = ['silly', 'test', 'before load']
+ const log2 = ['silly', 'test', 'after load']
+
+ process.emit('log', ...log1)
+ await npm.load()
+ process.emit('log', ...log2)
+
+ const debug = await debugFile()
+ t.equal(npm.logFiles.length, 1, 'one debug file')
+ t.match(debug, log1.join(' '), 'before load appears')
+ t.match(debug, log2.join(' '), 'after load log appears')
+})
+
+t.test('timings', async t => {
+ t.test('gets/sets timers', async t => {
+ const { npm, logs } = await loadMockNpm(t, { load: false })
+ process.emit('time', 'foo')
+ process.emit('time', 'bar')
+ t.match(npm.unfinishedTimers.get('foo'), Number, 'foo timer is a number')
+ t.match(npm.unfinishedTimers.get('bar'), Number, 'foo timer is a number')
+ process.emit('timeEnd', 'foo')
+ process.emit('timeEnd', 'bar')
+ process.emit('timeEnd', 'baz')
+ // npm timer is started by default
+ process.emit('timeEnd', 'npm')
+ t.match(logs.timing, [
+ ['foo', /Completed in [0-9]+ms/],
+ ['bar', /Completed in [0-9]+ms/],
+ ['npm', /Completed in [0-9]+ms/],
+ ])
+ t.match(logs.silly, [[
'timing',
"Tried to end timer that doesn't exist:",
'baz',
- ],
- ])
- t.notOk(npm.timers.has('foo'), 'foo timer is gone')
- t.notOk(npm.timers.has('bar'), 'bar timer is gone')
- t.match(npm.timings, { foo: Number, bar: Number })
- t.end()
+ ]])
+ t.notOk(npm.unfinishedTimers.has('foo'), 'foo timer is gone')
+ t.notOk(npm.unfinishedTimers.has('bar'), 'bar timer is gone')
+ t.match(npm.finishedTimers, { foo: Number, bar: Number, npm: Number })
+ t.end()
+ })
+
+ t.test('writes timings file', async t => {
+ const { npm, timingFile } = await loadMockNpm(t, {
+ config: { timing: true },
+ })
+ process.emit('time', 'foo')
+ process.emit('timeEnd', 'foo')
+ process.emit('time', 'bar')
+ npm.unload()
+ const timings = await timingFile()
+ t.match(timings, {
+ command: [],
+ logfile: String,
+ logfiles: [String],
+ version: String,
+ unfinished: {
+ bar: [Number, Number],
+ npm: [Number, Number],
+ },
+ foo: Number,
+ 'npm:load': Number,
+ })
+ })
+
+ t.test('does not write timings file with timers:false', async t => {
+ const { npm, timingFile } = await loadMockNpm(t, {
+ config: { false: true },
+ })
+ npm.unload()
+ await t.rejects(() => timingFile())
+ })
})
-t.test('output clears progress and console.logs the message', t => {
- const mock = mockNpm(t)
- const { Npm, logs } = mock
- const npm = new Npm()
- npm.output = mock.npmOutput
- const { log } = console
- const { log: { clearProgress, showProgress } } = npm
+t.test('output clears progress and console.logs the message', async t => {
+ t.plan(2)
let showingProgress = true
- npm.log.clearProgress = () => showingProgress = false
- npm.log.showProgress = () => showingProgress = true
- console.log = (...args) => {
- t.equal(showingProgress, false, 'should not be showing progress right now')
- logs.push(args)
- }
- t.teardown(() => {
- console.log = log
- npm.log.showProgress = showProgress
- npm.log.clearProgress = clearProgress
+ const logs = []
+ mockGlobals(t, {
+ 'console.log': (...args) => {
+ t.equal(showingProgress, false, 'should not be showing progress right now')
+ logs.push(args)
+ },
})
-
- npm.output('hello')
- t.strictSame(logs, [['hello']])
+ const { npm } = await loadMockNpm(t, {
+ load: false,
+ mocks: {
+ npmlog: {
+ clearProgress: () => showingProgress = false,
+ showProgress: () => showingProgress = true,
+ },
+ },
+ })
+ npm.originalOutput('hello')
+ t.match(logs, [['hello']])
t.end()
})
t.test('unknown command', async t => {
- const mock = mockNpm(t)
- const { Npm } = mock
- const npm = new Npm()
+ const { npm } = await loadMockNpm(t, { load: false })
await t.rejects(
npm.cmd('thisisnotacommand'),
{ code: 'EUNKNOWNCOMMAND' }
diff --git a/test/lib/utils/audit-error.js b/test/lib/utils/audit-error.js
index c683053cb..bcb7d8c16 100644
--- a/test/lib/utils/audit-error.js
+++ b/test/lib/utils/audit-error.js
@@ -3,14 +3,15 @@ const t = require('tap')
const LOGS = []
const OUTPUT = []
const output = (...msg) => OUTPUT.push(msg)
-const auditError = require('../../../lib/utils/audit-error.js')
+const auditError = t.mock('../../../lib/utils/audit-error.js', {
+ 'proc-log': {
+ warn: (...msg) => LOGS.push(msg),
+ },
+})
const npm = {
command: null,
flatOptions: {},
- log: {
- warn: (...msg) => LOGS.push(msg),
- },
output,
}
t.afterEach(() => {
diff --git a/test/lib/utils/cleanup-log-files.js b/test/lib/utils/cleanup-log-files.js
deleted file mode 100644
index e97cf36b5..000000000
--- a/test/lib/utils/cleanup-log-files.js
+++ /dev/null
@@ -1,79 +0,0 @@
-const t = require('tap')
-
-const glob = require('glob')
-const rimraf = require('rimraf')
-const mocks = { glob, rimraf }
-const cleanup = t.mock('../../../lib/utils/cleanup-log-files.js', {
- glob: (...args) => mocks.glob(...args),
- rimraf: (...args) => mocks.rimraf(...args),
-})
-const { basename } = require('path')
-
-const fs = require('fs')
-
-t.test('clean up those files', t => {
- const cache = t.testdir({
- _logs: {
- '1-debug.log': 'hello',
- '2-debug.log': 'hello',
- '3-debug.log': 'hello',
- '4-debug.log': 'hello',
- '5-debug.log': 'hello',
- },
- })
- const warn = (...warning) => t.fail('failed cleanup', { warning })
- return cleanup(cache, 3, warn).then(() => {
- t.strictSame(fs.readdirSync(cache + '/_logs').sort(), [
- '3-debug.log',
- '4-debug.log',
- '5-debug.log',
- ])
- })
-})
-
-t.test('nothing to clean up', t => {
- const cache = t.testdir({
- _logs: {
- '4-debug.log': 'hello',
- '5-debug.log': 'hello',
- },
- })
- const warn = (...warning) => t.fail('failed cleanup', { warning })
- return cleanup(cache, 3, warn).then(() => {
- t.strictSame(fs.readdirSync(cache + '/_logs').sort(), [
- '4-debug.log',
- '5-debug.log',
- ])
- })
-})
-
-t.test('glob fail', t => {
- mocks.glob = (pattern, cb) => cb(new Error('no globbity'))
- t.teardown(() => mocks.glob = glob)
- const cache = t.testdir({})
- const warn = (...warning) => t.fail('failed cleanup', { warning })
- return cleanup(cache, 3, warn)
-})
-
-t.test('rimraf fail', t => {
- mocks.rimraf = (file, cb) => cb(new Error('youll never rimraf me!'))
- t.teardown(() => mocks.rimraf = rimraf)
-
- const cache = t.testdir({
- _logs: {
- '1-debug.log': 'hello',
- '2-debug.log': 'hello',
- '3-debug.log': 'hello',
- '4-debug.log': 'hello',
- '5-debug.log': 'hello',
- },
- })
- const warnings = []
- const warn = (...warning) => warnings.push(basename(warning[2]))
- return cleanup(cache, 3, warn).then(() => {
- t.strictSame(warnings.sort((a, b) => a.localeCompare(b, 'en')), [
- '1-debug.log',
- '2-debug.log',
- ])
- })
-})
diff --git a/test/lib/utils/config/definitions.js b/test/lib/utils/config/definitions.js
index 7af0b6839..bf4b48709 100644
--- a/test/lib/utils/config/definitions.js
+++ b/test/lib/utils/config/definitions.js
@@ -1,11 +1,9 @@
const t = require('tap')
-
const { resolve } = require('path')
+const mockGlobals = require('../../../fixtures/mock-globals')
// have to fake the node version, or else it'll only pass on this one
-Object.defineProperty(process, 'version', {
- value: 'v14.8.0',
-})
+mockGlobals(t, { 'process.version': 'v14.8.0', 'process.env.NODE_ENV': undefined })
// also fake the npm version, so that it doesn't get reset every time
const pkg = require('../../../../package.json')
@@ -13,8 +11,6 @@ const pkg = require('../../../../package.json')
// this is a pain to keep typing
const defpath = '../../../../lib/utils/config/definitions.js'
-// set this in the test when we need it
-delete process.env.NODE_ENV
const definitions = require(defpath)
// Tie the definitions to a snapshot so that if they change we are forced to
@@ -43,22 +39,19 @@ t.test('basic flattening function camelCases from css-case', t => {
t.test('editor', t => {
t.test('has EDITOR and VISUAL, use EDITOR', t => {
- process.env.EDITOR = 'vim'
- process.env.VISUAL = 'mate'
+ mockGlobals(t, { 'process.env': { EDITOR: 'vim', VISUAL: 'mate' } })
const defs = t.mock(defpath)
t.equal(defs.editor.default, 'vim')
t.end()
})
t.test('has VISUAL but no EDITOR, use VISUAL', t => {
- delete process.env.EDITOR
- process.env.VISUAL = 'mate'
+ mockGlobals(t, { 'process.env': { EDITOR: undefined, VISUAL: 'mate' } })
const defs = t.mock(defpath)
t.equal(defs.editor.default, 'mate')
t.end()
})
t.test('has neither EDITOR nor VISUAL, system specific', t => {
- delete process.env.EDITOR
- delete process.env.VISUAL
+ mockGlobals(t, { 'process.env': { EDITOR: undefined, VISUAL: undefined } })
const defsWin = t.mock(defpath, {
[isWin]: true,
})
@@ -74,12 +67,12 @@ t.test('editor', t => {
t.test('shell', t => {
t.test('windows, env.ComSpec then cmd.exe', t => {
- process.env.ComSpec = 'command.com'
+ mockGlobals(t, { 'process.env.ComSpec': 'command.com' })
const defsComSpec = t.mock(defpath, {
[isWin]: true,
})
t.equal(defsComSpec.shell.default, 'command.com')
- delete process.env.ComSpec
+ mockGlobals(t, { 'process.env.ComSpec': undefined })
const defsNoComSpec = t.mock(defpath, {
[isWin]: true,
})
@@ -88,12 +81,12 @@ t.test('shell', t => {
})
t.test('nix, SHELL then sh', t => {
- process.env.SHELL = '/usr/local/bin/bash'
+ mockGlobals(t, { 'process.env.SHELL': '/usr/local/bin/bash' })
const defsShell = t.mock(defpath, {
[isWin]: false,
})
t.equal(defsShell.shell.default, '/usr/local/bin/bash')
- delete process.env.SHELL
+ mockGlobals(t, { 'process.env.SHELL': undefined })
const defsNoShell = t.mock(defpath, {
[isWin]: false,
})
@@ -136,43 +129,40 @@ t.test('local-address allowed types', t => {
})
t.test('unicode allowed?', t => {
- const { LC_ALL, LC_CTYPE, LANG } = process.env
- t.teardown(() => Object.assign(process.env, { LC_ALL, LC_CTYPE, LANG }))
+ const setGlobal = (obj = {}) => mockGlobals(t, { 'process.env': obj })
- process.env.LC_ALL = 'utf8'
- process.env.LC_CTYPE = 'UTF-8'
- process.env.LANG = 'Unicode utf-8'
+ setGlobal({ LC_ALL: 'utf8', LC_CTYPE: 'UTF-8', LANG: 'Unicode utf-8' })
const lcAll = t.mock(defpath)
t.equal(lcAll.unicode.default, true)
- process.env.LC_ALL = 'no unicode for youUUUU!'
+ setGlobal({ LC_ALL: 'no unicode for youUUUU!' })
const noLcAll = t.mock(defpath)
t.equal(noLcAll.unicode.default, false)
- delete process.env.LC_ALL
+ setGlobal({ LC_ALL: undefined })
const lcCtype = t.mock(defpath)
t.equal(lcCtype.unicode.default, true)
- process.env.LC_CTYPE = 'something other than unicode version 8'
+ setGlobal({ LC_CTYPE: 'something other than unicode version 8' })
const noLcCtype = t.mock(defpath)
t.equal(noLcCtype.unicode.default, false)
- delete process.env.LC_CTYPE
+ setGlobal({ LC_CTYPE: undefined })
const lang = t.mock(defpath)
t.equal(lang.unicode.default, true)
- process.env.LANG = 'ISO-8859-1'
+ setGlobal({ LANG: 'ISO-8859-1' })
const noLang = t.mock(defpath)
t.equal(noLang.unicode.default, false)
t.end()
})
t.test('cache', t => {
- process.env.LOCALAPPDATA = 'app/data/local'
+ mockGlobals(t, { 'process.env.LOCALAPPDATA': 'app/data/local' })
const defsWinLocalAppData = t.mock(defpath, {
[isWin]: true,
})
t.equal(defsWinLocalAppData.cache.default, 'app/data/local/npm-cache')
- delete process.env.LOCALAPPDATA
+ mockGlobals(t, { 'process.env.LOCALAPPDATA': undefined })
const defsWinNoLocalAppData = t.mock(defpath, {
[isWin]: true,
})
@@ -241,7 +231,7 @@ t.test('flatteners that populate flat.omit array', t => {
definitions.omit.flatten('omit', obj, flat)
t.strictSame(flat, { omit: ['optional'] }, 'do not omit what is included')
- process.env.NODE_ENV = 'production'
+ mockGlobals(t, { 'process.env.NODE_ENV': 'production' })
const defProdEnv = t.mock(defpath)
t.strictSame(defProdEnv.omit.default, ['dev'], 'omit dev in production')
t.end()
@@ -372,42 +362,79 @@ t.test('cache-min', t => {
})
t.test('color', t => {
- const { isTTY } = process.stdout
- t.teardown(() => process.stdout.isTTY = isTTY)
+ const setTTY = (stream, value) => mockGlobals(t, { [`process.${stream}.isTTY`]: value })
const flat = {}
const obj = { color: 'always' }
definitions.color.flatten('color', obj, flat)
- t.strictSame(flat, { color: true }, 'true when --color=always')
+ t.strictSame(flat, { color: true, logColor: true }, 'true when --color=always')
obj.color = false
definitions.color.flatten('color', obj, flat)
- t.strictSame(flat, { color: false }, 'true when --no-color')
+ t.strictSame(flat, { color: false, logColor: false }, 'true when --no-color')
- process.stdout.isTTY = false
+ setTTY('stdout', false)
obj.color = true
definitions.color.flatten('color', obj, flat)
- t.strictSame(flat, { color: false }, 'no color when stdout not tty')
- process.stdout.isTTY = true
+ t.strictSame(flat, { color: false, logColor: false }, 'no color when stdout not tty')
+ setTTY('stdout', true)
definitions.color.flatten('color', obj, flat)
- t.strictSame(flat, { color: true }, '--color turns on color when stdout is tty')
+ t.strictSame(flat, { color: true, logColor: false }, '--color turns on color when stdout is tty')
+ setTTY('stdout', false)
- delete process.env.NO_COLOR
+ setTTY('stderr', false)
+ obj.color = true
+ definitions.color.flatten('color', obj, flat)
+ t.strictSame(flat, { color: false, logColor: false }, 'no color when stderr not tty')
+ setTTY('stderr', true)
+ definitions.color.flatten('color', obj, flat)
+ t.strictSame(flat, { color: false, logColor: true }, '--color turns on color when stderr is tty')
+ setTTY('stderr', false)
+
+ const setColor = (value) => mockGlobals(t, { 'process.env.NO_COLOR': value })
+
+ setColor(undefined)
const defsAllowColor = t.mock(defpath)
t.equal(defsAllowColor.color.default, true, 'default true when no NO_COLOR env')
- process.env.NO_COLOR = '0'
+ setColor('0')
const defsNoColor0 = t.mock(defpath)
t.equal(defsNoColor0.color.default, true, 'default true when no NO_COLOR=0')
- process.env.NO_COLOR = '1'
+ setColor('1')
const defsNoColor1 = t.mock(defpath)
t.equal(defsNoColor1.color.default, false, 'default false when no NO_COLOR=1')
t.end()
})
+t.test('progress', t => {
+ const setEnv = ({ tty, term } = {}) => mockGlobals(t, {
+ 'process.stderr.isTTY': tty,
+ 'process.env.TERM': term,
+ })
+
+ const flat = {}
+
+ definitions.progress.flatten('progress', {}, flat)
+ t.strictSame(flat, { progress: false })
+
+ setEnv({ tty: true, term: 'notdumb' })
+ definitions.progress.flatten('progress', { progress: true }, flat)
+ t.strictSame(flat, { progress: true })
+
+ setEnv({ tty: false, term: 'notdumb' })
+ definitions.progress.flatten('progress', { progress: true }, flat)
+ t.strictSame(flat, { progress: false })
+
+ setEnv({ tty: true, term: 'dumb' })
+ definitions.progress.flatten('progress', { progress: true }, flat)
+ t.strictSame(flat, { progress: false })
+
+ t.end()
+})
+
t.test('retry options', t => {
const obj = {}
// <config>: flat.retry[<option>]
diff --git a/test/lib/utils/did-you-mean.js b/test/lib/utils/did-you-mean.js
index 185368d61..d3cb3a24f 100644
--- a/test/lib/utils/did-you-mean.js
+++ b/test/lib/utils/did-you-mean.js
@@ -1,11 +1,9 @@
const t = require('tap')
-const { real: mockNpm } = require('../../fixtures/mock-npm.js')
-const { Npm } = mockNpm(t)
-const npm = new Npm()
+const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
const dym = require('../../../lib/utils/did-you-mean.js')
t.test('did-you-mean', async t => {
- await npm.load()
+ const { npm } = await loadMockNpm(t)
t.test('with package.json', async t => {
const testdir = t.testdir({
'package.json': JSON.stringify({
diff --git a/test/lib/utils/display.js b/test/lib/utils/display.js
new file mode 100644
index 000000000..30cd2cc27
--- /dev/null
+++ b/test/lib/utils/display.js
@@ -0,0 +1,85 @@
+const t = require('tap')
+const log = require('../../../lib/utils/log-shim')
+const mockLogs = require('../../fixtures/mock-logs')
+const mockGlobals = require('../../fixtures/mock-globals')
+
+const mockDisplay = (t, mocks) => {
+ const { logs, logMocks } = mockLogs(mocks)
+ const Display = t.mock('../../../lib/utils/display', {
+ ...mocks,
+ ...logMocks,
+ })
+ const display = new Display()
+ t.teardown(() => display.off())
+ return { display, logs }
+}
+
+t.test('setup', async (t) => {
+ const { display } = mockDisplay(t)
+
+ display.load({ timing: true, loglevel: 'notice' })
+ t.equal(log.level, 'timing')
+
+ display.load({ timing: false, loglevel: 'notice' })
+ t.equal(log.level, 'notice')
+
+ display.load({ color: true })
+ t.equal(log.useColor(), true)
+
+ display.load({ unicode: true })
+ t.equal(log.gauge._theme.hasUnicode, true)
+
+ display.load({ unicode: false })
+ t.equal(log.gauge._theme.hasUnicode, false)
+
+ mockGlobals(t, { 'process.stderr.isTTY': true })
+ display.load({ progress: true })
+ t.equal(log.progressEnabled, true)
+})
+
+t.test('can log', async (t) => {
+ const explains = []
+ const { display, logs } = mockDisplay(t, {
+ npmlog: {
+ error: (...args) => logs.push(['error', ...args]),
+ warn: (...args) => logs.push(['warn', ...args]),
+ },
+ '../../../lib/utils/explain-eresolve.js': {
+ explain: (...args) => {
+ explains.push(args)
+ return 'explanation'
+ },
+ },
+ })
+
+ display.log('error', 'test')
+ t.match(logs.error, [['test']])
+
+ display.log('warn', 'ERESOLVE', 'hello', { some: 'object' })
+ t.match(logs.warn, [['ERESOLVE', 'hello']])
+ t.match(explains, [[{ some: 'object' }, false, 2]])
+})
+
+t.test('handles log throwing', async (t) => {
+ const errors = []
+ mockGlobals(t, {
+ 'console.error': (...args) => errors.push(args),
+ })
+ const { display } = mockDisplay(t, {
+ npmlog: {
+ verbose: () => {
+ throw new Error('verbose')
+ },
+ },
+ '../../../lib/utils/explain-eresolve.js': {
+ explain: () => {
+ throw new Error('explain')
+ },
+ },
+ })
+
+ display.log('warn', 'ERESOLVE', 'hello', { some: 'object' })
+ t.match(errors, [
+ [/attempt to log .* crashed/, Error('explain'), Error('verbose')],
+ ])
+})
diff --git a/test/lib/utils/error-message.js b/test/lib/utils/error-message.js
index 1959b9217..ddc88c1d9 100644
--- a/test/lib/utils/error-message.js
+++ b/test/lib/utils/error-message.js
@@ -1,87 +1,51 @@
const t = require('tap')
const path = require('path')
-const { real: mockNpm } = require('../../fixtures/mock-npm.js')
-const { Npm } = mockNpm(t, {
- '../../package.json': {
- version: '123.456.789-npm',
+const { load: _loadMockNpm } = require('../../fixtures/mock-npm.js')
+const mockGlobals = require('../../fixtures/mock-globals.js')
+const { cleanCwd, cleanDate } = require('../../fixtures/clean-snapshot.js')
+
+t.cleanSnapshot = p => cleanDate(cleanCwd(p))
+
+mockGlobals(t, {
+ process: {
+ getuid: () => 867,
+ getgid: () => 5309,
+ arch: 'x64',
+ version: '123.456.789-node',
+ platform: 'posix',
},
})
-const npm = new Npm()
-const { Npm: UnloadedNpm } = mockNpm(t, {
- '../../package.json': {
- version: '123.456.789-npm',
- },
-})
-const unloadedNpm = new UnloadedNpm()
-
-// make a bunch of stuff consistent for snapshots
-
-process.getuid = () => 867
-process.getgid = () => 5309
-
-Object.defineProperty(process, 'arch', {
- value: 'x64',
- configurable: true,
-})
-
-Object.defineProperty(process, 'version', {
- value: '123.456.789-node',
- configurable: true,
-})
-const CACHE = '/some/cache/dir'
-const testdir = t.testdir({})
-t.before(async () => {
- await npm.load()
- npm.localPrefix = testdir
- unloadedNpm.localPrefix = testdir
- npm.config.set('cache', CACHE)
- npm.config.set('node-version', '99.99.99')
- npm.version = '123.456.789-npm'
- unloadedNpm.version = '123.456.789-npm'
-})
-
-const { resolve } = require('path')
-
-const npmlog = require('npmlog')
-const verboseLogs = []
-npmlog.verbose = (...message) => {
- verboseLogs.push(message)
-}
-
-const EXPLAIN_CALLED = []
-const mocks = {
- '../../../lib/utils/explain-eresolve.js': {
- report: (...args) => {
- EXPLAIN_CALLED.push(args)
- return 'explanation'
+const loadMockNpm = async (t, { load, command, testdir, config } = {}) => {
+ const { npm, ...rest } = await _loadMockNpm(t, {
+ load,
+ testdir,
+ config,
+ mocks: {
+ '../../package.json': {
+ version: '123.456.789-npm',
+ },
},
- },
- // XXX ???
- get '../../../lib/utils/is-windows.js' () {
- return process.platform === 'win32'
- },
-}
-let errorMessage = t.mock('../../../lib/utils/error-message.js', { ...mocks })
-
-const beWindows = () => {
- Object.defineProperty(process, 'platform', {
- value: 'win32',
- configurable: true,
})
- errorMessage = t.mock('../../../lib/utils/error-message.js', { ...mocks })
+ if (command !== undefined) {
+ npm.command = command
+ }
+ return {
+ npm,
+ ...rest,
+ }
}
-const bePosix = () => {
- Object.defineProperty(process, 'platform', {
- value: 'posix',
- configurable: true,
- })
- errorMessage = t.mock('../../../lib/utils/error-message.js', { ...mocks })
-}
+const errorMessage = (er, { mocks, logMocks, npm } = {}) =>
+ t.mock('../../../lib/utils/error-message.js', { ...mocks, ...logMocks })(er, npm)
-t.test('just simple messages', t => {
- npm.command = 'audit'
+t.test('just simple messages', async t => {
+ const npm = await loadMockNpm(t, {
+ command: 'audit',
+ config: {
+ 'node-version': '99.99.99',
+ },
+ })
const codes = [
'ENOAUDIT',
'ENOLOCK',
@@ -108,7 +72,7 @@ t.test('just simple messages', t => {
'ERR_SOCKET_TIMEOUT',
]
t.plan(codes.length)
- codes.forEach(code => {
+ codes.forEach(async code => {
const path = '/some/path'
const pkgid = 'some@package'
const file = '/some/file'
@@ -124,8 +88,8 @@ t.test('just simple messages', t => {
})
})
-t.test('replace message/stack sensistive info', t => {
- npm.command = 'audit'
+t.test('replace message/stack sensistive info', async t => {
+ const npm = await loadMockNpm(t, { command: 'audit' })
const path = '/some/path'
const pkgid = 'some@package'
const file = '/some/file'
@@ -139,10 +103,10 @@ t.test('replace message/stack sensistive info', t => {
stack,
})
t.matchSnapshot(errorMessage(er, npm))
- t.end()
})
-t.test('bad engine without config loaded', t => {
+t.test('bad engine without config loaded', async t => {
+ const npm = await loadMockNpm(t, { load: false })
const path = '/some/path'
const pkgid = 'some@package'
const file = '/some/file'
@@ -154,11 +118,11 @@ t.test('bad engine without config loaded', t => {
file,
stack,
})
- t.matchSnapshot(errorMessage(er, unloadedNpm))
- t.end()
+ t.matchSnapshot(errorMessage(er, npm))
})
-t.test('enoent without a file', t => {
+t.test('enoent without a file', async t => {
+ const npm = await loadMockNpm(t)
const path = '/some/path'
const pkgid = 'some@package'
const stack = 'dummy stack trace'
@@ -169,11 +133,10 @@ t.test('enoent without a file', t => {
stack,
})
t.matchSnapshot(errorMessage(er, npm))
- t.end()
})
-t.test('enolock without a command', t => {
- npm.command = null
+t.test('enolock without a command', async t => {
+ const npm = await loadMockNpm(t, { command: null })
const path = '/some/path'
const pkgid = 'some@package'
const file = '/some/file'
@@ -186,12 +149,12 @@ t.test('enolock without a command', t => {
stack,
})
t.matchSnapshot(errorMessage(er, npm))
- t.end()
})
-t.test('default message', t => {
+t.test('default message', async t => {
+ const npm = await loadMockNpm(t)
t.matchSnapshot(errorMessage(new Error('error object'), npm))
- t.matchSnapshot(errorMessage('error string'), npm)
+ t.matchSnapshot(errorMessage('error string', npm))
t.matchSnapshot(errorMessage(Object.assign(new Error('cmd err'), {
cmd: 'some command',
signal: 'SIGYOLO',
@@ -199,10 +162,10 @@ t.test('default message', t => {
stdout: 'stdout',
stderr: 'stderr',
}), npm))
- t.end()
})
-t.test('args are cleaned', t => {
+t.test('args are cleaned', async t => {
+ const npm = await loadMockNpm(t)
t.matchSnapshot(errorMessage(Object.assign(new Error('cmd err'), {
cmd: 'some command',
signal: 'SIGYOLO',
@@ -210,35 +173,25 @@ t.test('args are cleaned', t => {
stdout: 'stdout',
stderr: 'stderr',
}), npm))
- t.end()
})
-t.test('eacces/eperm', t => {
- const runTest = (windows, loaded, cachePath, cacheDest) => t => {
+t.test('eacces/eperm', async t => {
+ const runTest = (windows, loaded, cachePath, cacheDest) => async t => {
if (windows) {
- beWindows()
- } else {
- bePosix()
+ mockGlobals(t, { 'process.platform': 'win32' })
}
-
- const path = `${cachePath ? CACHE : '/not/cache/dir'}/path`
- const dest = `${cacheDest ? CACHE : '/not/cache/dir'}/dest`
+ const npm = await loadMockNpm(t, { windows, load: loaded })
+ const path = `${cachePath ? npm.cache : '/not/cache/dir'}/path`
+ const dest = `${cacheDest ? npm.cache : '/not/cache/dir'}/dest`
const er = Object.assign(new Error('whoopsie'), {
code: 'EACCES',
path,
dest,
stack: 'dummy stack trace',
})
- verboseLogs.length = 0
- if (loaded) {
- t.matchSnapshot(errorMessage(er, npm))
- } else {
- t.matchSnapshot(errorMessage(er, unloadedNpm))
- }
- t.matchSnapshot(verboseLogs)
- t.end()
- verboseLogs.length = 0
+ t.matchSnapshot(errorMessage(er, npm))
+ t.matchSnapshot(npm.logs.verbose)
}
for (const windows of [true, false]) {
@@ -251,12 +204,13 @@ t.test('eacces/eperm', t => {
}
}
}
- t.end()
})
t.test('json parse', t => {
- t.test('merge conflict in package.json', t => {
- const dir = t.testdir({
+ mockGlobals(t, { 'process.argv': ['arg', 'v'] })
+
+ t.test('merge conflict in package.json', async t => {
+ const testdir = {
'package.json': `
{
"array": [
@@ -295,59 +249,35 @@ t.test('json parse', t => {
}
}
`,
- })
- const { prefix } = npm
- const { argv } = process
- t.teardown(() => {
- Object.defineProperty(npm, 'prefix', {
- value: prefix,
- configurable: true,
- })
- process.argv = argv
- })
- Object.defineProperty(npm, 'prefix', { value: dir, configurable: true })
- process.argv = ['arg', 'v']
+ }
+ const npm = await loadMockNpm(t, { testdir })
t.matchSnapshot(errorMessage(Object.assign(new Error('conflicted'), {
code: 'EJSONPARSE',
- path: resolve(dir, 'package.json'),
+ path: path.resolve(npm.prefix, 'package.json'),
}), npm))
t.end()
})
- t.test('just regular bad json in package.json', t => {
- const dir = t.testdir({
+ t.test('just regular bad json in package.json', async t => {
+ const testdir = {
'package.json': 'not even slightly json',
- })
- const { prefix } = npm
- const { argv } = process
- t.teardown(() => {
- Object.defineProperty(npm, 'prefix', {
- value: prefix,
- configurable: true,
- })
- process.argv = argv
- })
- Object.defineProperty(npm, 'prefix', { value: dir, configurable: true })
- process.argv = ['arg', 'v']
+ }
+ const npm = await loadMockNpm(t, { testdir })
t.matchSnapshot(errorMessage(Object.assign(new Error('not json'), {
code: 'EJSONPARSE',
- path: resolve(dir, 'package.json'),
+ path: path.resolve(npm.prefix, 'package.json'),
}), npm))
t.end()
})
- t.test('json somewhere else', t => {
- const dir = t.testdir({
+ t.test('json somewhere else', async t => {
+ const testdir = {
'blerg.json': 'not even slightly json',
- })
- const { argv } = process
- t.teardown(() => {
- process.argv = argv
- })
- process.argv = ['arg', 'v']
+ }
+ const npm = await loadMockNpm(t, { testdir })
t.matchSnapshot(errorMessage(Object.assign(new Error('not json'), {
code: 'EJSONPARSE',
- path: `${dir}/blerg.json`,
+ path: path.resolve(npm.prefix, 'blerg.json'),
}), npm))
t.end()
})
@@ -355,7 +285,9 @@ t.test('json parse', t => {
t.end()
})
-t.test('eotp/e401', t => {
+t.test('eotp/e401', async t => {
+ const npm = await loadMockNpm(t)
+
t.test('401, no auth headers', t => {
t.matchSnapshot(errorMessage(Object.assign(new Error('nope'), {
code: 'E401',
@@ -406,11 +338,11 @@ t.test('eotp/e401', t => {
})
}
})
-
- t.end()
})
-t.test('404', t => {
+t.test('404', async t => {
+ const npm = await loadMockNpm(t)
+
t.test('no package id', t => {
const er = Object.assign(new Error('404 not found'), { code: 'E404' })
t.matchSnapshot(errorMessage(er, npm))
@@ -448,10 +380,11 @@ t.test('404', t => {
t.matchSnapshot(errorMessage(er, npm))
t.end()
})
- t.end()
})
-t.test('bad platform', t => {
+t.test('bad platform', async t => {
+ const npm = await loadMockNpm(t)
+
t.test('string os/arch', t => {
const er = Object.assign(new Error('a bad plat'), {
pkgid: 'lodash@1.0.0',
@@ -484,19 +417,30 @@ t.test('bad platform', t => {
t.matchSnapshot(errorMessage(er, npm))
t.end()
})
-
- t.end()
})
-t.test('explain ERESOLVE errors', t => {
+t.test('explain ERESOLVE errors', async t => {
+ const npm = await loadMockNpm(t)
+ const EXPLAIN_CALLED = []
+
const er = Object.assign(new Error('could not resolve'), {
code: 'ERESOLVE',
})
- t.matchSnapshot(errorMessage(er, npm))
+
+ t.matchSnapshot(errorMessage(er, {
+ ...npm,
+ mocks: {
+ '../../../lib/utils/explain-eresolve.js': {
+ report: (...args) => {
+ EXPLAIN_CALLED.push(args)
+ return 'explanation'
+ },
+ },
+ },
+ }))
t.match(EXPLAIN_CALLED, [[
er,
- undefined,
+ false,
path.resolve(npm.cache, 'eresolve-report.txt'),
]])
- t.end()
})
diff --git a/test/lib/utils/exit-handler.js b/test/lib/utils/exit-handler.js
index adc7c3f4e..54bf48f89 100644
--- a/test/lib/utils/exit-handler.js
+++ b/test/lib/utils/exit-handler.js
@@ -1,177 +1,213 @@
-/* eslint-disable no-extend-native */
-/* eslint-disable no-global-assign */
const t = require('tap')
-const EventEmitter = require('events')
const os = require('os')
-const fs = require('fs')
-const path = require('path')
-
-const { real: mockNpm } = require('../../fixtures/mock-npm')
-
-// generic error to be used in tests
-const err = Object.assign(new Error('ERROR'), { code: 'ERROR' })
-err.stack = 'Error: ERROR'
-
-const redactCwd = (path) => {
- const normalizePath = p => p
- .replace(/\\+/g, '/')
- .replace(/\r\n/g, '\n')
- return normalizePath(path)
- .replace(new RegExp(normalizePath(process.cwd()), 'g'), '{CWD}')
+const EventEmitter = require('events')
+const { format } = require('../../../lib/utils/log-file')
+const { load: loadMockNpm } = require('../../fixtures/mock-npm')
+const mockGlobals = require('../../fixtures/mock-globals')
+const { cleanCwd, cleanDate } = require('../../fixtures/clean-snapshot')
+
+const pick = (obj, ...keys) => keys.reduce((acc, key) => {
+ acc[key] = obj[key]
+ return acc
+}, {})
+
+t.formatSnapshot = (obj) => {
+ if (Array.isArray(obj)) {
+ return obj
+ .map((i) => Array.isArray(i) ? i.join(' ') : i)
+ .join('\n')
+ }
+ return obj
}
-t.cleanSnapshot = (str) => redactCwd(str)
-
-const cacheFolder = t.testdir({})
-const logFile = path.resolve(cacheFolder, '_logs', 'expecteddate-debug.log')
-const timingFile = path.resolve(cacheFolder, '_timing.json')
-
-const { Npm } = mockNpm(t, {
- '../../package.json': {
- version: '1.0.0',
- },
-})
-const npm = new Npm()
-
-t.before(async () => {
- await npm.load()
- npm.config.set('cache', cacheFolder)
-})
+t.cleanSnapshot = (path) => cleanDate(cleanCwd(path))
+// Config loading is dependent on env so strip those from snapshots
+ .replace(/.*timing config:load:.*\n/gm, '')
+ .replace(/(Completed in )\d+(ms)/g, '$1{TIME}$2')
// cut off process from script so that it won't quit the test runner
// while trying to run through the myriad of cases. need to make it
// have all the functions signal-exit relies on so that it doesn't
// nerf itself, thinking global.process is broken or gone.
-const _process = process
-process = Object.assign(
- new EventEmitter(),
- {
- argv: ['/node', ..._process.argv.slice(1)],
- cwd: _process.cwd,
- env: _process.env,
+mockGlobals(t, {
+ process: Object.assign(new EventEmitter(), {
+ ...pick(process, 'execPath', 'stdout', 'stderr', 'cwd', 'env'),
+ argv: ['/node', ...process.argv.slice(1)],
version: 'v1.0.0',
+ kill: () => {},
+ reallyExit: (code) => process.exit(code),
+ pid: 123456,
exit: (code) => {
process.exitCode = code || process.exitCode || 0
process.emit('exit', process.exitCode)
},
- stdout: { write (_, cb) {
- cb()
- } },
- stderr: { write () {} },
- hrtime: _process.hrtime,
- kill: () => {},
- reallyExit: (code) => process.exit(code),
- pid: 123456,
+ }),
+}, { replace: true })
+
+const mockExitHandler = async (t, { init, load, testdir, config } = {}) => {
+ const errors = []
+ mockGlobals(t, { 'console.error': (err) => errors.push(err) })
+
+ const { npm, logMocks, ...rest } = await loadMockNpm(t, {
+ init,
+ load,
+ testdir,
+ mocks: {
+ '../../package.json': {
+ version: '1.0.0',
+ },
+ },
+ config: {
+ loglevel: 'notice',
+ ...config,
+ },
+ })
+
+ const exitHandler = t.mock('../../../lib/utils/exit-handler.js', {
+ '../../../lib/utils/error-message.js': (err) => ({
+ ...err,
+ summary: [['ERR SUMMARY', err.message]],
+ detail: [['ERR DETAIL', err.message]],
+ }),
+ os: {
+ type: () => 'Foo',
+ release: () => '1.0.0',
+ },
+ ...logMocks,
+ })
+
+ if (npm) {
+ exitHandler.setNpm(npm)
}
-)
-
-const osType = os.type
-const osRelease = os.release
-// overrides OS type/release for cross platform snapshots
-os.type = () => 'Foo'
-os.release = () => '1.0.0'
-
-// generates logfile name with mocked date
-const _toISOString = Date.prototype.toISOString
-Date.prototype.toISOString = () => 'expecteddate'
-
-const consoleError = console.error
-const errors = []
-console.error = (err) => {
- errors.push(err)
-}
-t.teardown(() => {
- os.type = osType
- os.release = osRelease
- // needs to put process back in its place in order for tap to exit properly
- process = _process
- Date.prototype.toISOString = _toISOString
- console.error = consoleError
-})
-t.afterEach(() => {
- errors.length = 0
- npm.log.level = 'silent'
- // clear out the 'A complete log' message
- npm.log.record.length = 0
- delete process.exitCode
-})
+ t.teardown(() => {
+ delete process.exitCode
+ process.removeAllListeners('exit')
+ })
-const mocks = {
- '../../../lib/utils/error-message.js': (err) => ({
- ...err,
- summary: [['ERR', err.message]],
- detail: [['ERR', err.message]],
- }),
+ return {
+ ...rest,
+ errors,
+ npm,
+ // // Make it async to make testing ergonomics a little
+ // // easier so we dont need to t.plan() every test to
+ // // make sure we get process.exit called
+ exitHandler: (...args) => new Promise(resolve => {
+ process.once('exit', resolve)
+ exitHandler(...args)
+ }),
+ }
}
-const exitHandler = t.mock('../../../lib/utils/exit-handler.js', mocks)
-exitHandler.setNpm(npm)
-
-t.test('exit handler never called - loglevel silent', (t) => {
- npm.log.level = 'silent'
- process.emit('exit', 1)
- const logData = fs.readFileSync(logFile, 'utf8')
- t.match(logData, 'Exit handler never called!')
- t.match(errors, [''], 'logs one empty string to console.error')
- t.end()
-})
+// Create errors with properties to be used in tests
+const err = (message = '', options = {}, noStack = false) => {
+ const e = Object.assign(
+ new Error(message),
+ typeof options !== 'object' ? { code: options } : options
+ )
+ e.stack = options.stack || `Error: ${message}`
+ if (noStack) {
+ delete e.stack
+ }
+ return e
+}
-t.test('exit handler never called - loglevel notice', (t) => {
- npm.log.level = 'notice'
- process.emit('exit', 1)
- const logData = fs.readFileSync(logFile, 'utf8')
- t.match(logData, 'Exit handler never called!')
- t.match(errors, ['', ''], 'logs two empty strings to console.error')
- t.end()
-})
+t.test('handles unknown error with logs and debug file', async (t) => {
+ const { exitHandler, debugFile, logs } = await mockExitHandler(t)
-t.test('handles unknown error', (t) => {
- t.plan(2)
+ await exitHandler(err('Unknown error', 'ECODE'))
- npm.log.level = 'notice'
+ const debugContent = await debugFile()
- process.once('timeEnd', (msg) => {
- t.equal(msg, 'npm', 'should trigger timeEnd for npm')
+ t.equal(process.exitCode, 1)
+ logs.forEach((logItem, i) => {
+ const logLines = format(i, ...logItem).trim().split(os.EOL)
+ logLines.forEach((line) => {
+ t.match(debugContent.trim(), line, 'log appears in debug file')
+ })
})
- exitHandler(err)
- const logData = fs.readFileSync(logFile, 'utf8')
- t.matchSnapshot(
- logData,
- 'should have expected log contents for unknown error'
- )
- t.end()
+ const lastLog = debugContent
+ .split('\n')
+ .reduce((__, l) => parseInt(l.match(/^(\d+)\s/)[1]))
+ t.equal(logs.length, lastLog + 1)
+ t.match(logs.error, [
+ ['code', 'ECODE'],
+ ['ERR SUMMARY', 'Unknown error'],
+ ['ERR DETAIL', 'Unknown error'],
+ ])
+ t.match(debugContent, /\d+ error code ECODE/)
+ t.match(debugContent, /\d+ error ERR SUMMARY Unknown error/)
+ t.match(debugContent, /\d+ error ERR DETAIL Unknown error/)
+ t.matchSnapshot(logs, 'logs')
+ t.matchSnapshot(debugContent, 'debug file contents')
})
-t.test('fail to write logfile', (t) => {
- t.plan(1)
-
- t.teardown(() => {
- npm.config.set('cache', cacheFolder)
+t.test('exit handler never called - loglevel silent', async (t) => {
+ const { logs, errors } = await mockExitHandler(t, {
+ config: { loglevel: 'silent' },
})
+ process.emit('exit', 1)
+ t.match(logs.error, [
+ ['', /Exit handler never called/],
+ ['', /error with npm itself/],
+ ])
+ t.strictSame(errors, [''], 'logs one empty string to console.error')
+})
- const badDir = t.testdir({
- _logs: 'is a file',
- })
+t.test('exit handler never called - loglevel notice', async (t) => {
+ const { logs, errors } = await mockExitHandler(t)
+ process.emit('exit', 1)
+ t.equal(process.exitCode, 1)
+ t.match(logs.error, [
+ ['', /Exit handler never called/],
+ ['', /error with npm itself/],
+ ])
+ t.strictSame(errors, ['', ''], 'logs two empty strings to console.error')
+})
+
+t.test('exit handler never called - no npm', async (t) => {
+ const { logs, errors } = await mockExitHandler(t, { init: false })
+ process.emit('exit', 1)
+ t.equal(process.exitCode, 1)
+ t.match(logs.error, [
+ ['', /Exit handler never called/],
+ ['', /error with npm itself/],
+ ])
+ t.strictSame(errors, [''], 'logs one empty string to console.error')
+})
- npm.config.set('cache', badDir)
+t.test('exit handler called - no npm', async (t) => {
+ const { exitHandler, errors } = await mockExitHandler(t, { init: false })
+ await exitHandler()
+ t.equal(process.exitCode, 1)
+ t.match(errors, [/Error: Exit prior to setting npm in exit handler/])
+})
- t.doesNotThrow(
- () => exitHandler(err),
- 'should not throw on cache write failure'
- )
+t.test('exit handler called - no npm with error', async (t) => {
+ const { exitHandler, errors } = await mockExitHandler(t, { init: false })
+ await exitHandler(err('something happened'))
+ t.equal(process.exitCode, 1)
+ t.match(errors, [/Error: something happened/])
})
-t.test('console.log output using --json', (t) => {
- t.plan(1)
+t.test('exit handler called - no npm with error without stack', async (t) => {
+ const { exitHandler, errors } = await mockExitHandler(t, { init: false })
+ await exitHandler(err('something happened', {}, true))
+ t.equal(process.exitCode, 1)
+ t.match(errors, [/something happened/])
+})
- npm.config.set('json', true)
- t.teardown(() => {
- npm.config.set('json', false)
+t.test('console.log output using --json', async (t) => {
+ const { exitHandler, errors } = await mockExitHandler(t, {
+ config: {
+ json: true,
+ },
})
- exitHandler(new Error('Error: EBADTHING Something happened'))
+ await exitHandler(err('Error: EBADTHING Something happened'))
+
+ t.equal(process.exitCode, 1)
t.same(
JSON.parse(errors[0]),
{
@@ -185,213 +221,223 @@ t.test('console.log output using --json', (t) => {
)
})
-t.test('throw a non-error obj', (t) => {
- t.plan(2)
+t.test('throw a non-error obj', async (t) => {
+ const { exitHandler, logs } = await mockExitHandler(t)
- const weirdError = {
+ await exitHandler({
code: 'ESOMETHING',
message: 'foo bar',
- }
-
- process.once('exit', code => {
- t.equal(code, 1, 'exits with exitCode 1')
})
- exitHandler(weirdError)
- t.match(
- npm.log.record.find(r => r.level === 'error'),
- { message: 'foo bar' }
- )
+
+ t.equal(process.exitCode, 1)
+ t.match(logs.error, [
+ ['weird error', { code: 'ESOMETHING', message: 'foo bar' }],
+ ])
})
-t.test('throw a string error', (t) => {
- t.plan(2)
- const error = 'foo bar'
+t.test('throw a string error', async (t) => {
+ const { exitHandler, logs } = await mockExitHandler(t)
- process.once('exit', code => {
- t.equal(code, 1, 'exits with exitCode 1')
- })
- exitHandler(error)
- t.match(
- npm.log.record.find(r => r.level === 'error'),
- { message: 'foo bar' }
- )
+ await exitHandler('foo bar')
+
+ t.equal(process.exitCode, 1)
+ t.match(logs.error, [
+ ['', 'foo bar'],
+ ])
})
-t.test('update notification', (t) => {
- const updateMsg = 'you should update npm!'
- npm.updateNotification = updateMsg
- npm.log.level = 'silent'
+t.test('update notification', async (t) => {
+ const { exitHandler, logs, npm } = await mockExitHandler(t)
+ npm.updateNotification = 'you should update npm!'
- t.teardown(() => {
- delete npm.updateNotification
- })
+ await exitHandler()
- exitHandler()
- t.match(
- npm.log.record.find(r => r.level === 'notice'),
- { message: 'you should update npm!' }
- )
- t.end()
+ t.match(logs.notice, [
+ ['', 'you should update npm!'],
+ ])
})
-t.test('npm.config not ready', (t) => {
- t.plan(1)
+t.test('npm.config not ready', async (t) => {
+ const { exitHandler, logs, errors } = await mockExitHandler(t, {
+ load: false,
+ })
- const { Npm: Unloaded } = mockNpm(t)
- const unloaded = new Unloaded()
+ await exitHandler()
- t.teardown(() => {
- exitHandler.setNpm(npm)
+ t.equal(process.exitCode, 1)
+ t.match(errors, [
+ /Error: Exit prior to config file resolving./,
+ ], 'should exit with config error msg')
+ t.match(logs.verbose, [
+ ['stack', /Error: Exit prior to config file resolving./],
+ ], 'should exit with config error msg')
+})
+
+t.test('timing with no error', async (t) => {
+ const { exitHandler, timingFile, npm, logs } = await mockExitHandler(t, {
+ config: {
+ timing: true,
+ },
})
- exitHandler.setNpm(unloaded)
+ await exitHandler()
+ const timingFileData = await timingFile()
+
+ t.equal(process.exitCode, 0)
+
+ t.match(logs.error, [
+ ['', /A complete log of this run can be found in:[\s\S]*-debug-\d\.log/],
+ ])
- exitHandler()
t.match(
- errors[0],
- /Error: Exit prior to config file resolving./,
- 'should exit with config error msg'
+ timingFileData,
+ Object.keys(npm.finishedTimers).reduce((acc, k) => {
+ acc[k] = Number
+ return acc
+ }, {})
)
- t.end()
+ t.strictSame(npm.unfinishedTimers, new Map())
+ t.match(timingFileData, {
+ command: [],
+ version: '1.0.0',
+ npm: Number,
+ logfile: String,
+ logfiles: [String],
+ })
})
-t.test('timing', (t) => {
- npm.config.set('timing', true)
-
- t.teardown(() => {
- fs.unlinkSync(timingFile)
- npm.config.set('timing', false)
+t.test('unfinished timers', async (t) => {
+ const { exitHandler, timingFile, npm } = await mockExitHandler(t, {
+ config: {
+ timing: true,
+ },
})
- exitHandler()
- const timingData = JSON.parse(fs.readFileSync(timingFile, 'utf8'))
- t.match(timingData, { version: '1.0.0', 'config:load:defaults': Number })
- t.end()
-})
+ process.emit('time', 'foo')
+ process.emit('time', 'bar')
-t.test('timing - with error', (t) => {
- npm.config.set('timing', true)
+ await exitHandler()
+ const timingFileData = await timingFile()
- t.teardown(() => {
- fs.unlinkSync(timingFile)
- npm.config.set('timing', false)
+ t.equal(process.exitCode, 0)
+ t.match(npm.unfinishedTimers, new Map([['foo', Number], ['bar', Number]]))
+ t.match(timingFileData, {
+ command: [],
+ version: '1.0.0',
+ npm: Number,
+ logfile: String,
+ logfiles: [String],
+ unfinished: {
+ foo: [Number, Number],
+ bar: [Number, Number],
+ },
})
-
- exitHandler(err)
- const timingData = JSON.parse(fs.readFileSync(timingFile, 'utf8'))
- t.match(timingData, { version: '1.0.0', 'config:load:defaults': Number })
- t.end()
})
-t.test('uses code from errno', (t) => {
- t.plan(1)
+t.test('uses code from errno', async (t) => {
+ const { exitHandler, logs } = await mockExitHandler(t)
- process.once('exit', code => {
- t.equal(code, 127, 'should set exitCode from errno')
- })
- exitHandler(Object.assign(
- new Error('Error with errno'),
- {
- errno: 127,
- }
- ))
+ await exitHandler(err('Error with errno', { errno: 127 }))
+ t.equal(process.exitCode, 127)
+ t.match(logs.error, [['errno', 127]])
})
-t.test('uses code from number', (t) => {
- t.plan(1)
+t.test('uses code from number', async (t) => {
+ const { exitHandler, logs } = await mockExitHandler(t)
- process.once('exit', code => {
- t.equal(code, 404, 'should set exitCode from a number')
- })
- exitHandler(Object.assign(
- new Error('Error with code type number'),
- {
- code: 404,
- }
- ))
+ await exitHandler(err('Error with code type number', 404))
+ t.equal(process.exitCode, 404)
+ t.match(logs.error, [['code', 404]])
})
-t.test('call exitHandler with no error', (t) => {
- t.plan(1)
- process.once('exit', code => {
- t.equal(code, 0, 'should end up with exitCode 0 (default)')
- })
- exitHandler()
+t.test('uses all err special properties', async t => {
+ const { exitHandler, logs } = await mockExitHandler(t)
+
+ const keys = ['code', 'syscall', 'file', 'path', 'dest', 'errno']
+ const properties = keys.reduce((acc, k) => {
+ acc[k] = `${k}-hey`
+ return acc
+ }, {})
+
+ await exitHandler(err('Error with code type number', properties))
+ t.equal(process.exitCode, 1)
+ t.match(logs.error, keys.map((k) => [k, `${k}-hey`]), 'all special keys get logged')
})
-t.test('defaults to log error msg if stack is missing', (t) => {
- const { Npm: Unloaded } = mockNpm(t)
- const unloaded = new Unloaded()
+t.test('verbose logs replace info on err props', async t => {
+ const { exitHandler, logs } = await mockExitHandler(t)
- t.teardown(() => {
- exitHandler.setNpm(npm)
- })
+ const keys = ['type', 'stack', 'statusCode', 'pkgid']
+ const properties = keys.reduce((acc, k) => {
+ acc[k] = `${k}-https://user:pass@registry.npmjs.org/`
+ return acc
+ }, {})
- exitHandler.setNpm(unloaded)
- const noStackErr = Object.assign(
- new Error('Error with no stack'),
- {
- code: 'ENOSTACK',
- errno: 127,
- }
+ await exitHandler(err('Error with code type number', properties))
+ t.equal(process.exitCode, 1)
+ t.match(
+ logs.verbose.filter(([p]) => p !== 'logfile'),
+ keys.map((k) => [k, `${k}-https://user:***@registry.npmjs.org/`]),
+ 'all special keys get replaced'
)
- delete noStackErr.stack
+})
- exitHandler(noStackErr)
- t.equal(errors[0], 'Error with no stack', 'should use error msg')
- t.end()
+t.test('call exitHandler with no error', async (t) => {
+ const { exitHandler, logs } = await mockExitHandler(t)
+
+ await exitHandler()
+
+ t.equal(process.exitCode, 0)
+ t.match(logs.error, [])
+})
+
+t.test('defaults to log error msg if stack is missing when unloaded', async (t) => {
+ const { exitHandler, logs, errors } = await mockExitHandler(t, { load: false })
+
+ await exitHandler(err('Error with no stack', { code: 'ENOSTACK', errno: 127 }, true))
+ t.equal(process.exitCode, 127)
+ t.same(errors, ['Error with no stack'], 'should use error msg')
+ t.match(logs.error, [
+ ['code', 'ENOSTACK'],
+ ['errno', 127],
+ ])
})
-t.test('exits uncleanly when only emitting exit event', (t) => {
- t.plan(2)
+t.test('exits uncleanly when only emitting exit event', async (t) => {
+ const { logs } = await mockExitHandler(t)
- npm.log.level = 'silent'
process.emit('exit')
- const logData = fs.readFileSync(logFile, 'utf8')
- t.match(logData, 'Exit handler never called!')
- t.match(process.exitCode, 1, 'exitCode coerced to 1')
+
+ t.match(logs.error, [['', 'Exit handler never called!']])
+ t.equal(process.exitCode, 1, 'exitCode coerced to 1')
t.end()
})
-t.test('do no fancy handling for shellouts', t => {
- const { command } = npm
- const LOG_RECORD = []
- npm.command = 'exec'
+t.test('do no fancy handling for shellouts', async t => {
+ const { exitHandler, npm, logs } = await mockExitHandler(t)
- t.teardown(() => {
- npm.command = command
- })
- t.beforeEach(() => LOG_RECORD.length = 0)
+ npm.command = 'exec'
- const loudNoises = () => npm.log.record
- .filter(({ level }) => ['warn', 'error'].includes(level))
+ const loudNoises = () =>
+ logs.filter(([level]) => ['warn', 'error'].includes(level))
- t.test('shellout with a numeric error code', t => {
- t.plan(2)
- process.once('exit', code => {
- t.equal(code, 5, 'got expected exit code')
- })
- exitHandler(Object.assign(new Error(), { code: 5 }))
+ t.test('shellout with a numeric error code', async t => {
+ await exitHandler(err('', 5))
+ t.equal(process.exitCode, 5, 'got expected exit code')
t.strictSame(loudNoises(), [], 'no noisy warnings')
})
- t.test('shellout without a numeric error code (something in npm)', t => {
- t.plan(2)
- process.once('exit', code => {
- t.equal(code, 1, 'got expected exit code')
- })
- exitHandler(Object.assign(new Error(), { code: 'banana stand' }))
+ t.test('shellout without a numeric error code (something in npm)', async t => {
+ await exitHandler(err('', 'banana stand'))
+ t.equal(process.exitCode, 1, 'got expected exit code')
// should log some warnings and errors, because something weird happened
t.strictNotSame(loudNoises(), [], 'bring the noise')
t.end()
})
- t.test('shellout with code=0 (extra weird?)', t => {
- t.plan(2)
- process.once('exit', code => {
- t.equal(code, 1, 'got expected exit code')
- })
- exitHandler(Object.assign(new Error(), { code: 0 }))
+ t.test('shellout with code=0 (extra weird?)', async t => {
+ await exitHandler(Object.assign(new Error(), { code: 0 }))
+ t.equal(process.exitCode, 1, 'got expected exit code')
t.strictNotSame(loudNoises(), [], 'bring the noise')
})
diff --git a/test/lib/utils/is-windows-bash.js b/test/lib/utils/is-windows-bash.js
index 94fde0ace..0fbebdf8e 100644
--- a/test/lib/utils/is-windows-bash.js
+++ b/test/lib/utils/is-windows-bash.js
@@ -1,4 +1,5 @@
const t = require('tap')
+const mockGlobal = require('../../fixtures/mock-globals.js')
const isWindowsBash = () => {
delete require.cache[require.resolve('../../../lib/utils/is-windows-bash.js')]
@@ -6,23 +7,24 @@ const isWindowsBash = () => {
return require('../../../lib/utils/is-windows-bash.js')
}
-Object.defineProperty(process, 'platform', {
- value: 'posix',
- configurable: true,
-})
-t.equal(isWindowsBash(), false, 'false when not windows')
+t.test('posix', (t) => {
+ mockGlobal(t, { 'process.platform': 'posix' })
+ t.equal(isWindowsBash(), false, 'false when not windows')
-Object.defineProperty(process, 'platform', {
- value: 'win32',
- configurable: true,
+ t.end()
})
-process.env.MSYSTEM = 'not ming'
-process.env.TERM = 'dumb'
-t.equal(isWindowsBash(), false, 'false when not mingw or cygwin')
-process.env.TERM = 'cygwin'
-t.equal(isWindowsBash(), true, 'true when cygwin')
+t.test('win32', (t) => {
+ mockGlobal(t, { 'process.platform': 'win32' })
+
+ mockGlobal(t, { 'process.env': { TERM: 'dumb', MSYSTEM: undefined } })
+ t.equal(isWindowsBash(), false, 'false when not mingw or cygwin')
+
+ mockGlobal(t, { 'process.env.TERM': 'cygwin' })
+ t.equal(isWindowsBash(), true, 'true when cygwin')
-process.env.MSYSTEM = 'MINGW64'
-process.env.TERM = 'dumb'
-t.equal(isWindowsBash(), true, 'true when mingw')
+ mockGlobal(t, { 'process.env': { TERM: 'dumb', MSYSTEM: 'MINGW64' } })
+ t.equal(isWindowsBash(), true, 'true when mingw')
+
+ t.end()
+})
diff --git a/test/lib/utils/log-file.js b/test/lib/utils/log-file.js
new file mode 100644
index 000000000..adc1a2e03
--- /dev/null
+++ b/test/lib/utils/log-file.js
@@ -0,0 +1,333 @@
+const t = require('tap')
+const _fs = require('fs')
+const fs = _fs.promises
+const path = require('path')
+const os = require('os')
+const fsMiniPass = require('fs-minipass')
+const rimraf = require('rimraf')
+const LogFile = require('../../../lib/utils/log-file.js')
+const { cleanCwd } = require('../../fixtures/clean-snapshot')
+
+t.cleanSnapshot = (path) => cleanCwd(path)
+
+const last = arr => arr[arr.length - 1]
+const range = (n) => Array.from(Array(n).keys())
+const makeOldLogs = (count) => {
+ const d = new Date()
+ d.setHours(-1)
+ d.setSeconds(0)
+ return range(count / 2).reduce((acc, i) => {
+ const cloneDate = new Date(d.getTime())
+ cloneDate.setSeconds(i)
+ acc[LogFile.fileName(LogFile.logId(cloneDate), 0)] = 'hello'
+ acc[LogFile.fileName(LogFile.logId(cloneDate), 1)] = 'hello'
+ return acc
+ }, {})
+}
+
+const cleanErr = (message) => {
+ const err = new Error(message)
+ const stack = err.stack.split('\n')
+ err.stack = stack[0] + '\n' + range(10)
+ .map((__, i) => stack[1].replace(/^(\s+at\s).*/, `$1stack trace line ${i}`))
+ .join('\n')
+ return err
+}
+
+const loadLogFile = async (t, { buffer = [], mocks, testdir = {}, ...options } = {}) => {
+ const root = t.testdir(testdir)
+ const MockLogFile = t.mock('../../../lib/utils/log-file.js', mocks)
+ const logFile = new MockLogFile(Object.keys(options).length ? options : undefined)
+ buffer.forEach((b) => logFile.log(...b))
+ await logFile.load({ dir: root, ...options })
+ t.teardown(() => logFile.off())
+ return {
+ root,
+ logFile,
+ LogFile,
+ readLogs: async () => {
+ const logDir = await fs.readdir(root)
+ const logFiles = logDir.map((f) => path.join(root, f))
+ .filter((f) => _fs.existsSync(f))
+ return Promise.all(logFiles.map(async (f) => {
+ const content = await fs.readFile(f, 'utf8')
+ const rawLogs = content.split(os.EOL)
+ return {
+ filename: f,
+ content,
+ rawLogs,
+ logs: rawLogs.filter(Boolean),
+ }
+ }))
+ },
+ }
+}
+
+t.test('init', async t => {
+ const maxLogsPerFile = 10
+ const { root, logFile, readLogs } = await loadLogFile(t, {
+ maxLogsPerFile,
+ maxFilesPerProcess: 20,
+ buffer: [['error', 'buffered']],
+ })
+
+ for (const i of range(50)) {
+ logFile.log('error', `log ${i}`)
+ }
+
+ // Ignored
+ logFile.log('pause')
+ logFile.log('resume')
+ logFile.log('pause')
+
+ for (const i of range(50)) {
+ logFile.log('verb', `log ${i}`)
+ }
+
+ logFile.off()
+ logFile.log('error', 'ignored')
+
+ const logs = await readLogs()
+ t.equal(logs.length, 11, 'total log files')
+ t.ok(logs.slice(0, 10).every(f => f.logs.length === maxLogsPerFile), 'max logs per file')
+ t.ok(last(logs).logs.length, 1, 'last file has remaining logs')
+ t.ok(logs.every(f => last(f.rawLogs) === ''), 'all logs end with newline')
+ t.strictSame(
+ logFile.files,
+ logs.map((l) => path.resolve(root, l.filename))
+ )
+})
+
+t.test('max files per process', async t => {
+ const maxLogsPerFile = 10
+ const maxFilesPerProcess = 5
+ const { logFile, readLogs } = await loadLogFile(t, {
+ maxLogsPerFile,
+ maxFilesPerProcess,
+ })
+
+ for (const i of range(maxLogsPerFile * maxFilesPerProcess)) {
+ logFile.log('error', `log ${i}`)
+ }
+
+ for (const i of range(5)) {
+ logFile.log('verbose', `log ${i}`)
+ }
+
+ const logs = await readLogs()
+ t.equal(logs.length, maxFilesPerProcess, 'total log files')
+ t.equal(last(last(logs).logs), '49 error log 49')
+})
+
+t.test('stream error', async t => {
+ let times = 0
+ const { logFile, readLogs } = await loadLogFile(t, {
+ maxLogsPerFile: 1,
+ maxFilesPerProcess: 99,
+ mocks: {
+ 'fs-minipass': {
+ WriteStreamSync: class {
+ constructor (...args) {
+ if (times >= 5) {
+ throw new Error('bad stream')
+ }
+ times++
+ return new fsMiniPass.WriteStreamSync(...args)
+ }
+ },
+ },
+ },
+ })
+
+ for (const i of range(10)) {
+ logFile.log('verbose', `log ${i}`)
+ }
+
+ const logs = await readLogs()
+ t.equal(logs.length, 5, 'total log files')
+})
+
+t.test('initial stream error', async t => {
+ const { logFile, readLogs } = await loadLogFile(t, {
+ mocks: {
+ 'fs-minipass': {
+ WriteStreamSync: class {
+ constructor (...args) {
+ throw new Error('no stream')
+ }
+ },
+ },
+ },
+ })
+
+ for (const i of range(10)) {
+ logFile.log('verbose', `log ${i}`)
+ }
+
+ const logs = await readLogs()
+ t.equal(logs.length, 0, 'total log files')
+})
+
+t.test('turns off', async t => {
+ const { logFile, readLogs } = await loadLogFile(t)
+
+ logFile.log('error', 'test')
+ logFile.off()
+ logFile.log('error', 'test2')
+ logFile.load()
+
+ const logs = await readLogs()
+ t.equal(logs.length, 1)
+ t.equal(logs[0].logs[0], '0 error test')
+})
+
+t.test('cleans logs', async t => {
+ const logsMax = 5
+ const { readLogs } = await loadLogFile(t, {
+ logsMax,
+ testdir: makeOldLogs(10),
+ })
+
+ const logs = await readLogs()
+ t.equal(logs.length, logsMax + 1)
+})
+
+t.test('doesnt clean current log by default', async t => {
+ const logsMax = 0
+ const { readLogs, logFile } = await loadLogFile(t, {
+ logsMax,
+ testdir: makeOldLogs(10),
+ })
+
+ logFile.log('error', 'test')
+
+ const logs = await readLogs()
+ t.equal(logs.length, 1)
+ t.match(last(logs).content, /\d+ error test/)
+})
+
+t.test('negative logs max', async t => {
+ const logsMax = -10
+ const { readLogs, logFile } = await loadLogFile(t, {
+ logsMax,
+ testdir: makeOldLogs(10),
+ })
+
+ logFile.log('error', 'test')
+
+ const logs = await readLogs()
+ t.equal(logs.length, 1)
+ t.match(last(logs).content, /\d+ error test/)
+})
+
+t.test('doesnt need to clean', async t => {
+ const logsMax = 20
+ const oldLogs = 10
+ const { readLogs } = await loadLogFile(t, {
+ logsMax,
+ testdir: makeOldLogs(oldLogs),
+ })
+
+ const logs = await readLogs()
+ t.equal(logs.length, oldLogs + 1)
+})
+
+t.test('glob error', async t => {
+ const { readLogs } = await loadLogFile(t, {
+ logsMax: 5,
+ mocks: {
+ glob: () => {
+ throw new Error('bad glob')
+ },
+ },
+ })
+
+ const logs = await readLogs()
+ t.equal(logs.length, 1)
+ t.match(last(logs).content, /error cleaning log files .* bad glob/)
+})
+
+t.test('rimraf error', async t => {
+ const logsMax = 5
+ const oldLogs = 10
+ let count = 0
+ const { readLogs } = await loadLogFile(t, {
+ logsMax,
+ testdir: makeOldLogs(oldLogs),
+ mocks: {
+ rimraf: (...args) => {
+ if (count >= 3) {
+ throw new Error('bad rimraf')
+ }
+ count++
+ return rimraf(...args)
+ },
+ },
+ })
+
+ const logs = await readLogs()
+ t.equal(logs.length, oldLogs - 3 + 1)
+ t.match(last(logs).content, /error removing log file .* bad rimraf/)
+})
+
+t.test('delete log file while open', async t => {
+ const { logFile, root, readLogs } = await loadLogFile(t)
+
+ logFile.log('error', '', 'log 1')
+ const [log] = await readLogs(true)
+ t.match(log.content, /\d+ error log 1/)
+
+ await fs.unlink(path.resolve(root, log.filename))
+
+ logFile.log('error', '', 'log 2')
+ const logs = await readLogs()
+
+ // XXX: do some retry logic after error?
+ t.strictSame(logs, [], 'logs arent written after error')
+})
+
+t.test('snapshot', async t => {
+ const { logFile, readLogs } = await loadLogFile(t)
+
+ logFile.log('error', '', 'no prefix')
+ logFile.log('error', 'prefix', 'with prefix')
+ logFile.log('error', 'prefix', 1, 2, 3)
+
+ const nestedObj = { obj: { with: { many: { props: 1 } } } }
+ logFile.log('verbose', '', nestedObj)
+ logFile.log('verbose', '', JSON.stringify(nestedObj))
+ logFile.log('verbose', '', JSON.stringify(nestedObj, null, 2))
+
+ const arr = ['test', 'with', 'an', 'array']
+ logFile.log('verbose', '', arr)
+ logFile.log('verbose', '', JSON.stringify(arr))
+ logFile.log('verbose', '', JSON.stringify(arr, null, 2))
+
+ const nestedArr = ['test', ['with', ['an', ['array']]]]
+ logFile.log('verbose', '', nestedArr)
+ logFile.log('verbose', '', JSON.stringify(nestedArr))
+ logFile.log('verbose', '', JSON.stringify(nestedArr, null, 2))
+
+ // XXX: multiple errors are hard to parse visually
+ // the second error should start on a newline
+ logFile.log(...[
+ 'error',
+ 'pre',
+ 'has',
+ 'many',
+ 'errors',
+ cleanErr('message'),
+ cleanErr('message2'),
+ ])
+
+ const err = new Error('message')
+ delete err.stack
+ logFile.log(...[
+ 'error',
+ 'nostack',
+ err,
+ ])
+
+ const logs = await readLogs()
+ t.matchSnapshot(logs.map(l => l.content).join('\n'))
+})
diff --git a/test/lib/utils/log-shim.js b/test/lib/utils/log-shim.js
new file mode 100644
index 000000000..dee4efbaa
--- /dev/null
+++ b/test/lib/utils/log-shim.js
@@ -0,0 +1,100 @@
+const t = require('tap')
+
+const makeShim = (mocks) => t.mock('../../../lib/utils/log-shim.js', mocks)
+
+const loggers = [
+ 'notice',
+ 'error',
+ 'warn',
+ 'info',
+ 'verbose',
+ 'http',
+ 'silly',
+ 'pause',
+ 'resume',
+]
+
+t.test('has properties', (t) => {
+ const shim = makeShim()
+
+ t.match(shim, {
+ level: String,
+ levels: {},
+ gauge: {},
+ stream: {},
+ heading: undefined,
+ enableColor: Function,
+ disableColor: Function,
+ enableUnicode: Function,
+ disableUnicode: Function,
+ enableProgress: Function,
+ disableProgress: Function,
+ ...loggers.reduce((acc, l) => {
+ acc[l] = Function
+ return acc
+ }, {}),
+ })
+
+ t.match(Object.keys(shim).sort(), [
+ 'level',
+ 'heading',
+ 'levels',
+ 'gauge',
+ 'stream',
+ 'tracker',
+ 'useColor',
+ 'enableColor',
+ 'disableColor',
+ 'enableUnicode',
+ 'disableUnicode',
+ 'enableProgress',
+ 'disableProgress',
+ 'progressEnabled',
+ 'clearProgress',
+ 'showProgress',
+ 'newItem',
+ 'newGroup',
+ ...loggers,
+ ].sort())
+
+ t.end()
+})
+
+t.test('works with npmlog/proclog proxy', t => {
+ const procLog = { silly: () => 'SILLY' }
+ const npmlog = { level: 'woo', enableColor: () => true }
+ const shim = makeShim({ npmlog, 'proc-log': procLog })
+
+ t.equal(shim.level, 'woo', 'can get a property')
+
+ npmlog.level = 'hey'
+ t.strictSame(
+ [shim.level, npmlog.level],
+ ['hey', 'hey'],
+ 'can get a property after update on npmlog'
+ )
+
+ shim.level = 'test'
+ t.strictSame(
+ [shim.level, npmlog.level],
+ ['test', 'test'],
+ 'can get a property after update on shim'
+ )
+
+ t.ok(shim.enableColor(), 'can call method on shim to call npmlog')
+ t.equal(shim.silly(), 'SILLY', 'can call method on proclog')
+ t.notOk(shim.LEVELS, 'only includes levels from npmlog')
+ t.throws(() => shim.gauge = 100, 'cant set getters properies')
+
+ t.end()
+})
+
+t.test('works with npmlog/proclog proxy', t => {
+ const shim = makeShim()
+
+ loggers.forEach((k) => {
+ t.doesNotThrow(() => shim[k]('test'))
+ })
+
+ t.end()
+})
diff --git a/test/lib/utils/npm-usage.js b/test/lib/utils/npm-usage.js
index 77254a80d..035d4bbb2 100644
--- a/test/lib/utils/npm-usage.js
+++ b/test/lib/utils/npm-usage.js
@@ -1,10 +1,8 @@
const t = require('tap')
-const { real: mockNpm } = require('../../fixtures/mock-npm.js')
-const { Npm } = mockNpm(t)
-const npm = new Npm()
+const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
t.test('usage', async t => {
- await npm.load()
+ const { npm } = await loadMockNpm(t)
t.afterEach(() => {
npm.config.set('viewer', null)
npm.config.set('long', false)
diff --git a/test/lib/utils/proc-log-listener.js b/test/lib/utils/proc-log-listener.js
deleted file mode 100644
index d580defa8..000000000
--- a/test/lib/utils/proc-log-listener.js
+++ /dev/null
@@ -1,41 +0,0 @@
-const t = require('tap')
-const { inspect } = require('util')
-
-const logs = []
-const npmlog = {
- warn: (...args) => logs.push(['warn', ...args]),
- verbose: (...args) => logs.push(['verbose', ...args]),
-}
-
-t.mock('../../../lib/utils/proc-log-listener.js', {
- npmlog,
-})()
-
-process.emit('log', 'warn', 'hello', 'i am a warning')
-t.strictSame(logs, [['warn', 'hello', 'i am a warning']])
-logs.length = 0
-
-const nopeError = new Error('nope')
-npmlog.warn = () => {
- throw nopeError
-}
-
-process.emit('log', 'warn', 'fail')
-t.strictSame(logs, [[
- 'verbose',
- `attempt to log ${inspect(['warn', 'fail'])} crashed`,
- nopeError,
-]])
-logs.length = 0
-
-npmlog.verbose = () => {
- throw nopeError
-}
-const consoleErrors = []
-console.error = (...args) => consoleErrors.push(args)
-process.emit('log', 'warn', 'fail2')
-t.strictSame(logs, [])
-t.strictSame(consoleErrors, [[
- `attempt to log ${inspect(['warn', 'fail2'])} crashed`,
- nopeError,
-]])
diff --git a/test/lib/utils/pulse-till-done.js b/test/lib/utils/pulse-till-done.js
index acbf66396..9f7a94614 100644
--- a/test/lib/utils/pulse-till-done.js
+++ b/test/lib/utils/pulse-till-done.js
@@ -1,18 +1,17 @@
const t = require('tap')
let pulseStarted = null
-const npmlog = {
- gauge: {
- pulse: () => {
- if (pulseStarted) {
- pulseStarted()
- }
- },
- },
-}
const pulseTillDone = t.mock('../../../lib/utils/pulse-till-done.js', {
- npmlog,
+ npmlog: {
+ gauge: {
+ pulse: () => {
+ if (pulseStarted) {
+ pulseStarted()
+ }
+ },
+ },
+ },
})
t.test('pulses (with promise)', async (t) => {
diff --git a/test/lib/utils/read-user-info.js b/test/lib/utils/read-user-info.js
index 35101f1d7..be805a2a8 100644
--- a/test/lib/utils/read-user-info.js
+++ b/test/lib/utils/read-user-info.js
@@ -7,11 +7,6 @@ const read = (opts, cb) => {
return cb(null, readResult)
}
-const npmlog = {
- clearProgress: () => {},
- showProgress: () => {},
-}
-
const npmUserValidate = {
username: (username) => {
if (username === 'invalid') {
@@ -29,12 +24,23 @@ const npmUserValidate = {
},
}
+let logMsg = null
const readUserInfo = t.mock('../../../lib/utils/read-user-info.js', {
read,
- npmlog,
+ npmlog: {
+ clearProgress: () => {},
+ showProgress: () => {},
+ },
+ 'proc-log': {
+ warn: (msg) => logMsg = msg,
+ },
'npm-user-validate': npmUserValidate,
})
+t.beforeEach(() => {
+ logMsg = null
+})
+
t.test('otp', async (t) => {
readResult = '1234'
t.teardown(() => {
@@ -75,11 +81,7 @@ t.test('username - invalid warns and retries', async (t) => {
readOpts = null
})
- let logMsg
- const log = {
- warn: (msg) => logMsg = msg,
- }
- const pResult = readUserInfo.username(null, null, { log })
+ const pResult = readUserInfo.username(null, null)
// have to swap it to a valid username after execution starts
// or it will loop forever
readResult = 'valid'
@@ -105,11 +107,7 @@ t.test('email - invalid warns and retries', async (t) => {
readOpts = null
})
- let logMsg
- const log = {
- warn: (msg) => logMsg = msg,
- }
- const pResult = readUserInfo.email(null, null, { log })
+ const pResult = readUserInfo.email(null, null)
readResult = 'foo@bar.baz'
const result = await pResult
t.equal(result, 'foo@bar.baz', 'received the email')
diff --git a/test/lib/utils/reify-output.js b/test/lib/utils/reify-output.js
index 9a1bffb40..4e9ed7133 100644
--- a/test/lib/utils/reify-output.js
+++ b/test/lib/utils/reify-output.js
@@ -1,7 +1,9 @@
const t = require('tap')
+const log = require('../../../lib/utils/log-shim')
-const log = require('npmlog')
-log.level = 'warn'
+const _level = log.level
+t.beforeEach(() => log.level = 'warn')
+t.teardown(() => log.level = _level)
t.cleanSnapshot = str => str.replace(/in [0-9]+m?s/g, 'in {TIME}')
@@ -237,7 +239,6 @@ t.test('showing and not showing audit report', async t => {
npm.output = out => {
t.fail('should not get output when silent', { actual: out })
}
- t.teardown(() => log.level = 'warn')
log.level = 'silent'
reifyOutput(npm, {
actualTree: { inventory: { size: 999 }, children: [] },
diff --git a/test/lib/utils/setup-log.js b/test/lib/utils/setup-log.js
deleted file mode 100644
index 7f907bc7e..000000000
--- a/test/lib/utils/setup-log.js
+++ /dev/null
@@ -1,296 +0,0 @@
-const t = require('tap')
-
-const settings = {
- level: 'warn',
-}
-t.afterEach(() => {
- Object.keys(settings).forEach(k => {
- delete settings[k]
- })
-})
-
-const WARN_CALLED = []
-const npmlog = {
- warn: (...args) => {
- WARN_CALLED.push(args)
- },
- levels: {
- silly: -Infinity,
- verbose: 1000,
- info: 2000,
- timing: 2500,
- http: 3000,
- notice: 3500,
- warn: 4000,
- error: 5000,
- silent: Infinity,
- },
- settings,
- enableColor: () => {
- settings.color = true
- },
- disableColor: () => {
- settings.color = false
- },
- enableUnicode: () => {
- settings.unicode = true
- },
- disableUnicode: () => {
- settings.unicode = false
- },
- enableProgress: () => {
- settings.progress = true
- },
- disableProgress: () => {
- settings.progress = false
- },
- get heading () {
- return settings.heading
- },
- set heading (h) {
- settings.heading = h
- },
- get level () {
- return settings.level
- },
- set level (l) {
- settings.level = l
- },
-}
-
-const EXPLAIN_CALLED = []
-const setupLog = t.mock('../../../lib/utils/setup-log.js', {
- '../../../lib/utils/explain-eresolve.js': {
- explain: (...args) => {
- EXPLAIN_CALLED.push(args)
- return 'explanation'
- },
- },
- npmlog,
-})
-
-const config = obj => ({
- get (k) {
- return obj[k]
- },
- set (k, v) {
- obj[k] = v
- },
-})
-
-t.test('setup with color=always and unicode', t => {
- npmlog.warn('ERESOLVE', 'hello', { some: 'object' })
- t.strictSame(EXPLAIN_CALLED, [], 'log.warn() not patched yet')
- t.strictSame(WARN_CALLED, [['ERESOLVE', 'hello', { some: 'object' }]])
- WARN_CALLED.length = 0
-
- setupLog(config({
- loglevel: 'warn',
- color: 'always',
- unicode: true,
- progress: false,
- }))
-
- npmlog.warn('ERESOLVE', 'hello', { some: { other: 'object' } })
- t.strictSame(EXPLAIN_CALLED, [[{ some: { other: 'object' } }, true, 2]],
- 'log.warn(ERESOLVE) patched to call explainEresolve()')
- t.strictSame(WARN_CALLED, [
- ['ERESOLVE', 'hello'],
- ['', 'explanation'],
- ], 'warn the explanation')
- EXPLAIN_CALLED.length = 0
- WARN_CALLED.length = 0
- npmlog.warn('some', 'other', 'thing')
- t.strictSame(EXPLAIN_CALLED, [], 'do not try to explain other things')
- t.strictSame(WARN_CALLED, [['some', 'other', 'thing']], 'warnings passed through')
-
- t.strictSame(settings, {
- level: 'warn',
- color: true,
- unicode: true,
- progress: false,
- heading: 'npm',
- })
-
- t.end()
-})
-
-t.test('setup with color=true, no unicode, and non-TTY terminal', t => {
- const { isTTY: stderrIsTTY } = process.stderr
- const { isTTY: stdoutIsTTY } = process.stdout
- t.teardown(() => {
- process.stderr.isTTY = stderrIsTTY
- process.stdout.isTTY = stdoutIsTTY
- })
- process.stderr.isTTY = false
- process.stdout.isTTY = false
-
- setupLog(config({
- loglevel: 'warn',
- color: false,
- progress: false,
- heading: 'asdf',
- }))
-
- t.strictSame(settings, {
- level: 'warn',
- color: false,
- unicode: false,
- progress: false,
- heading: 'asdf',
- })
-
- t.end()
-})
-
-t.test('setup with color=true, no unicode, and dumb TTY terminal', t => {
- const { isTTY: stderrIsTTY } = process.stderr
- const { isTTY: stdoutIsTTY } = process.stdout
- const { TERM } = process.env
- t.teardown(() => {
- process.stderr.isTTY = stderrIsTTY
- process.stdout.isTTY = stdoutIsTTY
- process.env.TERM = TERM
- })
- process.stderr.isTTY = true
- process.stdout.isTTY = true
- process.env.TERM = 'dumb'
-
- setupLog(config({
- loglevel: 'warn',
- color: true,
- progress: false,
- heading: 'asdf',
- }))
-
- t.strictSame(settings, {
- level: 'warn',
- color: true,
- unicode: false,
- progress: false,
- heading: 'asdf',
- })
-
- t.end()
-})
-
-t.test('setup with color=true, no unicode, and non-dumb TTY terminal', t => {
- const { isTTY: stderrIsTTY } = process.stderr
- const { isTTY: stdoutIsTTY } = process.stdout
- const { TERM } = process.env
- t.teardown(() => {
- process.stderr.isTTY = stderrIsTTY
- process.stdout.isTTY = stdoutIsTTY
- process.env.TERM = TERM
- })
- process.stderr.isTTY = true
- process.stdout.isTTY = true
- process.env.TERM = 'totes not dum'
-
- setupLog(config({
- loglevel: 'warn',
- color: true,
- progress: true,
- heading: 'asdf',
- }))
-
- t.strictSame(settings, {
- level: 'warn',
- color: true,
- unicode: false,
- progress: true,
- heading: 'asdf',
- })
-
- t.end()
-})
-
-t.test('setup with non-TTY stdout, TTY stderr', t => {
- const { isTTY: stderrIsTTY } = process.stderr
- const { isTTY: stdoutIsTTY } = process.stdout
- const { TERM } = process.env
- t.teardown(() => {
- process.stderr.isTTY = stderrIsTTY
- process.stdout.isTTY = stdoutIsTTY
- process.env.TERM = TERM
- })
- process.stderr.isTTY = true
- process.stdout.isTTY = false
- process.env.TERM = 'definitely not a dummy'
-
- setupLog(config({
- loglevel: 'warn',
- color: true,
- progress: true,
- heading: 'asdf',
- }))
-
- t.strictSame(settings, {
- level: 'warn',
- color: true,
- unicode: false,
- progress: true,
- heading: 'asdf',
- })
-
- t.end()
-})
-
-t.test('setup with TTY stdout, non-TTY stderr', t => {
- const { isTTY: stderrIsTTY } = process.stderr
- const { isTTY: stdoutIsTTY } = process.stdout
- const { TERM } = process.env
- t.teardown(() => {
- process.stderr.isTTY = stderrIsTTY
- process.stdout.isTTY = stdoutIsTTY
- process.env.TERM = TERM
- })
- process.stderr.isTTY = false
- process.stdout.isTTY = true
-
- setupLog(config({
- loglevel: 'warn',
- color: true,
- progress: true,
- heading: 'asdf',
- }))
-
- t.strictSame(settings, {
- level: 'warn',
- color: false,
- unicode: false,
- progress: false,
- heading: 'asdf',
- })
-
- t.end()
-})
-
-t.test('set loglevel to timing', t => {
- setupLog(config({
- timing: true,
- loglevel: 'notice',
- }))
- t.equal(settings.level, 'timing')
- t.end()
-})
-
-t.test('silent has no logging', t => {
- const { isTTY: stderrIsTTY } = process.stderr
- const { isTTY: stdoutIsTTY } = process.stdout
- const { TERM } = process.env
- t.teardown(() => {
- process.stderr.isTTY = stderrIsTTY
- process.stdout.isTTY = stdoutIsTTY
- process.env.TERM = TERM
- })
- process.stderr.isTTY = true
- process.stdout.isTTY = true
- process.env.TERM = 'totes not dum'
-
- setupLog(config({
- loglevel: 'silent',
- }))
- t.equal(settings.progress, false, 'progress disabled when silent')
- t.end()
-})
diff --git a/test/lib/utils/tar.js b/test/lib/utils/tar.js
index 19d949169..adc5cb364 100644
--- a/test/lib/utils/tar.js
+++ b/test/lib/utils/tar.js
@@ -2,18 +2,20 @@ const t = require('tap')
const pack = require('libnpmpack')
const ssri = require('ssri')
-const { logTar, getContents } = require('../../../lib/utils/tar.js')
+const { getContents } = require('../../../lib/utils/tar.js')
-const printLogs = (tarball, unicode) => {
+const mockTar = ({ notice }) => t.mock('../../../lib/utils/tar.js', {
+ 'proc-log': {
+ notice,
+ },
+})
+
+const printLogs = (tarball, options) => {
const logs = []
- logTar(tarball, {
- log: {
- notice: (...args) => {
- args.map(el => logs.push(el))
- },
- },
- unicode,
+ const { logTar } = mockTar({
+ notice: (...args) => args.map(el => logs.push(el)),
})
+ logTar(tarball, options)
return logs.join('\n')
}
@@ -41,16 +43,14 @@ t.test('should log tarball contents', async (t) => {
version: '1.0.0',
}, tarball)
- t.matchSnapshot(printLogs(tarballContents, false))
+ t.matchSnapshot(printLogs(tarballContents))
})
t.test('should log tarball contents with unicode', async (t) => {
- const { logTar } = t.mock('../../../lib/utils/tar.js', {
- npmlog: {
- notice: (str) => {
- t.ok(true, 'defaults to npmlog')
- return str
- },
+ const { logTar } = mockTar({
+ notice: (str) => {
+ t.ok(true, 'defaults to proc-log')
+ return str
},
})
@@ -64,26 +64,6 @@ t.test('should log tarball contents with unicode', async (t) => {
t.end()
})
-t.test('should default to npmlog', async (t) => {
- const { logTar } = t.mock('../../../lib/utils/tar.js', {
- npmlog: {
- notice: (str) => {
- t.ok(true, 'defaults to npmlog')
- return str
- },
- },
- })
-
- logTar({
- files: [],
- bundled: [],
- size: 0,
- unpackedSize: 0,
- integrity: '',
- })
- t.end()
-})
-
t.test('should getContents of a tarball', async (t) => {
const testDir = t.testdir({
'package.json': JSON.stringify({
diff --git a/test/lib/utils/timers.js b/test/lib/utils/timers.js
new file mode 100644
index 000000000..6127f346b
--- /dev/null
+++ b/test/lib/utils/timers.js
@@ -0,0 +1,82 @@
+const t = require('tap')
+const { resolve } = require('path')
+const fs = require('graceful-fs')
+const mockLogs = require('../../fixtures/mock-logs')
+
+const mockTimers = (t, options) => {
+ const { logs, logMocks } = mockLogs()
+ const Timers = t.mock('../../../lib/utils/timers', {
+ ...logMocks,
+ })
+ const timers = new Timers(options)
+ t.teardown(() => timers.off())
+ return { timers, logs }
+}
+
+t.test('getters', async (t) => {
+ const { timers } = mockTimers(t)
+ t.match(timers.unfinished, new Map())
+ t.match(timers.finished, {})
+})
+
+t.test('listens/stops on process', async (t) => {
+ const { timers } = mockTimers(t)
+ process.emit('time', 'foo')
+ process.emit('time', 'bar')
+ process.emit('timeEnd', 'bar')
+ t.match(timers.unfinished, new Map([['foo', Number]]))
+ t.match(timers.finished, { bar: Number })
+ timers.off()
+ process.emit('time', 'baz')
+ t.notOk(timers.unfinished.get('baz'))
+})
+
+t.test('initial timer', async (t) => {
+ const { timers } = mockTimers(t, { start: 'foo' })
+ process.emit('timeEnd', 'foo')
+ t.match(timers.finished, { foo: Number })
+})
+
+t.test('initial listener', async (t) => {
+ const events = []
+ const listener = (...args) => events.push(args)
+ const { timers } = mockTimers(t, { listener })
+ process.emit('time', 'foo')
+ process.emit('time', 'bar')
+ process.emit('timeEnd', 'bar')
+ timers.off(listener)
+ process.emit('timeEnd', 'foo')
+ t.equal(events.length, 1)
+ t.match(events, [['bar', Number]])
+})
+
+t.test('finish unstarted timer', async (t) => {
+ const { logs } = mockTimers(t)
+ process.emit('timeEnd', 'foo')
+ t.match(logs.silly, [['timing', /^Tried to end timer/, 'foo']])
+})
+
+t.test('writes file', async (t) => {
+ const { timers } = mockTimers(t)
+ const dir = t.testdir()
+ process.emit('time', 'foo')
+ process.emit('timeEnd', 'foo')
+ timers.load({ dir })
+ timers.writeFile({ some: 'data' })
+ const data = JSON.parse(fs.readFileSync(resolve(dir, '_timing.json')))
+ t.match(data, {
+ some: 'data',
+ foo: Number,
+ unfinished: {
+ npm: [Number, Number],
+ },
+ })
+})
+
+t.test('fails to write file', async (t) => {
+ const { logs, timers } = mockTimers(t)
+ timers.writeFile()
+ t.match(logs.warn, [
+ ['timing', 'could not write timing file', Error],
+ ])
+})
diff --git a/test/lib/utils/unsupported.js b/test/lib/utils/unsupported.js
index 4d806cefc..2703044a2 100644
--- a/test/lib/utils/unsupported.js
+++ b/test/lib/utils/unsupported.js
@@ -1,5 +1,6 @@
const t = require('tap')
const unsupported = require('../../../lib/utils/unsupported.js')
+const mockGlobals = require('../../fixtures/mock-globals.js')
const versions = [
// broken unsupported
@@ -55,42 +56,30 @@ t.test('checkForBrokenNode', t => {
// run it once to not fail
unsupported.checkForBrokenNode()
- const { exit } = process
- const { error } = console
- const versionPropDesc = Object.getOwnPropertyDescriptor(process, 'version')
-
- t.teardown(() => {
- process.exit = exit
- Object.defineProperty(process, 'version', versionPropDesc)
- console.error = error
- })
-
- // then make it a thing that fails
- process.exit = code => {
- t.equal(code, 1)
- t.strictSame(logs, expectLogs)
- t.end()
- }
- Object.defineProperty(process, 'version', { value: '1.2.3', configurable: true })
const logs = []
const expectLogs = [
'ERROR: npm is known not to run on Node.js 1.2.3',
"You'll need to upgrade to a newer Node.js version in order to use this",
'version of npm. You can find the latest version at https://nodejs.org/',
]
- console.error = msg => logs.push(msg)
+
+ // then make it a thing that fails
+ mockGlobals(t, {
+ 'console.error': msg => logs.push(msg),
+ 'process.version': '1.2.3',
+ 'process.exit': (code) => {
+ t.equal(code, 1)
+ t.strictSame(logs, expectLogs)
+ t.end()
+ },
+ })
+
unsupported.checkForBrokenNode()
})
t.test('checkForUnsupportedNode', t => {
- const npmlog = require('npmlog')
- const { warn } = npmlog
- const versionPropDesc = Object.getOwnPropertyDescriptor(process, 'version')
-
- t.teardown(() => {
- Object.defineProperty(process, 'version', versionPropDesc)
- npmlog.warn = warn
- })
+ // run it once to not fail or warn
+ unsupported.checkForUnsupportedNode()
const logs = []
const expectLogs = [
@@ -99,14 +88,15 @@ t.test('checkForUnsupportedNode', t => {
"can't make any promises that npm will work with this version.",
'You can find the latest version at https://nodejs.org/',
]
- npmlog.warn = (section, msg) => logs.push(msg)
-
- // run it once to not fail or warn
- unsupported.checkForUnsupportedNode()
// then make it a thing that fails
- Object.defineProperty(process, 'version', { value: '8.0.0' })
+ mockGlobals(t, {
+ 'console.error': msg => logs.push(msg),
+ 'process.version': '8.0.0',
+ })
+
unsupported.checkForUnsupportedNode()
+
t.strictSame(logs, expectLogs)
t.end()
})
diff --git a/test/lib/utils/update-notifier.js b/test/lib/utils/update-notifier.js
index 78ff93825..a7a800c60 100644
--- a/test/lib/utils/update-notifier.js
+++ b/test/lib/utils/update-notifier.js
@@ -36,18 +36,13 @@ const pacote = {
},
}
-const npm = {
+const defaultNpm = {
flatOptions,
- log: { useColor: () => true },
version: CURRENT_VERSION,
config: { get: k => k !== 'global' },
command: 'view',
argv: ['npm'],
}
-const npmNoColor = {
- ...npm,
- log: { useColor: () => false },
-}
const { basename } = require('path')
@@ -80,12 +75,6 @@ const fs = {
},
}
-const updateNotifier = t.mock('../../../lib/utils/update-notifier.js', {
- '@npmcli/ci-detect': () => ciMock,
- pacote,
- fs,
-})
-
t.afterEach(() => {
MANIFEST_REQUEST.length = 0
STAT_ERROR = null
@@ -94,16 +83,21 @@ t.afterEach(() => {
WRITE_ERROR = null
})
-const runUpdateNotifier = async npm => {
- await updateNotifier(npm)
- return npm.updateNotification
+const runUpdateNotifier = async ({ color = true, ...npmOptions } = {}) => {
+ const _npm = { ...defaultNpm, ...npmOptions }
+ await t.mock('../../../lib/utils/update-notifier.js', {
+ '@npmcli/ci-detect': () => ciMock,
+ pacote,
+ fs,
+ npmlog: { useColor: () => color },
+ })(_npm)
+ return _npm.updateNotification
}
t.test('situations in which we do not notify', t => {
t.test('nothing to do if notifier disabled', async t => {
t.equal(
await runUpdateNotifier({
- ...npm,
config: { get: k => k !== 'update-notifier' },
}),
null
@@ -114,7 +108,6 @@ t.test('situations in which we do not notify', t => {
t.test('do not suggest update if already updating', async t => {
t.equal(
await runUpdateNotifier({
- ...npm,
flatOptions: { ...flatOptions, global: true },
command: 'install',
argv: ['npm'],
@@ -127,7 +120,6 @@ t.test('situations in which we do not notify', t => {
t.test('do not suggest update if already updating with spec', async t => {
t.equal(
await runUpdateNotifier({
- ...npm,
flatOptions: { ...flatOptions, global: true },
command: 'install',
argv: ['npm@latest'],
@@ -138,31 +130,31 @@ t.test('situations in which we do not notify', t => {
})
t.test('do not update if same as latest', async t => {
- t.equal(await runUpdateNotifier(npm), null)
+ t.equal(await runUpdateNotifier(), null)
t.strictSame(MANIFEST_REQUEST, ['npm@latest'], 'requested latest version')
})
t.test('check if stat errors (here for coverage)', async t => {
STAT_ERROR = new Error('blorg')
- t.equal(await runUpdateNotifier(npm), null)
+ t.equal(await runUpdateNotifier(), null)
t.strictSame(MANIFEST_REQUEST, ['npm@latest'], 'requested latest version')
})
t.test('ok if write errors (here for coverage)', async t => {
WRITE_ERROR = new Error('grolb')
- t.equal(await runUpdateNotifier(npm), null)
+ t.equal(await runUpdateNotifier(), null)
t.strictSame(MANIFEST_REQUEST, ['npm@latest'], 'requested latest version')
})
t.test('ignore pacote failures (here for coverage)', async t => {
PACOTE_ERROR = new Error('pah-KO-tchay')
- t.equal(await runUpdateNotifier(npm), null)
+ t.equal(await runUpdateNotifier(), null)
t.strictSame(MANIFEST_REQUEST, ['npm@latest'], 'requested latest version')
})
t.test('do not update if newer than latest, but same as next', async t => {
- t.equal(await runUpdateNotifier({ ...npm, version: NEXT_VERSION }), null)
+ t.equal(await runUpdateNotifier({ version: NEXT_VERSION }), null)
const reqs = ['npm@latest', `npm@^${NEXT_VERSION}`]
t.strictSame(MANIFEST_REQUEST, reqs, 'requested latest and next versions')
})
t.test('do not update if on the latest beta', async t => {
- t.equal(await runUpdateNotifier({ ...npm, version: CURRENT_BETA }), null)
+ t.equal(await runUpdateNotifier({ version: CURRENT_BETA }), null)
const reqs = [`npm@^${CURRENT_BETA}`]
t.strictSame(MANIFEST_REQUEST, reqs, 'requested latest and next versions')
})
@@ -172,21 +164,21 @@ t.test('situations in which we do not notify', t => {
ciMock = null
})
ciMock = 'something'
- t.equal(await runUpdateNotifier(npm), null)
+ t.equal(await runUpdateNotifier(), null)
t.strictSame(MANIFEST_REQUEST, [], 'no requests for manifests')
})
t.test('only check weekly for GA releases', async t => {
// One week (plus five minutes to account for test environment fuzziness)
STAT_MTIME = Date.now() - 1000 * 60 * 60 * 24 * 7 + 1000 * 60 * 5
- t.equal(await runUpdateNotifier(npm), null)
+ t.equal(await runUpdateNotifier(), null)
t.strictSame(MANIFEST_REQUEST, [], 'no requests for manifests')
})
t.test('only check daily for betas', async t => {
// One day (plus five minutes to account for test environment fuzziness)
STAT_MTIME = Date.now() - 1000 * 60 * 60 * 24 + 1000 * 60 * 5
- t.equal(await runUpdateNotifier({ ...npm, version: HAVE_BETA }), null)
+ t.equal(await runUpdateNotifier({ version: HAVE_BETA }), null)
t.strictSame(MANIFEST_REQUEST, [], 'no requests for manifests')
})
@@ -196,9 +188,9 @@ t.test('situations in which we do not notify', t => {
t.test('notification situations', t => {
t.test('new beta available', async t => {
const version = HAVE_BETA
- t.matchSnapshot(await runUpdateNotifier({ ...npm, version }), 'color')
+ t.matchSnapshot(await runUpdateNotifier({ version }), 'color')
t.matchSnapshot(
- await runUpdateNotifier({ ...npmNoColor, version }),
+ await runUpdateNotifier({ version, color: false }),
'no color'
)
t.strictSame(MANIFEST_REQUEST, [`npm@^${version}`, `npm@^${version}`])
@@ -206,9 +198,9 @@ t.test('notification situations', t => {
t.test('patch to next version', async t => {
const version = NEXT_PATCH
- t.matchSnapshot(await runUpdateNotifier({ ...npm, version }), 'color')
+ t.matchSnapshot(await runUpdateNotifier({ version }), 'color')
t.matchSnapshot(
- await runUpdateNotifier({ ...npmNoColor, version }),
+ await runUpdateNotifier({ version, color: false }),
'no color'
)
t.strictSame(MANIFEST_REQUEST, [
@@ -221,9 +213,9 @@ t.test('notification situations', t => {
t.test('minor to next version', async t => {
const version = NEXT_MINOR
- t.matchSnapshot(await runUpdateNotifier({ ...npm, version }), 'color')
+ t.matchSnapshot(await runUpdateNotifier({ version }), 'color')
t.matchSnapshot(
- await runUpdateNotifier({ ...npmNoColor, version }),
+ await runUpdateNotifier({ version, color: false }),
'no color'
)
t.strictSame(MANIFEST_REQUEST, [
@@ -236,9 +228,9 @@ t.test('notification situations', t => {
t.test('patch to current', async t => {
const version = CURRENT_PATCH
- t.matchSnapshot(await runUpdateNotifier({ ...npm, version }), 'color')
+ t.matchSnapshot(await runUpdateNotifier({ version }), 'color')
t.matchSnapshot(
- await runUpdateNotifier({ ...npmNoColor, version }),
+ await runUpdateNotifier({ version, color: false }),
'no color'
)
t.strictSame(MANIFEST_REQUEST, ['npm@latest', 'npm@latest'])
@@ -246,9 +238,9 @@ t.test('notification situations', t => {
t.test('minor to current', async t => {
const version = CURRENT_MINOR
- t.matchSnapshot(await runUpdateNotifier({ ...npm, version }), 'color')
+ t.matchSnapshot(await runUpdateNotifier({ version }), 'color')
t.matchSnapshot(
- await runUpdateNotifier({ ...npmNoColor, version }),
+ await runUpdateNotifier({ version, color: false }),
'no color'
)
t.strictSame(MANIFEST_REQUEST, ['npm@latest', 'npm@latest'])
@@ -256,9 +248,9 @@ t.test('notification situations', t => {
t.test('major to current', async t => {
const version = CURRENT_MAJOR
- t.matchSnapshot(await runUpdateNotifier({ ...npm, version }), 'color')
+ t.matchSnapshot(await runUpdateNotifier({ version }), 'color')
t.matchSnapshot(
- await runUpdateNotifier({ ...npmNoColor, version }),
+ await runUpdateNotifier({ version, color: false }),
'no color'
)
t.strictSame(MANIFEST_REQUEST, ['npm@latest', 'npm@latest'])