|
var fs = require('fs')
|
|
var rpj = require('read-package-json')
|
|
var path = require('path')
|
|
var dz = require('dezalgo')
|
|
var once = require('once')
|
|
var readdir = require('readdir-scoped-modules')
|
|
var debug = require('debuglog')('rpt')
|
|
|
|
function asyncForEach (items, todo, done) {
|
|
var remaining = items.length
|
|
if (remaining === 0) return done()
|
|
var seenErr
|
|
items.forEach(function (item) {
|
|
todo(item, handleComplete)
|
|
})
|
|
function handleComplete (err) {
|
|
if (seenErr) return
|
|
if (err) {
|
|
seenErr = true
|
|
return done(err)
|
|
}
|
|
if (--remaining === 0) done()
|
|
}
|
|
}
|
|
|
|
function dpath (p) {
|
|
if (!p) return ''
|
|
if (p.indexOf(process.cwd()) === 0) {
|
|
p = p.substr(process.cwd().length + 1)
|
|
}
|
|
return p
|
|
}
|
|
|
|
module.exports = rpt
|
|
|
|
rpt.Node = Node
|
|
rpt.Link = Link
|
|
|
|
var ID = 0
|
|
function Node (pkg, logical, physical, er, cache, fromLink) {
|
|
if (!(this instanceof Node)) {
|
|
return new Node(pkg, logical, physical, er, cache)
|
|
}
|
|
|
|
var node = cache[physical] || this
|
|
if (fromLink && cache[physical]) return cache[physical]
|
|
|
|
debug(node.constructor.name, dpath(physical), pkg && pkg._id)
|
|
|
|
const parent = path.dirname(logical)
|
|
if (parent[0] === '@') {
|
|
node.name = path.basename(parent) + '/' + path.basename(logical)
|
|
} else {
|
|
node.name = path.basename(logical)
|
|
}
|
|
node.path = logical
|
|
node.realpath = physical
|
|
node.error = er
|
|
if (!cache[physical]) {
|
|
node.id = ID++
|
|
node.package = pkg || {}
|
|
node.parent = null
|
|
node.isLink = false
|
|
node.children = []
|
|
}
|
|
return cache[physical] = node
|
|
}
|
|
|
|
Node.prototype.package = null
|
|
Node.prototype.path = ''
|
|
Node.prototype.realpath = ''
|
|
Node.prototype.children = null
|
|
Node.prototype.error = null
|
|
|
|
function Link (pkg, logical, physical, realpath, er, cache) {
|
|
if (cache[physical]) return cache[physical]
|
|
|
|
if (!(this instanceof Link)) {
|
|
return new Link(pkg, logical, physical, realpath, er, cache)
|
|
}
|
|
|
|
cache[physical] = this
|
|
|
|
debug(this.constructor.name, dpath(physical), pkg && pkg._id)
|
|
|
|
const dir = path.dirname(logical)
|
|
const parent = path.dirname(dir)
|
|
if (parent[0] === '@') {
|
|
this.name = path.basename(parent) + '/' + path.basename(dir)
|
|
} else {
|
|
this.name = path.basename(dir)
|
|
}
|
|
this.id = ID++
|
|
this.path = logical
|
|
this.realpath = realpath
|
|
this.package = pkg || {}
|
|
this.parent = null
|
|
this.target = new Node(this.package, logical, realpath, er, cache, true)
|
|
this.isLink = true
|
|
this.children = this.target.children
|
|
this.error = er
|
|
}
|
|
|
|
Link.prototype = Object.create(Node.prototype, {
|
|
constructor: { value: Link }
|
|
})
|
|
Link.prototype.target = null
|
|
Link.prototype.realpath = ''
|
|
|
|
function loadNode (logical, physical, cache, cb) {
|
|
debug('loadNode', dpath(logical))
|
|
return fs.realpath(physical, thenReadPackageJson)
|
|
|
|
var realpath
|
|
function thenReadPackageJson (er, real) {
|
|
if (er) {
|
|
var node = new Node(null, logical, physical, er, cache)
|
|
return cb(null, node)
|
|
}
|
|
debug('realpath l=%j p=%j real=%j', dpath(logical), dpath(physical), dpath(real))
|
|
var pj = path.join(real, 'package.json')
|
|
realpath = real
|
|
return rpj(pj, thenCreateNode)
|
|
}
|
|
function thenCreateNode (er, pkg) {
|
|
pkg = pkg || null
|
|
var node
|
|
if (physical === realpath) {
|
|
node = new Node(pkg, logical, physical, er, cache)
|
|
} else {
|
|
node = new Link(pkg, logical, physical, realpath, er, cache)
|
|
}
|
|
|
|
cb(null, node)
|
|
}
|
|
}
|
|
|
|
function loadChildren (node, cache, filterWith, cb) {
|
|
debug('loadChildren', dpath(node.path))
|
|
// needed 'cause we process all kids async-like and errors
|
|
// short circuit, so we have to be sure that after an error
|
|
// the cbs from other kids don't result in calling cb a second
|
|
// (or more) time.
|
|
cb = once(cb)
|
|
var nm = path.join(node.path, 'node_modules')
|
|
var rm
|
|
return fs.realpath(path.join(node.path, 'node_modules'), thenReaddir)
|
|
|
|
function thenReaddir (er, real_nm) {
|
|
if (er) return cb(null, node)
|
|
rm = real_nm
|
|
readdir(nm, thenLoadKids)
|
|
}
|
|
|
|
function thenLoadKids (er, kids) {
|
|
// If there are no children, that's fine, just return
|
|
if (er) return cb(null, node)
|
|
|
|
kids = kids.filter(function (kid) {
|
|
return kid[0] !== '.' && (!filterWith || filterWith(node, kid))
|
|
})
|
|
|
|
asyncForEach(kids, thenLoadNode, thenSortChildren)
|
|
}
|
|
function thenLoadNode (kid, done) {
|
|
var kidPath = path.join(nm, kid)
|
|
var kidRealPath = path.join(rm, kid)
|
|
loadNode(kidPath, kidRealPath, cache, andAddNode(done))
|
|
}
|
|
function andAddNode (done) {
|
|
return function (er, kid) {
|
|
if (er) return done(er)
|
|
node.children.push(kid)
|
|
kid.parent = node
|
|
done()
|
|
}
|
|
}
|
|
function thenSortChildren (er) {
|
|
sortChildren(node)
|
|
cb(er, node)
|
|
}
|
|
}
|
|
|
|
function sortChildren (node) {
|
|
node.children = node.children.sort(function (a, b) {
|
|
a = a.package.name ? a.package.name.toLowerCase() : a.path
|
|
b = b.package.name ? b.package.name.toLowerCase() : b.path
|
|
return a > b ? 1 : -1
|
|
})
|
|
}
|
|
|
|
function loadTree (node, did, cache, filterWith, cb) {
|
|
debug('loadTree', dpath(node.path), !!cache[node.path])
|
|
|
|
if (did[node.realpath]) {
|
|
return dz(cb)(null, node)
|
|
}
|
|
|
|
did[node.realpath] = true
|
|
|
|
// needed 'cause we process all kids async-like and errors
|
|
// short circuit, so we have to be sure that after an error
|
|
// the cbs from other kids don't result in calling cb a second
|
|
// (or more) time.
|
|
cb = once(cb)
|
|
return loadChildren(node, cache, filterWith, thenProcessChildren)
|
|
|
|
function thenProcessChildren (er, node) {
|
|
if (er) return cb(er)
|
|
|
|
var kids = node.children.filter(function (kid) {
|
|
return !did[kid.realpath]
|
|
})
|
|
|
|
return asyncForEach(kids, loadTreeForKid, cb)
|
|
}
|
|
function loadTreeForKid (kid, done) {
|
|
loadTree(kid, did, cache, filterWith, done)
|
|
}
|
|
}
|
|
|
|
function rpt (root, filterWith, cb) {
|
|
if (!cb) {
|
|
cb = filterWith
|
|
filterWith = null
|
|
}
|
|
var cache = Object.create(null)
|
|
var topErr
|
|
var tree
|
|
return fs.realpath(root, thenLoadNode)
|
|
|
|
function thenLoadNode (er, realRoot) {
|
|
if (er) return cb(er)
|
|
debug('rpt', dpath(realRoot))
|
|
loadNode(root, realRoot, cache, thenLoadTree)
|
|
}
|
|
function thenLoadTree(er, node) {
|
|
// even if there's an error, it's fine, as long as we got a node
|
|
if (node) {
|
|
topErr = er
|
|
tree = node
|
|
loadTree(node, {}, cache, filterWith, thenHandleErrors)
|
|
} else {
|
|
cb(er)
|
|
}
|
|
}
|
|
function thenHandleErrors (er) {
|
|
cb(topErr && topErr.code !== 'ENOENT' ? topErr : er, tree)
|
|
}
|
|
}
|