rpt.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. var fs = require('fs')
  2. var rpj = require('read-package-json')
  3. var path = require('path')
  4. var dz = require('dezalgo')
  5. var once = require('once')
  6. var readdir = require('readdir-scoped-modules')
  7. var debug = require('debuglog')('rpt')
  8. function asyncForEach (items, todo, done) {
  9. var remaining = items.length
  10. if (remaining === 0) return done()
  11. var seenErr
  12. items.forEach(function (item) {
  13. todo(item, handleComplete)
  14. })
  15. function handleComplete (err) {
  16. if (seenErr) return
  17. if (err) {
  18. seenErr = true
  19. return done(err)
  20. }
  21. if (--remaining === 0) done()
  22. }
  23. }
  24. function dpath (p) {
  25. if (!p) return ''
  26. if (p.indexOf(process.cwd()) === 0) {
  27. p = p.substr(process.cwd().length + 1)
  28. }
  29. return p
  30. }
  31. module.exports = rpt
  32. rpt.Node = Node
  33. rpt.Link = Link
  34. var ID = 0
  35. function Node (pkg, logical, physical, er, cache, fromLink) {
  36. if (!(this instanceof Node)) {
  37. return new Node(pkg, logical, physical, er, cache)
  38. }
  39. var node = cache[physical] || this
  40. if (fromLink && cache[physical]) return cache[physical]
  41. debug(node.constructor.name, dpath(physical), pkg && pkg._id)
  42. const parent = path.dirname(logical)
  43. if (parent[0] === '@') {
  44. node.name = path.basename(parent) + '/' + path.basename(logical)
  45. } else {
  46. node.name = path.basename(logical)
  47. }
  48. node.path = logical
  49. node.realpath = physical
  50. node.error = er
  51. if (!cache[physical]) {
  52. node.id = ID++
  53. node.package = pkg || {}
  54. node.parent = null
  55. node.isLink = false
  56. node.children = []
  57. }
  58. return cache[physical] = node
  59. }
  60. Node.prototype.package = null
  61. Node.prototype.path = ''
  62. Node.prototype.realpath = ''
  63. Node.prototype.children = null
  64. Node.prototype.error = null
  65. function Link (pkg, logical, physical, realpath, er, cache) {
  66. if (cache[physical]) return cache[physical]
  67. if (!(this instanceof Link)) {
  68. return new Link(pkg, logical, physical, realpath, er, cache)
  69. }
  70. cache[physical] = this
  71. debug(this.constructor.name, dpath(physical), pkg && pkg._id)
  72. const dir = path.dirname(logical)
  73. const parent = path.dirname(dir)
  74. if (parent[0] === '@') {
  75. this.name = path.basename(parent) + '/' + path.basename(dir)
  76. } else {
  77. this.name = path.basename(dir)
  78. }
  79. this.id = ID++
  80. this.path = logical
  81. this.realpath = realpath
  82. this.package = pkg || {}
  83. this.parent = null
  84. this.target = new Node(this.package, logical, realpath, er, cache, true)
  85. this.isLink = true
  86. this.children = this.target.children
  87. this.error = er
  88. }
  89. Link.prototype = Object.create(Node.prototype, {
  90. constructor: { value: Link }
  91. })
  92. Link.prototype.target = null
  93. Link.prototype.realpath = ''
  94. function loadNode (logical, physical, cache, cb) {
  95. debug('loadNode', dpath(logical))
  96. return fs.realpath(physical, thenReadPackageJson)
  97. var realpath
  98. function thenReadPackageJson (er, real) {
  99. if (er) {
  100. var node = new Node(null, logical, physical, er, cache)
  101. return cb(null, node)
  102. }
  103. debug('realpath l=%j p=%j real=%j', dpath(logical), dpath(physical), dpath(real))
  104. var pj = path.join(real, 'package.json')
  105. realpath = real
  106. return rpj(pj, thenCreateNode)
  107. }
  108. function thenCreateNode (er, pkg) {
  109. pkg = pkg || null
  110. var node
  111. if (physical === realpath) {
  112. node = new Node(pkg, logical, physical, er, cache)
  113. } else {
  114. node = new Link(pkg, logical, physical, realpath, er, cache)
  115. }
  116. cb(null, node)
  117. }
  118. }
  119. function loadChildren (node, cache, filterWith, cb) {
  120. debug('loadChildren', dpath(node.path))
  121. // needed 'cause we process all kids async-like and errors
  122. // short circuit, so we have to be sure that after an error
  123. // the cbs from other kids don't result in calling cb a second
  124. // (or more) time.
  125. cb = once(cb)
  126. var nm = path.join(node.path, 'node_modules')
  127. var rm
  128. return fs.realpath(path.join(node.path, 'node_modules'), thenReaddir)
  129. function thenReaddir (er, real_nm) {
  130. if (er) return cb(null, node)
  131. rm = real_nm
  132. readdir(nm, thenLoadKids)
  133. }
  134. function thenLoadKids (er, kids) {
  135. // If there are no children, that's fine, just return
  136. if (er) return cb(null, node)
  137. kids = kids.filter(function (kid) {
  138. return kid[0] !== '.' && (!filterWith || filterWith(node, kid))
  139. })
  140. asyncForEach(kids, thenLoadNode, thenSortChildren)
  141. }
  142. function thenLoadNode (kid, done) {
  143. var kidPath = path.join(nm, kid)
  144. var kidRealPath = path.join(rm, kid)
  145. loadNode(kidPath, kidRealPath, cache, andAddNode(done))
  146. }
  147. function andAddNode (done) {
  148. return function (er, kid) {
  149. if (er) return done(er)
  150. node.children.push(kid)
  151. kid.parent = node
  152. done()
  153. }
  154. }
  155. function thenSortChildren (er) {
  156. sortChildren(node)
  157. cb(er, node)
  158. }
  159. }
  160. function sortChildren (node) {
  161. node.children = node.children.sort(function (a, b) {
  162. a = a.package.name ? a.package.name.toLowerCase() : a.path
  163. b = b.package.name ? b.package.name.toLowerCase() : b.path
  164. return a > b ? 1 : -1
  165. })
  166. }
  167. function loadTree (node, did, cache, filterWith, cb) {
  168. debug('loadTree', dpath(node.path), !!cache[node.path])
  169. if (did[node.realpath]) {
  170. return dz(cb)(null, node)
  171. }
  172. did[node.realpath] = true
  173. // needed 'cause we process all kids async-like and errors
  174. // short circuit, so we have to be sure that after an error
  175. // the cbs from other kids don't result in calling cb a second
  176. // (or more) time.
  177. cb = once(cb)
  178. return loadChildren(node, cache, filterWith, thenProcessChildren)
  179. function thenProcessChildren (er, node) {
  180. if (er) return cb(er)
  181. var kids = node.children.filter(function (kid) {
  182. return !did[kid.realpath]
  183. })
  184. return asyncForEach(kids, loadTreeForKid, cb)
  185. }
  186. function loadTreeForKid (kid, done) {
  187. loadTree(kid, did, cache, filterWith, done)
  188. }
  189. }
  190. function rpt (root, filterWith, cb) {
  191. if (!cb) {
  192. cb = filterWith
  193. filterWith = null
  194. }
  195. var cache = Object.create(null)
  196. var topErr
  197. var tree
  198. return fs.realpath(root, thenLoadNode)
  199. function thenLoadNode (er, realRoot) {
  200. if (er) return cb(er)
  201. debug('rpt', dpath(realRoot))
  202. loadNode(root, realRoot, cache, thenLoadTree)
  203. }
  204. function thenLoadTree(er, node) {
  205. // even if there's an error, it's fine, as long as we got a node
  206. if (node) {
  207. topErr = er
  208. tree = node
  209. loadTree(node, {}, cache, filterWith, thenHandleErrors)
  210. } else {
  211. cb(er)
  212. }
  213. }
  214. function thenHandleErrors (er) {
  215. cb(topErr && topErr.code !== 'ENOENT' ? topErr : er, tree)
  216. }
  217. }