diff --git a/lib/definitions/npm.d.ts b/lib/definitions/npm.d.ts index 9cb8c86d6b..4de36a8be6 100644 --- a/lib/definitions/npm.d.ts +++ b/lib/definitions/npm.d.ts @@ -3,4 +3,10 @@ declare module "npm" { var commands: { [index: string]: any }; var prefix: string; function load(config: Object, callback: (err: any, data: any) => void): void; + module config { + var loaded: boolean; + module sources { + var cli: { data: Object }; + } + } } \ No newline at end of file diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 2e3d1e73da..a8b8584e11 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -36,6 +36,7 @@ interface IPlatformData { targetedOS?: string[]; configurationFileName?: string; configurationFilePath?: string; + relativeToFrameworkConfigurationFilePath: string; mergeXmlConfig?: any[]; } diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 809bf76318..7008fcbd84 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -36,7 +36,8 @@ interface IPlatformProjectService { platformData: IPlatformData; validate(): IFuture; createProject(frameworkDir: string, frameworkVersion: string): IFuture; - interpolateData(projectRoot: string): IFuture; + interpolateData(): IFuture; + interpolateConfigurationFile(configurationFilePath?: string): IFuture; afterCreateProject(projectRoot: string): IFuture; buildProject(projectRoot: string, buildConfig?: IBuildConfig): IFuture; prepareProject(): IFuture; diff --git a/lib/node-package-manager.ts b/lib/node-package-manager.ts index 79ed92e7d2..47cce4927b 100644 --- a/lib/node-package-manager.ts +++ b/lib/node-package-manager.ts @@ -19,15 +19,24 @@ export class NodePackageManager implements INodePackageManager { } public load(config?: any): IFuture { - let future = new Future(); - npm.load(config, (err: Error) => { - if(err) { - future.throw(err); - } else { - future.return(); + if (npm.config.loaded) { + let data = npm.config.sources.cli.data; + Object.keys(data).forEach(k => delete data[k]); + if (config) { + _.assign(data, config); } - }); - return future; + return Future.fromResult(); + } else { + let future = new Future(); + npm.load(config, (err: Error) => { + if(err) { + future.throw(err); + } else { + future.return(); + } + }); + return future; + } } public install(packageName: string, pathToSave: string, config?: any): IFuture { diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 76fbe4eb03..810104bdb3 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -8,7 +8,7 @@ import * as semver from "semver"; import * as projectServiceBaseLib from "./platform-project-service-base"; import * as androidDebugBridgePath from "../common/mobile/android/android-debug-bridge"; -class AndroidProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService { +export class AndroidProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService { private static VALUES_DIRNAME = "values"; private static VALUES_VERSION_DIRNAME_PREFIX = AndroidProjectService.VALUES_DIRNAME + "-v"; private static ANDROID_PLATFORM_NAME = "android"; @@ -57,6 +57,7 @@ class AndroidProjectService extends projectServiceBaseLib.PlatformProjectService frameworkFilesExtensions: [".jar", ".dat", ".so"], configurationFileName: "AndroidManifest.xml", configurationFilePath: path.join(projectRoot, "src", "main", "AndroidManifest.xml"), + relativeToFrameworkConfigurationFilePath: path.join("src", "main", "AndroidManifest.xml"), mergeXmlConfig: [{ "nodename": "manifest", "attrname": "*" }, {"nodename": "application", "attrname": "*"}] }; } @@ -134,11 +135,10 @@ class AndroidProjectService extends projectServiceBaseLib.PlatformProjectService }).future()(); } - public interpolateData(projectRoot: string): IFuture { + public interpolateData(): IFuture { return (() => { - // Interpolate the activity name and package - let manifestPath = this.platformData.configurationFilePath; - shell.sed('-i', /__PACKAGE__/, this.$projectData.projectId, manifestPath); + // Interpolate the apilevel and package + this.interpolateConfigurationFile().wait(); let stringsFilePath = path.join(this.getAppResourcesDestinationDirectoryPath().wait(), 'values', 'strings.xml'); shell.sed('-i', /__NAME__/, this.$projectData.projectName, stringsFilePath); @@ -146,6 +146,13 @@ class AndroidProjectService extends projectServiceBaseLib.PlatformProjectService let gradleSettingsFilePath = path.join(this.platformData.projectRoot, "settings.gradle"); shell.sed('-i', /__PROJECT_NAME__/, this.getProjectNameFromId(), gradleSettingsFilePath); + }).future()(); + } + + public interpolateConfigurationFile(): IFuture { + return (() => { + let manifestPath = this.platformData.configurationFilePath; + shell.sed('-i', /__PACKAGE__/, this.$projectData.projectId, manifestPath); shell.sed('-i', /__APILEVEL__/, this.$options.sdk || this.$androidToolsInfo.getToolsInfo().wait().compileSdkVersion.toString(), manifestPath); }).future()(); } diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 550791e1d0..6edfdde827 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -60,6 +60,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ targetedOS: ['darwin'], configurationFileName: "Info.plist", configurationFilePath: path.join(projectRoot, this.$projectData.projectName, this.$projectData.projectName+"-Info.plist"), + relativeToFrameworkConfigurationFilePath: path.join("__PROJECT_NAME__", "__PROJECT_NAME__-Info.plist"), mergeXmlConfig: [{ "nodename": "plist", "attrname": "*" }, {"nodename": "dict", "attrname": "*"}] }; } @@ -118,20 +119,27 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ }).future()(); } - public interpolateData(projectRoot: string): IFuture { + public interpolateData(): IFuture { return (() => { - let infoPlistFilePath = path.join(projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER, util.format("%s-%s", IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER, "Info.plist")); - shell.sed('-i', "__CFBUNDLEIDENTIFIER__", this.$projectData.projectId, infoPlistFilePath); + let infoPlistFilePath = path.join(this.platformData.projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER, util.format("%s-%s", IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER, "Info.plist")); + this.interpolateConfigurationFile(infoPlistFilePath).wait(); - this.replaceFileName("-Info.plist", path.join(projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER)).wait(); - this.replaceFileName("-Prefix.pch", path.join(projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER)).wait(); - this.replaceFileName(IOSProjectService.XCODE_PROJECT_EXT_NAME, projectRoot).wait(); + let projectRootFilePath = path.join(this.platformData.projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER); + this.replaceFileName("-Info.plist", projectRootFilePath).wait(); + this.replaceFileName("-Prefix.pch", projectRootFilePath).wait(); + this.replaceFileName(IOSProjectService.XCODE_PROJECT_EXT_NAME, this.platformData.projectRoot).wait(); - let pbxprojFilePath = path.join(projectRoot, this.$projectData.projectName + IOSProjectService.XCODE_PROJECT_EXT_NAME, "project.pbxproj"); + let pbxprojFilePath = path.join(this.platformData.projectRoot, this.$projectData.projectName + IOSProjectService.XCODE_PROJECT_EXT_NAME, "project.pbxproj"); this.replaceFileContent(pbxprojFilePath).wait(); }).future()(); } + public interpolateConfigurationFile(configurationFilePath?: string): IFuture { + return (() => { + shell.sed('-i', "__CFBUNDLEIDENTIFIER__", this.$projectData.projectId, configurationFilePath || this.platformData.configurationFilePath); + }).future()(); + } + public afterCreateProject(projectRoot: string): IFuture { return (() => { this.$fs.rename(path.join(projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER), diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index caae4c28a7..6b31503d7d 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -102,7 +102,7 @@ export class PlatformService implements IPlatformService { this.$fs.deleteDirectory(path.join(frameworkDir, "../../")).wait(); } - platformData.platformProjectService.interpolateData(platformData.projectRoot).wait(); + platformData.platformProjectService.interpolateData().wait(); platformData.platformProjectService.afterCreateProject(platformData.projectRoot).wait(); this.$projectDataService.initialize(this.$projectData.projectDir); diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index 37b639993c..90314c499f 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -66,17 +66,6 @@ export class PluginsService implements IPluginsService { public remove(pluginName: string): IFuture { return (() => { - let isUninstallCommandExecuted = false; - - let executeUninstallCommand = () => { - return (() => { - if(!isUninstallCommandExecuted) { - this.executeNpmCommand(PluginsService.UNINSTALL_COMMAND_NAME, pluginName).wait(); - isUninstallCommandExecuted = true; - } - }).future()(); - }; - let removePluginNativeCodeAction = (modulesDestinationPath: string, platform: string, platformData: IPlatformData) => { return (() => { let pluginData = this.convertToPluginData(this.getNodeModuleData(pluginName).wait()); @@ -86,25 +75,7 @@ export class PluginsService implements IPluginsService { // Remove the plugin and call merge for another plugins that have configuration file let pluginConfigurationFilePath = this.getPluginConfigurationFilePath(pluginData, platformData); if(this.$fs.exists(pluginConfigurationFilePath).wait()) { - let tnsModulesDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, constants.TNS_MODULES_FOLDER_NAME); - let nodeModules = this.$broccoliBuilder.getChangedNodeModules(tnsModulesDestinationPath, platform).wait(); - - _(nodeModules) - .map(nodeModule => this.getNodeModuleData(pluginName).wait()) - .map(nodeModuleData => this.convertToPluginData(nodeModuleData)) - .filter(data => data.isPlugin && this.$fs.exists(this.getPluginConfigurationFilePath(data, platformData)).wait()) - .forEach((data, index) => { - executeUninstallCommand().wait(); - - if(index === 0) { - this.initializeConfigurationFileFromCache(platformData).wait(); - } - - if(data.name !== pluginName) { - this.merge(data, platformData).wait(); - } - }) - .value(); + this.merge(pluginData, platformData, (data: IPluginData) => data.name !== pluginData.name).wait(); } if(pluginData.pluginVariables) { @@ -114,7 +85,7 @@ export class PluginsService implements IPluginsService { }; this.executeForAllInstalledPlatforms(removePluginNativeCodeAction).wait(); - executeUninstallCommand().wait(); + this.executeNpmCommand(PluginsService.UNINSTALL_COMMAND_NAME, pluginName).wait(); let showMessage = true; let action = (modulesDestinationPath: string, platform: string, platformData: IPlatformData) => { @@ -137,15 +108,17 @@ export class PluginsService implements IPluginsService { return (() => { this.$projectDataService.initialize(this.$projectData.projectDir); let frameworkVersion = this.$projectDataService.getValue(platformData.frameworkPackageName).wait().version; - this.$npm.cache(platformData.frameworkPackageName, frameworkVersion).wait(); - let relativeConfigurationFilePath = path.relative(platformData.projectRoot, platformData.configurationFilePath); // We need to resolve this manager here due to some restrictions from npm api and in order to load PluginsService.NPM_CONFIG config let npmInstallationManager: INpmInstallationManager = this.$injector.resolve("npmInstallationManager"); + npmInstallationManager.addToCache(platformData.frameworkPackageName, frameworkVersion).wait(); + let cachedPackagePath = npmInstallationManager.getCachedPackagePath(platformData.frameworkPackageName, frameworkVersion); - let cachedConfigurationFilePath = path.join(cachedPackagePath, constants.PROJECT_FRAMEWORK_FOLDER_NAME, relativeConfigurationFilePath); + let cachedConfigurationFilePath = path.join(cachedPackagePath, constants.PROJECT_FRAMEWORK_FOLDER_NAME, platformData.relativeToFrameworkConfigurationFilePath); + let cachedConfigurationFileContent = this.$fs.readText(cachedConfigurationFilePath).wait(); + this.$fs.writeFile(platformData.configurationFilePath, cachedConfigurationFileContent).wait(); - shelljs.cp("-f", cachedConfigurationFilePath, path.dirname(platformData.configurationFilePath)); + platformData.platformProjectService.interpolateConfigurationFile().wait(); }).future()(); } @@ -164,7 +137,6 @@ export class PluginsService implements IPluginsService { shelljs.cp("-Rf", pluginData.fullPath, pluginDestinationPath); let pluginConfigurationFilePath = this.getPluginConfigurationFilePath(pluginData, platformData); - if(this.$fs.exists(pluginConfigurationFilePath).wait()) { this.merge(pluginData, platformData).wait(); } @@ -347,7 +319,7 @@ export class PluginsService implements IPluginsService { doc.parseFromString(xml, 'text/xml'); } - private merge(pluginData: IPluginData, platformData: IPlatformData): IFuture { + private mergeCore(pluginData: IPluginData, platformData: IPlatformData): IFuture { return (() => { let pluginConfigurationFilePath = this.getPluginConfigurationFilePath(pluginData, platformData); let configurationFilePath = platformData.configurationFilePath; @@ -368,6 +340,30 @@ export class PluginsService implements IPluginsService { }).future()(); } + private merge(pluginData: IPluginData, platformData: IPlatformData, mergeCondition?: (_pluginData: IPluginData) => boolean): IFuture { + return (() => { + let tnsModulesDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, constants.TNS_MODULES_FOLDER_NAME); + let nodeModules = this.$broccoliBuilder.getChangedNodeModules(tnsModulesDestinationPath, platformData.normalizedPlatformName.toLowerCase()).wait(); + + _(nodeModules) + .keys() + .map(nodeModule => this.getNodeModuleData(path.join(nodeModule, "package.json")).wait()) + .map(nodeModuleData => this.convertToPluginData(nodeModuleData)) + .filter(data => data.isPlugin && this.$fs.exists(this.getPluginConfigurationFilePath(data, platformData)).wait()) + .forEach((data, index) => { + if(index === 0) { + this.initializeConfigurationFileFromCache(platformData).wait(); + } + + if(!mergeCondition || (mergeCondition && mergeCondition(data))) { + this.mergeCore(data, platformData).wait(); + } + }) + .value(); + + }).future()(); + } + private getPluginConfigurationFilePath(pluginData: IPluginData, platformData: IPlatformData): string { let pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(platformData.normalizedPlatformName.toLowerCase()); let pluginConfigurationFilePath = path.join(pluginPlatformsFolderPath, platformData.configurationFileName); diff --git a/lib/tools/broccoli/builder.ts b/lib/tools/broccoli/builder.ts index 6a78eb89bf..868d135d4b 100644 --- a/lib/tools/broccoli/builder.ts +++ b/lib/tools/broccoli/builder.ts @@ -12,7 +12,6 @@ let through = require("through2"); export class Builder implements IBroccoliBuilder { constructor(private $fs: IFileSystem, - private $nodeModulesTree: INodeModulesTree, private $projectData: IProjectData, private $projectDataService: IProjectDataService, private $injector: IInjector, diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 25f70ba733..90c7ac0ead 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -28,6 +28,7 @@ class PlatformData implements IPlatformData { frameworkVersion = ""; appDestinationDirectoryPath = ""; appResourcesDestinationDirectoryPath = ""; + relativeToFrameworkConfigurationFilePath = ""; } class ErrorsNoFailStub implements IErrors { diff --git a/test/plugins-service.ts b/test/plugins-service.ts index 9abd0d25b3..c4d995fac7 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -5,6 +5,7 @@ import {Yok} from '../lib/common/yok'; import future = require("fibers/future"); import * as stubs from './stubs'; import {NodePackageManager} from "../lib/node-package-manager"; +import {NpmInstallationManager} from "../lib/npm-installation-manager"; import {FileSystem} from "../lib/common/file-system"; import {ProjectData} from "../lib/project-data"; import {ChildProcess} from "../lib/common/child-process"; @@ -23,6 +24,9 @@ import {ResourceLoader} from "../lib/common/resource-loader"; import {EOL} from "os"; import {PluginsService} from "../lib/services/plugins-service"; import {AddPluginCommand} from "../lib/commands/plugin/add-plugin"; +import {Builder} from "../lib/tools/broccoli/builder"; +import {AndroidProjectService} from "../lib/services/android-project-service"; +import {AndroidToolsInfo} from "../lib/android-tools-info"; import {assert} from "chai"; import * as path from "path"; import * as temp from "temp"; @@ -40,13 +44,17 @@ function createTestInjector() { testInjector.register("childProcess", ChildProcess); testInjector.register("platformService", PlatformService); testInjector.register("platformsData", PlatformsData); - testInjector.register("androidProjectService", {}); + testInjector.register("androidEmulatorServices", {}); + testInjector.register("androidToolsInfo", AndroidToolsInfo); + testInjector.register("sysInfo", {}); + testInjector.register("mobileHelper", {}); + testInjector.register("androidProjectService", AndroidProjectService); testInjector.register("iOSProjectService", {}); testInjector.register("devicesServices", {}); testInjector.register("projectDataService", ProjectDataService); testInjector.register("prompter", {}); testInjector.register("resources", ResourceLoader); - testInjector.register("broccoliBuilder", {}); + testInjector.register("broccoliBuilder", Builder); testInjector.register("options", Options); testInjector.register("errors", Errors); testInjector.register("logger", stubs.LoggerStub); @@ -71,7 +79,7 @@ function createTestInjector() { savePluginVariablesInProjectFile: (pluginData: IPluginData) => future.fromResult(), interpolatePluginVariables: (pluginData: IPluginData, pluginConfigurationFileContent: string) => future.fromResult(pluginConfigurationFileContent) }); - testInjector.register("npmInstallationManager", {}); + testInjector.register("npmInstallationManager", NpmInstallationManager); return testInjector; } @@ -87,7 +95,7 @@ function createProjectFile(testInjector: IInjector): string { "nativescript": { "id": "org.nativescript.Test", "tns-android": { - "version": "1.0.0" + "version": "1.4.0" } } }; @@ -203,7 +211,7 @@ describe("Plugins service", () => { }); it("fails when the plugin does not support the installed framework", () => { let isWarningMessageShown = false; - let expectedWarningMessage = "mySamplePlugin 1.0.1 for android is not compatible with the currently installed framework version 1.0.0."; + let expectedWarningMessage = "mySamplePlugin 1.5.0 for android is not compatible with the currently installed framework version 1.4.0."; // Creates plugin in temp folder let pluginName = "mySamplePlugin"; @@ -214,7 +222,7 @@ describe("Plugins service", () => { "version": "0.0.1", "nativescript": { "platforms": { - "android": "1.0.1" + "android": "1.5.0" } }, }; @@ -554,6 +562,14 @@ describe("Plugins service", () => { let appDestinationDirectoryPath = path.join(projectFolder, "platforms", "android"); fs.ensureDirectoryExists(path.join(appDestinationDirectoryPath, "app")).wait(); + // Mock projectData + let projectData = testInjector.resolve("projectData"); + projectData.projectId = "com.example.android.basiccontactables"; + + let configurationFilePath = path.join(appDestinationDirectoryPath, "src", "main", "AndroidManifest.xml"); + + let shelljs = require("shelljs"); + // Mock platformsData let platformsData = testInjector.resolve("platformsData"); platformsData.getPlatformData = (platform: string) => { @@ -561,10 +577,17 @@ describe("Plugins service", () => { appDestinationDirectoryPath: appDestinationDirectoryPath, frameworkPackageName: "tns-android", configurationFileName: "AndroidManifest.xml", - configurationFilePath: path.join(appDestinationDirectoryPath, "AndroidManifest.xml"), + configurationFilePath: configurationFilePath, + relativeToFrameworkConfigurationFilePath: path.join("src", "main", "AndroidManifest.xml"), mergeXmlConfig: [{ "nodename": "manifest", "attrname": "*" }], platformProjectService: { - preparePluginNativeCode: (pluginData: IPluginData) => future.fromResult() + preparePluginNativeCode: (pluginData: IPluginData) => future.fromResult(), + interpolateConfigurationFile: () => { + return (() => { + shelljs.sed('-i', /__PACKAGE__/, projectData.projectId, configurationFilePath); + shelljs.sed('-i', /__APILEVEL__/, "23", configurationFilePath); + }).future()(); + } }, normalizedPlatformName: "Android" }; @@ -574,6 +597,8 @@ describe("Plugins service", () => { let pluginPlatformsDirPath = path.join(projectFolder, "node_modules", pluginName, "platforms", "android"); fs.ensureDirectoryExists(pluginPlatformsDirPath).wait(); + shelljs.cp("-R", path.join(pluginFolderPath, "*"), path.join(projectFolder, "node_modules", pluginName)); + // Creates valid plugin's AndroidManifest.xml file let xml = '' + '' + @@ -584,11 +609,11 @@ describe("Plugins service", () => { pluginsService.prepare(pluginJsonData).wait(); - let expectedXml = ''; + let expectedXml = ''; expectedXml = helpers.stringReplaceAll(expectedXml, EOL, ""); expectedXml = helpers.stringReplaceAll(expectedXml, " ", ""); - let actualXml = fs.readText(path.join(appDestinationDirectoryPath, "AndroidManifest.xml")).wait(); + let actualXml = fs.readText(path.join(appDestinationDirectoryPath, "src", "main", "AndroidManifest.xml")).wait(); actualXml = helpers.stringReplaceAll(actualXml, "\n", ""); actualXml = helpers.stringReplaceAll(actualXml, " ", ""); diff --git a/test/stubs.ts b/test/stubs.ts index 904a583dd6..806d9c361a 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -258,6 +258,7 @@ export class PlatformsDataStub implements IPlatformsData { frameworkFilesExtensions: [], frameworkVersion: "", appDestinationDirectoryPath: "", + relativeToFrameworkConfigurationFilePath: "", preparePluginNativeCode: () => Future.fromResult(), removePluginNativeCode: () => Future.fromResult(), afterPrepareAllPlugins: () => Future.fromResult() @@ -281,7 +282,8 @@ export class PlatformProjectServiceStub implements IPlatformProjectService { validPackageNamesForDevice: [], frameworkFilesExtensions: [], frameworkVersion: "", - appDestinationDirectoryPath: "" + appDestinationDirectoryPath: "", + relativeToFrameworkConfigurationFilePath: "" }; } getAppResourcesDestinationDirectoryPath(): IFuture{ @@ -293,7 +295,10 @@ export class PlatformProjectServiceStub implements IPlatformProjectService { createProject(projectRoot: string, frameworkDir: string): IFuture { return Future.fromResult(); } - interpolateData(projectRoot: string): IFuture { + interpolateData(): IFuture { + return Future.fromResult(); + } + interpolateConfigurationFile(): IFuture { return Future.fromResult(); } afterCreateProject(projectRoot: string): IFuture {