123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- 'use strict'
- // walk the tree of deps starting from the top level list of bundled deps
- // Any deps at the top level that are depended on by a bundled dep that
- // does not have that dep in its own node_modules folder are considered
- // bundled deps as well. This list of names can be passed to npm-packlist
- // as the "bundled" argument. Additionally, packageJsonCache is shared so
- // packlist doesn't have to re-read files already consumed in this pass
- const fs = require('fs')
- const path = require('path')
- const EE = require('events').EventEmitter
- // we don't care about the package bins, but we share a pj cache
- // with other modules that DO care about it, so keep it nice.
- const normalizePackageBin = require('npm-normalize-package-bin')
- class BundleWalker extends EE {
- constructor (opt) {
- opt = opt || {}
- super(opt)
- this.path = path.resolve(opt.path || process.cwd())
- this.parent = opt.parent || null
- if (this.parent) {
- this.result = this.parent.result
- // only collect results in node_modules folders at the top level
- // since the node_modules in a bundled dep is included always
- if (!this.parent.parent) {
- const base = path.basename(this.path)
- const scope = path.basename(path.dirname(this.path))
- this.result.add(/^@/.test(scope) ? scope + '/' + base : base)
- }
- this.root = this.parent.root
- this.packageJsonCache = this.parent.packageJsonCache
- } else {
- this.result = new Set()
- this.root = this.path
- this.packageJsonCache = opt.packageJsonCache || new Map()
- }
- this.seen = new Set()
- this.didDone = false
- this.children = 0
- this.node_modules = []
- this.package = null
- this.bundle = null
- }
- addListener (ev, fn) {
- return this.on(ev, fn)
- }
- on (ev, fn) {
- const ret = super.on(ev, fn)
- if (ev === 'done' && this.didDone) {
- this.emit('done', this.result)
- }
- return ret
- }
- done () {
- if (!this.didDone) {
- this.didDone = true
- if (!this.parent) {
- const res = Array.from(this.result)
- this.result = res
- this.emit('done', res)
- } else {
- this.emit('done')
- }
- }
- }
- start () {
- const pj = path.resolve(this.path, 'package.json')
- if (this.packageJsonCache.has(pj))
- this.onPackage(this.packageJsonCache.get(pj))
- else
- this.readPackageJson(pj)
- return this
- }
- readPackageJson (pj) {
- fs.readFile(pj, (er, data) =>
- er ? this.done() : this.onPackageJson(pj, data))
- }
- onPackageJson (pj, data) {
- try {
- this.package = normalizePackageBin(JSON.parse(data + ''))
- } catch (er) {
- return this.done()
- }
- this.packageJsonCache.set(pj, this.package)
- this.onPackage(this.package)
- }
- allDepsBundled (pkg) {
- return Object.keys(pkg.dependencies || {}).concat(
- Object.keys(pkg.optionalDependencies || {}))
- }
- onPackage (pkg) {
- // all deps are bundled if we got here as a child.
- // otherwise, only bundle bundledDeps
- // Get a unique-ified array with a short-lived Set
- const bdRaw = this.parent ? this.allDepsBundled(pkg)
- : pkg.bundleDependencies || pkg.bundledDependencies || []
- const bd = Array.from(new Set(
- Array.isArray(bdRaw) ? bdRaw
- : bdRaw === true ? this.allDepsBundled(pkg)
- : Object.keys(bdRaw)))
- if (!bd.length)
- return this.done()
- this.bundle = bd
- const nm = this.path + '/node_modules'
- this.readModules()
- }
- readModules () {
- readdirNodeModules(this.path + '/node_modules', (er, nm) =>
- er ? this.onReaddir([]) : this.onReaddir(nm))
- }
- onReaddir (nm) {
- // keep track of what we have, in case children need it
- this.node_modules = nm
- this.bundle.forEach(dep => this.childDep(dep))
- if (this.children === 0)
- this.done()
- }
- childDep (dep) {
- if (this.node_modules.indexOf(dep) !== -1) {
- if (!this.seen.has(dep)) {
- this.seen.add(dep)
- this.child(dep)
- }
- } else if (this.parent) {
- this.parent.childDep(dep)
- }
- }
- child (dep) {
- const p = this.path + '/node_modules/' + dep
- this.children += 1
- const child = new BundleWalker({
- path: p,
- parent: this
- })
- child.on('done', _ => {
- if (--this.children === 0)
- this.done()
- })
- child.start()
- }
- }
- class BundleWalkerSync extends BundleWalker {
- constructor (opt) {
- super(opt)
- }
- start () {
- super.start()
- this.done()
- return this
- }
- readPackageJson (pj) {
- try {
- this.onPackageJson(pj, fs.readFileSync(pj))
- } catch (er) {}
- return this
- }
- readModules () {
- try {
- this.onReaddir(readdirNodeModulesSync(this.path + '/node_modules'))
- } catch (er) {
- this.onReaddir([])
- }
- }
- child (dep) {
- new BundleWalkerSync({
- path: this.path + '/node_modules/' + dep,
- parent: this
- }).start()
- }
- }
- const readdirNodeModules = (nm, cb) => {
- fs.readdir(nm, (er, set) => {
- if (er)
- cb(er)
- else {
- const scopes = set.filter(f => /^@/.test(f))
- if (!scopes.length)
- cb(null, set)
- else {
- const unscoped = set.filter(f => !/^@/.test(f))
- let count = scopes.length
- scopes.forEach(scope => {
- fs.readdir(nm + '/' + scope, (er, pkgs) => {
- if (er || !pkgs.length)
- unscoped.push(scope)
- else
- unscoped.push.apply(unscoped, pkgs.map(p => scope + '/' + p))
- if (--count === 0)
- cb(null, unscoped)
- })
- })
- }
- }
- })
- }
- const readdirNodeModulesSync = nm => {
- const set = fs.readdirSync(nm)
- const unscoped = set.filter(f => !/^@/.test(f))
- const scopes = set.filter(f => /^@/.test(f)).map(scope => {
- try {
- const pkgs = fs.readdirSync(nm + '/' + scope)
- return pkgs.length ? pkgs.map(p => scope + '/' + p) : [scope]
- } catch (er) {
- return [scope]
- }
- }).reduce((a, b) => a.concat(b), [])
- return unscoped.concat(scopes)
- }
- const walk = (options, callback) => {
- const p = new Promise((resolve, reject) => {
- new BundleWalker(options).on('done', resolve).on('error', reject).start()
- })
- return callback ? p.then(res => callback(null, res), callback) : p
- }
- const walkSync = options => {
- return new BundleWalkerSync(options).start().result
- }
- module.exports = walk
- walk.sync = walkSync
- walk.BundleWalker = BundleWalker
- walk.BundleWalkerSync = BundleWalkerSync
|