Repository: istanbuljs/nyc
Pull request #1053 (2019-04-04 14:06:59)

feat: Add support for --exclude-node-modules to subcommands.
This adds support for the `--exclude-node-modules` option/setting to
instrument, check-coverage and report sub-commands.

Add testing to verify that `--exclude-node-modules=false` is honored for
all commands.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/22602929/badge)](https://coveralls.io/builds/22602929)

Coverage remained the same at 96.429% when pulling **8678cefb3c9fb367b097286ddd2fbac07c1bee34 on coreyfarrell:excludeNodeModules** into **8da097e1bcfd0cb353a940bcf54eb1329cedb855 on istanbuljs:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/22602929/badge)](https://coveralls.io/builds/22602929)

Coverage remained the same at 96.429% when pulling **8678cefb3c9fb367b097286ddd2fbac07c1bee34 on coreyfarrell:excludeNodeModules** into **8da097e1bcfd0cb353a940bcf54eb1329cedb855 on istanbuljs:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/22602929/badge)](https://coveralls.io/builds/22602929)

Coverage remained the same at 96.429% when pulling **8678cefb3c9fb367b097286ddd2fbac07c1bee34 on coreyfarrell:excludeNodeModules** into **8da097e1bcfd0cb353a940bcf54eb1329cedb855 on istanbuljs:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/22602929/badge)](https://coveralls.io/builds/22602929)

Coverage remained the same at 96.429% when pulling **8678cefb3c9fb367b097286ddd2fbac07c1bee34 on coreyfarrell:excludeNodeModules** into **8da097e1bcfd0cb353a940bcf54eb1329cedb855 on istanbuljs:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/22602929/badge)](https://coveralls.io/builds/22602929)

Coverage remained the same at 96.429% when pulling **8678cefb3c9fb367b097286ddd2fbac07c1bee34 on coreyfarrell:excludeNodeModules** into **8da097e1bcfd0cb353a940bcf54eb1329cedb855 on istanbuljs:master**.


Patch:
diff --git a/bin/nyc.js b/bin/nyc.js
index c285c5a..80f97c4 100755
--- a/bin/nyc.js
+++ b/bin/nyc.js
@@ -65,35 +65,27 @@ if ([
   ), function (done) {
     var mainChildExitCode = process.exitCode
 
+    if (argv.showProcessTree || argv.buildProcessTree) {
+      nyc.writeProcessIndex()
+    }
+
     if (argv.checkCoverage) {
-      checkCoverage(argv)
+      nyc.checkCoverage({
+        lines: argv.lines,
+        functions: argv.functions,
+        branches: argv.branches,
+        statements: argv.statements
+      }, argv['per-file'])
       process.exitCode = process.exitCode || mainChildExitCode
-      if (!argv.silent) report(argv)
-      return done()
-    } else {
-      if (!argv.silent) report(argv)
-      return done()
     }
+
+    if (!argv.silent) {
+      nyc.report()
+    }
+
+    return done()
   })
 } else {
   // I don't have a clue what you're doing.
   yargs.showHelp()
 }
-
-function report (argv) {
-  process.env.NYC_CWD = process.cwd()
-
-  var nyc = new NYC(argv)
-  nyc.report()
-}
-
-function checkCoverage (argv, cb) {
-  process.env.NYC_CWD = process.cwd()
-
-  ;(new NYC(argv)).checkCoverage({
-    lines: argv.lines,
-    functions: argv.functions,
-    branches: argv.branches,
-    statements: argv.statements
-  }, argv['per-file'])
-}
diff --git a/bin/wrap.js b/bin/wrap.js
index b4c9161..1cff275 100644
--- a/bin/wrap.js
+++ b/bin/wrap.js
@@ -1,17 +1,20 @@
 var sw = require('spawn-wrap')
 var NYC = require('../index.js')
 
-var parentPid = process.env.NYC_PARENT_PID || '0'
-process.env.NYC_PARENT_PID = process.pid
-
 var config = {}
 if (process.env.NYC_CONFIG) config = JSON.parse(process.env.NYC_CONFIG)
 config.isChildProcess = true
 
 config._processInfo = {
-  ppid: parentPid,
+  pid: process.pid,
+  ppid: process.ppid,
+  parent: process.env.NYC_PROCESS_ID || null,
   root: process.env.NYC_ROOT_ID
 }
+if (process.env.NYC_PROCESSINFO_EXTERNAL_ID) {
+  config._processInfo.externalId = process.env.NYC_PROCESSINFO_EXTERNAL_ID
+  delete process.env.NYC_PROCESSINFO_EXTERNAL_ID
+}
 
 ;(new NYC(config)).wrap()
 
diff --git a/index.js b/index.js
index 00e6cc8..2502c17 100755
--- a/index.js
+++ b/index.js
@@ -2,7 +2,6 @@
 
 /* global __coverage__ */
 
-const arrify = require('arrify')
 const cachingTransform = require('caching-transform')
 const util = require('util')
 const findCacheDir = require('find-cache-dir')
@@ -44,12 +43,12 @@ function NYC (config) {
   this._buildProcessTree = this._showProcessTree || config.buildProcessTree
   this._eagerInstantiation = config.eager || false
   this.cwd = config.cwd || process.cwd()
-  this.reporter = arrify(config.reporter || 'text')
+  this.reporter = [].concat(config.reporter || 'text')
 
   this.cacheDirectory = (config.cacheDir && path.resolve(config.cacheDir)) || findCacheDir({ name: 'nyc', cwd: this.cwd })
   this.cache = Boolean(this.cacheDirectory && config.cache)
 
-  this.extensions = arrify(config.extension)
+  this.extensions = [].concat(config.extension || [])
     .concat('.js')
     .map(ext => ext.toLowerCase())
     .filter((item, pos, arr) => arr.indexOf(item) === pos)
@@ -68,7 +67,7 @@ function NYC (config) {
   })
 
   // require extensions can be provided as config in package.json.
-  this.require = arrify(config.require)
+  this.require = [].concat(config.require || [])
 
   this.transforms = this.extensions.reduce((transforms, ext) => {
     transforms[ext] = this._createTransform(ext)
@@ -313,6 +312,7 @@ NYC.prototype._wrapExit = function () {
 }
 
 NYC.prototype.wrap = function (bin) {
+  process.env.NYC_PROCESS_ID = this.processInfo.uuid
   this._addRequireHooks()
   this._wrapExit()
   this._loadAdditionalModules()
@@ -342,7 +342,7 @@ NYC.prototype.writeCoverageFile = function () {
     coverage = this.sourceMaps.remapCoverage(coverage)
   }
 
-  var id = this.generateUniqueID()
+  var id = this.processInfo.uuid
   var coverageFilename = path.resolve(this.tempDirectory(), id + '.json')
 
   fs.writeFileSync(
@@ -356,6 +356,7 @@ NYC.prototype.writeCoverageFile = function () {
   }
 
   this.processInfo.coverageFilename = coverageFilename
+  this.processInfo.files = Object.keys(coverage)
 
   fs.writeFileSync(
     path.resolve(this.processInfoDirectory(), id + '.json'),
@@ -413,6 +414,80 @@ NYC.prototype.report = function () {
   }
 }
 
+// XXX(@isaacs) Index generation should move to istanbul-lib-processinfo
+NYC.prototype.writeProcessIndex = function () {
+  const dir = this.processInfoDirectory()
+  const pidToUid = new Map()
+  const infoByUid = new Map()
+  const eidToUid = new Map()
+  const infos = fs.readdirSync(dir).filter(f => f !== 'index.json').map(f => {
+    try {
+      const info = JSON.parse(fs.readFileSync(path.resolve(dir, f), 'utf-8'))
+      info.children = []
+      pidToUid.set(info.uuid, info.pid)
+      pidToUid.set(info.pid, info.uuid)
+      infoByUid.set(info.uuid, info)
+      if (info.externalId) {
+        eidToUid.set(info.externalId, info.uuid)
+      }
+      return info
+    } catch (er) {
+      return null
+    }
+  }).filter(Boolean)
+
+  // create all the parent-child links and write back the updated info
+  infos.forEach(info => {
+    if (info.parent) {
+      const parentInfo = infoByUid.get(info.parent)
+      if (parentInfo.children.indexOf(info.uuid) === -1) {
+        parentInfo.children.push(info.uuid)
+      }
+    }
+  })
+
+  // figure out which files were touched by each process.
+  const files = infos.reduce((files, info) => {
+    info.files.forEach(f => {
+      files[f] = files[f] || []
+      files[f].push(info.uuid)
+    })
+    return files
+  }, {})
+
+  // build the actual index!
+  const index = infos.reduce((index, info) => {
+    index.processes[info.uuid] = {}
+    index.processes[info.uuid].parent = info.parent
+    if (info.externalId) {
+      if (index.externalIds[info.externalId]) {
+        throw new Error(`External ID ${info.externalId} used by multiple processes`)
+      }
+      index.processes[info.uuid].externalId = info.externalId
+      index.externalIds[info.externalId] = {
+        root: info.uuid,
+        children: info.children
+      }
+    }
+    index.processes[info.uuid].children = Array.from(info.children)
+    return index
+  }, { processes: {}, files: files, externalIds: {} })
+
+  // flatten the descendant sets of all the externalId procs
+  Object.keys(index.externalIds).forEach(eid => {
+    const { children } = index.externalIds[eid]
+    // push the next generation onto the list so we accumulate them all
+    for (let i = 0; i < children.length; i++) {
+      const nextGen = index.processes[children[i]].children
+      if (nextGen && nextGen.length) {
+        children.push(...nextGen.filter(uuid => children.indexOf(uuid) === -1))
+      }
+    }
+  })
+
+  fs.writeFileSync(path.resolve(dir, 'index.json'), JSON.stringify(index))
+}
+
 NYC.prototype.showProcessTree = function () {
   var processTree = ProcessInfo.buildProcessTree(this._loadProcessInfos())
 
@@ -449,19 +524,25 @@ NYC.prototype._checkCoverage = function (summary, thresholds, file) {
 }
 
 NYC.prototype._loadProcessInfos = function () {
-  var _this = this
-  var files = fs.readdirSync(this.processInfoDirectory())
-
-  return files.map(function (f) {
+  return fs.readdirSync(this.processInfoDirectory()).map(f => {
+    let data
     try {
-      return new ProcessInfo(JSON.parse(fs.readFileSync(
-        path.resolve(_this.processInfoDirectory(), f),
+      data = JSON.parse(fs.readFileSync(
+        path.resolve(this.processInfoDirectory(), f),
         'utf-8'
-      )))
+      ))
     } catch (e) { // handle corrupt JSON output.
-      return {}
+      return null
     }
-  })
+    if (f !== 'index.json') {
+      data.nodes = []
+      data = new ProcessInfo(data)
+    }
+    return { file: path.basename(f, '.json'), data: data }
+  }).filter(Boolean).reduce((infos, info) => {
+    infos[info.file] = info.data
+    return infos
+  }, {})
 }
 
 NYC.prototype.eachReport = function (filenames, iterator, baseDirectory) {
diff --git a/lib/commands/check-coverage.js b/lib/commands/check-coverage.js
index c302ee9..8eb36eb 100644
--- a/lib/commands/check-coverage.js
+++ b/lib/commands/check-coverage.js
@@ -13,6 +13,12 @@ exports.builder = function (yargs) {
       describe: 'a list of specific files and directories that should be excluded from coverage, glob patterns are supported, node_modules is always excluded',
       global: false
     })
+    .option('exclude-node-modules', {
+      default: true,
+      type: 'boolean',
+      describe: 'whether or not to exclude all node_module folders (i.e. **/node_modules/**) by default',
+      global: false
+    })
     .option('exclude-after-remap', {
       default: true,
       type: 'boolean',
diff --git a/lib/commands/instrument.js b/lib/commands/instrument.js
index 3c0fe9d..c22475c 100644
--- a/lib/commands/instrument.js
+++ b/lib/commands/instrument.js
@@ -59,6 +59,12 @@ exports.builder = function (yargs) {
       default: testExclude.defaultExclude,
       describe: 'a list of specific files and directories that should not be instrumented, glob patterns are supported'
     })
+    .option('exclude-node-modules', {
+      default: true,
+      type: 'boolean',
+      describe: 'whether or not to exclude all node_module folders (i.e. **/node_modules/**) by default',
+      global: false
+    })
     .option('es-modules', {
       default: true,
       type: 'boolean',
@@ -100,20 +106,7 @@ exports.handler = function (argv) {
     ? './lib/instrumenters/istanbul'
     : './lib/instrumenters/noop'
 
-  const nyc = new NYC({
-    instrumenter: argv.instrumenter,
-    sourceMap: argv.sourceMap,
-    produceSourceMap: argv.produceSourceMap,
-    extension: argv.extension,
-    require: argv.require,
-    cwd: argv.cwd,
-    compact: argv.compact,
-    preserveComments: argv.preserveComments,
-    include: argv.include,
-    exclude: argv.exclude,
-    esModules: argv.esModules,
-    exitOnError: argv.exitOnError
-  })
+  const nyc = new NYC(argv)
 
   nyc.instrumentAllFiles(argv.input, argv.output, err => {
     if (err) {
diff --git a/lib/commands/report.js b/lib/commands/report.js
index 8dfc0f4..739ff30 100644
--- a/lib/commands/report.js
+++ b/lib/commands/report.js
@@ -30,6 +30,12 @@ exports.builder = function (yargs) {
       describe: 'a list of specific files and directories that should be excluded from coverage, glob patterns are supported, node_modules is always excluded',
       global: false
     })
+    .option('exclude-node-modules', {
+      default: true,
+      type: 'boolean',
+      describe: 'whether or not to exclude all node_module folders (i.e. **/node_modules/**) by default',
+      global: false
+    })
     .option('exclude-after-remap', {
       default: true,
       type: 'boolean',
diff --git a/lib/config-util.js b/lib/config-util.js
index ef0e7a4..bcaf223 100644
--- a/lib/config-util.js
+++ b/lib/config-util.js
@@ -1,6 +1,5 @@
 'use strict'
 
-const arrify = require('arrify')
 const fs = require('fs')
 const path = require('path')
 const findUp = require('find-up')
@@ -37,10 +36,10 @@ Config.loadConfig = function (argv, cwd) {
     }
   }
 
-  if (config.require) config.require = arrify(config.require)
-  if (config.extension) config.extension = arrify(config.extension)
-  if (config.exclude) config.exclude = arrify(config.exclude)
-  if (config.include) config.include = arrify(config.include)
+  if (config.require) config.require = [].concat(config.require)
+  if (config.extension) config.extension = [].concat(config.extension)
+  if (config.exclude) config.exclude = [].concat(config.exclude)
+  if (config.include) config.include = [].concat(config.include)
 
   return config
 }
diff --git a/lib/process.js b/lib/process.js
index c77ca50..3a8cab8 100644
--- a/lib/process.js
+++ b/lib/process.js
@@ -1,9 +1,12 @@
 const archy = require('archy')
 const libCoverage = require('istanbul-lib-coverage')
+const uuid = require('uuid/v4')
 
 function ProcessInfo (defaults) {
   defaults = defaults || {}
 
+  this.uuid = null
+  this.parent = null
   this.pid = String(process.pid)
   this.argv = process.argv
   this.execArgv = process.execArgv
@@ -12,13 +15,14 @@ function ProcessInfo (defaults) {
   this.ppid = null
   this.root = null
   this.coverageFilename = null
-  this.nodes = [] // list of children, filled by buildProcessTree()
-
-  this._coverageMap = null
 
   for (var key in defaults) {
     this[key] = defaults[key]
   }
+
+  if (!this.uuid) {
+    this.uuid = uuid()
+  }
 }
 
 Object.defineProperty(ProcessInfo.prototype, 'label', {
@@ -36,29 +40,19 @@ Object.defineProperty(ProcessInfo.prototype, 'label', {
 })
 
 ProcessInfo.buildProcessTree = function (infos) {
-  var treeRoot = new ProcessInfo({ _label: 'nyc' })
-  var nodes = { }
-
-  infos = infos.sort(function (a, b) {
-    return a.time - b.time
-  })
-
-  infos.forEach(function (p) {
-    nodes[p.root + ':' + p.pid] = p
-  })
-
-  infos.forEach(function (p) {
-    if (!p.ppid) {
-      return
+  const treeRoot = new ProcessInfo({ _label: 'nyc', nodes: [] })
+  const index = infos.index
+  for (const id in index.processes) {
+    const node = infos[id]
+    if (!node) {
+      throw new Error(`Invalid entry in processinfo index: ${id}`)
     }
-
-    var parent = nodes[p.root + ':' + p.ppid]
-    if (!parent) {
-      parent = treeRoot
+    const idx = index.processes[id]
+    node.nodes = idx.children.map(id => infos[id]).sort((a, b) => a.time - b.time)
+    if (!node.parent) {
+      treeRoot.nodes.push(node)
     }
-
-    parent.nodes.push(p)
-  })
+  }
 
   return treeRoot
 }
diff --git a/package-lock.json b/package-lock.json
index dd60e2f..7bc532b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -285,7 +285,7 @@
         },
         "chalk": {
           "version": "1.1.3",
-          "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
           "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
           "dev": true,
           "requires": {
@@ -304,7 +304,7 @@
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "dev": true,
           "requires": {
@@ -846,7 +846,7 @@
       "dependencies": {
         "minimist": {
           "version": "1.2.0",
-          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         }
@@ -1355,7 +1355,7 @@
         },
         "load-json-file": {
           "version": "2.0.0",
-          "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
           "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
           "dev": true,
           "requires": {
@@ -1607,7 +1607,7 @@
     },
     "external-editor": {
       "version": "2.2.0",
-      "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
+      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
       "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==",
       "dev": true,
       "requires": {
@@ -1697,7 +1697,7 @@
     },
     "foreground-child": {
       "version": "1.5.6",
-      "resolved": "http://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz",
+      "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz",
       "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=",
       "requires": {
         "cross-spawn": "^4",
@@ -1826,7 +1826,7 @@
         },
         "load-json-file": {
           "version": "1.1.0",
-          "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
           "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
           "dev": true,
           "requires": {
@@ -1863,7 +1863,7 @@
         },
         "minimist": {
           "version": "1.2.0",
-          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         },
@@ -2273,7 +2273,7 @@
     },
     "is-obj": {
       "version": "1.0.1",
-      "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
       "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
       "dev": true
     },
@@ -2728,7 +2728,7 @@
         },
         "minimist": {
           "version": "1.2.0",
-          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         },
@@ -2813,7 +2813,7 @@
     },
     "minimist": {
       "version": "0.0.10",
-      "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
       "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
     },
     "minimist-options": {
@@ -2846,7 +2846,7 @@
     },
     "mkdirp": {
       "version": "0.5.1",
-      "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
       "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
       "requires": {
         "minimist": "0.0.8"
@@ -2854,7 +2854,7 @@
       "dependencies": {
         "minimist": {
           "version": "0.0.8",
-          "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
           "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
         }
       }
@@ -4393,7 +4393,7 @@
     },
     "readable-stream": {
       "version": "2.3.6",
-      "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
       "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
       "dev": true,
       "requires": {
@@ -4755,7 +4755,7 @@
       "dependencies": {
         "minimist": {
           "version": "1.2.0",
-          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         }
@@ -5069,7 +5069,7 @@
     },
     "through": {
       "version": "2.3.8",
-      "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
       "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
       "dev": true
     },
@@ -5264,7 +5264,7 @@
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "dev": true,
           "requires": {
@@ -5345,7 +5345,7 @@
     },
     "wrap-ansi": {
       "version": "2.1.0",
-      "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
       "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
       "requires": {
         "string-width": "^1.0.1",
@@ -5377,7 +5377,7 @@
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "requires": {
             "ansi-regex": "^2.0.0"
diff --git a/package.json b/package.json
index e07cf8d..ac3b7ee 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,6 @@
   "nyc": {
     "exclude": [
       "node_modules",
-      "bin",
       "coverage",
       "self-coverage",
       "test/fixtures/coverage.js",
@@ -68,7 +67,6 @@
   "license": "ISC",
   "dependencies": {
     "archy": "^1.0.0",
-    "arrify": "^1.0.1",
     "caching-transform": "^3.0.2",
     "convert-source-map": "^1.6.0",
     "find-cache-dir": "^2.1.0",


Test patch:
diff --git a/test/fixtures/exclude-node-modules/.gitignore b/test/fixtures/exclude-node-modules/.gitignore
new file mode 100644
index 0000000..9ef48a5
--- /dev/null
+++ b/test/fixtures/exclude-node-modules/.gitignore
@@ -0,0 +1,2 @@
+!node_modules
+node_modules/.cache/
diff --git a/test/fixtures/exclude-node-modules/bin/do-nothing.js b/test/fixtures/exclude-node-modules/bin/do-nothing.js
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/exclude-node-modules/node_modules/@istanbuljs/fake-module-1/index.js b/test/fixtures/exclude-node-modules/node_modules/@istanbuljs/fake-module-1/index.js
new file mode 100644
index 0000000..6be0237
--- /dev/null
+++ b/test/fixtures/exclude-node-modules/node_modules/@istanbuljs/fake-module-1/index.js
@@ -0,0 +1,1 @@
+console.log('hello world');
diff --git a/test/fixtures/exclude-node-modules/node_modules/@istanbuljs/fake-module-2/index.js b/test/fixtures/exclude-node-modules/node_modules/@istanbuljs/fake-module-2/index.js
new file mode 100644
index 0000000..6be0237
--- /dev/null
+++ b/test/fixtures/exclude-node-modules/node_modules/@istanbuljs/fake-module-2/index.js
@@ -0,0 +1,1 @@
+console.log('hello world');
diff --git a/test/fixtures/exclude-node-modules/package.json b/test/fixtures/exclude-node-modules/package.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/test/fixtures/exclude-node-modules/package.json
@@ -0,0 +1,1 @@
+{}
diff --git a/test/nyc-integration.js b/test/nyc-integration.js
index 999e510..88155a6 100644
--- a/test/nyc-integration.js
+++ b/test/nyc-integration.js
@@ -1127,7 +1127,13 @@ describe('the nyc cli', function () {
       proc.on('close', function (code) {
         code.should.equal(0)
         stdout.should.not.match(new RegExp('└─'))
-        fs.statSync(path.resolve(fixturesCLI, '.nyc_output', 'processinfo'))
+        const dir = path.resolve(fixturesCLI, '.nyc_output', 'processinfo')
+        fs.statSync(dir)
+        // make sure that the processinfo file has a numeric pid and ppid
+        const files = fs.readdirSync(dir).filter(f => f !== 'index.json')
+        const data = JSON.parse(fs.readFileSync(dir + '/' + files[0], 'utf8'))
+        data.pid.should.be.a('number')
+        data.ppid.should.be.a('number')
         done()
       })
     })
@@ -1731,6 +1737,138 @@ describe('the nyc cli', function () {
       })
     })
   })
+
+  describe('exclude-node-modules', () => {
+    const fixturesENM = path.resolve(__dirname, './fixtures/exclude-node-modules')
+    const globalArgs = [
+      bin,
+      '--all=true',
+      '--cache=false',
+      '--per-file=true',
+      '--exclude-node-modules=false',
+      '--include=node_modules/@istanbuljs/fake-module-1/**'
+    ]
+    const spawnOpts = {
+      cwd: fixturesENM,
+      env: env
+    }
+    const noCoverageError = `ERROR: Coverage for lines (0%) does not meet threshold (90%) for ${path.join(fixturesENM, 'node_modules/@istanbuljs/fake-module-1/index.js')}\n`
+
+    it('execute', done => {
+      function checkReport (code, stderr, stdout, next) {
+        code.should.equal(1)
+        stderr.should.equal(noCoverageError)
+        stdoutShouldEqual(stdout, `
+          ----------|----------|----------|----------|----------|-------------------|
+          File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
+          ----------|----------|----------|----------|----------|-------------------|
+          All files |        0 |      100 |      100 |        0 |                   |
+           index.js |        0 |      100 |      100 |        0 |                 1 |
+          ----------|----------|----------|----------|----------|-------------------|`)
+        next()
+      }
+
+      function executeMainCommand () {
+        const args = [
+          ...globalArgs,
+          '--check-coverage=true',
+          process.execPath, './bin/do-nothing.js'
+        ]
+
+        const proc = spawn(process.execPath, args, spawnOpts)
+
+        var stderr = ''
+        proc.stderr.on('data', function (chunk) {
+          stderr += chunk
+        })
+
+        var stdout = ''
+        proc.stdout.on('data', function (chunk) {
+          stdout += chunk
+        })
+
+        proc.on('close', code => checkReport(code, stderr, stdout, executeReport))
+      }
+
+      function executeReport () {
+        const args = [
+          ...globalArgs,
+          '--check-coverage=true',
+          'report'
+        ]
+
+        const proc = spawn(process.execPath, args, spawnOpts)
+
+        var stderr = ''
+        proc.stderr.on('data', function (chunk) {
+          stderr += chunk
+        })
+
+        var stdout = ''
+        proc.stdout.on('data', function (chunk) {
+          stdout += chunk
+        })
+
+        proc.on('close', code => checkReport(code, stderr, stdout, executeCheckCoverage))
+      }
+
+      function executeCheckCoverage () {
+        const args = [
+          ...globalArgs,
+          'check-coverage'
+        ]
+
+        const proc = spawn(process.execPath, args, spawnOpts)
+
+        var stderr = ''
+        proc.stderr.on('data', function (chunk) {
+          stderr += chunk
+        })
+
+        var stdout = ''
+        proc.stdout.on('data', function (chunk) {
+          stdout += chunk
+        })
+
+        proc.on('close', code => {
+          code.should.equal(1)
+          stderr.should.equal(noCoverageError)
+          stdoutShouldEqual(stdout, '')
+          done()
+        })
+      }
+
+      executeMainCommand()
+    })
+
+    it('instrument', done => {
+      const args = [
+        ...globalArgs,
+        'instrument',
+        'node_modules'
+      ]
+
+      const proc = spawn(process.execPath, args, spawnOpts)
+
+      var stderr = ''
+      proc.stderr.on('data', function (chunk) {
+        stderr += chunk
+      })
+
+      var stdout = ''
+      proc.stdout.on('data', function (chunk) {
+        stdout += chunk
+      })
+
+      proc.on('close', code => {
+        code.should.equal(0)
+        stderr.should.equal('')
+        stdout.should.match(/fake-module-1/)
+        stdout.should.not.match(/fake-module-2/)
+        done()
+      })
+    })
+  })
 })
 
 function stdoutShouldEqual (stdout, expected) {
diff --git a/test/processinfo.js b/test/processinfo.js
new file mode 100644
index 0000000..7117bee
--- /dev/null
+++ b/test/processinfo.js
@@ -0,0 +1,95 @@
+const { resolve } = require('path')
+const bin = resolve(__dirname, '../self-coverage/bin/nyc')
+const { spawn } = require('child_process')
+const t = require('tap')
+const rimraf = require('rimraf')
+const node = process.execPath
+const fixturesCLI = resolve(__dirname, './fixtures/cli')
+const tmp = 'processinfo-test'
+const fs = require('fs')
+const resolvedJS = resolve(fixturesCLI, 'selfspawn-fibonacci.js')
+
+rimraf.sync(resolve(fixturesCLI, tmp))
+t.teardown(() => rimraf.sync(resolve(fixturesCLI, tmp)))
+
+t.test('build some processinfo', t => {
+  var args = [
+    bin, '-t', tmp, '--build-process-tree',
+    node, 'selfspawn-fibonacci.js', '5'
+  ]
+  var proc = spawn(process.execPath, args, {
+    cwd: fixturesCLI,
+    env: {
+      PATH: process.env.PATH,
+      NYC_PROCESSINFO_EXTERNAL_ID: 'blorp'
+    }
+  })
+  // don't actually care about the output for this test, just the data
+  proc.stderr.resume()
+  proc.stdout.resume()
+  proc.on('close', (code, signal) => {
+    t.equal(code, 0)
+    t.equal(signal, null)
+    t.end()
+  })
+})
+
+t.test('validate the created processinfo data', t => {
+  const covs = fs.readdirSync(resolve(fixturesCLI, tmp))
+    .filter(f => f !== 'processinfo')
+  t.plan(covs.length * 2)
+
+  covs.forEach(f => {
+    fs.readFile(resolve(fixturesCLI, tmp, f), 'utf8', (er, covjson) => {
+      if (er) {
+        throw er
+      }
+      const covdata = JSON.parse(covjson)
+      t.same(Object.keys(covdata), [resolvedJS])
+      // should have matching processinfo for each cov json
+      const procInfoFile = resolve(fixturesCLI, tmp, 'processinfo', f)
+      fs.readFile(procInfoFile, 'utf8', (er, procInfoJson) => {
+        if (er) {
+          throw er
+        }
+        const procInfoData = JSON.parse(procInfoJson)
+        t.match(procInfoData, {
+          pid: Number,
+          ppid: Number,
+          uuid: f.replace(/\.json$/, ''),
+          argv: [
+            node,
+            resolvedJS,
+            /[1-5]/
+          ],
+          execArgv: [],
+          cwd: fixturesCLI,
+          time: Number,
+          root: /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/,
+          coverageFilename: resolve(fixturesCLI, tmp, f),
+          files: [ resolvedJS ]
+        })
+      })
+    })
+  })
+})
+
+t.test('check out the index', t => {
+  const indexFile = resolve(fixturesCLI, tmp, 'processinfo', 'index.json')
+  const indexJson = fs.readFileSync(indexFile, 'utf-8')
+  const index = JSON.parse(indexJson)
+  const u = /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/
+  t.match(index, {
+    processes: {},
+    files: {
+      [resolvedJS]: [ u, u, u, u, u, u, u, u, u ]
+    },
+    externalIds: {
+      blorp: {
+        root: u,
+        children: [ u, u, u, u, u, u, u, u ]
+      }
+    }
+  })
+  t.end()
+})