-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathutils.js
More file actions
385 lines (338 loc) · 10.9 KB
/
Copy pathutils.js
File metadata and controls
385 lines (338 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
const glob = require('glob');
const path = require('path');
const resolve = require('resolve');
/**
* If a package is designated as an app.
* @param {Object} pack The package
* @return {boolean} If the package is an app
*/
const isAppPackage = function(pack) {
return pack.build && pack.build.type === 'app';
};
/**
* If a package is designated as a config pack.
* @param {Object} pack The package
* @return {boolean} If the package is a config pack
*/
const isConfigPackage = function(pack) {
return pack.build && pack.build.type === 'config';
};
/**
* If a package is designated as a plugin
* @param {Object} pack The package
* @return {boolean} If the package is a plugin
*/
const isPluginPackage = function(pack) {
return pack.build && pack.build.type === 'plugin';
};
/**
* @param {Object} basePackage The alleged base package
* @param {Object} pluginPackage The alleged plugin package
* @param {Array=} optDepStack Dependancy stack to check pluginPackage is a plugin to the parent
* @return {boolean} If the plugin package is a plugin to the
* base package
*/
const isPluginOfPackage = function(basePackage, pluginPackage, optDepStack) {
if (optDepStack && isPluginPackage(pluginPackage)) {
// Grab the index of the current plugin
const pluginIndex = optDepStack.findIndex((item) => {
return item == pluginPackage.name;
});
// Get the parents name
const parentName = pluginIndex > 0 ? optDepStack[pluginIndex - 1] : '';
// If this is a plugin to the parent
if (pluginPackage.name.indexOf(parentName + '-') === 0) {
// If the parent IS the base package. Then this is a plugin of the package
if (parentName == basePackage.name) {
return true;
} else {
// Since this isnt a plugin to the base package, we only will accept it
// if the parent is a library
const parentPackage = getPackage(parentName);
return parentPackage ? parentPackage.build.type === 'lib' : false;
}
} else {
return false;
}
}
return isPluginPackage(pluginPackage) &&
pluginPackage.name.indexOf(basePackage.name + '-') === 0;
};
/**
* Get the real file system path from a glob path.
* @param {string} glob The glob path
* @return {string} The real path
*/
const realPath = function(glob) {
var wildIndex = glob.indexOf('*');
if (wildIndex) {
var prefix = glob.substr(0, wildIndex);
if (prefix) {
// resolve the portion of the path up to the first wildcard
var suffix = glob.substr(wildIndex);
return path.join(fs.realpathSync(prefix), suffix);
}
// started with a wildcard, return the original glob
return glob;
}
// no wildcard, use the original path
return fs.realpathSync(glob);
};
/**
* @param {string} filePath The path
* @return {string} The path or flattened path, whichever happens to exist
*/
const flattenPath = function(filePath) {
try {
filePath = realPath(filePath);
fs.accessSync(filePath, 'r');
return filePath;
} catch (e) {
}
return filePath.replace(/node_modules.*node_modules/, 'node_modules');
};
/**
* @param {Object<string, number>} map The map of files to priorities
* @return {function(a: string, b: string):number} compare function for sorting
*/
const getPrioritySort = function(map) {
/**
* @param {string} a First item
* @param {string} b Other item
* @return {number} per compare functions
*/
return function(a, b) {
var pa = map[a] || 0;
var pb = map[b] || 0;
return pa - pb;
};
};
/**
* @param {number} depth The depth to indent
* @return {string} The indent string
*/
const getIndent = function(depth) {
var indent = '';
for (var i = 1; i < depth; i++) {
indent += ' ';
}
if (depth > 0) {
indent += ' \u221F ';
}
return indent;
};
/**
* Get the `package.json` as a JSON object for a package.
* @param {string} packageName The package name.
* @return {Object|undefined} The resolved `package.json`, or undefined if not found.
*/
const getPackage = function(packageName) {
try {
return require(path.join(packageName, 'package.json'));
} catch (e) {
}
return undefined;
};
/**
* Resolve the absolute path for a file/directory under `node_modules`.
* @param {string} modulePath The relative path. Should begin with the module name.
* @param {string=} optBasedir Optional paths to resolve module location from.
* @return {string|undefined} The resolved path, or undefined if the module could not be found.
*/
const resolveModulePath = function(modulePath, optBasedir) {
try {
var parts = modulePath.split(/[\\\/]/);
if (parts && parts.length) {
// if the package is scoped, use the first two parts of the path. ie, @scope/package.
var packageName = parts[0].startsWith('@') ? path.join(parts.shift(), parts.shift()) : parts.shift();
var basePath = path.dirname(resolve.sync(path.join(packageName, 'package.json'), {
basedir: optBasedir
}));
// join the remaining path to the resource (if any)
return path.normalize(path.join(basePath, parts.join(path.sep)));
}
} catch (e) {
}
return undefined;
};
/**
* Search a list of files for lines matching a pattern.
* @param {!RegExp} pattern The pattern.
* @param {!Array<string>} files The file paths to search.
* @return {Promise<Array<Object>>} A promise that resolves to the matched files.
*/
const getMatchingLines = function(pattern, files) {
return Promise.reduce(files, function(matches, file) {
return fs.readFileAsync(file, 'utf8').then(function(content) {
var lines = content.split(/[\r\n]+/).filter(function(line) {
return pattern.test(line);
});
if (lines.length) {
matches.push({
file: file,
lines: lines
});
}
return matches;
});
}, []);
};
/**
* Search files in a directory and return lines matching a pattern.
* @param {RegExp} pattern The pattern to match.
* @param {string} directory The directory to search.
* @param {string|undefined} globPattern Glob pattern to filter the list of files to search.
* @return {Promise<Array<Object>>} A promise that resolves to the matched files.
*/
const findLines = function(pattern, directory, globPattern) {
globPattern = globPattern || '**/*';
return new Promise(function(resolve, reject) {
// find all files in the directory matching the glob pattern
glob(path.join(directory, globPattern), function(err, files) {
if (!err) {
// glob will always use backslashes, so map the files to use the system path separator
getMatchingLines(pattern, files.map((f) => path.resolve(f))).then(resolve);
} else {
// directory not found
resolve([]);
}
});
});
};
/**
* Get the sort priority for a package.
* @param {Object} pack The package.
* @param {number} depth The package depth.
* @param {Object} basePackage The base package.
* @return {number} The sort priority.
*/
const getPackagePriority = function(pack, depth, basePackage) {
//
// use priority if specified, so the load order can be controlled by the package.
// if no priority is present, use the resolved depth.
//
var priority = 0;
if (pack) {
if (pack.build && pack.build.priority !== undefined) {
priority = pack.build.priority;
} else {
priority = -depth * 10;
if (isConfigPackage(pack) ||
(isPluginPackage(pack) && isPluginOfPackage(basePackage, pack))) {
priority++;
}
}
}
return priority;
};
const groups = {
BASE: 0,
PLUGIN: 1000,
CONFIG: 10000
};
/**
* @param {?Array<string>} depStack
* @return {number}
*/
const getGroup = function(depStack) {
let group = groups.BASE;
if (depStack) {
let rootPackageName;
let pluginRegex;
let configRegex;
for (let i = 0, n = depStack.length; i < n; i++) {
if (i === 0) {
rootPackageName = depStack[i];
pluginRegex = new RegExp(`^${rootPackageName}-plugin-`);
configRegex = new RegExp(`^${rootPackageName}-config`);
} else {
if (pluginRegex && pluginRegex.test(depStack[i])) {
group = Math.max(group, groups.PLUGIN);
} else if (configRegex && configRegex.test(depStack[i])) {
group = Math.max(group, groups.CONFIG);
}
}
}
}
return group;
};
/**
* Sort config objects in ascending order.
* @param {Object} a First object
* @param {Object} b Second object
* @return {number} The sort order
*/
const priorityGroupDepthSort = function(a, b) {
const an = a.priority || a.group - a.depth;
const bn = b.priority || b.group - b.depth;
return an - bn;
};
/**
* @param {Array<Object>} list
* @return {function(Object, number, Array<string>):Promise}
*/
const getGroupDepthUpdater = function(list) {
return (pack, depth, depStack) => {
list.forEach(function(config) {
if (config.name === pack.name) {
const newGroup = getGroup(depStack);
if (newGroup < config.group) {
config.group = newGroup;
config.depth = depth;
} else if (newGroup === config.group) {
config.depth = Math.max(depth, config.depth);
}
}
});
return Promise.resolve();
};
};
/**
* Update dependencies that have already been resolved.
*
* @param {Object} dependencies The package dependencies.
* @param {Object} resolved Map of resolved dependencies.
* @param {number} depth The current depth.
* @param {Array<string>} depStack The current dependency stack.
* @param {Function} updater The update function.
* @param {Array<string>} updated Packages that have already been updated.
*
* @return {Promise} A promise that resolves when all dependencies have been updated.
*/
const updateDependencies = function(dependencies, resolved, depth, depStack, updater) {
const promises = [];
if (dependencies) {
for (const key in dependencies) {
const resolvedPack = resolved[key];
// Only update a package once to avoid dependency cycles
if (resolvedPack && resolvedPack.build && depStack.indexOf(resolvedPack.name) === -1) {
const newDepth = depth + 1;
const newDepStack = [...depStack, resolvedPack.name];
promises.push(updater(resolvedPack, newDepth, newDepStack));
promises.push(updateDependencies(resolvedPack.dependencies, resolved, newDepth, newDepStack,
updater));
}
}
}
return Promise.all(promises);
};
module.exports = {
findLines: findLines,
isAppPackage: isAppPackage,
isConfigPackage: isConfigPackage,
isPluginPackage: isPluginPackage,
isPluginOfPackage: isPluginOfPackage,
flattenPath: flattenPath,
Groups: groups,
getPrioritySort: getPrioritySort,
getGroup: getGroup,
getGroupDepthUpdater: getGroupDepthUpdater,
getIndent: getIndent,
getPackage: getPackage,
getPackagePriority: getPackagePriority,
priorityGroupDepthSort: priorityGroupDepthSort,
resolveModulePath: resolveModulePath,
updateDependencies: updateDependencies
};