From fdec58ff8aae86083055b6d29811ced798953634 Mon Sep 17 00:00:00 2001 From: plamen5kov Date: Mon, 17 Oct 2016 17:30:43 +0300 Subject: [PATCH 1/5] run npm list --prod command and get all dependency names --- .../node-modules/node-modules-builder.ts | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/tools/node-modules/node-modules-builder.ts b/lib/tools/node-modules/node-modules-builder.ts index 05840c9795..0b914d7745 100644 --- a/lib/tools/node-modules/node-modules-builder.ts +++ b/lib/tools/node-modules/node-modules-builder.ts @@ -10,7 +10,7 @@ import {sleep} from "../../../lib/common/helpers"; let glob = require("glob"); export class NodeModulesBuilder implements INodeModulesBuilder { - constructor( + constructor(private $childProcess: IChildProcess, private $fs: IFileSystem, private $projectData: IProjectData, private $projectDataService: IProjectDataService, @@ -133,6 +133,13 @@ export class NodeModulesBuilder implements INodeModulesBuilder { // Force copying if the destination doesn't exist. lastModifiedTime = null; } + + //TODO: plamen5kov: WIP + let depJsonStr = this.$childProcess.exec("npm list --prod --json").wait(); + let prodDependenciesJson = JSON.parse(depJsonStr); + let productionDepArray = this.getProductionDependencyNames(prodDependenciesJson); + + let nodeModules = this.getChangedNodeModules(absoluteOutputPath, platform, lastModifiedTime).wait(); const resolver = new NpmDependencyResolver(this.$projectData.projectDir); @@ -152,6 +159,29 @@ export class NodeModulesBuilder implements INodeModulesBuilder { }).future()(); } + private getProductionDependencyNames(inputJson: any): any { + var finalDependencies:any = {}; + var queue:any = []; + + inputJson.level = 0; + queue.push(inputJson) + + while(queue.length > 0) { + var parent = queue.pop(); + + if(parent.dependencies) { + for(var dep in parent.dependencies) { + var currentDependency = parent.dependencies[dep]; + currentDependency.level = parent.level + 1; + queue.push(currentDependency); + finalDependencies[dep] = currentDependency; + } + } + } + + return finalDependencies; + } + public cleanNodeModules(absoluteOutputPath: string, platform: string): void { shelljs.rm("-rf", absoluteOutputPath); } From 77f86a3b59a783c7439f2689f7bcd91f56ffd1d6 Mon Sep 17 00:00:00 2001 From: Peter Kanev Date: Thu, 20 Oct 2016 03:51:29 +0300 Subject: [PATCH 2/5] add npm-independent traversal to get project's production dependencies copy production node_modules as-is to the project's tns_modules --- .../node-modules/node-modules-builder.ts | 166 +++++++++++++++--- .../node-modules/node-modules-dest-copy.ts | 113 ++---------- 2 files changed, 155 insertions(+), 124 deletions(-) diff --git a/lib/tools/node-modules/node-modules-builder.ts b/lib/tools/node-modules/node-modules-builder.ts index 0b914d7745..13980dfc22 100644 --- a/lib/tools/node-modules/node-modules-builder.ts +++ b/lib/tools/node-modules/node-modules-builder.ts @@ -3,9 +3,9 @@ import * as fs from "fs"; import * as path from "path"; import * as shelljs from "shelljs"; import Future = require("fibers/future"); -import {NpmDependencyResolver, TnsModulesCopy, NpmPluginPrepare} from "./node-modules-dest-copy"; +import { TnsModulesCopy, NpmPluginPrepare } from "./node-modules-dest-copy"; import * as fiberBootstrap from "../../common/fiber-bootstrap"; -import {sleep} from "../../../lib/common/helpers"; +import { sleep } from "../../../lib/common/helpers"; let glob = require("glob"); @@ -37,7 +37,7 @@ export class NodeModulesBuilder implements INodeModulesBuilder { }, (er: Error, files: string[]) => { fiberBootstrap.run(() => { - while(this.$lockfile.check().wait()) { + while (this.$lockfile.check().wait()) { sleep(10); } @@ -85,7 +85,7 @@ export class NodeModulesBuilder implements INodeModulesBuilder { let intervalId = setInterval(() => { fiberBootstrap.run(() => { if (!this.$lockfile.check().wait() || future.isResolved()) { - if(!future.isResolved()) { + if (!future.isResolved()) { future.return(); } clearInterval(intervalId); @@ -133,57 +133,165 @@ export class NodeModulesBuilder implements INodeModulesBuilder { // Force copying if the destination doesn't exist. lastModifiedTime = null; } - - //TODO: plamen5kov: WIP - let depJsonStr = this.$childProcess.exec("npm list --prod --json").wait(); - let prodDependenciesJson = JSON.parse(depJsonStr); - let productionDepArray = this.getProductionDependencyNames(prodDependenciesJson); + let productionDependencies = this.getProductionDependencies(this.$projectData.projectDir); - let nodeModules = this.getChangedNodeModules(absoluteOutputPath, platform, lastModifiedTime).wait(); + // console.log(productionDependencies); - const resolver = new NpmDependencyResolver(this.$projectData.projectDir); - const resolvedDependencies = resolver.resolveDependencies(_.keys(nodeModules), platform); + // TODO: Pip3r4o - result is not used currently + // let nodeModules = this.getChangedNodeModules(absoluteOutputPath, platform, lastModifiedTime).wait(); if (!this.$options.bundle) { const tnsModulesCopy = this.$injector.resolve(TnsModulesCopy, { outputRoot: absoluteOutputPath }); - tnsModulesCopy.copyModules(resolvedDependencies, platform); + tnsModulesCopy.copyModules(productionDependencies, platform); } else { this.cleanNodeModules(absoluteOutputPath, platform); } const npmPluginPrepare = this.$injector.resolve(NpmPluginPrepare, {}); - npmPluginPrepare.preparePlugins(resolvedDependencies, platform); + npmPluginPrepare.preparePlugins(productionDependencies, platform); }).future()(); } - private getProductionDependencyNames(inputJson: any): any { - var finalDependencies:any = {}; - var queue:any = []; + public getProductionDependencies(projectPath: string) { + var deps: any = []; + var seen: any = {}; - inputJson.level = 0; - queue.push(inputJson) + var pJson = path.join(projectPath, "package.json"); + var nodeModulesDir = path.join(projectPath, "node_modules"); - while(queue.length > 0) { - var parent = queue.pop(); + var content = require(pJson); - if(parent.dependencies) { - for(var dep in parent.dependencies) { - var currentDependency = parent.dependencies[dep]; - currentDependency.level = parent.level + 1; - queue.push(currentDependency); - finalDependencies[dep] = currentDependency; + Object.keys(content.dependencies).forEach((key) => { + var depth = 0; + var directory = path.join(nodeModulesDir, key); + + // find and traverse child with name `key`, parent's directory -> dep.directory + traverseChild(key, directory, depth); + }); + + return filterUniqueDirectories(deps); + + function traverseChild(name: string, currentModulePath: string, depth: number) { + // check if key appears in a scoped module dependency + var isScoped = name.indexOf('@') === 0; + + if (!isScoped) { + // Check if child has been extracted in the parent's node modules, AND THEN in `node_modules` + // Slower, but prevents copying wrong versions if multiple of the same module are installed + // Will also prevent copying project's devDependency's version if current module depends on another version + var modulePath = path.join(currentModulePath, "node_modules", name); // /node_modules/parent/node_modules/ + var exists = ensureModuleExists(modulePath); + + if (exists) { + var dep = addDependency(deps, name, modulePath, depth + 1); + + traverseModule(modulePath, depth + 1, dep); + } else { + modulePath = path.join(nodeModulesDir, name); // /node_modules/ + exists = ensureModuleExists(modulePath); + + if(!exists) { + return; + } + + var dep = addDependency(deps, name, modulePath, 0); + + traverseModule(modulePath, 0, dep); + } + + } + // module is scoped + else { + var scopeSeparatorIndex = name.indexOf('/'); + var scope = name.substring(0, scopeSeparatorIndex); + var moduleName = name.substring(scopeSeparatorIndex + 1, name.length); + var scopedModulePath = path.join(nodeModulesDir, scope, moduleName); + + var exists = ensureModuleExists(scopedModulePath); + + if (exists) { + var dep = addDependency(deps, name, scopedModulePath, 0); + traverseModule(scopedModulePath, depth, dep); + } + else { + scopedModulePath = path.join(currentModulePath, "node_modules", scope, moduleName); + + exists = ensureModuleExists(scopedModulePath); + + if (!exists) { + return; + } + + var dep = addDependency(deps, name, scopedModulePath, depth + 1); + traverseModule(scopedModulePath, depth + 1, dep); + } + } + + function traverseModule(modulePath: string, depth: number, currentDependency: any) { + var packageJsonPath = path.join(modulePath, 'package.json'); + var packageJsonExists = fs.lstatSync(packageJsonPath).isFile(); + + if (packageJsonExists) { + var packageJsonContents = require(packageJsonPath); + + if (!!packageJsonContents.nativescript) { + // add `nativescript` property, necessary for resolving plugins + currentDependency.nativescript = packageJsonContents.nativescript; + } + + if (packageJsonContents.dependencies) { + Object.keys(packageJsonContents.dependencies).forEach((key) => { + + traverseChild(key, modulePath, depth); + }); + } + } + } + + function addDependency(deps: any[], name: string, directory: string, depth: number) { + var dep: any = {}; + dep.name = name; + dep.directory = directory; + dep.depth = depth; + + deps.push(dep); + + return dep; + } + + function ensureModuleExists(modulePath: string): boolean { + try { + var exists = fs.lstatSync(modulePath); + return exists.isDirectory(); + } catch (e) { + return false; } } } - return finalDependencies; + function filterUniqueDirectories(dependencies: any) { + var unique: any = []; + var distinct: any = []; + for (var i in dependencies) { + var dep = dependencies[i]; + if (distinct.indexOf(dep.directory) > -1) { + continue; + } + + distinct.push(dep.directory); + unique.push(dep); + } + + return unique; + } } public cleanNodeModules(absoluteOutputPath: string, platform: string): void { shelljs.rm("-rf", absoluteOutputPath); - } + } } + $injector.register("nodeModulesBuilder", NodeModulesBuilder); diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts index 0b42395769..4f679967d5 100644 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ b/lib/tools/node-modules/node-modules-dest-copy.ts @@ -10,87 +10,6 @@ export interface ILocalDependencyData extends IDependencyData { directory: string; } -export class NpmDependencyResolver { - constructor( - private projectDir: string - ) { - } - - private getDevDependencies(projectDir: string): IDictionary { - let projectFilePath = path.join(projectDir, constants.PACKAGE_JSON_FILE_NAME); - let projectFileContent = require(projectFilePath); - return projectFileContent.devDependencies || {}; - } - - public resolveDependencies(changedDirectories: string[], platform: string): IDictionary { - const devDependencies = this.getDevDependencies(this.projectDir); - const dependencies: IDictionary = Object.create(null); - - _.each(changedDirectories, changedDirectoryAbsolutePath => { - if (!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 (!devDependencies[fileContent.name] && fileContent.name && fileContent.version) { // Don't flatten dev dependencies and flatten only dependencies with valid package.json - let currentDependency: ILocalDependencyData = { - name: fileContent.name, - version: fileContent.version, - directory: path.dirname(packageJsonFilePath), - nativescript: fileContent.nativescript - }; - - let addedDependency = 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 dependency 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); - - dependencies[currentDependency.name] = currentDependency; - } - } else { - dependencies[currentDependency.name] = currentDependency; - } - } - }); - } - }); - return dependencies; - } - - private enumeratePackageJsonFilesSync(nodeModulesDirectoryPath: string, foundFiles?: string[]): string[] { - foundFiles = foundFiles || []; - if (fs.existsSync(nodeModulesDirectoryPath)) { - let contents = fs.readdirSync(nodeModulesDirectoryPath); - for (let i = 0; i < contents.length; ++i) { - 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(moduleDirectoryInNodeModules, constants.NODE_MODULES_FOLDER_NAME); - if (fs.existsSync(directoryPath)) { - this.enumeratePackageJsonFilesSync(directoryPath, foundFiles); - } else if (fs.statSync(moduleDirectoryInNodeModules).isDirectory()) { - // Scoped modules (e.g. @angular) are grouped in a subfolder and we need to enumerate them too. - this.enumeratePackageJsonFilesSync(moduleDirectoryInNodeModules, foundFiles); - } - } - } - return foundFiles; - } -} - export class TnsModulesCopy { constructor( private outputRoot: string, @@ -98,8 +17,10 @@ export class TnsModulesCopy { ) { } - public copyModules(dependencies: IDictionary, platform: string): void { - _.each(dependencies, dependency => { + public copyModules(dependencies: any[], platform: string): void { + for (var entry in dependencies) { + var dependency = dependencies[entry]; + this.copyDependencyDir(dependency); if (dependency.name === constants.TNS_CORE_MODULES_NAME) { @@ -110,22 +31,24 @@ export class TnsModulesCopy { 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.rm("-rf", path.join(this.outputRoot, dependency.name, "node_modules")); } - }); + } } - private copyDependencyDir(dependency: any): void { - let dependencyDir = path.dirname(dependency.name || ""); - let insideNpmScope = /^@/.test(dependencyDir); - let targetDir = this.outputRoot; - if (insideNpmScope) { - targetDir = path.join(this.outputRoot, dependencyDir); + private copyDependencyDir(dependency: any) { + if (dependency.depth === 0) { + let isScoped = dependency.name.indexOf("@") === 0; + let targetDir = this.outputRoot; + + if (isScoped) { + targetDir = path.join(this.outputRoot, dependency.name.substring(0, dependency.name.indexOf("/"))); + } + + shelljs.mkdir("-p", targetDir); + shelljs.cp("-Rf", dependency.directory, targetDir); + // console.log("Copied dependency: " + dependency.name); } - shelljs.mkdir("-p", targetDir); - shelljs.cp("-Rf", dependency.directory, targetDir); - shelljs.rm("-rf", path.join(targetDir, dependency.name, "node_modules")); } } From 7b10417d21279d4a5ef0351a6f86b8af6ea613db Mon Sep 17 00:00:00 2001 From: Peter Kanev Date: Mon, 24 Oct 2016 11:23:38 +0300 Subject: [PATCH 3/5] separate logic for determining production dependencies in a class fix broken tests --- lib/definitions/platform.d.ts | 4 + .../node-modules/node-modules-builder.ts | 143 +----------------- .../node-modules-dependencies-builder.ts | 112 ++++++++++++++ .../node-modules/node-modules-dest-copy.ts | 11 +- test/npm-support.ts | 55 +++---- 5 files changed, 147 insertions(+), 178 deletions(-) create mode 100644 lib/tools/node-modules/node-modules-dependencies-builder.ts diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 31daa127d6..95c558704c 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -56,3 +56,7 @@ interface INodeModulesBuilder { prepareNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date): IFuture; cleanNodeModules(absoluteOutputPath: string, platform: string): void; } + +interface INodeModulesDependenciesBuilder { + getProductionDependencies(projectPath: string): void; +} \ No newline at end of file diff --git a/lib/tools/node-modules/node-modules-builder.ts b/lib/tools/node-modules/node-modules-builder.ts index 13980dfc22..acfdfdda48 100644 --- a/lib/tools/node-modules/node-modules-builder.ts +++ b/lib/tools/node-modules/node-modules-builder.ts @@ -4,6 +4,7 @@ import * as path from "path"; import * as shelljs from "shelljs"; import Future = require("fibers/future"); import { TnsModulesCopy, NpmPluginPrepare } from "./node-modules-dest-copy"; +import { NodeModulesDependenciesBuilder } from "./node-modules-dependencies-builder"; import * as fiberBootstrap from "../../common/fiber-bootstrap"; import { sleep } from "../../../lib/common/helpers"; @@ -134,12 +135,8 @@ export class NodeModulesBuilder implements INodeModulesBuilder { lastModifiedTime = null; } - let productionDependencies = this.getProductionDependencies(this.$projectData.projectDir); - - // console.log(productionDependencies); - - // TODO: Pip3r4o - result is not used currently - // let nodeModules = this.getChangedNodeModules(absoluteOutputPath, platform, lastModifiedTime).wait(); + let dependenciesBuilder = this.$injector.resolve(NodeModulesDependenciesBuilder, {}); + let productionDependencies = dependenciesBuilder.getProductionDependencies(this.$projectData.projectDir); if (!this.$options.bundle) { const tnsModulesCopy = this.$injector.resolve(TnsModulesCopy, { @@ -155,140 +152,6 @@ export class NodeModulesBuilder implements INodeModulesBuilder { }).future()(); } - public getProductionDependencies(projectPath: string) { - var deps: any = []; - var seen: any = {}; - - var pJson = path.join(projectPath, "package.json"); - var nodeModulesDir = path.join(projectPath, "node_modules"); - - var content = require(pJson); - - Object.keys(content.dependencies).forEach((key) => { - var depth = 0; - var directory = path.join(nodeModulesDir, key); - - // find and traverse child with name `key`, parent's directory -> dep.directory - traverseChild(key, directory, depth); - }); - - return filterUniqueDirectories(deps); - - function traverseChild(name: string, currentModulePath: string, depth: number) { - // check if key appears in a scoped module dependency - var isScoped = name.indexOf('@') === 0; - - if (!isScoped) { - // Check if child has been extracted in the parent's node modules, AND THEN in `node_modules` - // Slower, but prevents copying wrong versions if multiple of the same module are installed - // Will also prevent copying project's devDependency's version if current module depends on another version - var modulePath = path.join(currentModulePath, "node_modules", name); // /node_modules/parent/node_modules/ - var exists = ensureModuleExists(modulePath); - - if (exists) { - var dep = addDependency(deps, name, modulePath, depth + 1); - - traverseModule(modulePath, depth + 1, dep); - } else { - modulePath = path.join(nodeModulesDir, name); // /node_modules/ - exists = ensureModuleExists(modulePath); - - if(!exists) { - return; - } - - var dep = addDependency(deps, name, modulePath, 0); - - traverseModule(modulePath, 0, dep); - } - - } - // module is scoped - else { - var scopeSeparatorIndex = name.indexOf('/'); - var scope = name.substring(0, scopeSeparatorIndex); - var moduleName = name.substring(scopeSeparatorIndex + 1, name.length); - var scopedModulePath = path.join(nodeModulesDir, scope, moduleName); - - var exists = ensureModuleExists(scopedModulePath); - - if (exists) { - var dep = addDependency(deps, name, scopedModulePath, 0); - traverseModule(scopedModulePath, depth, dep); - } - else { - scopedModulePath = path.join(currentModulePath, "node_modules", scope, moduleName); - - exists = ensureModuleExists(scopedModulePath); - - if (!exists) { - return; - } - - var dep = addDependency(deps, name, scopedModulePath, depth + 1); - traverseModule(scopedModulePath, depth + 1, dep); - } - } - - function traverseModule(modulePath: string, depth: number, currentDependency: any) { - var packageJsonPath = path.join(modulePath, 'package.json'); - var packageJsonExists = fs.lstatSync(packageJsonPath).isFile(); - - if (packageJsonExists) { - var packageJsonContents = require(packageJsonPath); - - if (!!packageJsonContents.nativescript) { - // add `nativescript` property, necessary for resolving plugins - currentDependency.nativescript = packageJsonContents.nativescript; - } - - if (packageJsonContents.dependencies) { - Object.keys(packageJsonContents.dependencies).forEach((key) => { - - traverseChild(key, modulePath, depth); - }); - } - } - } - - function addDependency(deps: any[], name: string, directory: string, depth: number) { - var dep: any = {}; - dep.name = name; - dep.directory = directory; - dep.depth = depth; - - deps.push(dep); - - return dep; - } - - function ensureModuleExists(modulePath: string): boolean { - try { - var exists = fs.lstatSync(modulePath); - return exists.isDirectory(); - } catch (e) { - return false; - } - } - } - - function filterUniqueDirectories(dependencies: any) { - var unique: any = []; - var distinct: any = []; - for (var i in dependencies) { - var dep = dependencies[i]; - if (distinct.indexOf(dep.directory) > -1) { - continue; - } - - distinct.push(dep.directory); - unique.push(dep); - } - - return unique; - } - } - public cleanNodeModules(absoluteOutputPath: string, platform: string): void { shelljs.rm("-rf", absoluteOutputPath); } diff --git a/lib/tools/node-modules/node-modules-dependencies-builder.ts b/lib/tools/node-modules/node-modules-dependencies-builder.ts new file mode 100644 index 0000000000..0f0af7c5b9 --- /dev/null +++ b/lib/tools/node-modules/node-modules-dependencies-builder.ts @@ -0,0 +1,112 @@ +import * as path from "path"; +import * as fs from "fs"; + +export class NodeModulesDependenciesBuilder implements INodeModulesDependenciesBuilder { + private projectPath: string; + private rootNodeModulesPath: string; + private resolvedDependencies: any[]; + private seen: any; + + public constructor(private $fs: IFileSystem) { + this.seen = {}; + this.resolvedDependencies = []; + } + + public getProductionDependencies(projectPath: string): any { + this.projectPath = projectPath; + this.rootNodeModulesPath = path.join(this.projectPath, "node_modules"); + + let projectPackageJsonpath = path.join(this.projectPath, "package.json"); + let packageJsonContent = this.$fs.readJson(projectPackageJsonpath).wait(); + + _.keys(packageJsonContent.dependencies).forEach(dependencyName => { + let depth = 0; + let directory = path.join(this.rootNodeModulesPath, dependencyName); + + // find and traverse child with name `key`, parent's directory -> dep.directory + this.traverseDependency(dependencyName, directory, depth); + }); + + return this.resolvedDependencies; + } + + private traverseDependency(name: string, currentModulePath: string, depth: number): void { + // Check if child has been extracted in the parent's node modules, AND THEN in `node_modules` + // Slower, but prevents copying wrong versions if multiple of the same module are installed + // Will also prevent copying project's devDependency's version if current module depends on another version + let modulePath = path.join(currentModulePath, "node_modules", name); // node_modules/parent/node_modules/ + let alternativeModulePath = path.join(this.rootNodeModulesPath, name); + + this.findModule(modulePath, alternativeModulePath, name, depth); + } + + private findModule(modulePath: string, alternativeModulePath: string, name: string, depth: number) { + let exists = this.moduleExists(modulePath); + + if (exists) { + if (this.seen[modulePath]) { + return; + } + + let dependency = this.addDependency(name, modulePath, depth + 1); + this.readModuleDependencies(modulePath, depth + 1, dependency); + } else { + modulePath = alternativeModulePath; // /node_modules/ + exists = this.moduleExists(modulePath); + + if (!exists) { + return; + } + + if (this.seen[modulePath]) { + return; + } + + let dependency = this.addDependency(name, modulePath, 0); + this.readModuleDependencies(modulePath, 0, dependency); + } + + this.seen[modulePath] = true; + } + + private readModuleDependencies(modulePath: string, depth: number, currentModule: any): void { + let packageJsonPath = path.join(modulePath, 'package.json'); + let packageJsonExists = fs.lstatSync(packageJsonPath).isFile(); + + if (packageJsonExists) { + let packageJsonContents = this.$fs.readJson(packageJsonPath).wait(); + + if (!!packageJsonContents.nativescript) { + // add `nativescript` property, necessary for resolving plugins + currentModule.nativescript = packageJsonContents.nativescript; + } + + _.keys(packageJsonContents.dependencies).forEach((dependencyName) => { + this.traverseDependency(dependencyName, modulePath, depth); + }); + } + } + + private addDependency(name: string, directory: string, depth: number): any { + let dependency: any = { + name, + directory, + depth + }; + + this.resolvedDependencies.push(dependency); + + return dependency; + } + + private moduleExists(modulePath: string): boolean { + try { + let exists = fs.lstatSync(modulePath); + return exists.isDirectory(); + } catch (e) { + return false; + } + } +} + +$injector.register("nodeModulesDependenciesBuilder", NodeModulesDependenciesBuilder); diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts index 4f679967d5..960d295328 100644 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ b/lib/tools/node-modules/node-modules-dest-copy.ts @@ -1,6 +1,4 @@ -import * as fs from "fs"; import * as path from "path"; -import * as semver from "semver"; import * as shelljs from "shelljs"; import * as constants from "../../constants"; import * as minimatch from "minimatch"; @@ -18,8 +16,8 @@ export class TnsModulesCopy { } public copyModules(dependencies: any[], platform: string): void { - for (var entry in dependencies) { - var dependency = dependencies[entry]; + for (let entry in dependencies) { + let dependency = dependencies[entry]; this.copyDependencyDir(dependency); @@ -30,13 +28,11 @@ export class TnsModulesCopy { 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.rm("-rf", path.join(this.outputRoot, dependency.name, "node_modules")); } } } - private copyDependencyDir(dependency: any) { + private copyDependencyDir(dependency: any): void { if (dependency.depth === 0) { let isScoped = dependency.name.indexOf("@") === 0; let targetDir = this.outputRoot; @@ -47,7 +43,6 @@ export class TnsModulesCopy { shelljs.mkdir("-p", targetDir); shelljs.cp("-Rf", dependency.directory, targetDir); - // console.log("Copied dependency: " + dependency.name); } } } diff --git a/test/npm-support.ts b/test/npm-support.ts index 5f6ab1eaa1..be69032469 100644 --- a/test/npm-support.ts +++ b/test/npm-support.ts @@ -16,20 +16,19 @@ import NodeModulesLib = require("../lib/tools/node-modules/node-modules-builder" import PluginsServiceLib = require("../lib/services/plugins-service"); import ChildProcessLib = require("../lib/common/child-process"); import ProjectFilesManagerLib = require("../lib/common/services/project-files-manager"); -import {DeviceAppDataFactory} from "../lib/common/mobile/device-app-data/device-app-data-factory"; -import {LocalToDevicePathDataFactory} from "../lib/common/mobile/local-to-device-path-data-factory"; -import {MobileHelper} from "../lib/common/mobile/mobile-helper"; -import {ProjectFilesProvider} from "../lib/providers/project-files-provider"; -import {DeviceAppDataProvider} from "../lib/providers/device-app-data-provider"; -import {MobilePlatformsCapabilities} from "../lib/mobile-platforms-capabilities"; -import {DevicePlatformsConstants} from "../lib/common/mobile/device-platforms-constants"; +import { DeviceAppDataFactory } from "../lib/common/mobile/device-app-data/device-app-data-factory"; +import { LocalToDevicePathDataFactory } from "../lib/common/mobile/local-to-device-path-data-factory"; +import { MobileHelper } from "../lib/common/mobile/mobile-helper"; +import { ProjectFilesProvider } from "../lib/providers/project-files-provider"; +import { DeviceAppDataProvider } from "../lib/providers/device-app-data-provider"; +import { MobilePlatformsCapabilities } from "../lib/mobile-platforms-capabilities"; +import { DevicePlatformsConstants } from "../lib/common/mobile/device-platforms-constants"; import { XmlValidator } from "../lib/xml-validator"; import { LockFile } from "../lib/lockfile"; import Future = require("fibers/future"); import path = require("path"); import temp = require("temp"); -import shelljs = require("shelljs"); temp.track(); let assert = require("chai").assert; @@ -191,6 +190,7 @@ describe("Npm support tests", () => { appDestinationFolderPath = projectSetup.appDestinationFolderPath; }); it("Ensures that the installed dependencies are prepared correctly", () => { + let fs: IFileSystem = testInjector.resolve("fs"); // Setup addDependencies(testInjector, projectFolder, { "bplist": "0.0.4" }).wait(); @@ -199,37 +199,28 @@ describe("Npm support tests", () => { // Assert let tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules"); - let lodashFolderPath = path.join(tnsModulesFolderPath, "lodash"); - let bplistFolderPath = path.join(tnsModulesFolderPath, "bplist"); - let bplistCreatorFolderPath = path.join(tnsModulesFolderPath, "bplist-creator"); - let bplistParserFolderPath = path.join(tnsModulesFolderPath, "bplist-parser"); - let fs = testInjector.resolve("fs"); - assert.isTrue(fs.exists(lodashFolderPath).wait()); - assert.isTrue(fs.exists(bplistFolderPath).wait()); - assert.isTrue(fs.exists(bplistCreatorFolderPath).wait()); - assert.isTrue(fs.exists(bplistParserFolderPath).wait()); + let results = fs.enumerateFilesInDirectorySync(tnsModulesFolderPath, (file, stat) => { + return true; + }, { enumerateDirectories: true }); + + assert.isTrue(results.filter((val) => _.endsWith(val, "lodash")).length === 1); + assert.isTrue(results.filter((val) => _.endsWith(val, path.join(tnsModulesFolderPath, "bplist"))).length === 1); + assert.isTrue(results.filter((val) => _.endsWith(val, "bplist-creator")).length === 1); + assert.isTrue(results.filter((val) => _.endsWith(val, "bplist-parser")).length === 1); }); it("Ensures that scoped dependencies are prepared correctly", () => { // Setup let fs = testInjector.resolve("fs"); let scopedName = "@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, version: "1.0.0" })).wait(); - + addDependencies(testInjector, projectFolder, dependencies).wait(); // Act preparePlatform(testInjector).wait(); - // Assert let tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules"); - let scopedDependencyPath = path.join(tnsModulesFolderPath, "@reactivex", "rxjs"); assert.isTrue(fs.exists(scopedDependencyPath).wait()); }); @@ -243,15 +234,19 @@ describe("Npm support tests", () => { 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 results = fs.enumerateFilesInDirectorySync(tnsModulesFolderPath, (file, stat) => { + return true; + }, { enumerateDirectories: true }); - let scopedDependencyPath = path.join(tnsModulesFolderPath, "@scoped-plugin", "inner-plugin"); - assert.isTrue(fs.exists(scopedDependencyPath).wait()); + let filteredResults = results.filter((val) => { + return _.endsWith(val, path.join("@scoped-plugin", "inner-plugin")); + }); + + assert.isTrue(filteredResults.length === 1); }); it("Ensures that tns_modules absent when bundling", () => { From 4c483d00016a4f6ba5b9aec167b1c47c06d06dc2 Mon Sep 17 00:00:00 2001 From: plamen5kov Date: Tue, 25 Oct 2016 15:17:35 +0300 Subject: [PATCH 4/5] removed unnecessary dependency --- lib/tools/node-modules/node-modules-builder.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/tools/node-modules/node-modules-builder.ts b/lib/tools/node-modules/node-modules-builder.ts index acfdfdda48..be0fcc564a 100644 --- a/lib/tools/node-modules/node-modules-builder.ts +++ b/lib/tools/node-modules/node-modules-builder.ts @@ -11,8 +11,7 @@ import { sleep } from "../../../lib/common/helpers"; let glob = require("glob"); export class NodeModulesBuilder implements INodeModulesBuilder { - constructor(private $childProcess: IChildProcess, - private $fs: IFileSystem, + constructor(private $fs: IFileSystem, private $projectData: IProjectData, private $projectDataService: IProjectDataService, private $injector: IInjector, From b569b6e1948630b43d9cc7f5947a5798c5e5afa2 Mon Sep 17 00:00:00 2001 From: Peter Kanev Date: Tue, 25 Oct 2016 16:18:31 +0300 Subject: [PATCH 5/5] remove node_modules of tns-core-modules if present when copied into tns_modules (npm2 hacky fix) --- lib/tools/node-modules/node-modules-dest-copy.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts index 960d295328..b6e2ec9615 100644 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ b/lib/tools/node-modules/node-modules-dest-copy.ts @@ -28,6 +28,8 @@ export class TnsModulesCopy { 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.rm("-rf", path.join(tnsCoreModulesResourcePath, "node_modules")); } } }