1 | 'use strict';
|
2 |
|
3 | const os = require('os');
|
4 | const path = require('path');
|
5 | const pify = require('pify');
|
6 | const commondir = require('commondir');
|
7 | const walk = require('walkdir');
|
8 | const dependencyTree = require('dependency-tree');
|
9 | const log = require('./log');
|
10 |
|
11 | const stat = pify(require('fs').stat);
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | const isWin = (os.platform() === 'win32');
|
18 |
|
19 | class Tree {
|
20 | |
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 | constructor(srcPaths, config) {
|
29 | this.srcPaths = srcPaths.map((s) => path.resolve(s));
|
30 | log('using src paths %o', this.srcPaths);
|
31 |
|
32 | this.config = config;
|
33 | log('using config %o', this.config);
|
34 |
|
35 | return this.getDirs()
|
36 | .then(this.setBaseDir.bind(this))
|
37 | .then(this.getFiles.bind(this))
|
38 | .then(this.generateTree.bind(this));
|
39 | }
|
40 |
|
41 | |
42 |
|
43 |
|
44 |
|
45 | setBaseDir(dirs) {
|
46 | if (this.config.baseDir) {
|
47 | this.baseDir = path.resolve(this.config.baseDir);
|
48 | } else {
|
49 | this.baseDir = commondir(dirs);
|
50 | }
|
51 |
|
52 | log('using base directory %s', this.baseDir);
|
53 | }
|
54 |
|
55 | |
56 |
|
57 |
|
58 |
|
59 | getDirs() {
|
60 | return Promise
|
61 | .all(this.srcPaths.map((srcPath) => {
|
62 | return stat(srcPath)
|
63 | .then((stats) => stats.isDirectory() ? srcPath : path.dirname(path.resolve(srcPath)));
|
64 | }));
|
65 | }
|
66 |
|
67 | |
68 |
|
69 |
|
70 |
|
71 | getFiles() {
|
72 | const files = [];
|
73 |
|
74 | return Promise
|
75 | .all(this.srcPaths.map((srcPath) => {
|
76 | return stat(srcPath)
|
77 | .then((stats) => {
|
78 | if (stats.isFile()) {
|
79 | if (this.isGitPath(srcPath)) {
|
80 | return;
|
81 | }
|
82 |
|
83 | files.push(path.resolve(srcPath));
|
84 |
|
85 | return;
|
86 | }
|
87 |
|
88 | walk.sync(srcPath, (filePath, stat) => {
|
89 | if (this.isGitPath(filePath) || this.isNpmPath(filePath) || !stat.isFile()) {
|
90 | return;
|
91 | }
|
92 |
|
93 | const ext = path.extname(filePath).replace('.', '');
|
94 |
|
95 | if (files.indexOf(filePath) < 0 && this.config.fileExtensions.indexOf(ext) >= 0) {
|
96 | files.push(filePath);
|
97 | }
|
98 | });
|
99 | });
|
100 | }))
|
101 | .then(() => files);
|
102 | }
|
103 |
|
104 | |
105 |
|
106 |
|
107 |
|
108 |
|
109 | generateTree(files) {
|
110 | const depTree = {};
|
111 | const visited = {};
|
112 | const nonExistent = [];
|
113 | const npmPaths = {};
|
114 | const pathCache = {};
|
115 |
|
116 | files.forEach((file) => {
|
117 | if (visited[file]) {
|
118 | return;
|
119 | }
|
120 |
|
121 | Object.assign(depTree, dependencyTree({
|
122 | filename: file,
|
123 | directory: this.baseDir,
|
124 | requireConfig: this.config.requireConfig,
|
125 | webpackConfig: this.config.webpackConfig,
|
126 | tsConfig: this.config.tsConfig,
|
127 | visited: visited,
|
128 | filter: (dependencyFilePath, traversedFilePath) => {
|
129 | let dependencyFilterRes = true;
|
130 | const isNpmPath = this.isNpmPath(dependencyFilePath);
|
131 |
|
132 | if (this.isGitPath(dependencyFilePath)) {
|
133 | return false;
|
134 | }
|
135 |
|
136 | if (this.config.dependencyFilter) {
|
137 | dependencyFilterRes = this.config.dependencyFilter(dependencyFilePath, traversedFilePath, this.baseDir);
|
138 | }
|
139 |
|
140 | if (this.config.includeNpm && isNpmPath) {
|
141 | (npmPaths[traversedFilePath] = npmPaths[traversedFilePath] || []).push(dependencyFilePath);
|
142 | }
|
143 |
|
144 | return !isNpmPath && (dependencyFilterRes || dependencyFilterRes === undefined);
|
145 | },
|
146 | detective: this.config.detectiveOptions,
|
147 | nonExistent: nonExistent
|
148 | }));
|
149 | });
|
150 |
|
151 | let tree = this.convertTree(depTree, {}, pathCache, npmPaths);
|
152 |
|
153 | for (const npmKey in npmPaths) {
|
154 | const id = this.processPath(npmKey, pathCache);
|
155 |
|
156 | npmPaths[npmKey].forEach((npmPath) => {
|
157 | tree[id].push(this.processPath(npmPath, pathCache));
|
158 | });
|
159 | }
|
160 |
|
161 | if (this.config.excludeRegExp) {
|
162 | tree = this.exclude(tree, this.config.excludeRegExp);
|
163 | }
|
164 |
|
165 | return {
|
166 | tree: this.sort(tree),
|
167 | skipped: nonExistent
|
168 | };
|
169 | }
|
170 |
|
171 | |
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 | convertTree(depTree, tree, pathCache) {
|
180 | for (const key in depTree) {
|
181 | const id = this.processPath(key, pathCache);
|
182 |
|
183 | if (!tree[id]) {
|
184 | tree[id] = [];
|
185 |
|
186 | for (const dep in depTree[key]) {
|
187 | tree[id].push(this.processPath(dep, pathCache));
|
188 | }
|
189 |
|
190 | this.convertTree(depTree[key], tree, pathCache);
|
191 | }
|
192 | }
|
193 |
|
194 | return tree;
|
195 | }
|
196 |
|
197 | |
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 | processPath(absPath, cache) {
|
204 | if (cache[absPath]) {
|
205 | return cache[absPath];
|
206 | }
|
207 |
|
208 | let relPath = path.relative(this.baseDir, absPath);
|
209 |
|
210 | if (isWin) {
|
211 | relPath = relPath.replace(/\\/g, '/');
|
212 | }
|
213 |
|
214 | cache[absPath] = relPath;
|
215 |
|
216 | return relPath;
|
217 | }
|
218 |
|
219 | |
220 |
|
221 |
|
222 |
|
223 |
|
224 | isNpmPath(path) {
|
225 | return path.indexOf('node_modules') >= 0;
|
226 | }
|
227 |
|
228 | |
229 |
|
230 |
|
231 |
|
232 |
|
233 | isGitPath(filePath) {
|
234 | return filePath.split(path.sep).indexOf('.git') !== -1;
|
235 | }
|
236 |
|
237 | |
238 |
|
239 |
|
240 |
|
241 |
|
242 |
|
243 | exclude(tree, excludeRegExp) {
|
244 | const regExpList = excludeRegExp.map((re) => new RegExp(re));
|
245 |
|
246 | function regExpFilter(id) {
|
247 | return regExpList.findIndex((regexp) => regexp.test(id)) < 0;
|
248 | }
|
249 |
|
250 | return Object
|
251 | .keys(tree)
|
252 | .filter(regExpFilter)
|
253 | .reduce((acc, id) => {
|
254 | acc[id] = tree[id].filter(regExpFilter);
|
255 | return acc;
|
256 | }, {});
|
257 | }
|
258 |
|
259 | |
260 |
|
261 |
|
262 |
|
263 |
|
264 | sort(tree) {
|
265 | return Object
|
266 | .keys(tree)
|
267 | .sort()
|
268 | .reduce((acc, id) => {
|
269 | acc[id] = tree[id].sort();
|
270 | return acc;
|
271 | }, {});
|
272 | }
|
273 | }
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 |
|
280 |
|
281 | module.exports = (srcPaths, config) => new Tree(srcPaths, config);
|