Skip to content

Flatten scoped dependencies of dependency #1781

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 81 additions & 76 deletions lib/tools/broccoli/node-modules-dest-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import Future = require("fibers/future");
* and tees a copy to the given path outside the tmp dir.
*/
export class DestCopy implements IBroccoliPlugin {
private dependencies: IDictionary<any> = null;
private devDependencies: IDictionary<any> = null;
private dependencies: IDictionary<any> = null;
private devDependencies: IDictionary<any> = null;

constructor(
private inputPath: string,
Expand All @@ -33,72 +33,72 @@ export class DestCopy implements IBroccoliPlugin {
this.devDependencies = this.getDevDependencies(projectDir);
}

public rebuildChangedDirectories(changedDirectories: string[], platform: string): void {
_.each(changedDirectories, changedDirectoryAbsolutePath => {
if(!this.devDependencies[path.basename(changedDirectoryAbsolutePath)]) {
let pathToPackageJson = path.join(changedDirectoryAbsolutePath, constants.PACKAGE_JSON_FILE_NAME);
let packageJsonFiles = fs.existsSync(pathToPackageJson) ? [pathToPackageJson] : [];
let nodeModulesFolderPath = path.join(changedDirectoryAbsolutePath, constants.NODE_MODULES_FOLDER_NAME);
packageJsonFiles = packageJsonFiles.concat(this.enumeratePackageJsonFilesSync(nodeModulesFolderPath));

_.each(packageJsonFiles, packageJsonFilePath => {
let fileContent = require(packageJsonFilePath);

if(!this.devDependencies[fileContent.name]) { // Don't flatten dev dependencies
let currentDependency = {
name: fileContent.name,
version: fileContent.version,
directory: path.dirname(packageJsonFilePath),
nativescript: fileContent.nativescript
};

let addedDependency = this.dependencies[currentDependency.name];
if (addedDependency) {
if (semver.gt(currentDependency.version, addedDependency.version)) {
let currentDependencyMajorVersion = semver.major(currentDependency.version);
let addedDependencyMajorVersion = semver.major(addedDependency.version);

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}`;
let logger = $injector.resolve("$logger");
currentDependencyMajorVersion === addedDependencyMajorVersion ? logger.out(message) : logger.warn(message);

public rebuildChangedDirectories(changedDirectories: string[], platform: string): void {
_.each(changedDirectories, changedDirectoryAbsolutePath => {
if (!this.devDependencies[path.basename(changedDirectoryAbsolutePath)]) {
let pathToPackageJson = path.join(changedDirectoryAbsolutePath, constants.PACKAGE_JSON_FILE_NAME);
let packageJsonFiles = fs.existsSync(pathToPackageJson) ? [pathToPackageJson] : [];
let nodeModulesFolderPath = path.join(changedDirectoryAbsolutePath, constants.NODE_MODULES_FOLDER_NAME);
packageJsonFiles = packageJsonFiles.concat(this.enumeratePackageJsonFilesSync(nodeModulesFolderPath));

_.each(packageJsonFiles, packageJsonFilePath => {
let fileContent = require(packageJsonFilePath);

if (!this.devDependencies[fileContent.name]) { // Don't flatten dev dependencies
let currentDependency = {
name: fileContent.name,
version: fileContent.version,
directory: path.dirname(packageJsonFilePath),
nativescript: fileContent.nativescript
};

let addedDependency = this.dependencies[currentDependency.name];
if (addedDependency) {
if (semver.gt(currentDependency.version, addedDependency.version)) {
let currentDependencyMajorVersion = semver.major(currentDependency.version);
let addedDependencyMajorVersion = semver.major(addedDependency.version);

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}`;
let logger = $injector.resolve("$logger");
currentDependencyMajorVersion === addedDependencyMajorVersion ? logger.out(message) : logger.warn(message);

this.dependencies[currentDependency.name] = currentDependency;
}
} else {
this.dependencies[currentDependency.name] = currentDependency;
}
} else {
this.dependencies[currentDependency.name] = currentDependency;
}
}
});
}
});
});
}
});

_.each(this.dependencies, dependency => {
this.copyDependencyDir(dependency);
_.each(this.dependencies, dependency => {
this.copyDependencyDir(dependency);

let isPlugin = !!dependency.nativescript;
if(isPlugin) {
this.$pluginsService.prepare(dependency, platform).wait();
}
let isPlugin = !!dependency.nativescript;
if (isPlugin) {
this.$pluginsService.prepare(dependency, platform).wait();
}

if (dependency.name === constants.TNS_CORE_MODULES_NAME) {
let tnsCoreModulesResourcePath = path.join(this.outputRoot, constants.TNS_CORE_MODULES_NAME);
if (dependency.name === constants.TNS_CORE_MODULES_NAME) {
let tnsCoreModulesResourcePath = path.join(this.outputRoot, constants.TNS_CORE_MODULES_NAME);

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

shelljs.cp("-Rf", path.join(tnsCoreModulesResourcePath, "*"), this.outputRoot);
this.$fs.deleteDirectory(tnsCoreModulesResourcePath).wait();
}
});
shelljs.cp("-Rf", path.join(tnsCoreModulesResourcePath, "*"), this.outputRoot);
this.$fs.deleteDirectory(tnsCoreModulesResourcePath).wait();
}
});

if(!_.isEmpty(this.dependencies)) {
this.$platformsData.getPlatformData(platform).platformProjectService.afterPrepareAllPlugins().wait();
if (!_.isEmpty(this.dependencies)) {
this.$platformsData.getPlatformData(platform).platformProjectService.afterPrepareAllPlugins().wait();
}
}
}

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

public rebuild(treeDiff: IDiffResult): void {
this.rebuildChangedDirectories(treeDiff.changedDirectories, "");
public rebuild(treeDiff: IDiffResult): void {
this.rebuildChangedDirectories(treeDiff.changedDirectories, "");

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

private getDevDependencies(projectDir: string): IDictionary<any> {
let projectFilePath = path.join(projectDir, constants.PACKAGE_JSON_FILE_NAME);
let projectFileContent = require(projectFilePath);
return projectFileContent.devDependencies || {};
}
private getDevDependencies(projectDir: string): IDictionary<any> {
let projectFilePath = path.join(projectDir, constants.PACKAGE_JSON_FILE_NAME);
let projectFileContent = require(projectFilePath);
return projectFileContent.devDependencies || {};
}

private enumeratePackageJsonFilesSync(nodeModulesDirectoryPath: string, foundFiles?: string[]): string[] {
private enumeratePackageJsonFilesSync(nodeModulesDirectoryPath: string, foundFiles?: string[]): string[] {
foundFiles = foundFiles || [];
if(fs.existsSync(nodeModulesDirectoryPath)) {
if (fs.existsSync(nodeModulesDirectoryPath)) {
let contents = fs.readdirSync(nodeModulesDirectoryPath);
for (let i = 0; i < contents.length; ++i) {
let packageJsonFilePath = path.join(nodeModulesDirectoryPath, contents[i], constants.PACKAGE_JSON_FILE_NAME);
let moduleName = contents[i];
let moduleDirectoryInNodeModules = path.join(nodeModulesDirectoryPath, moduleName);
let packageJsonFilePath = path.join(moduleDirectoryInNodeModules, constants.PACKAGE_JSON_FILE_NAME);
if (fs.existsSync(packageJsonFilePath)) {
foundFiles.push(packageJsonFilePath);
}

let directoryPath = path.join(nodeModulesDirectoryPath, contents[i], constants.NODE_MODULES_FOLDER_NAME);
let directoryPath = path.join(moduleDirectoryInNodeModules, constants.NODE_MODULES_FOLDER_NAME);
if (fs.existsSync(directoryPath)) {
this.enumeratePackageJsonFilesSync(directoryPath, foundFiles);
} else if (fs.statSync(moduleDirectoryInNodeModules).isDirectory()) {
// Some modules can be grouped in one folder and we need to enumerate them too (e.g. @angular).
this.enumeratePackageJsonFilesSync(moduleDirectoryInNodeModules, foundFiles);
}
}
}
return foundFiles;
}
}
}

export default wrapBroccoliPlugin(DestCopy);
Binary file added resources/test/plugin-with-scoped-dependency.zip
Binary file not shown.
35 changes: 28 additions & 7 deletions test/npm-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import shelljs = require("shelljs");
temp.track();

let assert = require("chai").assert;
let nodeModulesFolderName = "node_modules";

function createTestInjector(): IInjector {
let testInjector = new yok.Yok();
Expand Down Expand Up @@ -171,7 +172,7 @@ function addDependencies(testInjector: IInjector, projectFolder: string, depende
let currentDependencies = packageJsonData.dependencies;
_.extend(currentDependencies, dependencies);

if(devDependencies) {
if (devDependencies) {
let currentDevDependencies = packageJsonData.devDependencies;
_.extend(currentDevDependencies, devDependencies);
}
Expand All @@ -194,7 +195,7 @@ describe("Npm support tests", () => {
});
it("Ensures that the installed dependencies are prepared correctly", () => {
// Setup
addDependencies(testInjector, projectFolder, {"bplist": "0.0.4"}).wait();
addDependencies(testInjector, projectFolder, { "bplist": "0.0.4" }).wait();

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

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

it("Ensures that scoped dependencies are prepared correctly when are not in root level", () => {
// Setup
let customPluginName = "plugin-with-scoped-dependency";
let customPluginDirectory = temp.mkdirSync("custom-plugin-directory");

let fs: IFileSystem = testInjector.resolve("fs");
fs.unzip(path.join("resources", "test", `${customPluginName}.zip`), customPluginDirectory).wait();

addDependencies(testInjector, projectFolder, { "plugin-with-scoped-dependency": `file:${path.join(customPluginDirectory, customPluginName)}` }).wait();

// Act
preparePlatform(testInjector).wait();

// Assert
let tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules");

let scopedDependencyPath = path.join(tnsModulesFolderPath, "@scoped-plugin", "inner-plugin");
assert.isTrue(fs.exists(scopedDependencyPath).wait());
});

it("Ensures that tns_modules absent when bundling", () => {
let fs = testInjector.resolve("fs");
let lockfile = testInjector.resolve("lockfile");
Expand Down Expand Up @@ -294,19 +315,19 @@ describe("Flatten npm modules tests", () => {
assert.isFalse(fs.exists(gulpJshint).wait());

// Get all gulp dependencies
let gulpDependencies = fs.readDirectory(path.join(projectFolder, "node_modules", "gulp", "node_modules")).wait();
let gulpDependencies = fs.readDirectory(path.join(projectFolder, nodeModulesFolderName, "gulp", nodeModulesFolderName)).wait();
_.each(gulpDependencies, dependency => {
assert.isFalse(fs.exists(path.join(tnsModulesFolderPath, dependency)).wait());
});

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

// Get all gulp-jshint dependencies
let gulpJshintDependencies = fs.readDirectory(path.join(projectFolder, "node_modules", "gulp-jshint", "node_modules")).wait();
let gulpJshintDependencies = fs.readDirectory(path.join(projectFolder, nodeModulesFolderName, "gulp-jshint", nodeModulesFolderName)).wait();
_.each(gulpJshintDependencies, dependency => {
assert.isFalse(fs.exists(path.join(tnsModulesFolderPath, dependency)).wait());
});
Expand Down