|
1 | 1 | import * as path from "path";
|
2 |
| -import * as fs from "fs"; |
| 2 | +import { NODE_MODULES_FOLDER_NAME, PACKAGE_JSON_FILE_NAME } from "../../constants"; |
3 | 3 |
|
4 |
| -export class NodeModulesDependenciesBuilder implements INodeModulesDependenciesBuilder { |
5 |
| - private projectPath: string; |
6 |
| - private rootNodeModulesPath: string; |
7 |
| - private resolvedDependencies: any[]; |
8 |
| - private seen: any; |
9 |
| - |
10 |
| - public constructor(private $fs: IFileSystem) { |
11 |
| - this.seen = {}; |
12 |
| - this.resolvedDependencies = []; |
13 |
| - } |
14 |
| - |
15 |
| - public getProductionDependencies(projectPath: string): any[] { |
16 |
| - this.projectPath = projectPath; |
17 |
| - this.rootNodeModulesPath = path.join(this.projectPath, "node_modules"); |
18 |
| - |
19 |
| - let projectPackageJsonpath = path.join(this.projectPath, "package.json"); |
20 |
| - let packageJsonContent = this.$fs.readJson(projectPackageJsonpath); |
21 |
| - |
22 |
| - _.keys(packageJsonContent.dependencies).forEach(dependencyName => { |
23 |
| - let depth = 0; |
24 |
| - let directory = path.join(this.rootNodeModulesPath, dependencyName); |
25 |
| - |
26 |
| - // find and traverse child with name `key`, parent's directory -> dep.directory |
27 |
| - this.traverseDependency(dependencyName, directory, depth); |
28 |
| - }); |
29 |
| - |
30 |
| - return this.resolvedDependencies; |
31 |
| - } |
| 4 | +interface IDependencyDescription { |
| 5 | + parentDir: string; |
| 6 | + name: string; |
| 7 | + depth: number; |
| 8 | +} |
32 | 9 |
|
33 |
| - private traverseDependency(name: string, currentModulePath: string, depth: number): void { |
34 |
| - // Check if child has been extracted in the parent's node modules, AND THEN in `node_modules` |
35 |
| - // Slower, but prevents copying wrong versions if multiple of the same module are installed |
36 |
| - // Will also prevent copying project's devDependency's version if current module depends on another version |
37 |
| - let modulePath = path.join(currentModulePath, "node_modules", name); // node_modules/parent/node_modules/<package> |
38 |
| - let alternativeModulePath = path.join(this.rootNodeModulesPath, name); |
| 10 | +export class NodeModulesDependenciesBuilder implements INodeModulesDependenciesBuilder { |
| 11 | + public constructor(private $fs: IFileSystem) { } |
| 12 | + |
| 13 | + public getProductionDependencies(projectPath: string): IDependencyData[] { |
| 14 | + const rootNodeModulesPath = path.join(projectPath, NODE_MODULES_FOLDER_NAME); |
| 15 | + const projectPackageJsonPath = path.join(projectPath, PACKAGE_JSON_FILE_NAME); |
| 16 | + const packageJsonContent = this.$fs.readJson(projectPackageJsonPath); |
| 17 | + const dependencies = packageJsonContent && packageJsonContent.dependencies; |
| 18 | + |
| 19 | + let resolvedDependencies: IDependencyData[] = []; |
| 20 | + |
| 21 | + let queue: IDependencyDescription[] = _.keys(dependencies) |
| 22 | + .map(dependencyName => ({ |
| 23 | + parentDir: projectPath, |
| 24 | + name: dependencyName, |
| 25 | + depth: 0 |
| 26 | + })); |
| 27 | + |
| 28 | + while (queue.length) { |
| 29 | + const currentModule = queue.shift(); |
| 30 | + const resolvedDependency = this.findModule(rootNodeModulesPath, currentModule.parentDir, currentModule.name, currentModule.depth, resolvedDependencies); |
| 31 | + |
| 32 | + if (resolvedDependency && !_.some(resolvedDependencies, r => r.directory === resolvedDependency.directory)) { |
| 33 | + _.each(resolvedDependency.dependencies, d => { |
| 34 | + const dependency: IDependencyDescription = { name: d, parentDir: resolvedDependency.directory, depth: resolvedDependency.depth + 1 }; |
| 35 | + |
| 36 | + const shouldAdd = !_.some(queue, element => |
| 37 | + element.name === dependency.name && |
| 38 | + element.parentDir === dependency.parentDir && |
| 39 | + element.depth === dependency.depth); |
| 40 | + |
| 41 | + if (shouldAdd) { |
| 42 | + queue.push(dependency); |
| 43 | + } |
| 44 | + }); |
| 45 | + |
| 46 | + resolvedDependencies.push(resolvedDependency); |
| 47 | + } |
| 48 | + } |
39 | 49 |
|
40 |
| - this.findModule(modulePath, alternativeModulePath, name, depth); |
| 50 | + return resolvedDependencies; |
41 | 51 | }
|
42 | 52 |
|
43 |
| - private findModule(modulePath: string, alternativeModulePath: string, name: string, depth: number) { |
44 |
| - let exists = this.moduleExists(modulePath); |
| 53 | + private findModule(rootNodeModulesPath: string, parentModulePath: string, name: string, depth: number, resolvedDependencies: IDependencyData[]): IDependencyData { |
| 54 | + let modulePath = path.join(parentModulePath, NODE_MODULES_FOLDER_NAME, name); // node_modules/parent/node_modules/<package> |
| 55 | + const rootModulesPath = path.join(rootNodeModulesPath, name); |
| 56 | + let depthInNodeModules = depth; |
45 | 57 |
|
46 |
| - if (exists) { |
47 |
| - if (this.seen[modulePath]) { |
48 |
| - return; |
| 58 | + if (!this.moduleExists(modulePath)) { |
| 59 | + modulePath = rootModulesPath; // /node_modules/<package> |
| 60 | + if (!this.moduleExists(modulePath)) { |
| 61 | + return null; |
49 | 62 | }
|
50 | 63 |
|
51 |
| - let dependency = this.addDependency(name, modulePath, depth + 1); |
52 |
| - this.readModuleDependencies(modulePath, depth + 1, dependency); |
53 |
| - } else { |
54 |
| - modulePath = alternativeModulePath; // /node_modules/<package> |
55 |
| - exists = this.moduleExists(modulePath); |
| 64 | + depthInNodeModules = 0; |
| 65 | + } |
56 | 66 |
|
57 |
| - if (!exists) { |
58 |
| - return; |
59 |
| - } |
| 67 | + if (_.some(resolvedDependencies, r => r.name === name && r.directory === modulePath)) { |
| 68 | + return null; |
60 | 69 |
|
61 |
| - if (this.seen[modulePath]) { |
62 |
| - return; |
63 |
| - } |
64 |
| - |
65 |
| - let dependency = this.addDependency(name, modulePath, 0); |
66 |
| - this.readModuleDependencies(modulePath, 0, dependency); |
67 | 70 | }
|
68 | 71 |
|
69 |
| - this.seen[modulePath] = true; |
| 72 | + return this.getDependencyData(name, modulePath, depthInNodeModules); |
70 | 73 | }
|
71 | 74 |
|
72 |
| - private readModuleDependencies(modulePath: string, depth: number, currentModule: any): void { |
73 |
| - let packageJsonPath = path.join(modulePath, 'package.json'); |
74 |
| - let packageJsonExists = fs.lstatSync(packageJsonPath).isFile(); |
| 75 | + private getDependencyData(name: string, directory: string, depth: number): IDependencyData { |
| 76 | + const dependency: IDependencyData = { |
| 77 | + name, |
| 78 | + directory, |
| 79 | + depth |
| 80 | + }; |
| 81 | + |
| 82 | + const packageJsonPath = path.join(directory, PACKAGE_JSON_FILE_NAME); |
| 83 | + const packageJsonExists = this.$fs.getLsStats(packageJsonPath).isFile(); |
75 | 84 |
|
76 | 85 | if (packageJsonExists) {
|
77 | 86 | let packageJsonContents = this.$fs.readJson(packageJsonPath);
|
78 | 87 |
|
79 | 88 | if (!!packageJsonContents.nativescript) {
|
80 | 89 | // add `nativescript` property, necessary for resolving plugins
|
81 |
| - currentModule.nativescript = packageJsonContents.nativescript; |
| 90 | + dependency.nativescript = packageJsonContents.nativescript; |
82 | 91 | }
|
83 | 92 |
|
84 |
| - _.keys(packageJsonContents.dependencies).forEach((dependencyName) => { |
85 |
| - this.traverseDependency(dependencyName, modulePath, depth); |
86 |
| - }); |
| 93 | + dependency.dependencies = _.keys(packageJsonContents.dependencies); |
| 94 | + return dependency; |
87 | 95 | }
|
88 |
| - } |
89 | 96 |
|
90 |
| - private addDependency(name: string, directory: string, depth: number): any { |
91 |
| - let dependency: any = { |
92 |
| - name, |
93 |
| - directory, |
94 |
| - depth |
95 |
| - }; |
96 |
| - |
97 |
| - this.resolvedDependencies.push(dependency); |
98 |
| - |
99 |
| - return dependency; |
| 97 | + return null; |
100 | 98 | }
|
101 | 99 |
|
102 | 100 | private moduleExists(modulePath: string): boolean {
|
103 | 101 | try {
|
104 |
| - let exists = fs.lstatSync(modulePath); |
105 |
| - if (exists.isSymbolicLink()) { |
106 |
| - exists = fs.lstatSync(fs.realpathSync(modulePath)); |
| 102 | + let modulePathLsStat = this.$fs.getLsStats(modulePath); |
| 103 | + if (modulePathLsStat.isSymbolicLink()) { |
| 104 | + modulePathLsStat = this.$fs.getLsStats(this.$fs.realpath(modulePath)); |
107 | 105 | }
|
108 |
| - return exists.isDirectory(); |
| 106 | + |
| 107 | + return modulePathLsStat.isDirectory(); |
109 | 108 | } catch (e) {
|
110 | 109 | return false;
|
111 | 110 | }
|
|
0 commit comments