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:
-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'])