Skip to content

Commit 78de759

Browse files
Flatten scoped dependencies of dependency
When some dependency of the project has scoped dependency we need to enumerate all the subdirectories to flatten all dependencies correctly.
1 parent b956140 commit 78de759

File tree

3 files changed

+109
-83
lines changed

3 files changed

+109
-83
lines changed

lib/tools/broccoli/node-modules-dest-copy.ts

+81-76
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import Future = require("fibers/future");
1515
* and tees a copy to the given path outside the tmp dir.
1616
*/
1717
export class DestCopy implements IBroccoliPlugin {
18-
private dependencies: IDictionary<any> = null;
19-
private devDependencies: IDictionary<any> = null;
18+
private dependencies: IDictionary<any> = null;
19+
private devDependencies: IDictionary<any> = null;
2020

2121
constructor(
2222
private inputPath: string,
@@ -33,72 +33,72 @@ export class DestCopy implements IBroccoliPlugin {
3333
this.devDependencies = this.getDevDependencies(projectDir);
3434
}
3535

36-
public rebuildChangedDirectories(changedDirectories: string[], platform: string): void {
37-
_.each(changedDirectories, changedDirectoryAbsolutePath => {
38-
if(!this.devDependencies[path.basename(changedDirectoryAbsolutePath)]) {
39-
let pathToPackageJson = path.join(changedDirectoryAbsolutePath, constants.PACKAGE_JSON_FILE_NAME);
40-
let packageJsonFiles = fs.existsSync(pathToPackageJson) ? [pathToPackageJson] : [];
41-
let nodeModulesFolderPath = path.join(changedDirectoryAbsolutePath, constants.NODE_MODULES_FOLDER_NAME);
42-
packageJsonFiles = packageJsonFiles.concat(this.enumeratePackageJsonFilesSync(nodeModulesFolderPath));
43-
44-
_.each(packageJsonFiles, packageJsonFilePath => {
45-
let fileContent = require(packageJsonFilePath);
46-
47-
if(!this.devDependencies[fileContent.name]) { // Don't flatten dev dependencies
48-
let currentDependency = {
49-
name: fileContent.name,
50-
version: fileContent.version,
51-
directory: path.dirname(packageJsonFilePath),
52-
nativescript: fileContent.nativescript
53-
};
54-
55-
let addedDependency = this.dependencies[currentDependency.name];
56-
if (addedDependency) {
57-
if (semver.gt(currentDependency.version, addedDependency.version)) {
58-
let currentDependencyMajorVersion = semver.major(currentDependency.version);
59-
let addedDependencyMajorVersion = semver.major(addedDependency.version);
60-
61-
let message = `The depedency located at ${addedDependency.directory} with version ${addedDependency.version} will be replaced with dependency located at ${currentDependency.directory} with version ${currentDependency.version}`;
62-
let logger = $injector.resolve("$logger");
63-
currentDependencyMajorVersion === addedDependencyMajorVersion ? logger.out(message) : logger.warn(message);
64-
36+
public rebuildChangedDirectories(changedDirectories: string[], platform: string): void {
37+
_.each(changedDirectories, changedDirectoryAbsolutePath => {
38+
if (!this.devDependencies[path.basename(changedDirectoryAbsolutePath)]) {
39+
let pathToPackageJson = path.join(changedDirectoryAbsolutePath, constants.PACKAGE_JSON_FILE_NAME);
40+
let packageJsonFiles = fs.existsSync(pathToPackageJson) ? [pathToPackageJson] : [];
41+
let nodeModulesFolderPath = path.join(changedDirectoryAbsolutePath, constants.NODE_MODULES_FOLDER_NAME);
42+
packageJsonFiles = packageJsonFiles.concat(this.enumeratePackageJsonFilesSync(nodeModulesFolderPath));
43+
44+
_.each(packageJsonFiles, packageJsonFilePath => {
45+
let fileContent = require(packageJsonFilePath);
46+
47+
if (!this.devDependencies[fileContent.name]) { // Don't flatten dev dependencies
48+
let currentDependency = {
49+
name: fileContent.name,
50+
version: fileContent.version,
51+
directory: path.dirname(packageJsonFilePath),
52+
nativescript: fileContent.nativescript
53+
};
54+
55+
let addedDependency = this.dependencies[currentDependency.name];
56+
if (addedDependency) {
57+
if (semver.gt(currentDependency.version, addedDependency.version)) {
58+
let currentDependencyMajorVersion = semver.major(currentDependency.version);
59+
let addedDependencyMajorVersion = semver.major(addedDependency.version);
60+
61+
let message = `The depedency located at ${addedDependency.directory} with version ${addedDependency.version} will be replaced with dependency located at ${currentDependency.directory} with version ${currentDependency.version}`;
62+
let logger = $injector.resolve("$logger");
63+
currentDependencyMajorVersion === addedDependencyMajorVersion ? logger.out(message) : logger.warn(message);
64+
65+
this.dependencies[currentDependency.name] = currentDependency;
66+
}
67+
} else {
6568
this.dependencies[currentDependency.name] = currentDependency;
6669
}
67-
} else {
68-
this.dependencies[currentDependency.name] = currentDependency;
6970
}
70-
}
71-
});
72-
}
73-
});
71+
});
72+
}
73+
});
7474

75-
_.each(this.dependencies, dependency => {
76-
this.copyDependencyDir(dependency);
75+
_.each(this.dependencies, dependency => {
76+
this.copyDependencyDir(dependency);
7777

78-
let isPlugin = !!dependency.nativescript;
79-
if(isPlugin) {
80-
this.$pluginsService.prepare(dependency, platform).wait();
81-
}
78+
let isPlugin = !!dependency.nativescript;
79+
if (isPlugin) {
80+
this.$pluginsService.prepare(dependency, platform).wait();
81+
}
8282

83-
if (dependency.name === constants.TNS_CORE_MODULES_NAME) {
84-
let tnsCoreModulesResourcePath = path.join(this.outputRoot, constants.TNS_CORE_MODULES_NAME);
83+
if (dependency.name === constants.TNS_CORE_MODULES_NAME) {
84+
let tnsCoreModulesResourcePath = path.join(this.outputRoot, constants.TNS_CORE_MODULES_NAME);
8585

86-
// Remove .ts files
87-
let allFiles = this.$fs.enumerateFilesInDirectorySync(tnsCoreModulesResourcePath);
88-
let deleteFilesFutures = allFiles.filter(file => minimatch(file, "**/*.ts", {nocase: true})).map(file => this.$fs.deleteFile(file));
89-
Future.wait(deleteFilesFutures);
86+
// Remove .ts files
87+
let allFiles = this.$fs.enumerateFilesInDirectorySync(tnsCoreModulesResourcePath);
88+
let deleteFilesFutures = allFiles.filter(file => minimatch(file, "**/*.ts", { nocase: true })).map(file => this.$fs.deleteFile(file));
89+
Future.wait(deleteFilesFutures);
9090

91-
shelljs.cp("-Rf", path.join(tnsCoreModulesResourcePath, "*"), this.outputRoot);
92-
this.$fs.deleteDirectory(tnsCoreModulesResourcePath).wait();
93-
}
94-
});
91+
shelljs.cp("-Rf", path.join(tnsCoreModulesResourcePath, "*"), this.outputRoot);
92+
this.$fs.deleteDirectory(tnsCoreModulesResourcePath).wait();
93+
}
94+
});
9595

96-
if(!_.isEmpty(this.dependencies)) {
97-
this.$platformsData.getPlatformData(platform).platformProjectService.afterPrepareAllPlugins().wait();
96+
if (!_.isEmpty(this.dependencies)) {
97+
this.$platformsData.getPlatformData(platform).platformProjectService.afterPrepareAllPlugins().wait();
98+
}
9899
}
99-
}
100100

101-
private copyDependencyDir(dependency: any): void {
101+
private copyDependencyDir(dependency: any): void {
102102
let dependencyDir = path.dirname(dependency.name || "");
103103
let insideNpmScope = /^@/.test(dependencyDir);
104104
let targetDir = this.outputRoot;
@@ -108,42 +108,47 @@ export class DestCopy implements IBroccoliPlugin {
108108
shelljs.mkdir("-p", targetDir);
109109
shelljs.cp("-Rf", dependency.directory, targetDir);
110110
shelljs.rm("-rf", path.join(targetDir, dependency.name, "node_modules"));
111-
}
111+
}
112112

113-
public rebuild(treeDiff: IDiffResult): void {
114-
this.rebuildChangedDirectories(treeDiff.changedDirectories, "");
113+
public rebuild(treeDiff: IDiffResult): void {
114+
this.rebuildChangedDirectories(treeDiff.changedDirectories, "");
115115

116-
// Cache input tree
117-
let projectFilePath = path.join(this.projectDir, constants.PACKAGE_JSON_FILE_NAME);
118-
let projectFileContent = require(projectFilePath);
119-
projectFileContent[constants.NATIVESCRIPT_KEY_NAME][constants.NODE_MODULE_CACHE_PATH_KEY_NAME] = this.inputPath;
120-
fs.writeFileSync(projectFilePath, JSON.stringify(projectFileContent, null, "\t"), { encoding: "utf8" });
121-
}
116+
// Cache input tree
117+
let projectFilePath = path.join(this.projectDir, constants.PACKAGE_JSON_FILE_NAME);
118+
let projectFileContent = require(projectFilePath);
119+
projectFileContent[constants.NATIVESCRIPT_KEY_NAME][constants.NODE_MODULE_CACHE_PATH_KEY_NAME] = this.inputPath;
120+
fs.writeFileSync(projectFilePath, JSON.stringify(projectFileContent, null, "\t"), { encoding: "utf8" });
121+
}
122122

123-
private getDevDependencies(projectDir: string): IDictionary<any> {
124-
let projectFilePath = path.join(projectDir, constants.PACKAGE_JSON_FILE_NAME);
125-
let projectFileContent = require(projectFilePath);
126-
return projectFileContent.devDependencies || {};
127-
}
123+
private getDevDependencies(projectDir: string): IDictionary<any> {
124+
let projectFilePath = path.join(projectDir, constants.PACKAGE_JSON_FILE_NAME);
125+
let projectFileContent = require(projectFilePath);
126+
return projectFileContent.devDependencies || {};
127+
}
128128

129-
private enumeratePackageJsonFilesSync(nodeModulesDirectoryPath: string, foundFiles?: string[]): string[] {
129+
private enumeratePackageJsonFilesSync(nodeModulesDirectoryPath: string, foundFiles?: string[]): string[] {
130130
foundFiles = foundFiles || [];
131-
if(fs.existsSync(nodeModulesDirectoryPath)) {
131+
if (fs.existsSync(nodeModulesDirectoryPath)) {
132132
let contents = fs.readdirSync(nodeModulesDirectoryPath);
133133
for (let i = 0; i < contents.length; ++i) {
134-
let packageJsonFilePath = path.join(nodeModulesDirectoryPath, contents[i], constants.PACKAGE_JSON_FILE_NAME);
134+
let moduleName = contents[i];
135+
let moduleDirectoryInNodeModules = path.join(nodeModulesDirectoryPath, moduleName);
136+
let packageJsonFilePath = path.join(moduleDirectoryInNodeModules, constants.PACKAGE_JSON_FILE_NAME);
135137
if (fs.existsSync(packageJsonFilePath)) {
136138
foundFiles.push(packageJsonFilePath);
137139
}
138140

139-
let directoryPath = path.join(nodeModulesDirectoryPath, contents[i], constants.NODE_MODULES_FOLDER_NAME);
141+
let directoryPath = path.join(moduleDirectoryInNodeModules, constants.NODE_MODULES_FOLDER_NAME);
140142
if (fs.existsSync(directoryPath)) {
141143
this.enumeratePackageJsonFilesSync(directoryPath, foundFiles);
144+
} else if (fs.statSync(moduleDirectoryInNodeModules).isDirectory()) {
145+
// Some modules can be grouped in one folder and we need to enumerate them too (e.g. @angular).
146+
this.enumeratePackageJsonFilesSync(moduleDirectoryInNodeModules, foundFiles);
142147
}
143148
}
144149
}
145150
return foundFiles;
146-
}
151+
}
147152
}
148153

149154
export default wrapBroccoliPlugin(DestCopy);
1.69 KB
Binary file not shown.

test/npm-support.ts

+28-7
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import shelljs = require("shelljs");
3737
temp.track();
3838

3939
let assert = require("chai").assert;
40+
let nodeModulesFolderName = "node_modules";
4041

4142
function createTestInjector(): IInjector {
4243
let testInjector = new yok.Yok();
@@ -171,7 +172,7 @@ function addDependencies(testInjector: IInjector, projectFolder: string, depende
171172
let currentDependencies = packageJsonData.dependencies;
172173
_.extend(currentDependencies, dependencies);
173174

174-
if(devDependencies) {
175+
if (devDependencies) {
175176
let currentDevDependencies = packageJsonData.devDependencies;
176177
_.extend(currentDevDependencies, devDependencies);
177178
}
@@ -194,7 +195,7 @@ describe("Npm support tests", () => {
194195
});
195196
it("Ensures that the installed dependencies are prepared correctly", () => {
196197
// Setup
197-
addDependencies(testInjector, projectFolder, {"bplist": "0.0.4"}).wait();
198+
addDependencies(testInjector, projectFolder, { "bplist": "0.0.4" }).wait();
198199

199200
// Act
200201
preparePlatform(testInjector).wait();
@@ -216,15 +217,15 @@ describe("Npm support tests", () => {
216217
// Setup
217218
let fs = testInjector.resolve("fs");
218219
let scopedName = "@reactivex/rxjs";
219-
let scopedModule = path.join(projectFolder, "node_modules", "@reactivex/rxjs");
220+
let scopedModule = path.join(projectFolder, nodeModulesFolderName, "@reactivex/rxjs");
220221
let scopedPackageJson = path.join(scopedModule, "package.json");
221222
let dependencies: any = {};
222223
dependencies[scopedName] = "0.0.0-prealpha.3";
223224
// Do not pass dependencies object as the sinopia cannot work with scoped dependencies. Instead move them manually.
224225
addDependencies(testInjector, projectFolder, {}).wait();
225226
//create module dir, and add a package.json
226227
shelljs.mkdir("-p", scopedModule);
227-
fs.writeFile(scopedPackageJson, JSON.stringify({name: scopedName})).wait();
228+
fs.writeFile(scopedPackageJson, JSON.stringify({ name: scopedName })).wait();
228229

229230
// Act
230231
preparePlatform(testInjector).wait();
@@ -236,6 +237,26 @@ describe("Npm support tests", () => {
236237
assert.isTrue(fs.exists(scopedDependencyPath).wait());
237238
});
238239

240+
it("Ensures that scoped dependencies are prepared correctly when are not in root level", () => {
241+
// Setup
242+
let customPluginName = "plugin-with-scoped-dependency";
243+
let customPluginDirectory = temp.mkdirSync("custom-plugin-directory");
244+
245+
let fs: IFileSystem = testInjector.resolve("fs");
246+
fs.unzip(path.join("resources", "test", `${customPluginName}.zip`), customPluginDirectory).wait();
247+
248+
addDependencies(testInjector, projectFolder, { "plugin-with-scoped-dependency": `file:${path.join(customPluginDirectory, customPluginName)}` }).wait();
249+
250+
// Act
251+
preparePlatform(testInjector).wait();
252+
253+
// Assert
254+
let tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules");
255+
256+
let scopedDependencyPath = path.join(tnsModulesFolderPath, "@scoped-plugin", "inner-plugin");
257+
assert.isTrue(fs.exists(scopedDependencyPath).wait());
258+
});
259+
239260
it("Ensures that tns_modules absent when bundling", () => {
240261
let fs = testInjector.resolve("fs");
241262
let lockfile = testInjector.resolve("lockfile");
@@ -294,19 +315,19 @@ describe("Flatten npm modules tests", () => {
294315
assert.isFalse(fs.exists(gulpJshint).wait());
295316

296317
// Get all gulp dependencies
297-
let gulpDependencies = fs.readDirectory(path.join(projectFolder, "node_modules", "gulp", "node_modules")).wait();
318+
let gulpDependencies = fs.readDirectory(path.join(projectFolder, nodeModulesFolderName, "gulp", nodeModulesFolderName)).wait();
298319
_.each(gulpDependencies, dependency => {
299320
assert.isFalse(fs.exists(path.join(tnsModulesFolderPath, dependency)).wait());
300321
});
301322

302323
// Get all gulp-jscs dependencies
303-
let gulpJscsDependencies = fs.readDirectory(path.join(projectFolder, "node_modules", "gulp-jscs", "node_modules")).wait();
324+
let gulpJscsDependencies = fs.readDirectory(path.join(projectFolder, nodeModulesFolderName, "gulp-jscs", nodeModulesFolderName)).wait();
304325
_.each(gulpJscsDependencies, dependency => {
305326
assert.isFalse(fs.exists(path.join(tnsModulesFolderPath, dependency)).wait());
306327
});
307328

308329
// Get all gulp-jshint dependencies
309-
let gulpJshintDependencies = fs.readDirectory(path.join(projectFolder, "node_modules", "gulp-jshint", "node_modules")).wait();
330+
let gulpJshintDependencies = fs.readDirectory(path.join(projectFolder, nodeModulesFolderName, "gulp-jshint", nodeModulesFolderName)).wait();
310331
_.each(gulpJshintDependencies, dependency => {
311332
assert.isFalse(fs.exists(path.join(tnsModulesFolderPath, dependency)).wait());
312333
});

0 commit comments

Comments
 (0)