[6a3a178] | 1 | 'use strict'
|
---|
| 2 |
|
---|
| 3 | // walk the tree of deps starting from the top level list of bundled deps
|
---|
| 4 | // Any deps at the top level that are depended on by a bundled dep that
|
---|
| 5 | // does not have that dep in its own node_modules folder are considered
|
---|
| 6 | // bundled deps as well. This list of names can be passed to npm-packlist
|
---|
| 7 | // as the "bundled" argument. Additionally, packageJsonCache is shared so
|
---|
| 8 | // packlist doesn't have to re-read files already consumed in this pass
|
---|
| 9 |
|
---|
| 10 | const fs = require('fs')
|
---|
| 11 | const path = require('path')
|
---|
| 12 | const EE = require('events').EventEmitter
|
---|
| 13 | // we don't care about the package bins, but we share a pj cache
|
---|
| 14 | // with other modules that DO care about it, so keep it nice.
|
---|
| 15 | const normalizePackageBin = require('npm-normalize-package-bin')
|
---|
| 16 |
|
---|
| 17 | class BundleWalker extends EE {
|
---|
| 18 | constructor (opt) {
|
---|
| 19 | opt = opt || {}
|
---|
| 20 | super(opt)
|
---|
| 21 | this.path = path.resolve(opt.path || process.cwd())
|
---|
| 22 |
|
---|
| 23 | this.parent = opt.parent || null
|
---|
| 24 | if (this.parent) {
|
---|
| 25 | this.result = this.parent.result
|
---|
| 26 | // only collect results in node_modules folders at the top level
|
---|
| 27 | // since the node_modules in a bundled dep is included always
|
---|
| 28 | if (!this.parent.parent) {
|
---|
| 29 | const base = path.basename(this.path)
|
---|
| 30 | const scope = path.basename(path.dirname(this.path))
|
---|
| 31 | this.result.add(/^@/.test(scope) ? scope + '/' + base : base)
|
---|
| 32 | }
|
---|
| 33 | this.root = this.parent.root
|
---|
| 34 | this.packageJsonCache = this.parent.packageJsonCache
|
---|
| 35 | } else {
|
---|
| 36 | this.result = new Set()
|
---|
| 37 | this.root = this.path
|
---|
| 38 | this.packageJsonCache = opt.packageJsonCache || new Map()
|
---|
| 39 | }
|
---|
| 40 |
|
---|
| 41 | this.seen = new Set()
|
---|
| 42 | this.didDone = false
|
---|
| 43 | this.children = 0
|
---|
| 44 | this.node_modules = []
|
---|
| 45 | this.package = null
|
---|
| 46 | this.bundle = null
|
---|
| 47 | }
|
---|
| 48 |
|
---|
| 49 | addListener (ev, fn) {
|
---|
| 50 | return this.on(ev, fn)
|
---|
| 51 | }
|
---|
| 52 |
|
---|
| 53 | on (ev, fn) {
|
---|
| 54 | const ret = super.on(ev, fn)
|
---|
| 55 | if (ev === 'done' && this.didDone) {
|
---|
| 56 | this.emit('done', this.result)
|
---|
| 57 | }
|
---|
| 58 | return ret
|
---|
| 59 | }
|
---|
| 60 |
|
---|
| 61 | done () {
|
---|
| 62 | if (!this.didDone) {
|
---|
| 63 | this.didDone = true
|
---|
| 64 | if (!this.parent) {
|
---|
| 65 | const res = Array.from(this.result)
|
---|
| 66 | this.result = res
|
---|
| 67 | this.emit('done', res)
|
---|
| 68 | } else {
|
---|
| 69 | this.emit('done')
|
---|
| 70 | }
|
---|
| 71 | }
|
---|
| 72 | }
|
---|
| 73 |
|
---|
| 74 | start () {
|
---|
| 75 | const pj = path.resolve(this.path, 'package.json')
|
---|
| 76 | if (this.packageJsonCache.has(pj))
|
---|
| 77 | this.onPackage(this.packageJsonCache.get(pj))
|
---|
| 78 | else
|
---|
| 79 | this.readPackageJson(pj)
|
---|
| 80 | return this
|
---|
| 81 | }
|
---|
| 82 |
|
---|
| 83 | readPackageJson (pj) {
|
---|
| 84 | fs.readFile(pj, (er, data) =>
|
---|
| 85 | er ? this.done() : this.onPackageJson(pj, data))
|
---|
| 86 | }
|
---|
| 87 |
|
---|
| 88 | onPackageJson (pj, data) {
|
---|
| 89 | try {
|
---|
| 90 | this.package = normalizePackageBin(JSON.parse(data + ''))
|
---|
| 91 | } catch (er) {
|
---|
| 92 | return this.done()
|
---|
| 93 | }
|
---|
| 94 | this.packageJsonCache.set(pj, this.package)
|
---|
| 95 | this.onPackage(this.package)
|
---|
| 96 | }
|
---|
| 97 |
|
---|
| 98 | allDepsBundled (pkg) {
|
---|
| 99 | return Object.keys(pkg.dependencies || {}).concat(
|
---|
| 100 | Object.keys(pkg.optionalDependencies || {}))
|
---|
| 101 | }
|
---|
| 102 |
|
---|
| 103 | onPackage (pkg) {
|
---|
| 104 | // all deps are bundled if we got here as a child.
|
---|
| 105 | // otherwise, only bundle bundledDeps
|
---|
| 106 | // Get a unique-ified array with a short-lived Set
|
---|
| 107 | const bdRaw = this.parent ? this.allDepsBundled(pkg)
|
---|
| 108 | : pkg.bundleDependencies || pkg.bundledDependencies || []
|
---|
| 109 |
|
---|
| 110 | const bd = Array.from(new Set(
|
---|
| 111 | Array.isArray(bdRaw) ? bdRaw
|
---|
| 112 | : bdRaw === true ? this.allDepsBundled(pkg)
|
---|
| 113 | : Object.keys(bdRaw)))
|
---|
| 114 |
|
---|
| 115 | if (!bd.length)
|
---|
| 116 | return this.done()
|
---|
| 117 |
|
---|
| 118 | this.bundle = bd
|
---|
| 119 | const nm = this.path + '/node_modules'
|
---|
| 120 | this.readModules()
|
---|
| 121 | }
|
---|
| 122 |
|
---|
| 123 | readModules () {
|
---|
| 124 | readdirNodeModules(this.path + '/node_modules', (er, nm) =>
|
---|
| 125 | er ? this.onReaddir([]) : this.onReaddir(nm))
|
---|
| 126 | }
|
---|
| 127 |
|
---|
| 128 | onReaddir (nm) {
|
---|
| 129 | // keep track of what we have, in case children need it
|
---|
| 130 | this.node_modules = nm
|
---|
| 131 |
|
---|
| 132 | this.bundle.forEach(dep => this.childDep(dep))
|
---|
| 133 | if (this.children === 0)
|
---|
| 134 | this.done()
|
---|
| 135 | }
|
---|
| 136 |
|
---|
| 137 | childDep (dep) {
|
---|
| 138 | if (this.node_modules.indexOf(dep) !== -1) {
|
---|
| 139 | if (!this.seen.has(dep)) {
|
---|
| 140 | this.seen.add(dep)
|
---|
| 141 | this.child(dep)
|
---|
| 142 | }
|
---|
| 143 | } else if (this.parent) {
|
---|
| 144 | this.parent.childDep(dep)
|
---|
| 145 | }
|
---|
| 146 | }
|
---|
| 147 |
|
---|
| 148 | child (dep) {
|
---|
| 149 | const p = this.path + '/node_modules/' + dep
|
---|
| 150 | this.children += 1
|
---|
| 151 | const child = new BundleWalker({
|
---|
| 152 | path: p,
|
---|
| 153 | parent: this
|
---|
| 154 | })
|
---|
| 155 | child.on('done', _ => {
|
---|
| 156 | if (--this.children === 0)
|
---|
| 157 | this.done()
|
---|
| 158 | })
|
---|
| 159 | child.start()
|
---|
| 160 | }
|
---|
| 161 | }
|
---|
| 162 |
|
---|
| 163 | class BundleWalkerSync extends BundleWalker {
|
---|
| 164 | constructor (opt) {
|
---|
| 165 | super(opt)
|
---|
| 166 | }
|
---|
| 167 |
|
---|
| 168 | start () {
|
---|
| 169 | super.start()
|
---|
| 170 | this.done()
|
---|
| 171 | return this
|
---|
| 172 | }
|
---|
| 173 |
|
---|
| 174 | readPackageJson (pj) {
|
---|
| 175 | try {
|
---|
| 176 | this.onPackageJson(pj, fs.readFileSync(pj))
|
---|
| 177 | } catch (er) {}
|
---|
| 178 | return this
|
---|
| 179 | }
|
---|
| 180 |
|
---|
| 181 | readModules () {
|
---|
| 182 | try {
|
---|
| 183 | this.onReaddir(readdirNodeModulesSync(this.path + '/node_modules'))
|
---|
| 184 | } catch (er) {
|
---|
| 185 | this.onReaddir([])
|
---|
| 186 | }
|
---|
| 187 | }
|
---|
| 188 |
|
---|
| 189 | child (dep) {
|
---|
| 190 | new BundleWalkerSync({
|
---|
| 191 | path: this.path + '/node_modules/' + dep,
|
---|
| 192 | parent: this
|
---|
| 193 | }).start()
|
---|
| 194 | }
|
---|
| 195 | }
|
---|
| 196 |
|
---|
| 197 | const readdirNodeModules = (nm, cb) => {
|
---|
| 198 | fs.readdir(nm, (er, set) => {
|
---|
| 199 | if (er)
|
---|
| 200 | cb(er)
|
---|
| 201 | else {
|
---|
| 202 | const scopes = set.filter(f => /^@/.test(f))
|
---|
| 203 | if (!scopes.length)
|
---|
| 204 | cb(null, set)
|
---|
| 205 | else {
|
---|
| 206 | const unscoped = set.filter(f => !/^@/.test(f))
|
---|
| 207 | let count = scopes.length
|
---|
| 208 | scopes.forEach(scope => {
|
---|
| 209 | fs.readdir(nm + '/' + scope, (er, pkgs) => {
|
---|
| 210 | if (er || !pkgs.length)
|
---|
| 211 | unscoped.push(scope)
|
---|
| 212 | else
|
---|
| 213 | unscoped.push.apply(unscoped, pkgs.map(p => scope + '/' + p))
|
---|
| 214 | if (--count === 0)
|
---|
| 215 | cb(null, unscoped)
|
---|
| 216 | })
|
---|
| 217 | })
|
---|
| 218 | }
|
---|
| 219 | }
|
---|
| 220 | })
|
---|
| 221 | }
|
---|
| 222 |
|
---|
| 223 | const readdirNodeModulesSync = nm => {
|
---|
| 224 | const set = fs.readdirSync(nm)
|
---|
| 225 | const unscoped = set.filter(f => !/^@/.test(f))
|
---|
| 226 | const scopes = set.filter(f => /^@/.test(f)).map(scope => {
|
---|
| 227 | try {
|
---|
| 228 | const pkgs = fs.readdirSync(nm + '/' + scope)
|
---|
| 229 | return pkgs.length ? pkgs.map(p => scope + '/' + p) : [scope]
|
---|
| 230 | } catch (er) {
|
---|
| 231 | return [scope]
|
---|
| 232 | }
|
---|
| 233 | }).reduce((a, b) => a.concat(b), [])
|
---|
| 234 | return unscoped.concat(scopes)
|
---|
| 235 | }
|
---|
| 236 |
|
---|
| 237 | const walk = (options, callback) => {
|
---|
| 238 | const p = new Promise((resolve, reject) => {
|
---|
| 239 | new BundleWalker(options).on('done', resolve).on('error', reject).start()
|
---|
| 240 | })
|
---|
| 241 | return callback ? p.then(res => callback(null, res), callback) : p
|
---|
| 242 | }
|
---|
| 243 |
|
---|
| 244 | const walkSync = options => {
|
---|
| 245 | return new BundleWalkerSync(options).start().result
|
---|
| 246 | }
|
---|
| 247 |
|
---|
| 248 | module.exports = walk
|
---|
| 249 | walk.sync = walkSync
|
---|
| 250 | walk.BundleWalker = BundleWalker
|
---|
| 251 | walk.BundleWalkerSync = BundleWalkerSync
|
---|