1 | const fs = require('fs')
|
2 |
|
3 | const promisify = require('util').promisify || require('util-promisify')
|
4 | const { resolve, basename, dirname, join } = require('path')
|
5 | const rpj = promisify(require('read-package-json'))
|
6 | const readdir = promisify(require('readdir-scoped-modules'))
|
7 | const realpath = require('./realpath.js')
|
8 |
|
9 | let ID = 0
|
10 | class Node {
|
11 | constructor (pkg, logical, physical, er, cache) {
|
12 |
|
13 | const cached = cache.get(physical)
|
14 |
|
15 | if (cached && !cached.then)
|
16 | throw new Error('re-creating already instantiated node')
|
17 |
|
18 | cache.set(physical, this)
|
19 |
|
20 | const parent = basename(dirname(logical))
|
21 | if (parent.charAt(0) === '@')
|
22 | this.name = `${parent}/${basename(logical)}`
|
23 | else
|
24 | this.name = basename(logical)
|
25 | this.path = logical
|
26 | this.realpath = physical
|
27 | this.error = er
|
28 | this.id = ID++
|
29 | this.package = pkg || {}
|
30 | this.parent = null
|
31 | this.isLink = false
|
32 | this.children = []
|
33 | }
|
34 | }
|
35 |
|
36 | class Link extends Node {
|
37 | constructor (pkg, logical, physical, realpath, er, cache) {
|
38 | super(pkg, logical, physical, er, cache)
|
39 |
|
40 |
|
41 |
|
42 | const cachedTarget = cache.get(realpath)
|
43 | if (cachedTarget && cachedTarget.then)
|
44 | cachedTarget.then(node => {
|
45 | this.target = node
|
46 | this.children = node.children
|
47 | })
|
48 |
|
49 | this.target = cachedTarget || new Node(pkg, logical, realpath, er, cache)
|
50 | this.realpath = realpath
|
51 | this.isLink = true
|
52 | this.error = er
|
53 | this.children = this.target.children
|
54 | }
|
55 | }
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 | const newNode = (pkg, logical, physical, er, cache) =>
|
67 | process.env._TEST_RPT_SLOW_LINK_TARGET_ === '1'
|
68 | ? new Promise(res => setTimeout(() =>
|
69 | res(new Node(pkg, logical, physical, er, cache)), 10))
|
70 | : new Node(pkg, logical, physical, er, cache)
|
71 |
|
72 | const loadNode = (logical, physical, cache, rpcache, stcache) => {
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 | const cached = cache.get(physical)
|
79 |
|
80 | if (cached)
|
81 | return Promise.resolve(cached)
|
82 |
|
83 | const p = realpath(physical, rpcache, stcache, 0).then(real =>
|
84 | rpj(join(real, 'package.json'))
|
85 | .then(pkg => [pkg, null], er => [null, er])
|
86 | .then(([pkg, er]) =>
|
87 | physical === real ? newNode(pkg, logical, physical, er, cache)
|
88 | : new Link(pkg, logical, physical, real, er, cache)
|
89 | ),
|
90 |
|
91 | er => new Node(null, logical, physical, er, cache))
|
92 |
|
93 | cache.set(physical, p)
|
94 | return p
|
95 | }
|
96 |
|
97 | const loadChildren = (node, cache, filterWith, rpcache, stcache) => {
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 | const nm = join(node.path, 'node_modules')
|
110 | return realpath(nm, rpcache, stcache, 0)
|
111 | .then(rm => readdir(rm).then(kids => [rm, kids]))
|
112 | .then(([rm, kids]) => Promise.all(
|
113 | kids.filter(kid =>
|
114 | kid.charAt(0) !== '.' && (!filterWith || filterWith(node, kid)))
|
115 | .map(kid => loadNode(join(nm, kid), join(rm, kid), cache, rpcache, stcache)))
|
116 | ).then(kidNodes => {
|
117 | kidNodes.forEach(k => k.parent = node)
|
118 | node.children.push.apply(node.children, kidNodes.sort((a, b) =>
|
119 | (a.package.name ? a.package.name.toLowerCase() : a.path)
|
120 | .localeCompare(
|
121 | (b.package.name ? b.package.name.toLowerCase() : b.path)
|
122 | )))
|
123 | return node
|
124 | })
|
125 | .catch(() => node)
|
126 | }
|
127 |
|
128 | const loadTree = (node, did, cache, filterWith, rpcache, stcache) => {
|
129 |
|
130 |
|
131 | if (did.has(node.realpath))
|
132 | return Promise.resolve(node)
|
133 |
|
134 | did.add(node.realpath)
|
135 |
|
136 |
|
137 | return loadChildren(node, cache, filterWith, rpcache, stcache)
|
138 | .then(node => Promise.all(
|
139 | node.children
|
140 | .filter(kid => !did.has(kid.realpath))
|
141 | .map(kid => loadTree(kid, did, cache, filterWith, rpcache, stcache))
|
142 | )).then(() => node)
|
143 | }
|
144 |
|
145 |
|
146 | const rpt = (root, filterWith, cb) => {
|
147 | if (!cb && typeof filterWith === 'function') {
|
148 | cb = filterWith
|
149 | filterWith = null
|
150 | }
|
151 |
|
152 | const cache = new Map()
|
153 |
|
154 | const cwd = process.cwd()
|
155 | const rpcache = new Map([[ cwd, cwd ]])
|
156 | const stcache = new Map()
|
157 | const p = realpath(root, rpcache, stcache, 0)
|
158 | .then(realRoot => loadNode(root, realRoot, cache, rpcache, stcache))
|
159 | .then(node => loadTree(node, new Set(), cache, filterWith, rpcache, stcache))
|
160 |
|
161 | if (typeof cb === 'function')
|
162 | p.then(tree => cb(null, tree), cb)
|
163 |
|
164 | return p
|
165 | }
|
166 |
|
167 | rpt.Node = Node
|
168 | rpt.Link = Link
|
169 | module.exports = rpt
|