From 9b1673f86e178b52c60004a54db0ce27e42adb5a Mon Sep 17 00:00:00 2001 From: yyosifov Date: Thu, 20 Apr 2017 10:11:28 +0300 Subject: [PATCH 001/212] Yosifov/add test (#2710) * Setup Project Changes Service test suite * Add tests for GetPrepareInfo from ProjectChangesService * Add test to verify changes on a sync file has been applied. * Make ios/android lower case folder names, as it seems like in Linux the case-sensitivity plays a role * Fix platform names casing * Clone the test for Android for file changes * fix lint errors * Add tests for issue #2697 * Adding Tests to verify that the App_Resources have been reloaded * Adding tests for new/deleted platform/common files * Refactor the changes tests and add some more tests * Improve assertions to include message what was wrong with the directory structure * Minor messages improvement * remove delete file tests - seems we do not support this currently. * Adding tests to verify new files in App_Resources are detected and copied to destination. * Remove .only() which was used only for debugging purposes on the Travis CI --- test/base-service-test.ts | 16 ++ test/platform-service.ts | 447 ++++++++++++++++++++++++++++++-- test/project-changes-service.ts | 116 +++++++++ 3 files changed, 558 insertions(+), 21 deletions(-) create mode 100644 test/base-service-test.ts create mode 100644 test/project-changes-service.ts diff --git a/test/base-service-test.ts b/test/base-service-test.ts new file mode 100644 index 0000000000..5cff01b74e --- /dev/null +++ b/test/base-service-test.ts @@ -0,0 +1,16 @@ +import * as yok from "../lib/common/yok"; + +export abstract class BaseServiceTest { + protected injector: IInjector; + constructor() { + this.injector = new yok.Yok(); + + this.initInjector(); + } + + abstract initInjector(): void; + + resolve(name: string, ctorArguments?: IDictionary): any { + return this.injector.resolve(name); + } +} diff --git a/test/platform-service.ts b/test/platform-service.ts index 7d7216d184..532b277f00 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -89,6 +89,59 @@ function createTestInjector() { return testInjector; } +class CreatedTestData { + files: string[]; + + resources: { + ios: string[], + android: string[] + }; + + testDirData: { + tempFolder: string, + appFolderPath: string, + app1FolderPath: string, + appDestFolderPath: string, + appResourcesFolderPath: string + }; + + constructor() { + this.files = []; + this.resources = { + ios: [], + android: [] + }; + + this.testDirData = { + tempFolder: "", + appFolderPath: "", + app1FolderPath: "", + appDestFolderPath: "", + appResourcesFolderPath: "" + }; + } +} + +class DestinationFolderVerifier { + static verify(data: any, fs: IFileSystem) { + _.forOwn(data, (folder, folderRoot) => { + _.each(folder.filesWithContent || [], (file) => { + const filePath = path.join(folderRoot, file.name); + assert.isTrue(fs.exists(filePath), `Expected file ${filePath} to be present.`); + assert.equal(fs.readFile(filePath).toString(), file.content, `File content for ${filePath} doesn't match.`); + }); + + _.each(folder.missingFiles || [], (file) => { + assert.isFalse(fs.exists(path.join(folderRoot, file)), `Expected file ${file} to be missing.`); + }); + + _.each(folder.presentFiles || [], (file) => { + assert.isTrue(fs.exists(path.join(folderRoot, file)), `Expected file ${file} to be present.`); + }); + }); + } +} + describe('Platform Service Tests', () => { let platformService: IPlatformService, testInjector: IInjector; beforeEach(() => { @@ -307,11 +360,14 @@ describe('Platform Service Tests', () => { }); function prepareDirStructure() { - let tempFolder = temp.mkdirSync("prepare platform"); + let tempFolder = temp.mkdirSync("prepare_platform"); let appFolderPath = path.join(tempFolder, "app"); fs.createDirectory(appFolderPath); + let nodeModulesPath = path.join(tempFolder, "node_modules"); + fs.createDirectory(nodeModulesPath); + let testsFolderPath = path.join(appFolderPath, "tests"); fs.createDirectory(testsFolderPath); @@ -324,23 +380,8 @@ describe('Platform Service Tests', () => { return { tempFolder, appFolderPath, app1FolderPath, appDestFolderPath, appResourcesFolderPath }; } - async function testPreparePlatform(platformToTest: string, release?: boolean) { - let testDirData = prepareDirStructure(); - - // Add platform specific files to app and app1 folders - let platformSpecificFiles = [ - "test1.ios.js", "test1-ios-js", "test2.android.js", "test2-android-js" - ]; - - let destinationDirectories = [testDirData.appFolderPath, testDirData.app1FolderPath]; - - _.each(destinationDirectories, directoryPath => { - _.each(platformSpecificFiles, filePath => { - let fileFullPath = path.join(directoryPath, filePath); - fs.writeFile(fileFullPath, "testData"); - }); - }); - + async function execPreparePlatform(platformToTest: string, testDirData: any, + release?: boolean) { let platformsData = testInjector.resolve("platformsData"); platformsData.platformsNames = ["ios", "android"]; platformsData.getPlatformData = (platform: string) => { @@ -348,6 +389,7 @@ describe('Platform Service Tests', () => { appDestinationDirectoryPath: testDirData.appDestFolderPath, appResourcesDestinationDirectoryPath: testDirData.appResourcesFolderPath, normalizedPlatformName: platformToTest, + configurationFileName: platformToTest === "ios" ? "Info.plist" : "AndroidManifest.xml", projectRoot: testDirData.tempFolder, platformProjectService: { prepareProject: (): any => null, @@ -355,21 +397,71 @@ describe('Platform Service Tests', () => { createProject: (projectRoot: string, frameworkDir: string) => Promise.resolve(), interpolateData: (projectRoot: string) => Promise.resolve(), afterCreateProject: (projectRoot: string): any => null, - getAppResourcesDestinationDirectoryPath: () => "", + getAppResourcesDestinationDirectoryPath: (projectData: IProjectData, frameworkVersion?: string): string => { + if (platform.toLowerCase() === "ios") { + let dirPath = path.join(testDirData.appDestFolderPath, "Resources"); + fs.ensureDirectoryExists(dirPath); + return dirPath; + } else { + let dirPath = path.join(testDirData.appDestFolderPath, "src", "main", "res"); + fs.ensureDirectoryExists(dirPath); + return dirPath; + } + }, processConfigurationFilesFromAppResources: () => Promise.resolve(), ensureConfigurationFileInAppResources: (): any => null, interpolateConfigurationFile: (): void => undefined, - isPlatformPrepared: (projectRoot: string) => false + isPlatformPrepared: (projectRoot: string) => false, + prepareAppResources: (appResourcesDirectoryPath: string, projectData: IProjectData): void => undefined } }; }; let projectData = testInjector.resolve("projectData"); projectData.projectDir = testDirData.tempFolder; + projectData.appDirectoryPath = testDirData.appFolderPath; + projectData.appResourcesDirectoryPath = path.join(testDirData.appFolderPath, "App_Resources"); + projectData.projectName = "app"; platformService = testInjector.resolve("platformService"); const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: false, release: release }; await platformService.preparePlatform(platformToTest, appFilesUpdaterOptions, "", projectData, { provision: null, sdk: null }); + } + + async function testPreparePlatform(platformToTest: string, release?: boolean): Promise { + let testDirData = prepareDirStructure(); + let created: CreatedTestData = new CreatedTestData(); + created.testDirData = testDirData; + + // Add platform specific files to app and app1 folders + let platformSpecificFiles = [ + "test1.ios.js", "test1-ios-js", "test2.android.js", "test2-android-js", + "main.js" + ]; + + let destinationDirectories = [testDirData.appFolderPath, testDirData.app1FolderPath]; + + _.each(destinationDirectories, directoryPath => { + _.each(platformSpecificFiles, filePath => { + let fileFullPath = path.join(directoryPath, filePath); + fs.writeFile(fileFullPath, "testData"); + + created.files.push(fileFullPath); + }); + }); + + // Add App_Resources file to app and app1 folders + _.each(destinationDirectories, directoryPath => { + let iosIconFullPath = path.join(directoryPath, "App_Resources/iOS/icon.png"); + fs.writeFile(iosIconFullPath, "test-image"); + created.resources.ios.push(iosIconFullPath); + + let androidFullPath = path.join(directoryPath, "App_Resources/Android/icon.png"); + fs.writeFile(androidFullPath, "test-image"); + created.resources.android.push(androidFullPath); + }); + + await execPreparePlatform(platformToTest, testDirData, release); let test1FileName = platformToTest.toLowerCase() === "ios" ? "test1.js" : "test2.js"; let test2FileName = platformToTest.toLowerCase() === "ios" ? "test2.js" : "test1.js"; @@ -388,6 +480,13 @@ describe('Platform Service Tests', () => { // Asserts that the files in tests folder aren't copied assert.isFalse(fs.exists(path.join(testDirData.appDestFolderPath, "tests")), "Asserts that the files in tests folder aren't copied"); } + + return created; + } + + function updateFile(files: string[], fileName: string, content: string) { + let fileToUpdate = _.find(files, (f) => f.indexOf(fileName) !== -1); + fs.writeFile(fileToUpdate, content); } it("should process only files in app folder when preparing for iOS platform", async () => { @@ -406,6 +505,311 @@ describe('Platform Service Tests', () => { await testPreparePlatform("Android", true); }); + function getDefaultFolderVerificationData(platform: string, appDestFolderPath: string) { + let data: any = {}; + if (platform.toLowerCase() === "ios") { + data[path.join(appDestFolderPath, "app")] = { + missingFiles: ["test1.ios.js", "test2.android.js", "test2.js", "App_Resources"], + presentFiles: ["test1.js", "test2-android-js", "test1-ios-js", "main.js"] + }; + + data[appDestFolderPath] = { + filesWithContent: [ + { + name: "Resources/icon.png", + content: "test-image" + } + ] + }; + } else { + data[path.join(appDestFolderPath, "app")] = { + missingFiles: ["test1.android.js", "test2.ios.js", "test1.js"], + presentFiles: ["test2.js", "test2-android-js", "test1-ios-js"] + }; + + data[appDestFolderPath] = { + filesWithContent: [ + { + name: "src/main/res/icon.png", + content: "test-image" + } + ] + }; + } + + return data; + } + + function mergeModifications(def: any, mod: any) { + // custom merge to reflect changes + let merged: any = _.cloneDeep(def); + _.forOwn(mod, (modFolder, folderRoot) => { + // whole folder not present in Default + if (!def.hasOwnProperty(folderRoot)) { + merged[folderRoot] = _.cloneDeep(modFolder[folderRoot]); + } else { + let defFolder = def[folderRoot]; + merged[folderRoot].filesWithContent = _.merge(defFolder.filesWithContent || [], modFolder.filesWithContent || []); + merged[folderRoot].missingFiles = (defFolder.missingFiles || []).concat(modFolder.missingFiles || []); + merged[folderRoot].presentFiles = (defFolder.presentFiles || []).concat(modFolder.presentFiles || []); + + // remove the missingFiles from the presentFiles if they were initially there + if (modFolder.missingFiles) { + merged[folderRoot].presentFiles = _.difference(defFolder.presentFiles, modFolder.missingFiles); + } + + // remove the presentFiles from the missingFiles if they were initially there. + if (modFolder.presentFiles) { + merged[folderRoot].missingFiles = _.difference(defFolder.presentFiles, modFolder.presentFiles); + } + } + }); + + return merged; + } + + // Executes a changes test case: + // 1. Executes Prepare Platform for the Platform + // 2. Applies some changes to the App. Persists the expected Modifications + // 3. Executes again Prepare Platform for the Platform + // 4. Gets the Default Destination App Structure and merges it with the Modifications + // 5. Asserts the Destination App matches our expectations + async function testChangesApplied(platform: string, applyChangesFn: (createdTestData: CreatedTestData) => any) { + let createdTestData = await testPreparePlatform(platform); + + let modifications = applyChangesFn(createdTestData); + + await execPreparePlatform(platform, createdTestData.testDirData); + + let defaultStructure = getDefaultFolderVerificationData(platform, createdTestData.testDirData.appDestFolderPath); + + let merged = mergeModifications(defaultStructure, modifications); + + DestinationFolderVerifier.verify(merged, fs); + } + + it("should sync only changed files, without special folders (iOS)", async () => { + let applyChangesFn = (createdTestData: CreatedTestData) => { + // apply changes + const expectedFileContent = "updated-content-ios"; + updateFile(createdTestData.files, "test1.ios.js", expectedFileContent); + + // construct the folder modifications data + let modifications: any = {}; + modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { + filesWithContent: [ + { + name: "test1.js", + content: expectedFileContent + } + ] + }; + return modifications; + }; + await testChangesApplied("iOS", applyChangesFn); + }); + + it("should sync only changed files, without special folders (Android) #2697", async () => { + let applyChangesFn = (createdTestData: CreatedTestData) => { + // apply changes + const expectedFileContent = "updated-content-android"; + updateFile(createdTestData.files, "test2.android.js", expectedFileContent); + + // construct the folder modifications data + let modifications: any = {}; + modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { + filesWithContent: [ + { + name: "test2.js", + content: expectedFileContent + } + ] + }; + return modifications; + }; + await testChangesApplied("Android", applyChangesFn); + }); + + it("Ensure App_Resources get reloaded after change in the app folder (iOS) #2560", async () => { + let applyChangesFn = (createdTestData: CreatedTestData) => { + // apply changes + const expectedFileContent = "updated-icon-content"; + let iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/iOS/icon.png"); + fs.writeFile(iconPngPath, expectedFileContent); + + // construct the folder modifications data + let modifications: any = {}; + modifications[createdTestData.testDirData.appDestFolderPath] = { + filesWithContent: [ + { + name: "Resources/icon.png", + content: expectedFileContent + } + ] + }; + + return modifications; + }; + await testChangesApplied("iOS", applyChangesFn); + }); + + it("Ensure App_Resources get reloaded after change in the app folder (Android) #2560", async () => { + let applyChangesFn = (createdTestData: CreatedTestData) => { + // apply changes + const expectedFileContent = "updated-icon-content"; + let iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/Android/icon.png"); + fs.writeFile(iconPngPath, expectedFileContent); + + // construct the folder modifications data + let modifications: any = {}; + modifications[createdTestData.testDirData.appDestFolderPath] = { + filesWithContent: [ + { + name: "src/main/res/icon.png", + content: expectedFileContent + } + ] + }; + + return modifications; + }; + await testChangesApplied("Android", applyChangesFn); + }); + + it("Ensure App_Resources get reloaded after a new file appears in the app folder (iOS) #2560", async () => { + let applyChangesFn = (createdTestData: CreatedTestData) => { + // apply changes + const expectedFileContent = "new-file-content"; + let iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/iOS/new-file.png"); + fs.writeFile(iconPngPath, expectedFileContent); + + // construct the folder modifications data + let modifications: any = {}; + modifications[createdTestData.testDirData.appDestFolderPath] = { + filesWithContent: [ + { + name: "Resources/new-file.png", + content: expectedFileContent + } + ] + }; + + return modifications; + }; + await testChangesApplied("iOS", applyChangesFn); + }); + + it("Ensure App_Resources get reloaded after a new file appears in the app folder (Android) #2560", async () => { + let applyChangesFn = (createdTestData: CreatedTestData) => { + // apply changes + const expectedFileContent = "new-file-content"; + let iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/Android/new-file.png"); + fs.writeFile(iconPngPath, expectedFileContent); + + // construct the folder modifications data + let modifications: any = {}; + modifications[createdTestData.testDirData.appDestFolderPath] = { + filesWithContent: [ + { + name: "src/main/res/new-file.png", + content: expectedFileContent + } + ] + }; + + return modifications; + }; + await testChangesApplied("Android", applyChangesFn); + }); + + it("should sync new platform specific files (iOS)", async () => { + let applyChangesFn = (createdTestData: CreatedTestData) => { + // apply changes + const expectedFileContent = "new-content-ios"; + fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.ios.js"), expectedFileContent); + + // construct the folder modifications data + let modifications: any = {}; + modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { + filesWithContent: [ + { + name: "test3.js", + content: expectedFileContent + } + ] + }; + + return modifications; + }; + await testChangesApplied("iOS", applyChangesFn); + }); + + it("should sync new platform specific files (Android)", async () => { + let applyChangesFn = (createdTestData: CreatedTestData) => { + // apply changes + const expectedFileContent = "new-content-android"; + fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.android.js"), expectedFileContent); + + // construct the folder modifications data + let modifications: any = {}; + modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { + filesWithContent: [ + { + name: "test3.js", + content: expectedFileContent + } + ] + }; + + return modifications; + }; + await testChangesApplied("Android", applyChangesFn); + }); + + it("should sync new common files (iOS)", async () => { + let applyChangesFn = (createdTestData: CreatedTestData) => { + // apply changes + const expectedFileContent = "new-content-ios"; + fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.js"), expectedFileContent); + + // construct the folder modifications data + let modifications: any = {}; + modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { + filesWithContent: [ + { + name: "test3.js", + content: expectedFileContent + } + ] + }; + + return modifications; + }; + await testChangesApplied("iOS", applyChangesFn); + }); + + it("should sync new common file (Android)", async () => { + let applyChangesFn = (createdTestData: CreatedTestData) => { + // apply changes + const expectedFileContent = "new-content-android"; + fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.js"), expectedFileContent); + + // construct the folder modifications data + let modifications: any = {}; + modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { + filesWithContent: [ + { + name: "test3.js", + content: expectedFileContent + } + ] + }; + + return modifications; + }; + await testChangesApplied("Android", applyChangesFn); + }); + it("invalid xml is caught", async () => { require("colors"); let testDirData = prepareDirStructure(); @@ -433,7 +837,8 @@ describe('Platform Service Tests', () => { ensureConfigurationFileInAppResources: (): any => null, interpolateConfigurationFile: (): void => undefined, isPlatformPrepared: (projectRoot: string) => false - } + }, + frameworkPackageName: "tns-ios" }; }; diff --git a/test/project-changes-service.ts b/test/project-changes-service.ts new file mode 100644 index 0000000000..ccd3d9b177 --- /dev/null +++ b/test/project-changes-service.ts @@ -0,0 +1,116 @@ +import * as path from "path"; +import { BaseServiceTest } from "./base-service-test"; +import temp = require("temp"); +import { assert } from "chai"; +import { PlatformsData } from "../lib/platforms-data"; +import { ProjectChangesService } from "../lib/services/project-changes-service"; +import * as Constants from "../lib/constants"; +import { FileSystem } from "../lib/common/file-system"; + +// start tracking temporary folders/files +temp.track(); + +class ProjectChangesServiceTest extends BaseServiceTest { + public projectDir: string; + + constructor() { + super(); + } + + initInjector(): void { + this.projectDir = temp.mkdirSync("projectDir"); + this.injector.register("projectData", { + projectDir: this.projectDir + }); + + this.injector.register("platformsData", PlatformsData); + this.injector.register("androidProjectService", {}); + this.injector.register("iOSProjectService", {}); + + this.injector.register("fs", FileSystem); + this.injector.register("devicePlatformsConstants", {}); + this.injector.register("devicePlatformsConstants", {}); + this.injector.register("projectChangesService", ProjectChangesService); + + } + + get projectChangesService(): IProjectChangesService { + return this.injector.resolve("projectChangesService"); + } + + get projectData(): IProjectData { + return this.injector.resolve("projectData"); + } + + get platformsData(): any { + return this.injector.resolve("platformsData"); + } +} + +describe("Project Changes Service Tests", () => { + let serviceTest: ProjectChangesServiceTest; + beforeEach(() => { + serviceTest = new ProjectChangesServiceTest(); + + const platformsDir = path.join( + serviceTest.projectDir, + Constants.PLATFORMS_DIR_NAME + ); + + serviceTest.platformsData.getPlatformData = + (platform: string) => { + return { + projectRoot: path.join(platformsDir, platform) + }; + }; + }); + + describe("Get Prepare Info File Path", () => { + it("Gets the correct Prepare Info path for ios/android", () => { + for (let platform of ["ios", "android"]) { + let actualPrepareInfoPath = serviceTest.projectChangesService + .getPrepareInfoFilePath(platform, this.projectData); + + const expectedPrepareInfoPath = path.join(serviceTest.projectDir, + Constants.PLATFORMS_DIR_NAME, + platform, + ".nsprepareinfo"); + assert.equal(actualPrepareInfoPath, expectedPrepareInfoPath); + } + }); + }); + + describe("Get Prepare Info", () => { + it("Returns empty if file path doesn't exists", () => { + for (let platform of ["ios", "android"]) { + let projectInfo = serviceTest.projectChangesService.getPrepareInfo(platform, this.projectData); + + assert.isNull(projectInfo); + } + }); + + it("Reads the Prepare Info correctly", () => { + const fs: FileSystem = serviceTest.resolve("fs"); + for (let platform of ["ios", "android"]) { + // arrange + const prepareInfoPath = path.join(serviceTest.projectDir, Constants.PLATFORMS_DIR_NAME, + platform, ".nsprepareinfo"); + const expectedPrepareInfo = { + time: new Date().toString(), + bundle: true, + release: false, + changesRequireBuild: true, + changesRequireBuildTime: new Date().toString(), + iOSProvisioningProfileUUID: "provisioning_profile_test" + }; + fs.writeJson(prepareInfoPath, expectedPrepareInfo); + + // act + let actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo(platform, this.projectData); + + // assert + assert.deepEqual(actualPrepareInfo, expectedPrepareInfo); + } + }); + }); +}); From d992963a01906bb26f9f8374d708ccf42565088c Mon Sep 17 00:00:00 2001 From: Emil Tabakov Date: Fri, 21 Apr 2017 11:11:50 +0300 Subject: [PATCH 002/212] Fix installation scripts for Mac (#2714) * Fix android sdk commands to use sdkmanager * Replace brew install with brew cask * Fix installation for haxm * Change brew formulae repository before installing android sdk * Fix setting the ENV variable missing cast --- setup/native-script.rb | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) mode change 100644 => 100755 setup/native-script.rb diff --git a/setup/native-script.rb b/setup/native-script.rb old mode 100644 new mode 100755 index 81dc86b610..b70aa25d31 --- a/setup/native-script.rb +++ b/setup/native-script.rb @@ -76,7 +76,7 @@ def install(program_name, message, script, run_as_root = false, show_all_option end def install_environment_variable(name, value) - ENV[name] = value + ENV[name] = value.to_s execute("echo \"export #{name}=#{value}\" >> ~/.bash_profile", "Unable to set #{name}") @@ -93,7 +93,7 @@ def install_environment_variable(name, value) end install("Java SE Development Kit", "Installing the Java SE Development Kit... This might take some time, please, be patient. (You will be prompted for your password)", 'brew cask install java', false, false) -install("Android SDK", "Installing Android SDK", 'brew install android-sdk', false) +install("Android SDK", "Installing Android SDK", 'brew tap caskroom/cask; brew cask install android-sdk', false) unless ENV["ANDROID_HOME"] require 'pathname' @@ -129,30 +129,31 @@ def install_environment_variable(name, value) # the android tool will introduce a --accept-license option in subsequent releases error_msg = "There seem to be some problems with the Android configuration" -android_executable = File.join(ENV["ANDROID_HOME"], "tools", "android") -execute("echo y | #{android_executable} update sdk --filter platform-tools --all --no-ui", error_msg) -execute("echo y | #{android_executable} update sdk --filter tools --all --no-ui", error_msg) -execute("echo y | #{android_executable} update sdk --filter android-23 --all --no-ui", error_msg) -execute("echo y | #{android_executable} update sdk --filter build-tools-25.0.2 --all --no-ui", error_msg) -execute("echo y | #{android_executable} update sdk --filter build-tools-23.0.3 --all --no-ui", error_msg) -execute("echo y | #{android_executable} update sdk --filter extra-android-m2repository --all --no-ui", error_msg) -execute("echo y | #{android_executable} update sdk --filter extra-google-m2repository --all --no-ui", error_msg) +android_executable = File.join(ENV["ANDROID_HOME"], "tools", "bin", "sdkmanager") +execute("echo y | #{android_executable} \"platform-tools\"", error_msg) +execute("echo y | #{android_executable} \"tools\"", error_msg) +execute("echo y | #{android_executable} \"build-tools;25.0.2\"", error_msg) +execute("echo y | #{android_executable} \"platforms;android-25\"", error_msg) +execute("echo y | #{android_executable} \"platforms;android-24\"", error_msg) +execute("echo y | #{android_executable} \"platforms;android-23\"", error_msg) +execute("echo y | #{android_executable} \"platforms;android-22\"", error_msg) +execute("echo y | #{android_executable} \"platforms;android-21\"", error_msg) +execute("echo y | #{android_executable} \"platforms;android-19\"", error_msg) +execute("echo y | #{android_executable} \"platforms;android-18\"", error_msg) +execute("echo y | #{android_executable} \"platforms;android-17\"", error_msg) puts "Do you want to install Android emulator? (y/n)" if gets.chomp.downcase == "y" puts "Do you want to install HAXM (Hardware accelerated Android emulator)? (y/n)" if gets.chomp.downcase == "y" - execute("echo y | #{android_executable} update sdk --filter extra-intel-Hardware_Accelerated_Execution_Manager --all --no-ui", error_msg) - + execute("echo y | #{android_executable} \"extras;intel;Hardware_Accelerated_Execution_Manager\"", error_msg) + haxm_silent_installer = File.join(ENV["ANDROID_HOME"], "extras", "intel", "Hardware_Accelerated_Execution_Manager", "silent_install.sh") - execute("#{haxm_silent_installer}", "There seem to be some problems with the Android configuration") - - execute("echo y | #{android_executable} update sdk --filter sys-img-x86-android-23 --all --no-ui", error_msg) - execute("echo no | #{android_executable} create avd -n Emulator-Api23-Default -t android-23 --abi default/x86 -c 12M -f", error_msg) - else - execute("echo y | #{android_executable} update sdk --filter sys-img-armeabi-v7a-android-23 --all --no-ui", error_msg) - execute("echo no | #{android_executable} create avd -n Emulator-Api23-Default -t android-23 --abi default/armeabi-v7a -c 12M -f", error_msg) + execute("sudo #{haxm_silent_installer}", "There seem to be some problems with the Android configuration") + else end + execute("echo y | #{android_executable} \"system-images;android-25;google_apis;x86\"", error_msg) + execute("echo y | #{android_executable} \"system-images;android-24;default;x86\"", error_msg) end puts "The ANDROID_HOME and JAVA_HOME environment variables have been added to your .bash_profile/.zprofile" From 2af361568ceb4c592ee4554e243877605fc606a9 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Wed, 19 Apr 2017 17:23:58 +0300 Subject: [PATCH 003/212] Fix debug on iOS simulator with watch (#2721) During `tns debug ios`, in case you make changes, the application must be restarted and the debugger must attached again. However, in many cases we kill the old lldb process and immediately try to start the new one. The childProcess.kill operation finishes, but lldb process does not die immedietely. So in some occasions, the attach of new debugger fails. This leads to multiple errors - you cannot start this application on simulator anymore, you cannot exit CLI's process with `Ctrl + C`, etc. Fix this by attaching to "close" event of the processes and waiting for them to be really finish their execution. --- lib/services/ios-debug-service.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index 22b01dffaf..26105413af 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -78,20 +78,31 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService } _.forEach(this._sockets, socket => socket.destroy()); + this._sockets = []; if (this._lldbProcess) { this._lldbProcess.stdin.write("process detach\n"); - this._lldbProcess.kill(); + + await this.killProcess(this._lldbProcess); this._lldbProcess = undefined; } if (this._childProcess) { - this._childProcess.kill(); + await this.killProcess(this._childProcess); this._childProcess = undefined; } } + private async killProcess(childProcess: ChildProcess): Promise { + if (childProcess) { + return new Promise((resolve, reject) => { + childProcess.on("close", resolve); + childProcess.kill(); + }); + } + } + private async emulatorDebugBrk(debugData: IDebugData, debugOptions: IDebugOptions): Promise { let args = debugOptions.debugBrk ? "--nativescript-debug-brk" : "--nativescript-debug-start"; let child_process = await this.$iOSEmulatorServices.runApplicationOnEmulator(debugData.pathToAppPackage, { From 5c3ea300c95c64eabd7bc88a1fdf9f2f2863541a Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Fri, 21 Apr 2017 11:12:28 +0300 Subject: [PATCH 004/212] Implement extensibility model for CLI (#2724) Implement extensibilty of CLI that allows anyone to add easily create packages that add new functionality to NativeScript CLI. The packages are installed in a specific directory, so they are persisted through CLI's updated. The directory where extensions are installed contains a package.json and each extension is npm package installed there. The extensions can be mainatined in two different ways: - navigate to the directory where extensions are installed and use `npm` for install/uninstall/update of packages. - use CLI's commands to update them: `tns extension install `, `tns extension uninstall `, `tns extension` Implement extensibilityService that executes all operations and expose it to public API. In {N} CLI the extensions are loaded in the entry point, before parsing command line arguments. This way extensions can add new commands. In Fusion, after CLI is required as a library, the `extensibilityService.loadExtensions` method should be called. It returns array of Promises - one for each installed extension. Add help for the new commands, but do not link the new commands in other commands help for the moment. Add unit tests for the new service. --- PublicAPI.md | 201 +++--- docs/man_pages/general/extension-install.md | 35 + docs/man_pages/general/extension-uninstall.md | 31 + docs/man_pages/general/extension.md | 25 + lib/bootstrap.ts | 7 + .../extensibility/install-extension.ts | 13 + lib/commands/extensibility/list-extensions.ts | 24 + .../extensibility/uninstall-extension.ts | 14 + lib/definitions/extensibility.d.ts | 48 ++ lib/definitions/platform.d.ts | 2 +- lib/definitions/require.d.ts | 12 + lib/nativescript-cli.ts | 19 +- lib/services/extensibility-service.ts | 137 ++++ lib/services/require-service.ts | 7 + test/nativescript-cli-lib.ts | 3 +- test/services/extensibility-service.ts | 612 ++++++++++++++++++ 16 files changed, 1104 insertions(+), 86 deletions(-) create mode 100644 docs/man_pages/general/extension-install.md create mode 100644 docs/man_pages/general/extension-uninstall.md create mode 100644 docs/man_pages/general/extension.md create mode 100644 lib/commands/extensibility/install-extension.ts create mode 100644 lib/commands/extensibility/list-extensions.ts create mode 100644 lib/commands/extensibility/uninstall-extension.ts create mode 100644 lib/definitions/extensibility.d.ts create mode 100644 lib/definitions/require.d.ts create mode 100644 lib/services/extensibility-service.ts create mode 100644 lib/services/require-service.ts create mode 100644 test/services/extensibility-service.ts diff --git a/PublicAPI.md b/PublicAPI.md index e29610ab9a..0434108d54 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -3,35 +3,17 @@ Public API This document describes all methods that can be invoked when NativeScript CLI is required as library, i.e. - - - - - - - - - - -
- JavaScript - - TypeScript -
-
+```JavaScript
 const tns = require("nativescript");
-
-
-
-import * as tns from "nativescript";
-
-
+``` ## Module projectService `projectService` modules allow you to create new NativeScript application. -* `createProject(projectSettings: IProjectSettings): Promise` - Creates new NativeScript application. By passing `projectSettings` argument you specify the name of the application, the template that will be used, etc.: +### createProject +* Description: `createProject(projectSettings: IProjectSettings): Promise` - Creates new NativeScript application. By passing `projectSettings` argument you specify the name of the application, the template that will be used, etc.: + ```TypeScript /** * Describes available settings when creating new NativeScript application. @@ -73,71 +55,130 @@ interface IProjectSettings { } ``` -Sample usage: - - - - - - - - - -
- JavaScript - - TypeScript -
-
+* Sample usage:
+```JavaScript
 const projectSettings = {
 	projectName: "my-ns-app",
-    template: "ng",
-    pathToProject: "/home/my-user/project-dir"
+	template: "ng",
+	pathToProject: "/home/my-user/project-dir"
 };
 
 tns.projectService.createProject(projectSettings)
 	.then(() => console.log("Project successfully created."))
-    .catch((err) => console.log("Unable to create project, reason: ", err);
-
-
-
-const projectSettings: IProjectSettings = {
-	projectName: "my-ns-app",
-    template: "ng",
-    pathToProject: "/home/my-user/project-dir"
-};
+	.catch((err) => console.log("Unable to create project, reason: ", err);
+```
 
-tns.projectService.createProject(projectSettings)
-	.then(() => console.log("Project successfully created."))
-    .catch((err) => console.log("Unable to create project, reason: ", err);
-
-
- -* `isValidNativeScriptProject(projectDir: string): boolean` - Checks if the specified path is a valid NativeScript project. Returns `true` in case the directory is a valid project, `false` otherwise. - -Sample usage: - - - - - - - - - -
- JavaScript - - TypeScript -
-
-const isValidProject = tns.projectService.isValidNativeScriptProject("/tmp/myProject");
-
-
-
+### isValidNativeScriptProject
+* Definition: `isValidNativeScriptProject(projectDir: string): boolean` - Checks if the specified path is a valid NativeScript project. Returns `true` in case the directory is a valid project, `false` otherwise.
+
+* Sample usage:
+```JavaScript
 const isValidProject = tns.projectService.isValidNativeScriptProject("/tmp/myProject");
-
-
+console.log(isValidProject); // true or false +``` + +## extensibilityService +`extensibilityService` module gives access to methods for working with CLI's extensions - list, install, uninstall, load them. The extensions add new functionality to CLI, so once an extension is loaded, all methods added to it's public API are accessible directly through CLI when it is used as a library. Extensions may also add new commands, so they are accessible through command line when using NativeScript CLI. + +A common interface describing the results of a method is `IExtensionData`: +```TypeScript +/** + * Describes each extension. + */ +interface IExtensionData { + /** + * The name of the extension. + */ + extensionName: string; +} +``` + +### installExtension +Installs specified extension and loads it in the current process, so the functionality that it adds can be used immediately. + +* Definition: +```TypeScript +/** + * Installs and loads specified extension. + * @param {string} extensionName Name of the extension to be installed. It may contain version as well, i.e. myPackage, myPackage@1.0.0, myPackage.tgz, https://github.com/myOrganization/myPackage/tarball/master, https://github.com/myOrganization/myPackage etc. + * @returns {Promise} Information about installed extensions. + */ +installExtension(extensionName: string): Promise; +``` + +* Usage: +```JavaScript +tns.extensibilityService.installExtension("extension-package") + .then(extensionData => console.log(`Successfully installed extension ${extensionData.extensionName}.`)) + .catch(err => console.log("Failed to install extension.")); +``` + +### uninstallExtension +Uninstalls specified extensions, so its functionality will no longer be available through CLI. + +* Definition: +```TypeScript +/** + * Uninstalls extension from the installation. + * @param {string} extensionName Name of the extension to be uninstalled. + * @returns {Promise} + */ +uninstallExtension(extensionName: string): Promise; +``` + +* Usage: +```JavaScript +tns.extensibilityService.uninstallExtension("extension-package") + .then(() => console.log("Successfully uninstalled extension.")) + .catch(err => console.log("Failed to uninstall extension.")); +``` + +### getInstalledExtensions +Gets information about all installed extensions. + +* Definition: +```TypeScript +/** + * Gets information about installed dependencies - names and versions. + * @returns {IStringDictionary} + */ +getInstalledExtensions(): IStringDictionary; +``` + +* Usage: +```JavaScript +const installedExtensions = tns.extensibilityService.getInstalledExtensions(); +for (let extensionName in installedExtensions) { + const version = installedExtensions[extensionName]; + console.log(`The extension ${extensionName} is installed with version ${version}.`); +} +``` + +### loadExtensions +Loads all currently installed extensions. The method returns array of Promises, one for each installed extension. In case any of the extensions cannot be loaded, only its Promise is rejected. + +* Definition +```TypeScript +/** + * Loads all extensions, so their methods and commands can be used from CLI. + * For each of the extensions, a new Promise is returned. It will be rejected in case the extension cannot be loaded. However other promises will not be reflected by this failure. + * In case a promise is rejected, the error will have additional property (extensionName) that shows which is the extension that cannot be loaded in the process. + * @returns {Promise[]} Array of promises, each is resolved with information about loaded extension. + */ +loadExtensions(): Promise[]; +``` + +* Usage: +```JavaScript +const loadExtensionsPromises = tns.extensibilityService.loadExtensions(); +for (let promise of loadExtensionsPromises) { + promise.then(extensionData => console.log(`Loaded extension: ${extensionData.extensionName}.`), + err => { + console.log(`Failed to load extension: ${err.extensionName}`); + console.log(err); + }); +} +``` ## How to add a new method to Public API CLI is designed as command line tool and when it is used as a library, it does not give you access to all of the methods. This is mainly implementation detail. Most of the CLI's code is created to work in command line, not as a library, so before adding method to public API, most probably it will require some modification. diff --git a/docs/man_pages/general/extension-install.md b/docs/man_pages/general/extension-install.md new file mode 100644 index 0000000000..0c98c4a3d6 --- /dev/null +++ b/docs/man_pages/general/extension-install.md @@ -0,0 +1,35 @@ +extension install +========== + +Usage | Synopsis +------|------- +General | `$ tns extension install ` + +Installs specified extension. Each extension adds additional functionality that's accessible directly from NativeScript CLI. + +### Attributes + +* `` is any of the following. + * A `` or `@` where `` is the name of a package that is published in the npm registry and `` is a valid version of this plugin. + * A `` to the directory which contains the extension, including its `package.json` file. + * A `` to a `.tar.gz` archive containing a directory with the extension and its `package.json` file. + * A `` which resolves to a `.tar.gz` archive containing a directory with the extension and its `package.json` file. + * A `` which resolves to a `.tar.gz` archive containing a directory with the extension and its `package.json` file. + +<% if(isHtml) { %> +### Related Commands + +Command | Description +----------|---------- +[extension](extension.html) | Prints information about all installed extensions. +[extension-uninstall](extension-uninstall.html) | Uninstalls specified extension. +[autocomplete-status](autocomplete-status.html) | Prints the current status of your command-line completion settings. +[autocomplete-enable](autocomplete-enable.html) | Configures your current command-line completion settings. +[autocomplete-disable](autocomplete-disable.html) | Disables command-line completion for bash and zsh shells. +[usage-reporting](usage-reporting.html) | Configures anonymous usage reporting for the NativeScript CLI. +[error-reporting](error-reporting.html) | Configures anonymous error reporting for the NativeScript CLI. +[doctor](doctor.html) | Checks your system for configuration problems which might prevent the NativeScript CLI from working properly. +[proxy](proxy.html) | Displays proxy settings. +[proxy clear](proxy-clear.html) | Clears proxy settings. +[proxy set](proxy-set.html) | Sets proxy settings. +<% } %> \ No newline at end of file diff --git a/docs/man_pages/general/extension-uninstall.md b/docs/man_pages/general/extension-uninstall.md new file mode 100644 index 0000000000..8c7be09c88 --- /dev/null +++ b/docs/man_pages/general/extension-uninstall.md @@ -0,0 +1,31 @@ +extension uninstall +========== + +Usage | Synopsis +------|------- +General | `$ tns extension uninstall ` + +Uninstalls specified extension. After that you will not be able to use the functionality that this extensions adds to NativeScript CLI. + +### Attributes + +* `` is the name of the extension as listed in its `package.json` file. + +<% if(isHtml) { %> +### Related Commands + +Command | Description +----------|---------- +[extension](extension.html) | Prints information about all installed extensions. +[extension-uninstall](extension-uninstall.html) | Uninstalls specified extension. +[extension-install](extension-install.html) | Installs specified extension. +[autocomplete-status](autocomplete-status.html) | Prints the current status of your command-line completion settings. +[autocomplete-enable](autocomplete-enable.html) | Configures your current command-line completion settings. +[autocomplete-disable](autocomplete-disable.html) | Disables command-line completion for bash and zsh shells. +[usage-reporting](usage-reporting.html) | Configures anonymous usage reporting for the NativeScript CLI. +[error-reporting](error-reporting.html) | Configures anonymous error reporting for the NativeScript CLI. +[doctor](doctor.html) | Checks your system for configuration problems which might prevent the NativeScript CLI from working properly. +[proxy](proxy.html) | Displays proxy settings. +[proxy clear](proxy-clear.html) | Clears proxy settings. +[proxy set](proxy-set.html) | Sets proxy settings. +<% } %> \ No newline at end of file diff --git a/docs/man_pages/general/extension.md b/docs/man_pages/general/extension.md new file mode 100644 index 0000000000..d0ed3b5413 --- /dev/null +++ b/docs/man_pages/general/extension.md @@ -0,0 +1,25 @@ +extension +========== + +Usage | Synopsis +------|------- +General | `$ tns extension` + +Prints information about all installed extensions. + +<% if(isHtml) { %> +### Related Commands + +Command | Description +----------|---------- +[extension-install](extension-install.html) | Installs specified extension. +[autocomplete-status](autocomplete-status.html) | Prints the current status of your command-line completion settings. +[autocomplete-enable](autocomplete-enable.html) | Configures your current command-line completion settings. +[autocomplete-disable](autocomplete-disable.html) | Disables command-line completion for bash and zsh shells. +[usage-reporting](usage-reporting.html) | Configures anonymous usage reporting for the NativeScript CLI. +[error-reporting](error-reporting.html) | Configures anonymous error reporting for the NativeScript CLI. +[doctor](doctor.html) | Checks your system for configuration problems which might prevent the NativeScript CLI from working properly. +[proxy](proxy.html) | Displays proxy settings. +[proxy clear](proxy-clear.html) | Clears proxy settings. +[proxy set](proxy-set.html) | Sets proxy settings. +<% } %> \ No newline at end of file diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 5c7a73533d..cb26b7ef7d 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -125,3 +125,10 @@ $injector.require("projectChangesService", "./services/project-changes-service") $injector.require("emulatorPlatformService", "./services/emulator-platform-service"); $injector.require("staticConfig", "./config"); + +$injector.require("requireService", "./services/require-service"); + +$injector.requireCommand("extension|*list", "./commands/extensibility/list-extensions"); +$injector.requireCommand("extension|install", "./commands/extensibility/install-extension"); +$injector.requireCommand("extension|uninstall", "./commands/extensibility/uninstall-extension"); +$injector.requirePublic("extensibilityService", "./services/extensibility-service"); diff --git a/lib/commands/extensibility/install-extension.ts b/lib/commands/extensibility/install-extension.ts new file mode 100644 index 0000000000..91bea2a76a --- /dev/null +++ b/lib/commands/extensibility/install-extension.ts @@ -0,0 +1,13 @@ +export class InstallExtensionCommand implements ICommand { + constructor(private $extensibilityService: IExtensibilityService, + private $stringParameterBuilder: IStringParameterBuilder, + private $logger: ILogger) { } + + public async execute(args: string[]): Promise { + const extensionData = await this.$extensibilityService.installExtension(args[0]); + this.$logger.info(`Successfully installed extension ${extensionData.extensionName}.`); + } + + allowedParameters: ICommandParameter[] = [this.$stringParameterBuilder.createMandatoryParameter("You have to provide a valid name for extension that you want to install.")]; +} +$injector.registerCommand("extension|install", InstallExtensionCommand); diff --git a/lib/commands/extensibility/list-extensions.ts b/lib/commands/extensibility/list-extensions.ts new file mode 100644 index 0000000000..aabc3f9c13 --- /dev/null +++ b/lib/commands/extensibility/list-extensions.ts @@ -0,0 +1,24 @@ +import * as helpers from "../../common/helpers"; + +export class ListExtensionsCommand implements ICommand { + constructor(private $extensibilityService: IExtensibilityService, + private $logger: ILogger) { } + + public async execute(args: string[]): Promise { + const installedExtensions = this.$extensibilityService.getInstalledExtensions(); + if (_.keys(installedExtensions).length) { + this.$logger.info("Installed extensions:"); + const data = _.map(installedExtensions, (version, name) => { + return [name, version]; + }); + + const table = helpers.createTable(["Name", "Version"], data); + this.$logger.out(table.toString()); + } else { + this.$logger.info("No extensions installed."); + } + } + + allowedParameters: ICommandParameter[] = []; +} +$injector.registerCommand("extension|*list", ListExtensionsCommand); diff --git a/lib/commands/extensibility/uninstall-extension.ts b/lib/commands/extensibility/uninstall-extension.ts new file mode 100644 index 0000000000..76ad4f882c --- /dev/null +++ b/lib/commands/extensibility/uninstall-extension.ts @@ -0,0 +1,14 @@ +export class UninstallExtensionCommand implements ICommand { + constructor(private $extensibilityService: IExtensibilityService, + private $stringParameterBuilder: IStringParameterBuilder, + private $logger: ILogger) { } + + public async execute(args: string[]): Promise { + const extensionName = args[0]; + await this.$extensibilityService.uninstallExtension(extensionName); + this.$logger.info(`Successfully uninstalled extension ${extensionName}`); + } + + allowedParameters: ICommandParameter[] = [this.$stringParameterBuilder.createMandatoryParameter("You have to provide a valid name for extension that you want to uninstall.")]; +} +$injector.registerCommand("extension|uninstall", UninstallExtensionCommand); diff --git a/lib/definitions/extensibility.d.ts b/lib/definitions/extensibility.d.ts new file mode 100644 index 0000000000..3aa9dba5cd --- /dev/null +++ b/lib/definitions/extensibility.d.ts @@ -0,0 +1,48 @@ +/** + * Describes each extension. + */ +interface IExtensionData { + /** + * The name of the extension. + */ + extensionName: string; +} + +/** + * Defines methods for working with CLI's extensions. + */ +interface IExtensibilityService { + /** + * Installs and loads specified extension. + * @param {string} extensionName Name of the extension to be installed. It may contain version as well, i.e. myPackage, myPackage@1.0.0, + * myPackage.tgz, https://github.com/myOrganization/myPackage/tarball/master, https://github.com/myOrganization/myPackage, etc. + * @returns {Promise} Information about installed extensions. + */ + installExtension(extensionName: string): Promise; + + /** + * Uninstalls extension from the installation. + * @param {string} extensionName Name of the extension to be uninstalled. + * @returns {Promise} + */ + uninstallExtension(extensionName: string): Promise; + + /** + * Loads all extensions, so their methods and commands can be used from CLI. + * For each of the extensions, a new Promise is returned. It will be rejected in case the extension cannot be loaded. However other promises will not be reflected by this failure. + * In case a promise is rejected, the error will have additional property (extensionName) that shows which is the extension that cannot be loaded in the process. + * @returns {Promise[]} Array of promises, each is resolved with information about loaded extension. + */ + loadExtensions(): Promise[]; + + /** + * Gets information about installed dependencies - names and versions. + * @returns {IStringDictionary} + */ + getInstalledExtensions(): IStringDictionary; +} + +/** + * Describes the error that will be raised when a problem with extension is detected. + */ +interface IExtensionLoadingError extends Error, IExtensionData { } \ No newline at end of file diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 8f5e22fc8d..638d675f7c 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -211,7 +211,7 @@ interface IPlatformSpecificData { provision: any; /** - * Target SDK for Android.s + * Target SDK for Android. */ sdk: string; } diff --git a/lib/definitions/require.d.ts b/lib/definitions/require.d.ts new file mode 100644 index 0000000000..83a40e595a --- /dev/null +++ b/lib/definitions/require.d.ts @@ -0,0 +1,12 @@ +/** + * Describes methods available in the require. + */ +interface IRequireService { + + /** + * Wrapper for the Node.js `require` method. + * @param {string} module Module to be required. + * @returns {any} The result of the require action. + */ + require(module: string): any; +} diff --git a/lib/nativescript-cli.ts b/lib/nativescript-cli.ts index 3d7434c688..143c5268d5 100644 --- a/lib/nativescript-cli.ts +++ b/lib/nativescript-cli.ts @@ -5,14 +5,25 @@ shelljs.config.fatal = true; import { installUncaughtExceptionListener } from "./common/errors"; installUncaughtExceptionListener(process.exit); +import { settlePromises } from "./common/helpers"; + (async () => { - let config: Config.IConfig = $injector.resolve("$config"); - let err: IErrors = $injector.resolve("$errors"); + const config: Config.IConfig = $injector.resolve("$config"); + const err: IErrors = $injector.resolve("$errors"); err.printCallStack = config.DEBUG; - let commandDispatcher: ICommandDispatcher = $injector.resolve("commandDispatcher"); + const logger: ILogger = $injector.resolve("logger"); + + const extensibilityService: IExtensibilityService = $injector.resolve("extensibilityService"); + try { + await settlePromises(extensibilityService.loadExtensions()); + } catch (err) { + logger.trace("Unable to load extensions. Error is: ", err); + } + + const commandDispatcher: ICommandDispatcher = $injector.resolve("commandDispatcher"); - let messages: IMessagesService = $injector.resolve("$messagesService"); + const messages: IMessagesService = $injector.resolve("$messagesService"); messages.pathsToMessageJsonFiles = [/* Place client-specific json message file paths here */]; if (process.argv[2] === "completion") { diff --git a/lib/services/extensibility-service.ts b/lib/services/extensibility-service.ts new file mode 100644 index 0000000000..756e037f00 --- /dev/null +++ b/lib/services/extensibility-service.ts @@ -0,0 +1,137 @@ +import * as path from "path"; +import { cache, exported } from "../common/decorators"; +import * as constants from "../constants"; + +export class ExtensibilityService implements IExtensibilityService { + private get pathToExtensions(): string { + return path.join(this.$options.profileDir, "extensions"); + } + + private get pathToPackageJson(): string { + return path.join(this.pathToExtensions, constants.PACKAGE_JSON_FILE_NAME); + } + + constructor(private $fs: IFileSystem, + private $logger: ILogger, + private $npm: INodePackageManager, + private $options: IOptions, + private $requireService: IRequireService) { + } + + @exported("extensibilityService") + public async installExtension(extensionName: string): Promise { + this.$logger.trace(`Start installation of extension '${extensionName}'.`); + + await this.assertPackageJsonExists(); + + const npmOpts: any = { + save: true, + ["save-exact"]: true + }; + + const localPath = path.resolve(extensionName); + const packageName = this.$fs.exists(localPath) ? localPath : extensionName; + + const realName = (await this.$npm.install(packageName, this.pathToExtensions, npmOpts))[0]; + this.$logger.trace(`Finished installation of extension '${extensionName}'. Trying to load it now.`); + + // In case the extension is already installed, the $npm.install method will not return the name of the package. + // Fallback to the original value. + // NOTE: This will not be required once $npm.install starts working correctly. + return await this.loadExtension(realName || extensionName); + } + + @exported("extensibilityService") + public async uninstallExtension(extensionName: string): Promise { + this.$logger.trace(`Start uninstallation of extension '${extensionName}'.`); + + await this.assertPackageJsonExists(); + + await this.$npm.uninstall(extensionName, { save: true }, this.pathToExtensions); + + this.$logger.trace(`Finished uninstallation of extension '${extensionName}'.`); + } + + @exported("extensibilityService") + public loadExtensions(): Promise[] { + this.$logger.trace("Loading extensions."); + + let dependencies: IStringDictionary = null; + + try { + dependencies = this.getInstalledExtensions(); + } catch (err) { + this.$logger.trace(`Error while getting installed dependencies: ${err.message}. No extensions will be loaded.`); + } + + return _.keys(dependencies) + .map(name => this.loadExtension(name)); + } + + @exported("extensibilityService") + public getInstalledExtensions(): IStringDictionary { + if (this.$fs.exists(this.pathToPackageJson)) { + return this.$fs.readJson(this.pathToPackageJson).dependencies; + } + + return null; + } + + private async loadExtension(extensionName: string): Promise { + try { + await this.assertExtensionIsInstalled(extensionName); + + const pathToExtension = path.join(this.pathToExtensions, constants.NODE_MODULES_FOLDER_NAME, extensionName); + this.$requireService.require(pathToExtension); + return { extensionName }; + } catch (error) { + this.$logger.warn(`Error while loading ${extensionName} is: ${error.message}`); + const err = new Error(`Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds.`); + err.extensionName = extensionName; + throw err; + } + } + + private async assertExtensionIsInstalled(extensionName: string): Promise { + this.$logger.trace(`Asserting extension ${extensionName} is installed.`); + const installedExtensions = this.$fs.readDirectory(path.join(this.pathToExtensions, constants.NODE_MODULES_FOLDER_NAME)); + + if (installedExtensions.indexOf(extensionName) === -1) { + this.$logger.trace(`Extension ${extensionName} is not installed, starting installation.`); + await this.installExtension(extensionName); + } + + this.$logger.trace(`Extension ${extensionName} is installed.`); + } + + @cache() + private assertExtensionsDirExists(): void { + if (!this.$fs.exists(this.pathToExtensions)) { + this.$fs.createDirectory(this.pathToExtensions); + } + } + + @cache() + private assertPackageJsonExists(): void { + this.assertExtensionsDirExists(); + + if (!this.$fs.exists(this.pathToPackageJson)) { + this.$logger.trace(`Creating ${this.pathToPackageJson}.`); + + // create default package.json + this.$fs.writeJson(this.pathToPackageJson, { + name: "nativescript-extensibility", + version: "1.0.0", + description: "The place where all packages that extend CLI will be installed.", + license: "Apache-2.0", + readme: "The place where all packages that extend CLI will be installed.", + repository: "none", + dependencies: {} + }); + + this.$logger.trace(`Created ${this.pathToPackageJson}.`); + } + } +} + +$injector.register("extensibilityService", ExtensibilityService); diff --git a/lib/services/require-service.ts b/lib/services/require-service.ts new file mode 100644 index 0000000000..6902389e02 --- /dev/null +++ b/lib/services/require-service.ts @@ -0,0 +1,7 @@ +export class RequireService implements IRequireService { + public require(module: string): any { + return require(module); + } +} + +$injector.register("requireService", RequireService); diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index f4fe159e97..394eebd871 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -16,7 +16,8 @@ describe("nativescript-cli-lib", () => { deviceEmitter: null, projectService: ["createProject", "isValidNativeScriptProject"], localBuildService: ["build"], - deviceLogProvider: null + deviceLogProvider: null, + extensibilityService: ["loadExtensions", "getInstalledExtensions", "installExtension", "uninstallExtension"] }; const pathToEntryPoint = path.join(__dirname, "..", "lib", "nativescript-cli-lib.js").replace(/\\/g, "\\\\"); diff --git a/test/services/extensibility-service.ts b/test/services/extensibility-service.ts new file mode 100644 index 0000000000..3e02472ee8 --- /dev/null +++ b/test/services/extensibility-service.ts @@ -0,0 +1,612 @@ +import { ExtensibilityService } from "../../lib/services/extensibility-service"; +import { Yok } from "../../lib/common/yok"; +import * as stubs from "../stubs"; +import { assert } from "chai"; +import * as constants from "../../lib/constants"; +import * as path from "path"; + +describe("extensibilityService", () => { + const getTestInjector = (): IInjector => { + const testInjector = new Yok(); + testInjector.register("fs", {}); + testInjector.register("logger", stubs.LoggerStub); + testInjector.register("npm", {}); + testInjector.register("options", { + profileDir: "profileDir" + }); + testInjector.register("requireService", { + require: (pathToRequire: string): any => undefined + }); + return testInjector; + }; + + describe("installExtension", () => { + describe("fails", () => { + it("when extensions dir does not exist and trying to create it fails", async () => { + const expectedErrorMessage = "Unable to create dir"; + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => false; + fs.createDirectory = (dirToCreate: string): void => { + throw new Error(expectedErrorMessage); + }; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + await assert.isRejected(extensibilityService.installExtension("extensionToInstall"), expectedErrorMessage); + }); + + it("when extensions dir exists, but default package.json is missing and trying to create it fails", async () => { + const expectedErrorMessage = "Unable to write json"; + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) !== constants.PACKAGE_JSON_FILE_NAME; + fs.writeJson = (pathToFile: string, content: any) => { + throw new Error(expectedErrorMessage); + }; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + await assert.isRejected(extensibilityService.installExtension("extensionToInstall"), expectedErrorMessage); + }); + + it("when npm install fails", async () => { + const expectedErrorMessage = "Unable to install package"; + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => true; + const npm: INodePackageManager = testInjector.resolve("npm"); + npm.install = async (packageName: string, pathToSave: string, config?: any): Promise => { + throw new Error(expectedErrorMessage); + }; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + await assert.isRejected(extensibilityService.installExtension("extensionToInstall"), expectedErrorMessage); + }); + }); + + describe("passes correct arguments to npm install", () => { + const getArgsPassedToNpmInstallDuringInstallExtensionCall = async (userSpecifiedValue: string, testInjector?: IInjector): Promise => { + testInjector = testInjector || getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => true; + + fs.readDirectory = (dir: string): string[] => [userSpecifiedValue]; + + const npm: INodePackageManager = testInjector.resolve("npm"); + let argsPassedToNpmInstall: any = {}; + npm.install = async (packageName: string, pathToSave: string, config?: any): Promise => { + argsPassedToNpmInstall.packageName = packageName; + argsPassedToNpmInstall.pathToSave = pathToSave; + argsPassedToNpmInstall.config = config; + return [userSpecifiedValue]; + }; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + await extensibilityService.installExtension(userSpecifiedValue); + + return argsPassedToNpmInstall; + }; + + const assertPackageNamePassedToNpmInstall = async (userSpecifiedValue: string, expectedValue: string): Promise => { + const argsPassedToNpmInstall = await getArgsPassedToNpmInstallDuringInstallExtensionCall(userSpecifiedValue); + assert.deepEqual(argsPassedToNpmInstall.packageName, expectedValue); + }; + + it("passes full path for installation, when trying to install local package (user specifies relative path)", async () => { + const extensionName = "../extension1"; + await assertPackageNamePassedToNpmInstall(extensionName, path.resolve(extensionName)); + }); + + it("passes the value specified by user for installation, when the local path does not exist", async () => { + const extensionName = "extension1"; + await assertPackageNamePassedToNpmInstall(extensionName, path.resolve(extensionName)); + }); + + it("passes save and save-exact options to npm install", async () => { + const extensionName = "extension1"; + const argsPassedToNpmInstall = await getArgsPassedToNpmInstallDuringInstallExtensionCall(extensionName); + const expectedNpmConfg: any = { save: true }; + expectedNpmConfg["save-exact"] = true; + assert.deepEqual(argsPassedToNpmInstall.config, expectedNpmConfg); + }); + + it("passes full path to extensions dir for installation", async () => { + const extensionName = "extension1"; + const testInjector = getTestInjector(); + const options: IOptions = testInjector.resolve("options"); + options.profileDir = "my-profile-dir"; + + const expectedDirForInstallation = path.join(options.profileDir, "extensions"); + const argsPassedToNpmInstall = await getArgsPassedToNpmInstallDuringInstallExtensionCall(extensionName, testInjector); + assert.deepEqual(argsPassedToNpmInstall.pathToSave, expectedDirForInstallation); + }); + }); + + it("returns the name of the installed extension", async () => { + const extensionName = "extension1"; + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) !== extensionName; + + fs.readDirectory = (dir: string): string[] => [extensionName]; + + const npm: INodePackageManager = testInjector.resolve("npm"); + npm.install = async (packageName: string, pathToSave: string, config?: any): Promise => [extensionName]; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + const actualResult = await extensibilityService.installExtension(extensionName); + assert.deepEqual(actualResult, { extensionName }); + }); + + it("throws error that has extensionName property when unable to load extension", async () => { + const expectedErrorMessage = "Require failed"; + + const extensionName = "extension1"; + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) !== extensionName; + + fs.readDirectory = (dir: string): string[] => [extensionName]; + + const npm: INodePackageManager = testInjector.resolve("npm"); + npm.install = async (packageName: string, pathToSave: string, config?: any): Promise => [extensionName]; + + const requireService: IRequireService = testInjector.resolve("requireService"); + requireService.require = (pathToRequire: string) => { + throw new Error(expectedErrorMessage); + }; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + let isErrorRaised = false; + try { + await extensibilityService.installExtension(extensionName); + } catch (err) { + isErrorRaised = true; + assert.deepEqual(err.message, `Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds.`); + assert.deepEqual(err.extensionName, extensionName); + } + + assert.isTrue(isErrorRaised); + }); + }); + + describe("loadExtensions", () => { + describe("returns correct results for each extension", () => { + it("resolves all Promises with correct values when all extensions can be loaded", async () => { + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + const extensionNames = ["extension1", "extension2", "extension3"]; + fs.exists = (pathToCheck: string): boolean => true; + fs.readDirectory = (dir: string): string[] => { + assert.deepEqual(path.basename(dir), constants.NODE_MODULES_FOLDER_NAME); + // Simulates extensions are installed in node_modules + return extensionNames; + }; + + fs.readJson = (filename: string, encoding?: string): any => { + const dependencies: any = {}; + _.each(extensionNames, name => { + dependencies[name] = "1.0.0"; + }); + + return { dependencies }; + }; + + const expectedResults: IExtensionData[] = _.map(extensionNames, extensionName => ({ extensionName })); + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + const actualResult = await Promise.all(extensibilityService.loadExtensions()); + assert.deepEqual(actualResult, expectedResults); + }); + + it("installs extensions that are available in package.json, but are not available in node_modules", async () => { + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + const extensionNames = ["extension1", "extension2", "extension3"]; + fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) !== extensionNames[0]; + + let isFirstReadDirExecution = true; + fs.readDirectory = (dir: string): string[] => { + assert.deepEqual(path.basename(dir), constants.NODE_MODULES_FOLDER_NAME); + // Simulates extensions are installed in node_modules + if (isFirstReadDirExecution) { + isFirstReadDirExecution = false; + return extensionNames.filter(ext => ext !== "extension1"); + } else { + return extensionNames; + } + }; + + fs.readJson = (filename: string, encoding?: string): any => { + const dependencies: any = {}; + _.each(extensionNames, name => { + dependencies[name] = "1.0.0"; + }); + + return { dependencies }; + }; + + let isNpmInstallCalled = false; + const npm: INodePackageManager = testInjector.resolve("npm"); + npm.install = async (packageName: string, pathToSave: string, config?: any): Promise => { + assert.deepEqual(packageName, extensionNames[0]); + isNpmInstallCalled = true; + return [packageName]; + }; + + const expectedResults: IExtensionData[] = _.map(extensionNames, extensionName => ({ extensionName })); + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + const actualResult = await Promise.all(extensibilityService.loadExtensions()); + assert.deepEqual(actualResult, expectedResults); + assert.isTrue(isNpmInstallCalled); + }); + + it("rejects only promises for extensions that cannot be loaded", async () => { + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + const extensionNames = ["extension1", "extension2", "extension3"]; + fs.exists = (pathToCheck: string): boolean => true; + fs.readDirectory = (dir: string): string[] => { + assert.deepEqual(path.basename(dir), constants.NODE_MODULES_FOLDER_NAME); + // Simulates extensions are installed in node_modules + return extensionNames; + }; + + fs.readJson = (filename: string, encoding?: string): any => { + const dependencies: any = {}; + _.each(extensionNames, name => { + dependencies[name] = "1.0.0"; + }); + + return { dependencies }; + }; + + const requireService: IRequireService = testInjector.resolve("requireService"); + requireService.require = (module: string) => { + if (path.basename(module) === extensionNames[0]) { + throw new Error("Unable to load module."); + } + }; + + const expectedResults: any[] = _.map(extensionNames, extensionName => ({ extensionName })); + expectedResults[0] = new Error("Unable to load extension extension1. You will not be able to use the functionality that it adds."); + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + const promises = extensibilityService.loadExtensions(); + assert.deepEqual(promises.length, extensionNames.length); + + for (let index = 0; index < promises.length; index++) { + const loadExtensionPromise = promises[index]; + await loadExtensionPromise + .then(result => assert.deepEqual(result, expectedResults[index]), + err => { + assert.deepEqual(err.message, expectedResults[index].message); + assert.deepEqual(err.extensionName, extensionNames[index]); + }); + }; + }); + + it("rejects all promises when unable to read node_modules dir (simulate EPERM error)", async () => { + const testInjector = getTestInjector(); + const extensionNames = ["extension1", "extension2", "extension3"]; + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) === "extensions" || path.basename(pathToCheck) === constants.PACKAGE_JSON_FILE_NAME; + fs.readJson = (filename: string, encoding?: string): any => { + const dependencies: any = {}; + _.each(extensionNames, name => { + dependencies[name] = "1.0.0"; + }); + + return { dependencies }; + }; + + let isReadDirCalled = false; + fs.readDirectory = (dir: string): string[] => { + isReadDirCalled = true; + assert.deepEqual(path.basename(dir), constants.NODE_MODULES_FOLDER_NAME); + throw new Error(`Unable to read ${constants.NODE_MODULES_FOLDER_NAME} dir.`); + }; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + const promises = extensibilityService.loadExtensions(); + + for (let index = 0; index < promises.length; index++) { + const loadExtensionPromise = promises[index]; + await loadExtensionPromise.then(res => { throw new Error("Shouldn't get here!"); }, + err => { + const extensionName = extensionNames[index]; + assert.deepEqual(err.message, `Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds.`); + assert.deepEqual(err.extensionName, extensionName); + }); + }; + + assert.deepEqual(promises.length, extensionNames.length); + assert.isTrue(isReadDirCalled, "readDirectory should have been called for the extensions."); + }); + + it("rejects all promises when unable to install extensions to extension dir (simulate EPERM error)", async () => { + const testInjector = getTestInjector(); + const extensionNames = ["extension1", "extension2", "extension3"]; + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) === "extensions" || path.basename(pathToCheck) === constants.PACKAGE_JSON_FILE_NAME; + fs.readJson = (filename: string, encoding?: string): any => { + const dependencies: any = {}; + _.each(extensionNames, name => { + dependencies[name] = "1.0.0"; + }); + + return { dependencies }; + }; + + let isReadDirCalled = false; + fs.readDirectory = (dir: string): string[] => { + isReadDirCalled = true; + assert.deepEqual(path.basename(dir), constants.NODE_MODULES_FOLDER_NAME); + return []; + }; + + let isNpmInstallCalled = false; + const npm: INodePackageManager = testInjector.resolve("npm"); + npm.install = async (packageName: string, pathToSave: string, config?: any): Promise => { + assert.deepEqual(packageName, extensionNames[0]); + isNpmInstallCalled = true; + throw new Error(`Unable to install to ${constants.NODE_MODULES_FOLDER_NAME} dir.`); + }; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + const promises = extensibilityService.loadExtensions(); + + for (let index = 0; index < promises.length; index++) { + const loadExtensionPromise = promises[index]; + await loadExtensionPromise.then(res => { + console.log("######### res = ", res); throw new Error("Shouldn't get here!"); + }, + err => { + const extensionName = extensionNames[index]; + assert.deepEqual(err.message, `Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds.`); + assert.deepEqual(err.extensionName, extensionName); + }); + }; + + assert.deepEqual(promises.length, extensionNames.length); + assert.isTrue(isNpmInstallCalled, "Npm install should have been called for the extensions."); + assert.isTrue(isReadDirCalled, "readDirectory should have been called for the extensions."); + }); + + it("does not return any promises when its unable to create extensions dir", () => { + + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => false; + const expectedErrorMessage = "Unable to create dir"; + fs.createDirectory = (dirToCreate: string): void => { + throw new Error(expectedErrorMessage); + }; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + const promises = extensibilityService.loadExtensions(); + + assert.deepEqual(promises.length, 0); + }); + + it("does not return any promises when its unable to read extensions package.json", () => { + + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => true; + const expectedErrorMessage = "Unable to read json"; + fs.readJson = (filename: string, encoding?: string): any => { + throw new Error(expectedErrorMessage); + }; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + const promises = extensibilityService.loadExtensions(); + + assert.deepEqual(promises.length, 0); + }); + + it("does not fail when package.json in extension dir does not exist", async () => { + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => { + // Add the assert here, so we are sure the only call to fs.exists is for package.json of the extensions dir. + assert.deepEqual(path.basename(pathToCheck), constants.PACKAGE_JSON_FILE_NAME); + return false; + }; + + const expectedResults: IExtensionData[] = []; + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + const actualResult = await Promise.all(extensibilityService.loadExtensions()); + assert.deepEqual(actualResult, expectedResults, "When there's no package.json in extensions dir, there's nothing for loading."); + }); + + it("does not fail when unable to read extensions dir package.json", async () => { + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => { + // Add the assert here, so we are sure the only call to fs.exists is for package.json of the extensions dir. + assert.deepEqual(path.basename(pathToCheck), constants.PACKAGE_JSON_FILE_NAME); + return true; + }; + + fs.readJson = (filename: string, encoding?: string): any => { + throw new Error("Unable to read JSON"); + }; + + const expectedResults: IExtensionData[] = []; + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + const actualResult = await Promise.all(extensibilityService.loadExtensions()); + assert.deepEqual(actualResult, expectedResults, "When unable to read package.json in extensions dir, there's nothing for loading."); + }); + + }); + }); + + describe("uninstallExtension", () => { + describe("fails", () => { + it("when extensions dir does not exist and trying to create it fails", async () => { + const expectedErrorMessage = "Unable to create dir"; + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => false; + fs.createDirectory = (dirToCreate: string): void => { + throw new Error(expectedErrorMessage); + }; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + await assert.isRejected(extensibilityService.uninstallExtension("extensionToInstall"), expectedErrorMessage); + }); + + it("when extensions dir exists, but default package.json is missing and trying to create it fails", async () => { + const expectedErrorMessage = "Unable to write json"; + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) !== constants.PACKAGE_JSON_FILE_NAME; + fs.writeJson = (pathToFile: string, content: any) => { + throw new Error(expectedErrorMessage); + }; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + await assert.isRejected(extensibilityService.uninstallExtension("extensionToInstall"), expectedErrorMessage); + }); + + it("when npm uninstall fails", async () => { + const expectedErrorMessage = "Unable to install package"; + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => true; + const npm: INodePackageManager = testInjector.resolve("npm"); + npm.uninstall = async (packageName: string, config?: any, path?: string): Promise => { + throw new Error(expectedErrorMessage); + }; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + await assert.isRejected(extensibilityService.uninstallExtension("extensionToInstall"), expectedErrorMessage); + }); + }); + + describe("passes correct arguments to npm uninstall", () => { + const getArgsPassedToNpmUninstallDuringUninstallExtensionCall = async (userSpecifiedValue: string, testInjector?: IInjector): Promise => { + testInjector = testInjector || getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => true; + + fs.readDirectory = (dir: string): string[] => [userSpecifiedValue]; + + const npm: INodePackageManager = testInjector.resolve("npm"); + let argsPassedToNpmInstall: any = {}; + npm.uninstall = async (packageName: string, config?: any, path?: string): Promise => { + argsPassedToNpmInstall.packageName = packageName; + argsPassedToNpmInstall.pathToSave = path; + argsPassedToNpmInstall.config = config; + return [userSpecifiedValue]; + }; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + await extensibilityService.uninstallExtension(userSpecifiedValue); + + return argsPassedToNpmInstall; + }; + + const assertPackageNamePassedToNpmUninstall = async (userSpecifiedValue: string, expectedValue: string): Promise => { + const argsPassedToNpmInstall = await getArgsPassedToNpmUninstallDuringUninstallExtensionCall(userSpecifiedValue); + assert.deepEqual(argsPassedToNpmInstall.packageName, expectedValue); + }; + + it("passes the value specified by user for installation", async () => { + const extensionName = "extension1"; + await assertPackageNamePassedToNpmUninstall(extensionName, extensionName); + + const relativePathToExtension = "../extension1"; + await assertPackageNamePassedToNpmUninstall(relativePathToExtension, relativePathToExtension); + }); + + it("passes save option to npm uninstall", async () => { + const extensionName = "extension1"; + const argsPassedToNpmUninstall = await getArgsPassedToNpmUninstallDuringUninstallExtensionCall(extensionName); + const expectedNpmConfg: any = { save: true }; + assert.deepEqual(argsPassedToNpmUninstall.config, expectedNpmConfg); + }); + + it("passes full path to extensions dir for uninstallation", async () => { + const extensionName = "extension1"; + const testInjector = getTestInjector(); + const options: IOptions = testInjector.resolve("options"); + options.profileDir = "my-profile-dir"; + + const expectedDirForInstallation = path.join(options.profileDir, "extensions"); + const argsPassedToNpmUninstall = await getArgsPassedToNpmUninstallDuringUninstallExtensionCall(extensionName, testInjector); + assert.deepEqual(argsPassedToNpmUninstall.pathToSave, expectedDirForInstallation); + }); + }); + + it("executes successfully uninstall operation", async () => { + const extensionName = "extension1"; + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) !== extensionName; + + fs.readDirectory = (dir: string): string[] => [extensionName]; + + const npm: INodePackageManager = testInjector.resolve("npm"); + npm.uninstall = async (packageName: string, config?: any, path?: string): Promise => [extensionName]; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + await extensibilityService.uninstallExtension(extensionName); + }); + + }); + + describe("getInstalledExtensions", () => { + it("fails when unable to read package.json from extensions dir", () => { + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string) => true; + const expectedErrorMessage = "Failed to read package.json"; + fs.readJson = (filename: string, encoding?: string): any => { + throw new Error(expectedErrorMessage); + }; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + assert.throws(() => extensibilityService.getInstalledExtensions(), expectedErrorMessage); + }); + + it("returns null when there's no package.json dir", () => { + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string) => false; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + assert.isNull(extensibilityService.getInstalledExtensions()); + }); + + it("returns undefined when package.json does not have dependencies section", () => { + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string) => true; + fs.readJson = (filename: string, encoding?: string): any => { + return {}; + }; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + assert.isUndefined(extensibilityService.getInstalledExtensions()); + }); + + it("returns dependencies section of package.json", () => { + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string) => true; + const dependencies = { + "dep1": "1.0.0", + "dep2": "~1.0.0", + "dep3": "^1.0.0" + }; + + fs.readJson = (filename: string, encoding?: string): any => { + return { dependencies }; + }; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + assert.deepEqual(extensibilityService.getInstalledExtensions(), dependencies); + }); + }); +}); From b094d1f7938e4a107968374d46c39c58cebe4f51 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Tue, 25 Apr 2017 10:09:05 +0300 Subject: [PATCH 005/212] Do not start emulator when `--available-devices` is passed (#2736) In case there's no devices attached and no emulators running, trying ` devices --available-devices` will start emulator. In order to fix this, modify the `startEmulatorIfNecessary` method to skip the starting in case `skipInferPlatform` option is passed. This option indicates that we are not concerned of specific platform, so the method does not know which is the target platform for which to start emulator. Add unit test for this behavior. --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index d874d4d627..b96484eb79 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit d874d4d627b6b6b9ecf8b717016e387508dc07d5 +Subproject commit b96484eb79704a84a6b2e3f94376072ac77065d8 From 34214c95441ecb2e66a8708aa41cca4562f7de2e Mon Sep 17 00:00:00 2001 From: Peter Kanev Date: Tue, 25 Apr 2017 11:21:47 +0300 Subject: [PATCH 006/212] Install karma peer dependencies on `test init` (#2693) * implicitly install karma-'s peer dependencies on test init command * add exception handling when installing packages already present in the project * log warning instead of throwing errors when a package's name couldn't be determined when installed using the node-package-manager service --- lib/commands/test-init.ts | 19 +++++++++++++++++++ lib/node-package-manager.ts | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/commands/test-init.ts b/lib/commands/test-init.ts index 0593173dbc..69736ac8a7 100644 --- a/lib/commands/test-init.ts +++ b/lib/commands/test-init.ts @@ -37,6 +37,25 @@ class TestInitCommand implements ICommand { 'save-dev': true, optional: false, }); + + const modulePath = path.join(projectDir, "node_modules", mod); + const modulePackageJsonPath = path.join(modulePath, "package.json"); + const modulePackageJsonContent = this.$fs.readJson(modulePackageJsonPath); + const modulePeerDependencies = modulePackageJsonContent.peerDependencies || {}; + + for (let peerDependency in modulePeerDependencies) { + let dependencyVersion = modulePeerDependencies[peerDependency] || "*"; + + // catch errors when a peerDependency is already installed + // e.g karma is installed; karma-jasmine depends on karma and will try to install it again + try { + await this.$npm.install(`${peerDependency}@${dependencyVersion}`, projectDir, { + 'save-dev': true + }); + } catch (e) { + this.$logger.error(e.message); + } + } } await this.$pluginsService.add('nativescript-unit-test-runner', this.$projectData); diff --git a/lib/node-package-manager.ts b/lib/node-package-manager.ts index fd863640b4..45591a8122 100644 --- a/lib/node-package-manager.ts +++ b/lib/node-package-manager.ts @@ -76,10 +76,10 @@ export class NodePackageManager implements INodePackageManager { let diff = dependencyDiff.concat(devDependencyDiff); if (diff.length <= 0 && dependenciesBefore.length === dependenciesAfter.length && packageName !== pathToSave) { - this.$errors.failWithoutHelp(`The plugin ${packageName} is already installed`); + this.$logger.warn(`The plugin ${packageName} is already installed`); } if (diff.length <= 0 && dependenciesBefore.length !== dependenciesAfter.length) { - this.$errors.failWithoutHelp(`Couldn't install package correctly`); + this.$logger.warn(`Couldn't install package ${packageName} correctly`); } return diff; From 430200b9bca9b54fc3d7971ffb04ab49df5f6277 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 25 Apr 2017 12:44:40 +0300 Subject: [PATCH 007/212] Update to latest common --- lib/common | 2 +- test/stubs.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/common b/lib/common index b96484eb79..c81c94373b 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit b96484eb79704a84a6b2e3f94376072ac77065d8 +Subproject commit c81c94373b25572644222001ff25ec32e0869ec4 diff --git a/test/stubs.ts b/test/stubs.ts index be91dca62e..00f09ba5cd 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -169,6 +169,8 @@ export class FileSystemStub implements IFileSystem { } deleteEmptyParents(directory: string): void { } + + utimes(path: string, atime: Date, mtime: Date): void { } } export class ErrorsStub implements IErrors { From 76a619f0ab36c985a317951a8ebb40e581fbf1b1 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Mon, 24 Apr 2017 11:58:31 +0300 Subject: [PATCH 008/212] Expose service for configuration --- PublicAPI.md | 39 ++++++++++++++++++++++++++++++++++++ test/nativescript-cli-lib.ts | 1 + 2 files changed, 40 insertions(+) diff --git a/PublicAPI.md b/PublicAPI.md index 0434108d54..7d3b1ef545 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -180,6 +180,45 @@ for (let promise of loadExtensionsPromises) { } ``` +## settings +`settings` module provides a way to configure various settings. + +### set +Used to set various settings in order to modify the behavior of some methods. +* Auxiliary interfaces: +```TypeScript +/** + * Describes configuration settings that modify the behavior of some methods. + */ +interface IConfigurationSettings { + /** + * This string will be used when constructing the UserAgent http header. + * @type {string} + */ + userAgentName: string; +} +``` + +* Definition: +```TypeScript +/** + * Describes service used to confugure various settings. + */ +interface ISettingsService { + /** + * Used to set various settings in order to modify the behavior of some methods. + * @param {IConfigurationSettings} settings Settings which will modify the behaviour of some methods. + * @returns {void} + */ + setSettings(settings: IConfigurationSettings): void; +} +``` + +* Usage: +```JavaScript +tns.settingsService.setSettings({ userAgentName: "myUserAgent" }); +``` + ## How to add a new method to Public API CLI is designed as command line tool and when it is used as a library, it does not give you access to all of the methods. This is mainly implementation detail. Most of the CLI's code is created to work in command line, not as a library, so before adding method to public API, most probably it will require some modification. For example the `$options` injected module contains information about all `--` options passed on the terminal. When the CLI is used as a library, the options are not populated. Before adding method to public API, make sure its implementation does not rely on `$options`. diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index 394eebd871..374c2748c4 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -13,6 +13,7 @@ describe("nativescript-cli-lib", () => { }); const publicApi: any = { + settingsService: ["setSettings"], deviceEmitter: null, projectService: ["createProject", "isValidNativeScriptProject"], localBuildService: ["build"], From de44599cab62790924a5ec8d7b8cc8cd0184852a Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Wed, 19 Apr 2017 17:25:23 +0300 Subject: [PATCH 009/212] Expose npm operations Expose node-package-manager functonality to be usable when CLI is `require`-d. * Remove occurrences of `this.$options` in node-package-manager * Start using `--dry-run` and `--json` for parsing output --- lib/bootstrap.ts | 2 +- lib/commands/install.ts | 12 +- lib/commands/test-init.ts | 8 +- lib/declarations.d.ts | 174 +++++++++++++++++++++++- lib/node-package-manager.ts | 98 +++++++------ lib/npm-installation-manager.ts | 4 +- lib/services/android-project-service.ts | 7 +- lib/services/doctor-service.ts | 9 +- lib/services/platform-service.ts | 17 +-- lib/services/plugins-service.ts | 21 ++- lib/services/project-service.ts | 22 ++- 11 files changed, 297 insertions(+), 77 deletions(-) diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index cb26b7ef7d..eb70d055e6 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -65,7 +65,7 @@ $injector.requireCommand("appstore|upload", "./commands/appstore-upload"); $injector.requireCommand("publish|ios", "./commands/appstore-upload"); $injector.require("itmsTransporterService", "./services/itmstransporter-service"); -$injector.require("npm", "./node-package-manager"); +$injector.requirePublic("npm", "./node-package-manager"); $injector.require("npmInstallationManager", "./npm-installation-manager"); $injector.require("lockfile", "./lockfile"); $injector.require("dynamicHelpProvider", "./dynamic-help-provider"); diff --git a/lib/commands/install.ts b/lib/commands/install.ts index a39423ec9c..ced759e70b 100644 --- a/lib/commands/install.ts +++ b/lib/commands/install.ts @@ -14,8 +14,8 @@ export class InstallCommand implements ICommand { private $fs: IFileSystem, private $stringParameter: ICommandParameter, private $npm: INodePackageManager) { - this.$projectData.initializeProjectData(); - } + this.$projectData.initializeProjectData(); + } public async execute(args: string[]): Promise { return args[0] ? this.installModule(args[0]) : this.installProjectDependencies(); @@ -51,7 +51,13 @@ export class InstallCommand implements ICommand { moduleName = devPrefix + moduleName; } - await this.$npm.install(moduleName, projectDir, { 'save-dev': true }); + await this.$npm.install(moduleName, projectDir, { + 'save-dev': true, + disableNpmInstall: this.$options.disableNpmInstall, + frameworkPath: this.$options.frameworkPath, + ignoreScripts: this.$options.ignoreScripts, + path: this.$options.path + }); } } diff --git a/lib/commands/test-init.ts b/lib/commands/test-init.ts index 69736ac8a7..2a54cdf01b 100644 --- a/lib/commands/test-init.ts +++ b/lib/commands/test-init.ts @@ -17,8 +17,8 @@ class TestInitCommand implements ICommand { private $resources: IResourceLoader, private $pluginsService: IPluginsService, private $logger: ILogger) { - this.$projectData.initializeProjectData(); - } + this.$projectData.initializeProjectData(); + } public async execute(args: string[]): Promise { let projectDir = this.$projectData.projectDir; @@ -36,6 +36,10 @@ class TestInitCommand implements ICommand { await this.$npm.install(mod, projectDir, { 'save-dev': true, optional: false, + disableNpmInstall: this.$options.disableNpmInstall, + frameworkPath: this.$options.frameworkPath, + ignoreScripts: this.$options.ignoreScripts, + path: this.$options.path }); const modulePath = path.join(projectDir, "node_modules", mod); diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 9746e88b14..c042b73b76 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -1,5 +1,12 @@ interface INodePackageManager { - install(packageName: string, pathToSave: string, config?: any): Promise; + /** + * Installs dependency + * @param {string} packageName The name of the dependency - can be a path, a url or a string. + * @param {string} pathToSave The destination of the installation. + * @param {INodePackageManagerInstallOptions} config Additional options that can be passed to manipulate installation. + * @return {Promise} Information about installed package. + */ + install(packageName: string, pathToSave: string, config: INodePackageManagerInstallOptions): Promise; uninstall(packageName: string, config?: any, path?: string): Promise; view(packageName: string, config: Object): Promise; search(filter: string[], config: any): Promise; @@ -13,6 +20,160 @@ interface INpmInstallationManager { getInspectorFromCache(inspectorNpmPackageName: string, projectDir: string): Promise; } +/** + * Describes options that can be passed to manipulate package installation. + */ +interface INodePackageManagerInstallOptions extends INpmInstallConfigurationOptions, IDictionary { + /** + * Destination of the installation. + * @type {string} + * @optional + */ + path?: string; +} + +/** + * Describes information about dependency packages. + */ +interface INpmDependencyInfo { + /** + * Dependency name. + */ + [key: string]: { + /** + * Dependency version. + * @type {string} + */ + version: string; + /** + * How was the dependency resolved. For example: lodash@latest or underscore@>=1.8.3 <2.0.0 + * @type {string} + */ + from: string; + /** + * Where was the dependency resolved from. For example: https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz + * @type {string} + */ + resolved: string; + /** + * Dependencies of the dependency. + * @type {INpmDependencyInfo} + */ + dependencies?: INpmDependencyInfo; + /** + * Set to true when the dependency is invalid. + * @type {boolean} + */ + invalid?: boolean; + /** + * If invalid is set to true this will contain errors which make the dependency invalid. + */ + problems?: string[]; + } +} + +/** + * Describes information about peer dependency packages. + */ +interface INpmPeerDependencyInfo { + required: { + /** + * Id used in package.json - for example: zone.js@^0.8.4 + * @type {string} + */ + _id: string; + /** + * Dependency name. + * @type {string} + */ + name: string; + /** + * Dependency version. + * @type {string} + */ + version: string; + /** + * If peerMissing below is set to true this will contain information about missing peers. + */ + peerMissing?: [ + { + /** + * The id of the package which requires the unmet peer dependency. + * @type {string} + */ + requiredBy: string; + /** + * The id of the unmet peer dependency. + * @type {string} + */ + requires: string; + } + ]; + /** + * Dependencies of the dependency. + * @type {INpmDependencyInfo} + */ + dependencies: INpmDependencyInfo; + /** + * Whether the dependency was found or not. + * @type {boolean} + */ + _found: boolean; + }; + /** + * Set to true if peer dependency unmet. + * @type {boolean} + */ + peerMissing: boolean; +} + +/** + * Describes information returned by the npm CLI upon calling install with --json flag. + */ +interface INpmInstallCLIResult { + /** + * The name of the destination package. Note that this is not the installed package. + * @type {string} + */ + name: string; + /** + * The version of the destination package. Note that this is not the installed package. + * @type {string} + */ + version: string; + /** + * Installed dependencies. Note that whenever installing a particular dependency it is listed as the first key and after it any number of peer dependencies may follow. + * Whenever installing npm prints the information by reversing the tree of operations and because the initial dependency was installed last it is listed first. + * @type {INpmDependencyInfo | INpmPeerDependencyInfo} + */ + dependencies: INpmDependencyInfo | INpmPeerDependencyInfo; + /** + * Describes problems that might have occurred during installation. For example missing peer dependencies. + */ + problems?: string[]; +} + +/** + * Describes information about installed package. + */ +interface INpmInstallResultInfo { + /** + * Installed package's name. + * @type {string} + */ + name: string; + /** + * Installed package's version. + * @type {string} + */ + version: string; + /** + * The original output that npm CLI produced upon installation. + * @type {INpmInstallCLIResult} + */ + originalOutput: INpmInstallCLIResult; +} + interface INpmInstallOptions { pathToSave?: string; version?: string; @@ -112,7 +273,13 @@ interface IAndroidReleaseOptions { keyStorePath?: string; } -interface IOptions extends ICommonOptions, IBundle, IPlatformTemplate, IEmulator, IClean, IProvision, ITeamIdentifier, IAndroidReleaseOptions { +interface INpmInstallConfigurationOptions { + frameworkPath: string; + disableNpmInstall: boolean; + ignoreScripts: boolean; //npm flag +} + +interface IOptions extends ICommonOptions, IBundle, IPlatformTemplate, IEmulator, IClean, IProvision, ITeamIdentifier, IAndroidReleaseOptions, INpmInstallConfigurationOptions { all: boolean; client: boolean; compileSdk: number; @@ -121,10 +288,7 @@ interface IOptions extends ICommonOptions, IBundle, IPlatformTemplate, IEmulator forDevice: boolean; framework: string; frameworkName: string; - frameworkPath: string; frameworkVersion: string; - ignoreScripts: boolean; //npm flag - disableNpmInstall: boolean; ipa: string; tsc: boolean; ng: boolean; diff --git a/lib/node-package-manager.ts b/lib/node-package-manager.ts index 45591a8122..6f71be1975 100644 --- a/lib/node-package-manager.ts +++ b/lib/node-package-manager.ts @@ -1,46 +1,70 @@ import * as path from "path"; +import { exported } from "./common/decorators"; export class NodePackageManager implements INodePackageManager { constructor(private $fs: IFileSystem, private $hostInfo: IHostInfo, private $errors: IErrors, private $childProcess: IChildProcess, - private $logger: ILogger, - private $options: IOptions) { } + private $logger: ILogger) { } - public async install(packageName: string, pathToSave: string, config?: any): Promise { - if (this.$options.disableNpmInstall) { + @exported("npm") + public async install(packageName: string, pathToSave: string, config: INodePackageManagerInstallOptions): Promise { + if (config.disableNpmInstall) { return; } - if (this.$options.ignoreScripts) { - config = config || {}; + if (config.ignoreScripts) { config["ignore-scripts"] = true; } let packageJsonPath = path.join(pathToSave, "package.json"); let jsonContentBefore = this.$fs.readJson(packageJsonPath); - let dependenciesBefore = _.keys(jsonContentBefore.dependencies).concat(_.keys(jsonContentBefore.devDependencies)); let flags = this.getFlagsString(config, true); let params = ["install"]; - if (packageName !== pathToSave) { - params.push(packageName); //because npm install ${pwd} on mac tries to install itself as a dependency (windows and linux have no such issues) + const isInstallingAllDependencies = packageName === pathToSave; + if (!isInstallingAllDependencies) { + params.push(packageName); } + params = params.concat(flags); - let pwd = pathToSave; + let cwd = pathToSave; + // Npm creates `etc` directory in installation dir when --prefix is passed + // https://github.com/npm/npm/issues/11486 + // we should delete it if it was created because of us + const etcDirectoryLocation = path.join(cwd, "etc"); + const etcExistsPriorToInstallation = this.$fs.exists(etcDirectoryLocation); + //TODO: plamen5kov: workaround is here for a reason (remove whole file later) - if (this.$options.path) { + if (config.path) { let relativePathFromCwdToSource = ""; - if (this.$options.frameworkPath) { - relativePathFromCwdToSource = path.relative(this.$options.frameworkPath, pathToSave); + if (config.frameworkPath) { + relativePathFromCwdToSource = path.relative(config.frameworkPath, pathToSave); if (this.$fs.exists(relativePathFromCwdToSource)) { packageName = relativePathFromCwdToSource; } } } + try { - let spawnResult: ISpawnResult = await this.$childProcess.spawnFromEvent(this.getNpmExecutableName(), params, "close", { cwd: pwd, stdio: "inherit" }); - this.$logger.out(spawnResult.stdout); + let spawnResult: ISpawnResult = await this.$childProcess.spawnFromEvent(this.getNpmExecutableName(), params, "close", { cwd, stdio: "inherit" }); + if (!etcExistsPriorToInstallation) { + this.$fs.deleteDirectory(etcDirectoryLocation); + } + + // Whenever calling npm install without any arguments (hence installing all dependencies) no output is emitted on stdout + // Luckily, whenever you call npm install to install all dependencies chances are you won't need the name/version of the package you're installing because there is none. + if (isInstallingAllDependencies) { + return null; + } + + params = params.concat(["--json", "--dry-run", "--prefix", cwd]); + // After the actual install runs successfully execute a dry-run in order to get information about the package. + // We cannot use the actual install with --json to get the information because of post-install scripts which may print on stdout + // dry-run install is quite fast when the dependencies are already installed even for many dependencies (e.g. angular) so we can live with this approach + // We need the --prefix here because without it no output is emitted on stdout because all the dependencies are already installed. + spawnResult = await this.$childProcess.spawnFromEvent(this.getNpmExecutableName(), params, "close"); + return this.parseNpmInstallResult(spawnResult.stdout); } catch (err) { if (err.message && err.message.indexOf("EPEERINVALID") !== -1) { // Not installed peer dependencies are treated by npm 2 as errors, but npm 3 treats them as warnings. @@ -53,48 +77,21 @@ export class NodePackageManager implements INodePackageManager { throw err; } } - - let jsonContentAfter = this.$fs.readJson(path.join(pathToSave, "package.json")); - let dependenciesAfter = _.keys(jsonContentAfter.dependencies).concat(_.keys(jsonContentAfter.devDependencies)); - - /** This diff is done in case the installed pakcage is a URL address, a path to local directory or a .tgz file - * in these cases we don't have the package name and we can't rely on "npm install --json"" option - * to get the project name because we have to parse the output from the stdout and we have no controll over it (so other messages may be mangled in stdout) - * The solution is to compare package.json project dependencies before and after install and get the name of the installed package, - * even if it's installed through local path or URL. If command installes more than one package, only the package originally installed is returned. - */ - let dependencyDiff = _(jsonContentAfter.dependencies) - .omitBy((val: string, key: string) => jsonContentBefore && jsonContentBefore.dependencies && jsonContentBefore.dependencies[key] && jsonContentBefore.dependencies[key] === val) - .keys() - .value(); - - let devDependencyDiff = _(jsonContentAfter.devDependencies) - .omitBy((val: string, key: string) => jsonContentBefore && jsonContentBefore.devDependencies && jsonContentBefore.devDependencies[key] && jsonContentBefore.devDependencies[key] === val) - .keys() - .value(); - - let diff = dependencyDiff.concat(devDependencyDiff); - - if (diff.length <= 0 && dependenciesBefore.length === dependenciesAfter.length && packageName !== pathToSave) { - this.$logger.warn(`The plugin ${packageName} is already installed`); - } - if (diff.length <= 0 && dependenciesBefore.length !== dependenciesAfter.length) { - this.$logger.warn(`Couldn't install package ${packageName} correctly`); } - return diff; - } - + @exported("npm") public async uninstall(packageName: string, config?: any, path?: string): Promise { let flags = this.getFlagsString(config, false); return this.$childProcess.exec(`npm uninstall ${packageName} ${flags}`, { cwd: path }); } + @exported("npm") public async search(filter: string[], config: any): Promise { let args = (([filter] || [])).concat(config.silent); return this.$childProcess.exec(`npm search ${args.join(" ")}`); } + @exported("npm") public async view(packageName: string, config: Object): Promise { const wrappedConfig = _.extend({}, config, { json: true }); // always require view response as JSON @@ -138,6 +135,17 @@ export class NodePackageManager implements INodePackageManager { return array.join(" "); } + + private parseNpmInstallResult(npmInstallOutput: string): INpmInstallResultInfo { + const originalOutput: INpmInstallCLIResult = JSON.parse(npmInstallOutput); + const name = _.head(_.keys(originalOutput.dependencies)); + const dependency = _.pick(originalOutput.dependencies, name); + return { + name, + originalOutput, + version: dependency[name].version + }; + } } $injector.register("npm", NodePackageManager); diff --git a/lib/npm-installation-manager.ts b/lib/npm-installation-manager.ts index d295cec3db..36275ed836 100644 --- a/lib/npm-installation-manager.ts +++ b/lib/npm-installation-manager.ts @@ -91,7 +91,7 @@ export class NpmInstallationManager implements INpmInstallationManager { } let installedModuleNames = await this.npmInstall(packageName, pathToSave, version, dependencyType); - let installedPackageName = installedModuleNames[0]; + let installedPackageName = installedModuleNames.name; let pathToInstalledPackage = path.join(pathToSave, "node_modules", installedPackageName); return pathToInstalledPackage; @@ -103,7 +103,7 @@ export class NpmInstallationManager implements INpmInstallationManager { return str.length < 2083 && url.test(str); } - private async npmInstall(packageName: string, pathToSave: string, version: string, dependencyType: string): Promise { + private async npmInstall(packageName: string, pathToSave: string, version: string, dependencyType: string): Promise { this.$logger.out("Installing ", packageName); packageName = packageName + (version ? `@${version}` : ""); diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 1401628ae0..2cbc4df214 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -138,11 +138,14 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject this.cleanResValues(targetSdkVersion, projectData, frameworkVersion); - let npmConfig = { + let npmConfig: INodePackageManagerInstallOptions = { "save": true, "save-dev": true, "save-exact": true, - "silent": true + "silent": true, + disableNpmInstall: false, + frameworkPath: null, + ignoreScripts: false }; let projectPackageJson: any = this.$fs.readJson(projectData.projectFilePath); diff --git a/lib/services/doctor-service.ts b/lib/services/doctor-service.ts index 0900ee28e0..4a8662ab8c 100644 --- a/lib/services/doctor-service.ts +++ b/lib/services/doctor-service.ts @@ -182,7 +182,14 @@ class DoctorService implements IDoctorService { let spinner = new clui.Spinner("Installing iOS runtime."); try { spinner.start(); - await this.$npm.install("tns-ios", projDir, { global: false, "ignore-scripts": true, production: true, save: true }); + await this.$npm.install("tns-ios", projDir, { + global: false, + production: true, + save: true, + disableNpmInstall: false, + frameworkPath: null, + ignoreScripts: true + }); spinner.stop(); let iosDir = path.join(projDir, "node_modules", "tns-ios", "framework"); this.$fs.writeFile( diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 1df21befb2..69a20bb538 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -176,17 +176,14 @@ export class PlatformService extends EventEmitter implements IPlatformService { if (selectedTemplate) { let tempDir = temp.mkdirSync("platform-template"); + this.$fs.writeJson(path.join(tempDir, constants.PACKAGE_JSON_FILE_NAME), {}); try { - /* - * Output of npm.install is array of arrays. For example: - * [ [ 'test-android-platform-template@0.0.1', - * 'C:\\Users\\~1\\AppData\\Local\\Temp\\1\\platform-template11627-15560-rm3ngx\\node_modules\\test-android-platform-template', - * undefined, - * undefined, - * '..\\..\\..\\android-platform-template' ] ] - * Project successfully created. - */ - let pathToTemplate = (await this.$npm.install(selectedTemplate, tempDir))[0]; + const npmInstallResult = await this.$npm.install(selectedTemplate, tempDir, { + disableNpmInstall: false, + frameworkPath: null, + ignoreScripts: false + }); + let pathToTemplate = path.join(tempDir, constants.NODE_MODULES_FOLDER_NAME, npmInstallResult.name); return { selectedTemplate, pathToTemplate }; } catch (err) { this.$logger.trace("Error while trying to install specified template: ", err); diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index 6e6c3b09b3..e332de1eee 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -22,6 +22,15 @@ export class PluginsService implements IPluginsService { return this.$injector.resolve("projectFilesManager"); } + private get npmInstallOptions(): INodePackageManagerInstallOptions { + return _.merge({ + disableNpmInstall: this.$options.disableNpmInstall, + frameworkPath: this.$options.frameworkPath, + ignoreScripts: this.$options.ignoreScripts, + path: this.$options.path + }, PluginsService.NPM_CONFIG); + } + constructor(private $npm: INodePackageManager, private $fs: IFileSystem, private $options: IOptions, @@ -35,7 +44,8 @@ export class PluginsService implements IPluginsService { if (possiblePackageName.indexOf(".tgz") !== -1 && this.$fs.exists(possiblePackageName)) { plugin = possiblePackageName; } - let name = (await this.$npm.install(plugin, projectData.projectDir, PluginsService.NPM_CONFIG))[0]; + + let name = (await this.$npm.install(plugin, projectData.projectDir, this.npmInstallOptions)).name; let pathToRealNpmPackageJson = path.join(projectData.projectDir, "node_modules", name, "package.json"); let realNpmPackageJson = this.$fs.readJson(pathToRealNpmPackageJson); @@ -158,7 +168,12 @@ export class PluginsService implements IPluginsService { let notInstalledDependencies = _.difference(allDependencies, installedDependencies); if (this.$options.force || notInstalledDependencies.length) { this.$logger.trace("Npm install will be called from CLI. Force option is: ", this.$options.force, " Not installed dependencies are: ", notInstalledDependencies); - await this.$npm.install(projectData.projectDir, projectData.projectDir, { "ignore-scripts": this.$options.ignoreScripts }); + await this.$npm.install(projectData.projectDir, projectData.projectDir, { + disableNpmInstall: this.$options.disableNpmInstall, + frameworkPath: this.$options.frameworkPath, + ignoreScripts: this.$options.ignoreScripts, + path: this.$options.path + }); } } @@ -249,7 +264,7 @@ export class PluginsService implements IPluginsService { private async executeNpmCommand(npmCommandName: string, npmCommandArguments: string, projectData: IProjectData): Promise { if (npmCommandName === PluginsService.INSTALL_COMMAND_NAME) { - await this.$npm.install(npmCommandArguments, projectData.projectDir, PluginsService.NPM_CONFIG); + await this.$npm.install(npmCommandArguments, projectData.projectDir, this.npmInstallOptions); } else if (npmCommandName === PluginsService.UNINSTALL_COMMAND_NAME) { await this.$npm.uninstall(npmCommandArguments, PluginsService.NPM_CONFIG, projectData.projectDir); } diff --git a/lib/services/project-service.ts b/lib/services/project-service.ts index 150430e873..bae198342f 100644 --- a/lib/services/project-service.ts +++ b/lib/services/project-service.ts @@ -51,13 +51,23 @@ export class ProjectService implements IProjectService { await this.ensureAppResourcesExist(projectDir); let packageName = constants.TNS_CORE_MODULES_NAME; - await this.$npm.install(packageName, projectDir, { save: true, "save-exact": true }); + await this.$npm.install(packageName, projectDir, { + save: true, + "save-exact": true, + disableNpmInstall: false, + frameworkPath: null, + ignoreScripts: projectOptions.ignoreScripts + }); let templatePackageJsonData = this.getDataFromJson(templatePath); this.mergeProjectAndTemplateProperties(projectDir, templatePackageJsonData); //merging dependencies from template (dev && prod) this.removeMergedDependencies(projectDir, templatePackageJsonData); - await this.$npm.install(projectDir, projectDir, { "ignore-scripts": projectOptions.ignoreScripts }); + await this.$npm.install(projectDir, projectDir, { + disableNpmInstall: false, + frameworkPath: null, + ignoreScripts: projectOptions.ignoreScripts + }); let templatePackageJson = this.$fs.readJson(path.join(templatePath, "package.json")); await this.$npm.uninstall(templatePackageJson.name, { save: true }, projectDir); @@ -112,7 +122,13 @@ export class ProjectService implements IProjectService { // the template installed doesn't have App_Resources -> get from a default template let defaultTemplateName = constants.RESERVED_TEMPLATE_NAMES["default"]; - await this.$npm.install(defaultTemplateName, projectDir, { save: true, }); + await this.$npm.install(defaultTemplateName, projectDir, { + save: true, + disableNpmInstall: false, + frameworkPath: null, + ignoreScripts: false + }); + let defaultTemplateAppResourcesPath = path.join(projectDir, constants.NODE_MODULES_FOLDER_NAME, defaultTemplateName, constants.APP_RESOURCES_FOLDER_NAME); From 0b70f39cfc81a5b9fa7eb3378fb0997e07bfcc3a Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Thu, 20 Apr 2017 16:04:34 +0300 Subject: [PATCH 010/212] Docs and tests --- PublicAPI.md | 120 ++++++++++++++++++++++++++++++++ lib/declarations.d.ts | 26 ++++++- lib/definitions/plugins.d.ts | 1 - lib/node-package-manager.ts | 10 +-- lib/services/plugins-service.ts | 5 -- test/nativescript-cli-lib.ts | 1 + 6 files changed, 150 insertions(+), 13 deletions(-) diff --git a/PublicAPI.md b/PublicAPI.md index 7d3b1ef545..77d3743373 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -219,6 +219,126 @@ interface ISettingsService { tns.settingsService.setSettings({ userAgentName: "myUserAgent" }); ``` +## npm +`npm` module provides a way to interact with npm specifically the use of install, uninstall, search and view commands. + +### install +Installs specified package. Note that you can use the third argument in order to pass different options to the installation like `ignore-scripts`, `save` or `save-exact` which work exactly like they would if you would execute npm from the command line and pass them as `--` flags. +* Auxiliary interfaces: +```TypeScript +/** + * Describes information about installed package. + */ +interface INpmInstallResultInfo { + /** + * Installed package's name. + * @type {string} + */ + name: string; + /** + * Installed package's version. + * @type {string} + */ + version: string; + /** + * The original output that npm CLI produced upon installation. + * @type {INpmInstallCLIResult} + */ + originalOutput: INpmInstallCLIResult; +} +``` + +* Definition: +```TypeScript +/** + * Installs dependency + * @param {string} packageName The name of the dependency - can be a path, a url or a string. + * @param {string} pathToSave The destination of the installation. + * @param {IDictionary} config Additional options that can be passed to manipulate installation. + * @return {Promise} Information about installed package. +*/ +install(packageName: string, pathToSave: string, config: IDictionary): Promise; +``` + +* Usage: +```JavaScript +tns.npm.install("lodash", "/tmp/myProject", { save: true }).then(result => { + console.log(`${result.name} installed successfully`); +}, err => { + console.log("An error occurred during installation", err); +}); +``` + +### uninstall +Uninstalls a specified package. + +* Definition: +```TypeScript +/** + * Uninstalls a dependency + * @param {string} packageName The name of the dependency. + * @param {IDictionary} config Additional options that can be passed to manipulate uninstallation. + * @param {string} path The destination of the uninstallation. + * @return {Promise} The output of the uninstallation. +*/ +uninstall(packageName: string, config?: IDictionary, path?: string): Promise; +``` + +* Usage: +```JavaScript +tns.npm.uninstall("lodash", "/tmp/myProject", { save: true }).then(output => { + console.log(`Uninstalled successfully, output: ${output}`); +}, err => { + console.log("An error occurred during uninstallation", err); +}); +``` + +### search +Searches for a package using keywords. + +* Definition: +```TypeScript +/** + * Searches for a package. + * @param {string[]} filter Keywords with which to perform the search. + * @param {IDictionary} config Additional options that can be passed to manipulate search. + * @return {Promise} The output of the uninstallation. + */ +search(filter: string[], config: IDictionary): Promise; +``` + +* Usage: +```JavaScript +tns.npm.search(["nativescript", "cloud"], { silent: true }).then(output => { + console.log(`Found: ${output}`); +}, err => { + console.log("An error occurred during searching", err); +}); +``` + +### view +Provides information about a given package. + +* Definition +```TypeScript +/** + * Provides information about a given package. + * @param {string} packageName The name of the package. + * @param {IDictionary} config Additional options that can be passed to manipulate view. + * @return {Promise} Object, containing information about the package. + */ +view(packageName: string, config: Object): Promise; +``` + +* Usage: +```JavaScript +tns.npm.view(["nativescript"], {}).then(result => { + console.log(`${result.name}'s latest version is ${result["dist-tags"].latest}`); +}, err => { + console.log("An error occurred during viewing", err); +}); +``` + ## How to add a new method to Public API CLI is designed as command line tool and when it is used as a library, it does not give you access to all of the methods. This is mainly implementation detail. Most of the CLI's code is created to work in command line, not as a library, so before adding method to public API, most probably it will require some modification. For example the `$options` injected module contains information about all `--` options passed on the terminal. When the CLI is used as a library, the options are not populated. Before adding method to public API, make sure its implementation does not rely on `$options`. diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index c042b73b76..7721fc9169 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -7,9 +7,31 @@ interface INodePackageManager { * @return {Promise} Information about installed package. */ install(packageName: string, pathToSave: string, config: INodePackageManagerInstallOptions): Promise; - uninstall(packageName: string, config?: any, path?: string): Promise; + + /** + * Uninstalls a dependency + * @param {string} packageName The name of the dependency. + * @param {IDictionary} config Additional options that can be passed to manipulate uninstallation. + * @param {string} path The destination of the uninstallation. + * @return {Promise} The output of the uninstallation. + */ + uninstall(packageName: string, config?: IDictionary, path?: string): Promise; + + /** + * Provides information about a given package. + * @param {string} packageName The name of the package. + * @param {IDictionary} config Additional options that can be passed to manipulate view. + * @return {Promise} Object, containing information about the package. + */ view(packageName: string, config: Object): Promise; - search(filter: string[], config: any): Promise; + + /** + * Searches for a package. + * @param {string[]} filter Keywords with which to perform the search. + * @param {IDictionary} config Additional options that can be passed to manipulate search. + * @return {Promise} The output of the uninstallation. + */ + search(filter: string[], config: IDictionary): Promise; } interface INpmInstallationManager { diff --git a/lib/definitions/plugins.d.ts b/lib/definitions/plugins.d.ts index aed9cb69c5..99c50b7893 100644 --- a/lib/definitions/plugins.d.ts +++ b/lib/definitions/plugins.d.ts @@ -1,7 +1,6 @@ interface IPluginsService { add(plugin: string, projectData: IProjectData): Promise; // adds plugin by name, github url, local path and et. remove(pluginName: string, projectData: IProjectData): Promise; // removes plugin only by name - getAvailable(filter: string[]): Promise>; // gets all available plugins prepare(pluginData: IDependencyData, platform: string, projectData: IProjectData): Promise; getAllInstalledPlugins(projectData: IProjectData): Promise; ensureAllDependenciesAreInstalled(projectData: IProjectData): Promise; diff --git a/lib/node-package-manager.ts b/lib/node-package-manager.ts index 6f71be1975..179d624a8a 100644 --- a/lib/node-package-manager.ts +++ b/lib/node-package-manager.ts @@ -80,15 +80,15 @@ export class NodePackageManager implements INodePackageManager { } @exported("npm") - public async uninstall(packageName: string, config?: any, path?: string): Promise { - let flags = this.getFlagsString(config, false); + public async uninstall(packageName: string, config?: any, path?: string): Promise { + const flags = this.getFlagsString(config, false); return this.$childProcess.exec(`npm uninstall ${packageName} ${flags}`, { cwd: path }); } @exported("npm") - public async search(filter: string[], config: any): Promise { - let args = (([filter] || [])).concat(config.silent); - return this.$childProcess.exec(`npm search ${args.join(" ")}`); + public async search(filter: string[], config: any): Promise { + const flags = this.getFlagsString(config, false); + return this.$childProcess.exec(`npm search ${filter.join(" ")} ${flags}`); } @exported("npm") diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index e332de1eee..1af98fb70c 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -103,11 +103,6 @@ export class PluginsService implements IPluginsService { } } - public getAvailable(filter: string[]): Promise> { - let silent: boolean = true; - return this.$npm.search(filter, { "silent": silent }); - } - public async validate(platformData: IPlatformData, projectData: IProjectData): Promise { return await platformData.platformProjectService.validatePlugins(projectData); } diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index 374c2748c4..e78cd3c5ae 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -18,6 +18,7 @@ describe("nativescript-cli-lib", () => { projectService: ["createProject", "isValidNativeScriptProject"], localBuildService: ["build"], deviceLogProvider: null, + npm: ["install", "uninstall", "view", "search"], extensibilityService: ["loadExtensions", "getInstalledExtensions", "installExtension", "uninstallExtension"] }; From 9c41267da7bbc48371d77ffcb543b2d6deeb21df Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Tue, 25 Apr 2017 15:46:42 +0300 Subject: [PATCH 011/212] Fix comments --- lib/commands/add-platform.ts | 2 +- lib/commands/appstore-upload.ts | 8 +-- lib/commands/build.ts | 2 +- lib/commands/clean-app.ts | 6 +- lib/commands/debug.ts | 2 +- lib/commands/deploy.ts | 2 +- lib/commands/emulate.ts | 6 +- lib/commands/install.ts | 2 +- lib/commands/platform-clean.ts | 6 +- lib/commands/prepare.ts | 6 +- lib/commands/run.ts | 6 +- lib/commands/update-platform.ts | 2 +- lib/commands/update.ts | 8 +-- lib/declarations.d.ts | 11 +++- lib/definitions/platform.d.ts | 22 +++---- lib/definitions/project.d.ts | 4 +- lib/npm-installation-manager.ts | 4 +- lib/providers/livesync-provider.ts | 2 +- lib/services/android-project-service.ts | 14 ++--- lib/services/ios-project-service.ts | 6 +- .../livesync/platform-livesync-service.ts | 2 +- lib/services/local-build-service.ts | 7 ++- lib/services/platform-service.ts | 60 +++++++++---------- lib/services/test-execution-service.ts | 8 +-- test/npm-support.ts | 2 +- test/platform-service.ts | 54 +++++++++-------- 26 files changed, 137 insertions(+), 117 deletions(-) diff --git a/lib/commands/add-platform.ts b/lib/commands/add-platform.ts index b41fccf62c..647c84ab74 100644 --- a/lib/commands/add-platform.ts +++ b/lib/commands/add-platform.ts @@ -9,7 +9,7 @@ export class AddPlatformCommand implements ICommand { } public async execute(args: string[]): Promise { - await this.$platformService.addPlatforms(args, this.$options.platformTemplate, this.$projectData, { provision: this.$options.provision, sdk: this.$options.sdk }, this.$options.frameworkPath); + await this.$platformService.addPlatforms(args, this.$options.platformTemplate, this.$projectData, this.$options.provision, this.$options.frameworkPath); } public async canExecute(args: string[]): Promise { diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index 89fa327030..7576664371 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -15,8 +15,8 @@ export class PublishIOS implements ICommand { private $options: IOptions, private $prompter: IPrompter, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants) { - this.$projectData.initializeProjectData(); - } + this.$projectData.initializeProjectData(); + } private get $platformsData(): IPlatformsData { return this.$injector.resolve("platformsData"); @@ -71,12 +71,12 @@ export class PublishIOS implements ICommand { }; this.$logger.info("Building .ipa with the selected mobile provision and/or certificate."); // This is not very correct as if we build multiple targets we will try to sign all of them using the signing identity here. - await this.$platformService.preparePlatform(platform, appFilesUpdaterOptions, this.$options.platformTemplate, this.$projectData, { provision: this.$options.provision, sdk: this.$options.sdk }); + await this.$platformService.preparePlatform(platform, appFilesUpdaterOptions, this.$options.platformTemplate, this.$projectData, this.$options); await this.$platformService.buildPlatform(platform, iOSBuildConfig, this.$projectData); ipaFilePath = this.$platformService.lastOutputPath(platform, iOSBuildConfig, this.$projectData); } else { this.$logger.info("No .ipa, mobile provision or certificate set. Perfect! Now we'll build .xcarchive and let Xcode pick the distribution certificate and provisioning profile for you when exporting .ipa for AppStore submission."); - await this.$platformService.preparePlatform(platform, appFilesUpdaterOptions, this.$options.platformTemplate, this.$projectData, { provision: this.$options.provision, sdk: this.$options.sdk }); + await this.$platformService.preparePlatform(platform, appFilesUpdaterOptions, this.$options.platformTemplate, this.$projectData, this.$options); let platformData = this.$platformsData.getPlatformData(platform, this.$projectData); let iOSProjectService = platformData.platformProjectService; diff --git a/lib/commands/build.ts b/lib/commands/build.ts index d8107e9cb7..65be9a7ac4 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -9,7 +9,7 @@ export class BuildCommandBase { public async executeCore(args: string[]): Promise { let platform = args[0].toLowerCase(); const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; - await this.$platformService.preparePlatform(platform, appFilesUpdaterOptions, this.$options.platformTemplate, this.$projectData, { provision: this.$options.provision, sdk: this.$options.sdk }); + await this.$platformService.preparePlatform(platform, appFilesUpdaterOptions, this.$options.platformTemplate, this.$projectData, this.$options); this.$options.clean = true; const buildConfig: IBuildConfig = { buildForDevice: this.$options.forDevice, diff --git a/lib/commands/clean-app.ts b/lib/commands/clean-app.ts index 699846b120..ed79c44977 100644 --- a/lib/commands/clean-app.ts +++ b/lib/commands/clean-app.ts @@ -2,13 +2,13 @@ export class CleanAppCommandBase { constructor(protected $options: IOptions, protected $projectData: IProjectData, private $platformService: IPlatformService) { - this.$projectData.initializeProjectData(); - } + this.$projectData.initializeProjectData(); + } public async execute(args: string[]): Promise { let platform = args[0].toLowerCase(); const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; - return this.$platformService.cleanDestinationApp(platform, appFilesUpdaterOptions, this.$options.platformTemplate, this.$projectData, { provision: this.$options.provision, sdk: this.$options.sdk }); + return this.$platformService.cleanDestinationApp(platform, appFilesUpdaterOptions, this.$options.platformTemplate, this.$projectData, this.$options); } } diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 62e4dc312b..f0cbd14051 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -41,7 +41,7 @@ export abstract class DebugPlatformCommand implements ICommand { const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; - await this.$platformService.deployPlatform(this.$devicesService.platform, appFilesUpdaterOptions, deployOptions, this.$projectData, { provision: this.$options.provision, sdk: this.$options.sdk }); + await this.$platformService.deployPlatform(this.$devicesService.platform, appFilesUpdaterOptions, deployOptions, this.$projectData, this.$options); this.$config.debugLivesync = true; let applicationReloadAction = async (deviceAppData: Mobile.IDeviceAppData): Promise => { let projectData: IProjectData = this.$injector.resolve("projectData"); diff --git a/lib/commands/deploy.ts b/lib/commands/deploy.ts index 66bb49355a..ef6f904f4e 100644 --- a/lib/commands/deploy.ts +++ b/lib/commands/deploy.ts @@ -27,7 +27,7 @@ export class DeployOnDeviceCommand implements ICommand { keyStorePassword: this.$options.keyStorePassword, keyStorePath: this.$options.keyStorePath }; - return this.$platformService.deployPlatform(args[0], appFilesUpdaterOptions, deployOptions, this.$projectData, { provision: this.$options.provision, sdk: this.$options.sdk }); + return this.$platformService.deployPlatform(args[0], appFilesUpdaterOptions, deployOptions, this.$projectData, this.$options); } public async canExecute(args: string[]): Promise { diff --git a/lib/commands/emulate.ts b/lib/commands/emulate.ts index bdc9429723..dfb25faf2c 100644 --- a/lib/commands/emulate.ts +++ b/lib/commands/emulate.ts @@ -3,8 +3,8 @@ export class EmulateCommandBase { private $projectData: IProjectData, private $logger: ILogger, private $platformService: IPlatformService) { - this.$projectData.initializeProjectData(); - } + this.$projectData.initializeProjectData(); + } public async executeCore(args: string[]): Promise { this.$logger.warn(`Emulate command is deprecated and will soon be removed. Please use "tns run " instead. All options available for "tns emulate" are present in "tns run" command. To run on all available emulators, use "tns run --emulator".`); @@ -27,7 +27,7 @@ export class EmulateCommandBase { keyStorePassword: this.$options.keyStorePassword, keyStorePath: this.$options.keyStorePath }; - return this.$platformService.emulatePlatform(args[0], appFilesUpdaterOptions, emulateOptions, this.$projectData, { provision: this.$options.provision, sdk: this.$options.sdk }); + return this.$platformService.emulatePlatform(args[0], appFilesUpdaterOptions, emulateOptions, this.$projectData, this.$options.provision); } } diff --git a/lib/commands/install.ts b/lib/commands/install.ts index ced759e70b..56b1cd329b 100644 --- a/lib/commands/install.ts +++ b/lib/commands/install.ts @@ -31,7 +31,7 @@ export class InstallCommand implements ICommand { const frameworkPackageData = this.$projectDataService.getNSValue(this.$projectData.projectDir, platformData.frameworkPackageName); if (frameworkPackageData && frameworkPackageData.version) { try { - await this.$platformService.addPlatforms([`${platform}@${frameworkPackageData.version}`], this.$options.platformTemplate, this.$projectData, { provision: this.$options.provision, sdk: this.$options.sdk }, this.$options.frameworkPath); + await this.$platformService.addPlatforms([`${platform}@${frameworkPackageData.version}`], this.$options.platformTemplate, this.$projectData, this.$options, this.$options.frameworkPath); } catch (err) { error = `${error}${EOL}${err}`; } diff --git a/lib/commands/platform-clean.ts b/lib/commands/platform-clean.ts index 579e4b1040..e5c334b9d7 100644 --- a/lib/commands/platform-clean.ts +++ b/lib/commands/platform-clean.ts @@ -5,11 +5,11 @@ export class CleanCommand implements ICommand { private $projectData: IProjectData, private $platformService: IPlatformService, private $errors: IErrors) { - this.$projectData.initializeProjectData(); - } + this.$projectData.initializeProjectData(); + } public async execute(args: string[]): Promise { - await this.$platformService.cleanPlatforms(args, this.$options.platformTemplate, this.$projectData, {provision: this.$options.provision, sdk: this.$options.sdk }); + await this.$platformService.cleanPlatforms(args, this.$options.platformTemplate, this.$projectData, this.$options); } public async canExecute(args: string[]): Promise { diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts index 648771aba3..20b85624d3 100644 --- a/lib/commands/prepare.ts +++ b/lib/commands/prepare.ts @@ -5,12 +5,12 @@ export class PrepareCommand implements ICommand { private $platformService: IPlatformService, private $projectData: IProjectData, private $platformCommandParameter: ICommandParameter) { - this.$projectData.initializeProjectData(); - } + this.$projectData.initializeProjectData(); + } public async execute(args: string[]): Promise { const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; - await this.$platformService.preparePlatform(args[0], appFilesUpdaterOptions, this.$options.platformTemplate, this.$projectData, { provision: this.$options.provision, sdk: this.$options.sdk }); + await this.$platformService.preparePlatform(args[0], appFilesUpdaterOptions, this.$options.platformTemplate, this.$projectData, this.$options); } public async canExecute(args: string[]): Promise { diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 753c90e7fa..3da1b60f6b 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -4,8 +4,8 @@ export class RunCommandBase { protected $projectData: IProjectData, protected $options: IOptions, protected $emulatorPlatformService: IEmulatorPlatformService) { - this.$projectData.initializeProjectData(); - } + this.$projectData.initializeProjectData(); + } public async executeCore(args: string[]): Promise { @@ -25,7 +25,7 @@ export class RunCommandBase { keyStorePath: this.$options.keyStorePath }; - await this.$platformService.deployPlatform(args[0], appFilesUpdaterOptions, deployOptions, this.$projectData, { provision: this.$options.provision, sdk: this.$options.sdk }); + await this.$platformService.deployPlatform(args[0], appFilesUpdaterOptions, deployOptions, this.$projectData, this.$options); if (this.$options.bundle) { this.$options.watch = false; diff --git a/lib/commands/update-platform.ts b/lib/commands/update-platform.ts index 5ff02b6509..d66257aa2e 100644 --- a/lib/commands/update-platform.ts +++ b/lib/commands/update-platform.ts @@ -9,7 +9,7 @@ export class UpdatePlatformCommand implements ICommand { } public async execute(args: string[]): Promise { - await this.$platformService.updatePlatforms(args, this.$options.platformTemplate, this.$projectData, { provision: this.$options.provision, sdk: this.$options.sdk }); + await this.$platformService.updatePlatforms(args, this.$options.platformTemplate, this.$projectData, this.$options); } public async canExecute(args: string[]): Promise { diff --git a/lib/commands/update.ts b/lib/commands/update.ts index 49c3eee4ea..d7fff8024b 100644 --- a/lib/commands/update.ts +++ b/lib/commands/update.ts @@ -12,8 +12,8 @@ export class UpdateCommand implements ICommand { private $projectDataService: IProjectDataService, private $fs: IFileSystem, private $logger: ILogger) { - this.$projectData.initializeProjectData(); - } + this.$projectData.initializeProjectData(); + } public async execute(args: string[]): Promise { let folders = ["lib", "hooks", "platforms", "node_modules"]; @@ -83,12 +83,12 @@ export class UpdateCommand implements ICommand { platforms = platforms.concat(packagePlatforms); if (args.length === 1) { for (let platform of platforms) { - await this.$platformService.addPlatforms([platform + "@" + args[0]], this.$options.platformTemplate, this.$projectData, { provision: this.$options.provision, sdk: this.$options.sdk }, this.$options.frameworkPath); + await this.$platformService.addPlatforms([platform + "@" + args[0]], this.$options.platformTemplate, this.$projectData, this.$options, this.$options.frameworkPath); } await this.$pluginsService.add("tns-core-modules@" + args[0], this.$projectData); } else { - await this.$platformService.addPlatforms(platforms, this.$options.platformTemplate, this.$projectData, { provision: this.$options.provision, sdk: this.$options.sdk }, this.$options.frameworkPath); + await this.$platformService.addPlatforms(platforms, this.$options.platformTemplate, this.$projectData, this.$options, this.$options.frameworkPath); await this.$pluginsService.add("tns-core-modules", this.$projectData); } diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 7721fc9169..ccb24e46f6 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -295,12 +295,19 @@ interface IAndroidReleaseOptions { keyStorePath?: string; } -interface INpmInstallConfigurationOptions { +interface INpmInstallConfigurationOptionsBase { frameworkPath: string; - disableNpmInstall: boolean; ignoreScripts: boolean; //npm flag } +interface INpmInstallConfigurationOptions extends INpmInstallConfigurationOptionsBase { + disableNpmInstall: boolean; +} + +interface ICreateProjectOptions extends INpmInstallConfigurationOptionsBase { + pathToTemplate?: string; +} + interface IOptions extends ICommonOptions, IBundle, IPlatformTemplate, IEmulator, IClean, IProvision, ITeamIdentifier, IAndroidReleaseOptions, INpmInstallConfigurationOptions { all: boolean; client: boolean; diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 638d675f7c..d93d820770 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -1,7 +1,7 @@ interface IPlatformService extends NodeJS.EventEmitter { - cleanPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, framework?: string): Promise; + cleanPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, framework?: string): Promise; - addPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, frameworkPath?: string): Promise; + addPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, frameworkPath?: string): Promise; /** * Gets list of all installed platforms (the ones for which /platforms/ exists). @@ -32,7 +32,7 @@ interface IPlatformService extends NodeJS.EventEmitter { */ removePlatforms(platforms: string[], projectData: IProjectData): Promise; - updatePlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise; + updatePlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise; /** * Ensures that the specified platform and its dependencies are installed. @@ -43,11 +43,11 @@ interface IPlatformService extends NodeJS.EventEmitter { * @param {IAppFilesUpdaterOptions} appFilesUpdaterOptions Options needed to instantiate AppFilesUpdater class. * @param {string} platformTemplate The name of the platform template. * @param {IProjectData} projectData DTO with information about the project. - * @param {IPlatformSpecificData} platformSpecificData Platform specific data required for project preparation. + * @param {IAddPlatformCoreOptions} config Options required for project preparation/creation. * @param {Array} filesToSync Files about to be synced to device. * @returns {boolean} true indicates that the platform was prepared. */ - preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, filesToSync?: Array): Promise; + preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, filesToSync?: Array): Promise; /** * Determines whether a build is necessary. A build is necessary when one of the following is true: @@ -106,10 +106,10 @@ interface IPlatformService extends NodeJS.EventEmitter { * @param {IAppFilesUpdaterOptions} appFilesUpdaterOptions Options needed to instantiate AppFilesUpdater class. * @param {IDeployPlatformOptions} deployOptions Various options that can manage the deploy operation. * @param {IProjectData} projectData DTO with information about the project. - * @param {any} platformSpecificData Platform specific data required for project preparation. + * @param {IAddPlatformCoreOptions} config Options required for project preparation/creation. * @returns {void} */ - deployPlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, deployOptions: IDeployPlatformOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise; + deployPlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, deployOptions: IDeployPlatformOptions, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise; /** * Runs the application on specified platform. Assumes that the application is already build and installed. Fails if this is not true. @@ -126,12 +126,12 @@ interface IPlatformService extends NodeJS.EventEmitter { * @param {IAppFilesUpdaterOptions} appFilesUpdaterOptions Options needed to instantiate AppFilesUpdater class. * @param {IEmulatePlatformOptions} emulateOptions Various options that can manage the emulate operation. * @param {IProjectData} projectData DTO with information about the project. - * @param {any} platformSpecificData Platform specific data required for project preparation. + * @param {IAddPlatformCoreOptions} config Options required for project preparation/creation. * @returns {void} */ - emulatePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, emulateOptions: IEmulatePlatformOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise; + emulatePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, emulateOptions: IEmulatePlatformOptions, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise; - cleanDestinationApp(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise; + cleanDestinationApp(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise; validatePlatformInstalled(platform: string, projectData: IProjectData): void; /** @@ -201,6 +201,8 @@ interface IPlatformService extends NodeJS.EventEmitter { trackActionForPlatform(actionData: ITrackPlatformAction): Promise; } +interface IAddPlatformCoreOptions extends IPlatformSpecificData, ICreateProjectOptions { } + /** * Platform specific data required for project preparation. */ diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index e6f6a5a281..fd44de57ef 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -69,7 +69,7 @@ interface IProjectData { * @param {string} projectDir Project root directory. * @returns {void} */ - initializeProjectData(projectDir? :string): void; + initializeProjectData(projectDir?: string): void; } interface IProjectDataService { @@ -158,7 +158,7 @@ interface IiOSBuildConfig extends IBuildForDevice, IDeviceIdentifier, IProvision interface IPlatformProjectService extends NodeJS.EventEmitter { getPlatformData(projectData: IProjectData): IPlatformData; validate(projectData: IProjectData): Promise; - createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData, pathToTemplate?: string): Promise; + createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData, config: ICreateProjectOptions): Promise; interpolateData(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise; interpolateConfigurationFile(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): void; diff --git a/lib/npm-installation-manager.ts b/lib/npm-installation-manager.ts index 36275ed836..0add11ef09 100644 --- a/lib/npm-installation-manager.ts +++ b/lib/npm-installation-manager.ts @@ -90,8 +90,8 @@ export class NpmInstallationManager implements INpmInstallationManager { version = version || await this.getLatestCompatibleVersion(packageName); } - let installedModuleNames = await this.npmInstall(packageName, pathToSave, version, dependencyType); - let installedPackageName = installedModuleNames.name; + let installResultInfo = await this.npmInstall(packageName, pathToSave, version, dependencyType); + let installedPackageName = installResultInfo.name; let pathToInstalledPackage = path.join(pathToSave, "node_modules", installedPackageName); return pathToInstalledPackage; diff --git a/lib/providers/livesync-provider.ts b/lib/providers/livesync-provider.ts index a175688275..0724a34fd0 100644 --- a/lib/providers/livesync-provider.ts +++ b/lib/providers/livesync-provider.ts @@ -53,7 +53,7 @@ export class LiveSyncProvider implements ILiveSyncProvider { public async preparePlatformForSync(platform: string, provision: any, projectData: IProjectData): Promise { const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; - await this.$platformService.preparePlatform(platform, appFilesUpdaterOptions, this.$options.platformTemplate, projectData, { provision: provision, sdk: this.$options.sdk }); + await this.$platformService.preparePlatform(platform, appFilesUpdaterOptions, this.$options.platformTemplate, projectData, this.$options); } public canExecuteFastSync(filePath: string, projectData: IProjectData, platform: string): boolean { diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 2cbc4df214..dd0e3d9dbe 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -106,7 +106,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject Promise.resolve(); } - public async createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData, pathToTemplate?: string): Promise { + public async createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData, config: ICreateProjectOptions): Promise { if (semver.lt(frameworkVersion, AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE)) { this.$errors.failWithoutHelp(`The NativeScript CLI requires Android runtime ${AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE} or later to work properly.`); } @@ -118,10 +118,10 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject this.$logger.trace(`Using Android SDK '${targetSdkVersion}'.`); this.copy(this.getPlatformData(projectData).projectRoot, frameworkDir, "libs", "-R"); - if (pathToTemplate) { + if (config.pathToTemplate) { let mainPath = path.join(this.getPlatformData(projectData).projectRoot, "src", "main"); this.$fs.createDirectory(mainPath); - shell.cp("-R", path.join(path.resolve(pathToTemplate), "*"), mainPath); + shell.cp("-R", path.join(path.resolve(config.pathToTemplate), "*"), mainPath); } else { this.copy(this.getPlatformData(projectData).projectRoot, frameworkDir, "src", "-R"); } @@ -139,13 +139,13 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject this.cleanResValues(targetSdkVersion, projectData, frameworkVersion); let npmConfig: INodePackageManagerInstallOptions = { - "save": true, + save: true, "save-dev": true, "save-exact": true, - "silent": true, + silent: true, disableNpmInstall: false, - frameworkPath: null, - ignoreScripts: false + frameworkPath: config.frameworkPath, + ignoreScripts: config.ignoreScripts }; let projectPackageJson: any = this.$fs.readJson(projectData.projectFilePath); diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index f68525d998..ab138c670c 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -120,14 +120,14 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } // TODO: Remove Promise, reason: readDirectory - unable until androidProjectService has async operations. - public async createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData, pathToTemplate?: string): Promise { + public async createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData, config: ICreateProjectOptions): Promise { this.$fs.ensureDirectoryExists(path.join(this.getPlatformData(projectData).projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER)); - if (pathToTemplate) { + if (config.pathToTemplate) { // Copy everything except the template from the runtime this.$fs.readDirectory(frameworkDir) .filter(dirName => dirName.indexOf(IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER) === -1) .forEach(dirName => shell.cp("-R", path.join(frameworkDir, dirName), this.getPlatformData(projectData).projectRoot)); - shell.cp("-rf", path.join(pathToTemplate, "*"), this.getPlatformData(projectData).projectRoot); + shell.cp("-rf", path.join(config.pathToTemplate, "*"), this.getPlatformData(projectData).projectRoot); } else { shell.cp("-R", path.join(frameworkDir, "*"), this.getPlatformData(projectData).projectRoot); } diff --git a/lib/services/livesync/platform-livesync-service.ts b/lib/services/livesync/platform-livesync-service.ts index 1aea236585..4b926dfa77 100644 --- a/lib/services/livesync/platform-livesync-service.ts +++ b/lib/services/livesync/platform-livesync-service.ts @@ -135,7 +135,7 @@ export abstract class PlatformLiveSyncServiceBase implements IPlatformLiveSyncSe let batch = this.batch[platform]; await batch.syncFiles(async (filesToSync: string[]) => { const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; - await this.$platformService.preparePlatform(this.liveSyncData.platform, appFilesUpdaterOptions, this.$options.platformTemplate, projectData, { provision: this.$options.provision, sdk: this.$options.sdk }, filesToSync); + await this.$platformService.preparePlatform(this.liveSyncData.platform, appFilesUpdaterOptions, this.$options.platformTemplate, projectData, this.$options, filesToSync); let canExecute = this.getCanExecuteAction(this.liveSyncData.platform, this.liveSyncData.appIdentifier); let deviceFileAction = (deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => this.transferFiles(deviceAppData, localToDevicePaths, this.liveSyncData.projectFilesPath, !filePath); let action = this.getSyncAction(filesToSync, deviceFileAction, afterFileSyncAction, projectData); diff --git a/lib/services/local-build-service.ts b/lib/services/local-build-service.ts index f76b8ad6bc..8bf4cb0bb9 100644 --- a/lib/services/local-build-service.ts +++ b/lib/services/local-build-service.ts @@ -10,7 +10,12 @@ export class LocalBuildService extends EventEmitter { public async build(platform: string, platformBuildOptions: IPlatformBuildData, platformTemplate?: string): Promise { this.$projectData.initializeProjectData(platformBuildOptions.projectDir); - await this.$platformService.preparePlatform(platform, platformBuildOptions, platformTemplate, this.$projectData, { provision: platformBuildOptions.provision, sdk: null }); + await this.$platformService.preparePlatform(platform, platformBuildOptions, platformTemplate, this.$projectData, { + provision: platformBuildOptions.provision, + sdk: null, + frameworkPath: null, + ignoreScripts: false + }); const handler = (data: any) => { data.projectDir = platformBuildOptions.projectDir; this.emit(BUILD_OUTPUT_EVENT_NAME, data); diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 69a20bb538..633bfd55f9 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -46,7 +46,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { super(); } - public async cleanPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, framworkPath?: string): Promise { + public async cleanPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, framworkPath?: string): Promise { for (let platform of platforms) { let version: string = this.getCurrentPlatformVersion(platform, projectData); @@ -56,16 +56,16 @@ export class PlatformService extends EventEmitter implements IPlatformService { } await this.removePlatforms([platform], projectData); - await this.addPlatforms([platformWithVersion], platformTemplate, projectData, platformSpecificData); + await this.addPlatforms([platformWithVersion], platformTemplate, projectData, config); } } - public async addPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, frameworkPath?: string): Promise { + public async addPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, frameworkPath?: string): Promise { let platformsDir = projectData.platformsDir; this.$fs.ensureDirectoryExists(platformsDir); for (let platform of platforms) { - await this.addPlatform(platform.toLowerCase(), platformTemplate, projectData, platformSpecificData, frameworkPath); + await this.addPlatform(platform.toLowerCase(), platformTemplate, projectData, config, frameworkPath); } } @@ -80,7 +80,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { return version; } - private async addPlatform(platformParam: string, platformTemplate: string, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, frameworkPath?: string): Promise { + private async addPlatform(platformParam: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, frameworkPath?: string): Promise { let data = platformParam.split("@"), platform = data[0].toLowerCase(), version = data[1]; @@ -130,7 +130,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { let frameworkDir = path.join(downloadedPackagePath, constants.PROJECT_FRAMEWORK_FOLDER_NAME); frameworkDir = path.resolve(frameworkDir); - let coreModuleName = await this.addPlatformCore(platformData, frameworkDir, platformTemplate, projectData, platformSpecificData); + let coreModuleName = await this.addPlatformCore(platformData, frameworkDir, platformTemplate, projectData, config); await this.$npm.uninstall(coreModuleName, { save: true }, projectData.projectDir); } catch (err) { this.$fs.deleteDirectory(platformPath); @@ -143,16 +143,16 @@ export class PlatformService extends EventEmitter implements IPlatformService { } - private async addPlatformCore(platformData: IPlatformData, frameworkDir: string, platformTemplate: string, projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise { - let coreModuleData = this.$fs.readJson(path.join(frameworkDir, "../", "package.json")); + private async addPlatformCore(platformData: IPlatformData, frameworkDir: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { + let coreModuleData = this.$fs.readJson(path.join(frameworkDir, "..", "package.json")); let installedVersion = coreModuleData.version; let coreModuleName = coreModuleData.name; let customTemplateOptions = await this.getPathToPlatformTemplate(platformTemplate, platformData.frameworkPackageName, projectData.projectDir); - let pathToTemplate = customTemplateOptions && customTemplateOptions.pathToTemplate; - await platformData.platformProjectService.createProject(path.resolve(frameworkDir), installedVersion, projectData, pathToTemplate); + config.pathToTemplate = customTemplateOptions && customTemplateOptions.pathToTemplate; + await platformData.platformProjectService.createProject(path.resolve(frameworkDir), installedVersion, projectData, config); platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData); - await platformData.platformProjectService.interpolateData(projectData, platformSpecificData); + await platformData.platformProjectService.interpolateData(projectData, config); platformData.platformProjectService.afterCreateProject(platformData.projectRoot, projectData); let frameworkPackageNameData: any = { version: installedVersion }; @@ -214,7 +214,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { return _.filter(this.$platformsData.platformsNames, p => { return this.isPlatformPrepared(p, projectData); }); } - public async preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, filesToSync?: Array): Promise { + public async preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, filesToSync?: Array): Promise { this.validatePlatform(platform, projectData); await this.trackProjectType(projectData); @@ -230,8 +230,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { let platformData = this.$platformsData.getPlatformData(platform, projectData); await this.$pluginsService.validate(platformData, projectData); - await this.ensurePlatformInstalled(platform, platformTemplate, projectData, platformSpecificData); - let changesInfo = this.$projectChangesService.checkForChanges(platform, projectData, { bundle: appFilesUpdaterOptions.bundle, release: appFilesUpdaterOptions.release, provision: platformSpecificData.provision }); + await this.ensurePlatformInstalled(platform, platformTemplate, projectData, config); + let changesInfo = this.$projectChangesService.checkForChanges(platform, projectData, { bundle: appFilesUpdaterOptions.bundle, release: appFilesUpdaterOptions.release, provision: config.provision }); this.$logger.trace("Changes info in prepare platform:", changesInfo); @@ -245,7 +245,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - await this.preparePlatformCore(platform, appFilesUpdaterOptions, projectData, platformSpecificData, changesInfo, filesToSync); + await this.preparePlatformCore(platform, appFilesUpdaterOptions, projectData, config, changesInfo, filesToSync); this.$projectChangesService.savePrepareInfo(platform, projectData); } else { this.$logger.out("Skipping prepare."); @@ -477,8 +477,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$logger.out(`Successfully installed on device with identifier '${device.deviceInfo.identifier}'.`); } - public async deployPlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, deployOptions: IDeployPlatformOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise { - await this.preparePlatform(platform, appFilesUpdaterOptions, deployOptions.platformTemplate, projectData, platformSpecificData); + public async deployPlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, deployOptions: IDeployPlatformOptions, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { + await this.preparePlatform(platform, appFilesUpdaterOptions, deployOptions.platformTemplate, projectData, config); let options: Mobile.IDevicesServicesInitializationOptions = { platform: platform, deviceId: deployOptions.device, emulator: deployOptions.emulator }; @@ -528,7 +528,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { await this.$devicesService.execute(action, this.getCanExecuteAction(platform, runOptions)); } - public async emulatePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, emulateOptions: IEmulatePlatformOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise { + public async emulatePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, emulateOptions: IEmulatePlatformOptions, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { if (emulateOptions.avd) { this.$logger.warn(`Option --avd is no longer supported. Please use --device instead!`); return Promise.resolve(); @@ -559,7 +559,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - await this.deployPlatform(platform, appFilesUpdaterOptions, emulateOptions, projectData, platformSpecificData); + await this.deployPlatform(platform, appFilesUpdaterOptions, emulateOptions, projectData, config); return this.startApplication(platform, emulateOptions, projectData.projectId); } @@ -601,8 +601,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { return null; } - public async cleanDestinationApp(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise { - await this.ensurePlatformInstalled(platform, platformTemplate, projectData, platformSpecificData); + public async cleanDestinationApp(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { + await this.ensurePlatformInstalled(platform, platformTemplate, projectData, config); const appSourceDirectoryPath = path.join(projectData.projectDir, constants.APP_FOLDER_NAME); let platformData = this.$platformsData.getPlatformData(platform, projectData); @@ -657,16 +657,16 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - public async updatePlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise { + public async updatePlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { for (let platformParam of platforms) { let data = platformParam.split("@"), platform = data[0], version = data[1]; if (this.isPlatformInstalled(platform, projectData)) { - await this.updatePlatform(platform, version, platformTemplate, projectData, platformSpecificData); + await this.updatePlatform(platform, version, platformTemplate, projectData, config); } else { - await this.addPlatform(platformParam, platformTemplate, projectData, platformSpecificData); + await this.addPlatform(platformParam, platformTemplate, projectData, config); } }; } @@ -718,9 +718,9 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - public async ensurePlatformInstalled(platform: string, platformTemplate: string, projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise { + public async ensurePlatformInstalled(platform: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { if (!this.isPlatformInstalled(platform, projectData)) { - await this.addPlatform(platform, platformTemplate, projectData, platformSpecificData); + await this.addPlatform(platform, platformTemplate, projectData, config); } } @@ -780,7 +780,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { return this.getLatestApplicationPackage(platformData.emulatorBuildOutputPath || platformData.deviceBuildOutputPath, platformData.getValidPackageNames({ isForDevice: false, isReleaseBuild: buildConfig.release })); } - private async updatePlatform(platform: string, version: string, platformTemplate: string, projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise { + private async updatePlatform(platform: string, version: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { let platformData = this.$platformsData.getPlatformData(platform, projectData); let data = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); @@ -801,7 +801,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } if (!semver.gt(currentVersion, newVersion)) { - await this.updatePlatformCore(platformData, { currentVersion, newVersion, canUpdate, platformTemplate }, projectData, platformSpecificData); + await this.updatePlatformCore(platformData, { currentVersion, newVersion, canUpdate, platformTemplate }, projectData, config); } else if (semver.eq(currentVersion, newVersion)) { this.$errors.fail("Current and new version are the same."); } else { @@ -813,11 +813,11 @@ export class PlatformService extends EventEmitter implements IPlatformService { } - private async updatePlatformCore(platformData: IPlatformData, updateOptions: IUpdatePlatformOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise { + private async updatePlatformCore(platformData: IPlatformData, updateOptions: IUpdatePlatformOptions, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { let packageName = platformData.normalizedPlatformName.toLowerCase(); await this.removePlatforms([packageName], projectData); packageName = updateOptions.newVersion ? `${packageName}@${updateOptions.newVersion}` : packageName; - await this.addPlatform(packageName, updateOptions.platformTemplate, projectData, platformSpecificData); + await this.addPlatform(packageName, updateOptions.platformTemplate, projectData, config); this.$logger.out("Successfully updated to version ", updateOptions.newVersion); } diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index a547c224e4..838fab565a 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -55,7 +55,7 @@ class TestExecutionService implements ITestExecutionService { this.$fs.writeFile(path.join(projectDir, TestExecutionService.SOCKETIO_JS_FILE_NAME), socketIoJs); const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; - if (!await this.$platformService.preparePlatform(platform, appFilesUpdaterOptions, this.$options.platformTemplate, projectData, { provision: this.$options.provision, sdk: this.$options.sdk })) { + if (!await this.$platformService.preparePlatform(platform, appFilesUpdaterOptions, this.$options.platformTemplate, projectData, this.$options)) { this.$errors.failWithoutHelp("Verify that listed files are well-formed and try again the operation."); } this.detourEntryPoint(projectFilesPath); @@ -70,7 +70,7 @@ class TestExecutionService implements ITestExecutionService { provision: this.$options.provision, teamId: this.$options.teamId }; - await this.$platformService.deployPlatform(platform, appFilesUpdaterOptions, deployOptions, projectData, { provision: this.$options.provision, sdk: this.$options.sdk }); + await this.$platformService.deployPlatform(platform, appFilesUpdaterOptions, deployOptions, projectData, this.$options); await this.$usbLiveSyncService.liveSync(platform, projectData); if (this.$options.debugBrk) { @@ -125,7 +125,7 @@ class TestExecutionService implements ITestExecutionService { const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; // Prepare the project AFTER the TestExecutionService.CONFIG_FILE_NAME file is created in node_modules // so it will be sent to device. - if (!await this.$platformService.preparePlatform(platform, appFilesUpdaterOptions, this.$options.platformTemplate, projectData, { provision: this.$options.provision, sdk: this.$options.sdk })) { + if (!await this.$platformService.preparePlatform(platform, appFilesUpdaterOptions, this.$options.platformTemplate, projectData, this.$options)) { this.$errors.failWithoutHelp("Verify that listed files are well-formed and try again the operation."); } @@ -145,7 +145,7 @@ class TestExecutionService implements ITestExecutionService { const debugData = this.getDebugData(platform, projectData, deployOptions); await debugService.debug(debugData, this.$options); } else { - await this.$platformService.deployPlatform(platform, appFilesUpdaterOptions, deployOptions, projectData, { provision: this.$options.provision, sdk: this.$options.sdk }); + await this.$platformService.deployPlatform(platform, appFilesUpdaterOptions, deployOptions, projectData, this.$options); await this.$usbLiveSyncService.liveSync(platform, projectData); } }; diff --git a/test/npm-support.ts b/test/npm-support.ts index 261cfe9ad0..e69d9a4d69 100644 --- a/test/npm-support.ts +++ b/test/npm-support.ts @@ -192,7 +192,7 @@ async function preparePlatform(testInjector: IInjector): Promise { projectData.initializeProjectData(); const options: IOptions = testInjector.resolve("options"); - await platformService.preparePlatform("android", { bundle: options.bundle, release: options.release }, "", projectData, { provision: options.provision, sdk: options.sdk }); + await platformService.preparePlatform("android", { bundle: options.bundle, release: options.release }, "", projectData, options); } describe("Npm support tests", () => { diff --git a/test/platform-service.ts b/test/platform-service.ts index 532b277f00..54df6f0626 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -144,6 +144,13 @@ class DestinationFolderVerifier { describe('Platform Service Tests', () => { let platformService: IPlatformService, testInjector: IInjector; + const config: IAddPlatformCoreOptions = { + ignoreScripts: false, + provision: null, + sdk: null, + frameworkPath: null + }; + beforeEach(() => { testInjector = createTestInjector(); testInjector.register("fs", stubs.FileSystemStub); @@ -156,22 +163,21 @@ describe('Platform Service Tests', () => { let fs = testInjector.resolve("fs"); fs.exists = () => false; let projectData: IProjectData = testInjector.resolve("projectData"); - - await platformService.addPlatforms(["Android"], "", projectData, null); - await platformService.addPlatforms(["ANDROID"], "", projectData, null); - await platformService.addPlatforms(["AnDrOiD"], "", projectData, null); - await platformService.addPlatforms(["androiD"], "", projectData, null); - - await platformService.addPlatforms(["iOS"], "", projectData, null); - await platformService.addPlatforms(["IOS"], "", projectData, null); - await platformService.addPlatforms(["IoS"], "", projectData, null); - await platformService.addPlatforms(["iOs"], "", projectData, null); + await platformService.addPlatforms(["Android"], "", projectData, config); + await platformService.addPlatforms(["ANDROID"], "", projectData, config); + await platformService.addPlatforms(["AnDrOiD"], "", projectData, config); + await platformService.addPlatforms(["androiD"], "", projectData, config); + + await platformService.addPlatforms(["iOS"], "", projectData, config); + await platformService.addPlatforms(["IOS"], "", projectData, config); + await platformService.addPlatforms(["IoS"], "", projectData, config); + await platformService.addPlatforms(["iOs"], "", projectData, config); }); it("should fail if platform is already installed", async () => { let projectData: IProjectData = testInjector.resolve("projectData"); // By default fs.exists returns true, so the platforms directory should exists - await assert.isRejected(platformService.addPlatforms(["android"], "", projectData, null)); - await assert.isRejected(platformService.addPlatforms(["ios"], "", projectData, null)); + await assert.isRejected(platformService.addPlatforms(["android"], "", projectData, config)); + await assert.isRejected(platformService.addPlatforms(["ios"], "", projectData, config)); }); it("should fail if npm is unavalible", async () => { let fs = testInjector.resolve("fs"); @@ -183,7 +189,7 @@ describe('Platform Service Tests', () => { let projectData: IProjectData = testInjector.resolve("projectData"); try { - await platformService.addPlatforms(["android"], "", projectData, null); + await platformService.addPlatforms(["android"], "", projectData, config); } catch (err) { assert.equal(errorMessage, err.message); } @@ -206,8 +212,8 @@ describe('Platform Service Tests', () => { let projectData: IProjectData = testInjector.resolve("projectData"); - await platformService.addPlatforms(["android"], "", projectData, null); - await platformService.addPlatforms(["ios"], "", projectData, null); + await platformService.addPlatforms(["android"], "", projectData, config); + await platformService.addPlatforms(["ios"], "", projectData, config); }); it("should install latest platform if no information found in package.json's nativescript key", async () => { let fs = testInjector.resolve("fs"); @@ -224,8 +230,8 @@ describe('Platform Service Tests', () => { let projectData: IProjectData = testInjector.resolve("projectData"); - await platformService.addPlatforms(["android"], "", projectData, null); - await platformService.addPlatforms(["ios"], "", projectData, null); + await platformService.addPlatforms(["android"], "", projectData, config); + await platformService.addPlatforms(["ios"], "", projectData, config); }); }); describe("#add platform(ios)", () => { @@ -242,7 +248,7 @@ describe('Platform Service Tests', () => { }; try { - await platformService.addPlatforms(["ios"], "", projectData, null); + await platformService.addPlatforms(["ios"], "", projectData, config); } catch (err) { assert.equal(errorMessage, err.message); } @@ -262,7 +268,7 @@ describe('Platform Service Tests', () => { let projectData: IProjectData = testInjector.resolve("projectData"); try { - await platformService.addPlatforms(["android"], "", projectData, null); + await platformService.addPlatforms(["android"], "", projectData, config); } catch (err) { assert.equal(errorMessage, err.message); } @@ -294,7 +300,7 @@ describe('Platform Service Tests', () => { it("shouldn't fail when platforms are added", async () => { let projectData: IProjectData = testInjector.resolve("projectData"); testInjector.resolve("fs").exists = () => false; - await platformService.addPlatforms(["android"], "", projectData, null); + await platformService.addPlatforms(["android"], "", projectData, config); testInjector.resolve("fs").exists = () => true; await platformService.removePlatforms(["android"], projectData); @@ -324,10 +330,10 @@ describe('Platform Service Tests', () => { return Promise.resolve(); }; - await platformService.cleanPlatforms(["android"], "", projectData, null); + await platformService.cleanPlatforms(["android"], "", projectData, config); nsValueObject[VERSION_STRING] = versionString; - await platformService.cleanPlatforms(["ios"], "", projectData, null); + await platformService.cleanPlatforms(["ios"], "", projectData, config); }); }); @@ -425,7 +431,7 @@ describe('Platform Service Tests', () => { platformService = testInjector.resolve("platformService"); const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: false, release: release }; - await platformService.preparePlatform(platformToTest, appFilesUpdaterOptions, "", projectData, { provision: null, sdk: null }); + await platformService.preparePlatform(platformToTest, appFilesUpdaterOptions, "", projectData, { provision: null, sdk: null, frameworkPath: null, ignoreScripts: false }); } async function testPreparePlatform(platformToTest: string, release?: boolean): Promise { @@ -851,7 +857,7 @@ describe('Platform Service Tests', () => { try { testInjector.resolve("$logger").warn = (text: string) => warnings += text; const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: false, release: false }; - await platformService.preparePlatform("android", appFilesUpdaterOptions, "", projectData, { provision: null, sdk: null }); + await platformService.preparePlatform("android", appFilesUpdaterOptions, "", projectData, { provision: null, sdk: null, frameworkPath: null, ignoreScripts: false }); } finally { testInjector.resolve("$logger").warn = oldLoggerWarner; } From a9b14a31f5fd8d7b45ce4f6d3d0e6ebc7ef3c290 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Tue, 25 Apr 2017 17:50:20 +0300 Subject: [PATCH 012/212] Rebase --- lib/commands/test-init.ts | 6 +++++- lib/services/extensibility-service.ts | 7 ++----- test/services/extensibility-service.ts | 8 ++++---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/commands/test-init.ts b/lib/commands/test-init.ts index 2a54cdf01b..b04a261e0f 100644 --- a/lib/commands/test-init.ts +++ b/lib/commands/test-init.ts @@ -54,7 +54,11 @@ class TestInitCommand implements ICommand { // e.g karma is installed; karma-jasmine depends on karma and will try to install it again try { await this.$npm.install(`${peerDependency}@${dependencyVersion}`, projectDir, { - 'save-dev': true + 'save-dev': true, + disableNpmInstall: false, + frameworkPath: this.$options.frameworkPath, + ignoreScripts: this.$options.ignoreScripts, + path: this.$options.path }); } catch (e) { this.$logger.error(e.message); diff --git a/lib/services/extensibility-service.ts b/lib/services/extensibility-service.ts index 756e037f00..3eaadf6456 100644 --- a/lib/services/extensibility-service.ts +++ b/lib/services/extensibility-service.ts @@ -32,13 +32,10 @@ export class ExtensibilityService implements IExtensibilityService { const localPath = path.resolve(extensionName); const packageName = this.$fs.exists(localPath) ? localPath : extensionName; - const realName = (await this.$npm.install(packageName, this.pathToExtensions, npmOpts))[0]; + const installResultInfo = await this.$npm.install(packageName, this.pathToExtensions, npmOpts); this.$logger.trace(`Finished installation of extension '${extensionName}'. Trying to load it now.`); - // In case the extension is already installed, the $npm.install method will not return the name of the package. - // Fallback to the original value. - // NOTE: This will not be required once $npm.install starts working correctly. - return await this.loadExtension(realName || extensionName); + return await this.loadExtension(installResultInfo.name); } @exported("extensibilityService") diff --git a/test/services/extensibility-service.ts b/test/services/extensibility-service.ts index 3e02472ee8..25d80063a9 100644 --- a/test/services/extensibility-service.ts +++ b/test/services/extensibility-service.ts @@ -77,7 +77,7 @@ describe("extensibilityService", () => { argsPassedToNpmInstall.packageName = packageName; argsPassedToNpmInstall.pathToSave = pathToSave; argsPassedToNpmInstall.config = config; - return [userSpecifiedValue]; + return { name: userSpecifiedValue }; }; const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); @@ -130,7 +130,7 @@ describe("extensibilityService", () => { fs.readDirectory = (dir: string): string[] => [extensionName]; const npm: INodePackageManager = testInjector.resolve("npm"); - npm.install = async (packageName: string, pathToSave: string, config?: any): Promise => [extensionName]; + npm.install = async (packageName: string, pathToSave: string, config?: any): Promise => ({ name: extensionName }); const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); const actualResult = await extensibilityService.installExtension(extensionName); @@ -148,7 +148,7 @@ describe("extensibilityService", () => { fs.readDirectory = (dir: string): string[] => [extensionName]; const npm: INodePackageManager = testInjector.resolve("npm"); - npm.install = async (packageName: string, pathToSave: string, config?: any): Promise => [extensionName]; + npm.install = async (packageName: string, pathToSave: string, config?: any): Promise => ({ name: extensionName }); const requireService: IRequireService = testInjector.resolve("requireService"); requireService.require = (pathToRequire: string) => { @@ -230,7 +230,7 @@ describe("extensibilityService", () => { npm.install = async (packageName: string, pathToSave: string, config?: any): Promise => { assert.deepEqual(packageName, extensionNames[0]); isNpmInstallCalled = true; - return [packageName]; + return { name: packageName }; }; const expectedResults: IExtensionData[] = _.map(extensionNames, extensionName => ({ extensionName })); From a389de14c62743072ee1347f211b1f18fa453e97 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Wed, 26 Apr 2017 12:29:35 +0300 Subject: [PATCH 013/212] Final fix --- lib/commands/add-platform.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/commands/add-platform.ts b/lib/commands/add-platform.ts index 647c84ab74..48ff06a732 100644 --- a/lib/commands/add-platform.ts +++ b/lib/commands/add-platform.ts @@ -9,7 +9,7 @@ export class AddPlatformCommand implements ICommand { } public async execute(args: string[]): Promise { - await this.$platformService.addPlatforms(args, this.$options.platformTemplate, this.$projectData, this.$options.provision, this.$options.frameworkPath); + await this.$platformService.addPlatforms(args, this.$options.platformTemplate, this.$projectData, this.$options, this.$options.frameworkPath); } public async canExecute(args: string[]): Promise { From b3e50adc9472c0e9c9edfbf97fbe06be84d5bc59 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 26 Apr 2017 00:15:34 +0300 Subject: [PATCH 014/212] Improve debugService public API When using Chrome DevTools to debug iOS applications, CLI returns a url that points to a specific commit of the dev tools. This way, in case a new version of the dev tools introduces a breaking change, the debugging will still work. However, in some cases (inside Electron app), we cannot use the remote url, so we must use the bundled one. So introduce a new option (only available when requiring CLI as a library), that defines that the returned url will use the bundled dev tools. The default behavior (used in CLI), will still return the url that includes remote url. Also change the definition of `debug` method in the interface, so now the DebugService can safely implement it. Add a new check in the debug service - in case the device's status is not Connected, we are unable to start debug operation. So fail with correct error message in this case. Add JSDocs for all debug related interfaces. Add documentation in PublicAPI.md for the `debugService`. Add debugService to tests of public API. --- PublicAPI.md | 127 +++++++++++++++++++++++++++++- lib/commands/debug.ts | 4 +- lib/common | 2 +- lib/definitions/debug.d.ts | 95 +++++++++++++++++++++- lib/services/debug-service.ts | 13 +-- lib/services/ios-debug-service.ts | 8 +- test/nativescript-cli-lib.ts | 3 +- 7 files changed, 237 insertions(+), 15 deletions(-) diff --git a/PublicAPI.md b/PublicAPI.md index 77d3743373..6eea5ffc55 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -180,10 +180,10 @@ for (let promise of loadExtensionsPromises) { } ``` -## settings -`settings` module provides a way to configure various settings. +## settingsService +`settingsService` module provides a way to configure various settings. -### set +### setSettings Used to set various settings in order to modify the behavior of some methods. * Auxiliary interfaces: ```TypeScript @@ -339,9 +339,128 @@ tns.npm.view(["nativescript"], {}).then(result => { }); ``` +## debugService +Provides methods for debugging applications on devices. The service is also event emitter, that raises the following events: +* `connectionError` event - this event is raised when the debug operation cannot start on iOS device. The causes can be: + * Application is not running on the specified iOS Device. + * Application is not built in debug configuration on the specified iOS device. + The event is raised with the following information: +```TypeScript +{ + /** + * Device identifier on which the debug process cannot start. + */ + deviceId: string; + + /** + * The error message. + */ + message: string; + + /** + * Code of the error. + */ + code: number +} +``` + +* Usage: +```JavaScript +tns.debugService.on("connectionError", errorData => { + console.log(`Unable to start debug operation on device ${errorData.deviceId}. Error is: ${errorData.message}.`); +}); +``` + +### debug +The `debug` method allows starting a debug operation for specified application on a specific device. The method returns a Promise, which is resolved with a url. The url should be opened in Chrome DevTools in order to debug the application. + +The returned Promise will be rejected in case any error occurs. It will also be rejected in case: +1. Specified deviceIdentifier is not found in current list of attached devices. +1. The device, specified as deviceIdentifier is connected but not trusted. +1. The specified application is not installed on the device. +1. Trying to debug applications on connected iOS device on Linux. +1. In case the application is not running on the specified device. +1. In case the installed application is not built in debug configuration. + +* Definition: +```TypeScript +/** + * Starts debug operation based on the specified debug data. + * @param {IDebugData} debugData Describes information for device and application that will be debugged. + * @param {IDebugOptions} debugOptions Describe possible options to modify the behaivor of the debug operation, for example stop on the first line. + * @returns {Promise} URL that should be opened in Chrome DevTools. + */ +debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; +``` + +The type of arguments that you can pass are described below: +```TypeScript +/** + * Describes information for starting debug process. + */ +interface IDebugData { + /** + * Id of the device on which the debug process will be started. + */ + deviceIdentifier: string; + + /** + * Application identifier of the app that it will be debugged. + */ + applicationIdentifier: string; + + /** + * Path to .app built for iOS Simulator. + */ + pathToAppPackage?: string; + + /** + * The name of the application, for example `MyProject`. + */ + projectName?: string; + + /** + * Path to project. + */ + projectDir?: string; +} + +/** + * Describes all options that define the behavior of debug. + */ +interface IDebugOptions { + /** + * Defines if bundled Chrome DevTools should be used or specific commit. Valid for iOS only. + */ + useBundledDevTools?: boolean; +} +``` + +* Usage: +```JavaScript +tns.debugService.on("connectionError", errorData => { + console.log(`Unable to start debug operation on device ${errorData.deviceId}. Error is: ${errorData.message}.`); +}); + +const debugData = { + deviceIdentifier: "4df18f307d8a8f1b", + applicationIdentifier: "com.telerik.app1", + projectName: "app1", + projectDir: "/Users/myUser/app1" +}; + +const debugOptions = { + useBundledDevTools: true +}; + +tns.debugService.debug(debugData, debugOptions) + .then(url => console.log(`Open the following url in Chrome DevTools: ${url}`)) + .catch(err => console.log(`Unable to start debug operation, reason: ${err.message}.`)); +``` + ## How to add a new method to Public API CLI is designed as command line tool and when it is used as a library, it does not give you access to all of the methods. This is mainly implementation detail. Most of the CLI's code is created to work in command line, not as a library, so before adding method to public API, most probably it will require some modification. For example the `$options` injected module contains information about all `--` options passed on the terminal. When the CLI is used as a library, the options are not populated. Before adding method to public API, make sure its implementation does not rely on `$options`. More information how to add a method to public API is available [here](https://github.com/telerik/mobile-cli-lib#how-to-make-a-method-public). -After that add each method that you've exposed to the tests in `tests/nativescript-cli-lib.ts` file. There you'll find an object describing each publicly available module and the methods that you can call. +After that add each method that you've exposed to the tests in `tests/nativescript-cli-lib.ts` file. There you'll find an object describing each publicly available module and the methods that you can call. \ No newline at end of file diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index f0cbd14051..5df14356c2 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -36,7 +36,7 @@ export abstract class DebugPlatformCommand implements ICommand { await this.$platformService.trackProjectType(this.$projectData); if (this.$options.start) { - return this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); + return this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); } const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; @@ -58,7 +58,7 @@ export abstract class DebugPlatformCommand implements ICommand { const buildConfig: IBuildConfig = _.merge({ buildForDevice: !deviceAppData.device.isEmulator }, deployOptions); debugData.pathToAppPackage = this.$platformService.lastOutputPath(this.debugService.platform, buildConfig, projectData); - this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); + this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); }; return this.$usbLiveSyncService.liveSync(this.$devicesService.platform, this.$projectData, applicationReloadAction); diff --git a/lib/common b/lib/common index c81c94373b..350b00f2ff 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit c81c94373b25572644222001ff25ec32e0869ec4 +Subproject commit 350b00f2fff61d2db04e8817dae5e0bc94dc6e12 diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 752b2ad762..efae147388 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -1,31 +1,124 @@ +/** + * Describes information for starting debug process. + */ interface IDebugData { + /** + * Id of the device on which the debug process will be started. + */ deviceIdentifier: string; + + /** + * Application identifier of the app that it will be debugged. + */ applicationIdentifier: string; + + /** + * Path to .app built for iOS Simulator. + */ pathToAppPackage?: string; + + /** + * The name of the application, for example `MyProject`. + */ projectName?: string; + + /** + * Path to project. + */ projectDir?: string; } +/** + * Describes all options that define the behavior of debug. + */ interface IDebugOptions { + /** + * Defines if Chrome-Dev Tools should be used for debugging. + */ chrome?: boolean; + + /** + * Defines if thе application is already started on device. + */ start?: boolean; + + /** + * Defines if we should stop the currently running debug process. + */ stop?: boolean; + + /** + * Defines if debug process is for emulator (not for real device). + */ emulator?: boolean; + + /** + * Defines if the debug process should break on the first line. + */ debugBrk?: boolean; + + /** + * Defines if the debug process will not have a client attached (i.e. the process will be started, but NativeScript Inspector will not be started and it will not attach to the running debug process). + */ client?: boolean; + + /** + * Defines if the process will watch for further changes in the project and transferrs them to device immediately, resulting in restar of the debug process. + */ justlaunch?: boolean; + + /** + * Defines if bundled Chrome DevTools should be used or specific commit. Valid for iOS only. + */ + useBundledDevTools?: boolean; } +/** + * Describes methods to create debug data object used by other methods. + */ interface IDebugDataService { + /** + * Creates the debug data based on specified options. + * @param {IProjectData} projectData The data describing project that will be debugged. + * @param {IOptions} options The options based on which debugData will be created + * @returns {IDebugData} Data describing the required information for starting debug process. + */ createDebugData(projectData: IProjectData, options: IOptions): IDebugData; } +/** + * Describes methods for debug operation. + */ interface IDebugService extends NodeJS.EventEmitter { - debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; + /** + * Starts debug operation based on the specified debug data. + * @param {IDebugData} debugData Describes information for device and application that will be debugged. + * @param {IDebugOptions} debugOptions Describe possible options to modify the behaivor of the debug operation, for example stop on the first line. + * @returns {Promise} Array of URLs that can be used for debugging or a string representing a single url that can be used for debugging. + */ + debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; } +/** + * Describes actions required for debugging on specific platform (Android or iOS). + */ interface IPlatformDebugService extends IDebugService { + /** + * Starts debug operation. + * @param {IDebugData} debugData Describes information for device and application that will be debugged. + * @param {IDebugOptions} debugOptions Describe possible options to modify the behaivor of the debug operation, for example stop on the first line. + * @returns {Promise} + */ debugStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise; + + /** + * Stops debug operation. + * @returns {Promise} + */ debugStop(): Promise + + /** + * Mobile platform of the device - Android or iOS. + */ platform: string; } diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index d1195add67..9955906d45 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -1,10 +1,9 @@ import { platform } from "os"; import { EventEmitter } from "events"; import { CONNECTION_ERROR_EVENT_NAME } from "../constants"; +import { CONNECTED_STATUS } from "../common/constants"; -// This service can't implement IDebugService because -// the debug method returns only one result. -class DebugService extends EventEmitter { +class DebugService extends EventEmitter implements IDebugService { constructor(private $devicesService: Mobile.IDevicesService, private $androidDebugService: IPlatformDebugService, private $iOSDebugService: IPlatformDebugService, @@ -23,6 +22,10 @@ class DebugService extends EventEmitter { this.$errors.failWithoutHelp(`Can't find device with identifier ${debugData.deviceIdentifier}`); } + if (device.deviceInfo.status !== CONNECTED_STATUS) { + this.$errors.failWithoutHelp(`The device with identifier ${debugData.deviceIdentifier} is unreachable. Make sure it is Trusted and try again.`); + } + if (!(await device.applicationManager.isApplicationInstalled(debugData.applicationIdentifier))) { this.$errors.failWithoutHelp(`The application ${debugData.applicationIdentifier} is not installed on device with identifier ${debugData.deviceIdentifier}.`); } @@ -51,9 +54,9 @@ class DebugService extends EventEmitter { this.$errors.failWithoutHelp(`Debugging on iOS devices is not supported for ${platform()} yet.`); } - result = await debugService.debug(debugData, debugOptions); + result = await debugService.debug(debugData, debugOptions); } else if (this.$mobileHelper.isAndroidPlatform(device.deviceInfo.platform)) { - result = await debugService.debug(debugData, debugOptions); + result = await debugService.debug(debugData, debugOptions); } return _.first(result); diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index 26105413af..a3ba5e165a 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -197,7 +197,13 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService this._socketProxy = await this.$socketProxyFactory.createWebSocketProxy(this.getSocketFactory(device)); const commitSHA = "02e6bde1bbe34e43b309d4ef774b1168d25fd024"; // corresponds to 55.0.2883 Chrome version - return `chrome-devtools://devtools/remote/serve_file/@${commitSHA}/inspector.html?experiments=true&ws=localhost:${this._socketProxy.options.port}`; + let chromeDevToolsPrefix = `chrome-devtools://devtools/remote/serve_file/@${commitSHA}`; + + if (debugOptions.useBundledDevTools) { + chromeDevToolsPrefix = "chrome-devtools://devtools/bundled"; + } + + return `${chromeDevToolsPrefix}/inspector.html?experiments=true&ws=localhost:${this._socketProxy.options.port}`; } else { this._socketProxy = await this.$socketProxyFactory.createTCPSocketProxy(this.getSocketFactory(device)); await this.openAppInspector(this._socketProxy.address(), debugData, debugOptions); diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index e78cd3c5ae..d9929cf3d8 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -19,7 +19,8 @@ describe("nativescript-cli-lib", () => { localBuildService: ["build"], deviceLogProvider: null, npm: ["install", "uninstall", "view", "search"], - extensibilityService: ["loadExtensions", "getInstalledExtensions", "installExtension", "uninstallExtension"] + extensibilityService: ["loadExtensions", "getInstalledExtensions", "installExtension", "uninstallExtension"], + debugService: ["debug"] }; const pathToEntryPoint = path.join(__dirname, "..", "lib", "nativescript-cli-lib.js").replace(/\\/g, "\\\\"); From 8b730f3d01d53d52dc6202c03eed063db69e1e9a Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 26 Apr 2017 02:56:02 +0300 Subject: [PATCH 015/212] Add tests for debugService Add tests for debugService. Add verification that in case device's OS is not supported (for example WP8), we'll throw correct error. --- lib/definitions/debug.d.ts | 2 +- lib/services/debug-service.ts | 15 +- lib/services/ios-debug-service.ts | 9 +- test/services/debug-service.ts | 292 ++++++++++++++++++++++++++++++ 4 files changed, 309 insertions(+), 9 deletions(-) create mode 100644 test/services/debug-service.ts diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index efae147388..3a1c62a37b 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -33,7 +33,7 @@ interface IDebugData { */ interface IDebugOptions { /** - * Defines if Chrome-Dev Tools should be used for debugging. + * Defines if Chrome DevTools should be used for debugging. */ chrome?: boolean; diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index 9955906d45..afc9b5d81b 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -3,7 +3,7 @@ import { EventEmitter } from "events"; import { CONNECTION_ERROR_EVENT_NAME } from "../constants"; import { CONNECTED_STATUS } from "../common/constants"; -class DebugService extends EventEmitter implements IDebugService { +export class DebugService extends EventEmitter implements IDebugService { constructor(private $devicesService: Mobile.IDevicesService, private $androidDebugService: IPlatformDebugService, private $iOSDebugService: IPlatformDebugService, @@ -16,10 +16,9 @@ class DebugService extends EventEmitter implements IDebugService { public async debug(debugData: IDebugData, options: IDebugOptions): Promise { const device = this.$devicesService.getDeviceByIdentifier(debugData.deviceIdentifier); - const debugService = this.getDebugService(device); if (!device) { - this.$errors.failWithoutHelp(`Can't find device with identifier ${debugData.deviceIdentifier}`); + this.$errors.failWithoutHelp(`Cannot find device with identifier ${debugData.deviceIdentifier}.`); } if (device.deviceInfo.status !== CONNECTED_STATUS) { @@ -38,9 +37,15 @@ class DebugService extends EventEmitter implements IDebugService { // After we find a way to check on iOS we should use it here. const isAppRunning = true; let result: string[]; - debugOptions.chrome = !debugOptions.client; + debugOptions.chrome = true; + + const debugService = this.getDebugService(device); + if (!debugService) { + this.$errors.failWithoutHelp(`Unsupported device OS: ${device.deviceInfo.platform}. You can debug your applications only on iOS or Android.`); + } + if (this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform)) { - if (device.isEmulator && !debugData.pathToAppPackage) { + if (device.isEmulator && !debugData.pathToAppPackage && debugOptions.debugBrk) { this.$errors.failWithoutHelp("To debug on iOS simulator you need to provide path to the app package."); } diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index a3ba5e165a..60600caaf6 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -196,11 +196,14 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService if (debugOptions.chrome) { this._socketProxy = await this.$socketProxyFactory.createWebSocketProxy(this.getSocketFactory(device)); - const commitSHA = "02e6bde1bbe34e43b309d4ef774b1168d25fd024"; // corresponds to 55.0.2883 Chrome version - let chromeDevToolsPrefix = `chrome-devtools://devtools/remote/serve_file/@${commitSHA}`; + let chromeDevToolsPrefix = `chrome-devtools://devtools/`; if (debugOptions.useBundledDevTools) { - chromeDevToolsPrefix = "chrome-devtools://devtools/bundled"; + chromeDevToolsPrefix += "bundled"; + } else { + // corresponds to 55.0.2883 Chrome version + const commitSHA = "02e6bde1bbe34e43b309d4ef774b1168d25fd024"; + chromeDevToolsPrefix += `remote/serve_file/@${commitSHA}`; } return `${chromeDevToolsPrefix}/inspector.html?experiments=true&ws=localhost:${this._socketProxy.options.port}`; diff --git a/test/services/debug-service.ts b/test/services/debug-service.ts new file mode 100644 index 0000000000..ca0f5d086c --- /dev/null +++ b/test/services/debug-service.ts @@ -0,0 +1,292 @@ +import { DebugService } from "../../lib/services/debug-service"; +import { Yok } from "../../lib/common/yok"; +import * as stubs from "../stubs"; +import { assert } from "chai"; +import { EventEmitter } from "events"; +import * as constants from "../../lib/common/constants"; +import { CONNECTION_ERROR_EVENT_NAME } from "../../lib/constants"; + +const fakeChromeDebugUrl = "fakeChromeDebugUrl"; +class PlatformDebugService extends EventEmitter /* implements IPlatformDebugService */ { + public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + return [fakeChromeDebugUrl]; + } +}; + +interface IDebugTestDeviceInfo { + deviceInfo: { + status: string; + platform: string; + }; + + isEmulator: boolean; +}; + +interface IDebugTestData { + isDeviceFound: boolean; + deviceInformation: IDebugTestDeviceInfo; + isApplicationInstalledOnDevice: boolean; + hostInfo: { + isWindows: boolean; + isDarwin: boolean; + }; +}; + +const getDefaultDeviceInformation = (platform?: string): IDebugTestDeviceInfo => ({ + deviceInfo: { + status: constants.CONNECTED_STATUS, + platform: platform || "Android" + }, + + isEmulator: false +}); + +const getDefaultTestData = (platform?: string): IDebugTestData => ({ + isDeviceFound: true, + deviceInformation: getDefaultDeviceInformation(platform), + isApplicationInstalledOnDevice: true, + hostInfo: { + isWindows: false, + isDarwin: true + } +}); + +describe("debugService", () => { + const getTestInjectorForTestConfiguration = (testData: IDebugTestData): IInjector => { + const testInjector = new Yok(); + testInjector.register("devicesService", { + getDeviceByIdentifier: (identifier: string): Mobile.IDevice => { + return testData.isDeviceFound ? + { + deviceInfo: testData.deviceInformation.deviceInfo, + + applicationManager: { + isApplicationInstalled: async (appIdentifier: string): Promise => testData.isApplicationInstalledOnDevice + }, + + isEmulator: testData.deviceInformation.isEmulator + } : null; + } + }); + + testInjector.register("androidDebugService", PlatformDebugService); + + testInjector.register("iOSDebugService", PlatformDebugService); + + testInjector.register("mobileHelper", { + isAndroidPlatform: (platform: string) => { + return platform.toLowerCase() === "android"; + }, + isiOSPlatform: (platform: string) => { + return platform.toLowerCase() === "ios"; + } + }); + + testInjector.register("errors", stubs.ErrorsStub); + + testInjector.register("hostInfo", testData.hostInfo); + + return testInjector; + }; + + describe("debug", () => { + const getDebugData = (deviceIdentifier?: string): IDebugData => ({ + applicationIdentifier: "org.nativescript.app1", + deviceIdentifier: deviceIdentifier || "Nexus5", + projectDir: "/Users/user/app1", + projectName: "app1" + }); + + describe("rejects the result promise when", () => { + const assertIsRejected = async (testData: IDebugTestData, expectedError: string, userSpecifiedOptions?: IDebugOptions): Promise => { + const testInjector = getTestInjectorForTestConfiguration(testData); + const debugService = testInjector.resolve(DebugService); + + const debugData = getDebugData(); + await assert.isRejected(debugService.debug(debugData, userSpecifiedOptions), expectedError); + }; + + it("there's no attached device as the specified identifier", async () => { + const testData = getDefaultTestData(); + testData.isDeviceFound = false; + + await assertIsRejected(testData, "Cannot find device with identifier"); + }); + + it("the device is not trusted", async () => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.status = constants.UNREACHABLE_STATUS; + + await assertIsRejected(testData, "is unreachable. Make sure it is Trusted "); + }); + + it("the application is not installed on device", async () => { + const testData = getDefaultTestData(); + testData.isApplicationInstalledOnDevice = false; + + await assertIsRejected(testData, "is not installed on device with identifier"); + }); + + it("the OS is neither Windows or macOS and device is iOS", async () => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.platform = "iOS"; + testData.hostInfo.isDarwin = testData.hostInfo.isWindows = false; + + await assertIsRejected(testData, "Debugging on iOS devices is not supported for"); + }); + + it("device is neither iOS or Android", async () => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.platform = "WP8"; + + await assertIsRejected(testData, "Unsupported device OS:"); + }); + + it("when trying to debug on iOS Simulator on macOS, debug-brk is passed, but pathToAppPackage is not", async () => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.platform = "iOS"; + testData.deviceInformation.isEmulator = true; + + await assertIsRejected(testData, "To debug on iOS simulator you need to provide path to the app package.", { debugBrk: true }); + }); + + const assertIsRejectedWhenPlatformDebugServiceFails = async (platform: string): Promise => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.platform = platform; + + const testInjector = getTestInjectorForTestConfiguration(testData); + const expectedErrorMessage = "Platform specific error"; + const platformDebugService = testInjector.resolve(`${platform}DebugService`); + platformDebugService.debug = async (debugData: IDebugData, debugOptions: IDebugOptions): Promise => { + throw new Error(expectedErrorMessage); + }; + + const debugService = testInjector.resolve(DebugService); + + const debugData = getDebugData(); + await assert.isRejected(debugService.debug(debugData, null), expectedErrorMessage); + }; + + it("androidDebugService's debug method fails", async () => { + await assertIsRejectedWhenPlatformDebugServiceFails("android"); + }); + + it("iOSDebugService's debug method fails", async () => { + await assertIsRejectedWhenPlatformDebugServiceFails("iOS"); + }); + }); + + describe("passes correct args to", () => { + const assertPassedDebugOptions = async (platform: string, userSpecifiedOptions?: IDebugOptions, hostInfo?: { isWindows: boolean, isDarwin: boolean }): Promise => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.platform = platform; + if (hostInfo) { + testData.hostInfo = hostInfo; + } + + const testInjector = getTestInjectorForTestConfiguration(testData); + const platformDebugService = testInjector.resolve(`${platform}DebugService`); + let passedDebugOptions: IDebugOptions = null; + platformDebugService.debug = async (debugData: IDebugData, debugOptions: IDebugOptions): Promise => { + passedDebugOptions = debugOptions; + return []; + }; + + const debugService = testInjector.resolve(DebugService); + + const debugData = getDebugData(); + await assert.isFulfilled(debugService.debug(debugData, userSpecifiedOptions)); + assert.isTrue(passedDebugOptions.chrome); + assert.isTrue(passedDebugOptions.start); + + return passedDebugOptions; + }; + + _.each(["android", "iOS"], platform => { + describe(`${platform}DebugService's debug method`, () => { + describe("on macOS", () => { + it("passes chrome and start as true, when no debugOptions are passed to debugService", async () => { + await assertPassedDebugOptions(platform); + }); + + it("when calling debug service with chrome and start set to false, should disregard them and set both to true", async () => { + await assertPassedDebugOptions(platform, { chrome: false, start: false }); + }); + + it("passes other custom options without modification", async () => { + const passedDebugOptions = await assertPassedDebugOptions(platform, { emulator: true, useBundledDevTools: true }); + assert.isTrue(passedDebugOptions.useBundledDevTools); + assert.isTrue(passedDebugOptions.emulator); + }); + }); + + describe("on Windows", () => { + const assertEmulatorOption = (passedDebugOptions: IDebugOptions) => { + if (platform === "iOS") { + assert.isFalse(passedDebugOptions.emulator); + } + }; + + it("passes chrome and start as true, when no debugOptions are passed to debugService", async () => { + const passedDebugOptions = await assertPassedDebugOptions(platform, null, { isWindows: true, isDarwin: false }); + assertEmulatorOption(passedDebugOptions); + }); + + it("when calling debug service with chrome and start set to false, should disregard them and set both to true", async () => { + const passedDebugOptions = await assertPassedDebugOptions(platform, { chrome: false, start: false }, { isWindows: true, isDarwin: false }); + assertEmulatorOption(passedDebugOptions); + }); + + it("passes other custom options without modification", async () => { + const passedDebugOptions = await assertPassedDebugOptions(platform, { debugBrk: true, useBundledDevTools: true }); + assert.isTrue(passedDebugOptions.useBundledDevTools); + assert.isTrue(passedDebugOptions.debugBrk); + }); + }); + + }); + }); + }); + + describe(`raises ${CONNECTION_ERROR_EVENT_NAME} event`, () => { + _.each(["android", "iOS"], platform => { + it(`when ${platform}DebugService raises ${CONNECTION_ERROR_EVENT_NAME} event`, async () => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.platform = platform; + + const testInjector = getTestInjectorForTestConfiguration(testData); + const debugService = testInjector.resolve(DebugService); + let dataRaisedForConnectionError: any = null; + debugService.on(CONNECTION_ERROR_EVENT_NAME, (data: any) => { + dataRaisedForConnectionError = data; + }); + + const debugData = getDebugData(); + await assert.isFulfilled(debugService.debug(debugData, null)); + + const expectedErrorData = { deviceId: "deviceId", message: "my message", code: 2048 }; + const platformDebugService = testInjector.resolve(`${platform}DebugService`); + platformDebugService.emit(CONNECTION_ERROR_EVENT_NAME, expectedErrorData); + assert.deepEqual(dataRaisedForConnectionError, expectedErrorData); + }); + }); + }); + + describe("returns chrome url returned by platform specific debug service", () => { + _.each(["android", "iOS"], platform => { + it(`for ${platform} device`, async () => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.platform = platform; + + const testInjector = getTestInjectorForTestConfiguration(testData); + const debugService = testInjector.resolve(DebugService); + + const debugData = getDebugData(); + const url = await debugService.debug(debugData, null); + + assert.deepEqual(url, fakeChromeDebugUrl); + }); + }); + }); + }); +}); From ec539bbec892229dd4491fbe59d0a65387b9c00e Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Thu, 27 Apr 2017 09:10:30 +0300 Subject: [PATCH 016/212] Fix emulate command (#2743) During refactoring we've decided to pass the whole `$options` object from commands to services. However we've forgotten to change the emulate command. Pass correct argument which will fix the issue. --- lib/commands/emulate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/commands/emulate.ts b/lib/commands/emulate.ts index dfb25faf2c..51cb3068ef 100644 --- a/lib/commands/emulate.ts +++ b/lib/commands/emulate.ts @@ -27,7 +27,7 @@ export class EmulateCommandBase { keyStorePassword: this.$options.keyStorePassword, keyStorePath: this.$options.keyStorePath }; - return this.$platformService.emulatePlatform(args[0], appFilesUpdaterOptions, emulateOptions, this.$projectData, this.$options.provision); + return this.$platformService.emulatePlatform(args[0], appFilesUpdaterOptions, emulateOptions, this.$projectData, this.$options); } } From 9306f383f143d2acdc72c8c0dadacfd82e3dd36f Mon Sep 17 00:00:00 2001 From: Panayot Cankov Date: Thu, 27 Apr 2017 09:38:13 +0300 Subject: [PATCH 017/212] Fix some issues about the provision switch (#2705) --- lib/bootstrap.ts | 3 + lib/declarations.d.ts | 2 +- lib/definitions/project-changes.d.ts | 7 +- lib/definitions/project.d.ts | 6 + lib/node/pbxproj-dom-xcode.ts | 7 + lib/node/xcode.ts | 11 ++ lib/services/android-project-service.ts | 4 + lib/services/ios-project-service.ts | 67 +++++-- lib/services/ios-provision-service.ts | 2 +- lib/services/platform-service.ts | 2 +- lib/services/project-changes-service.ts | 23 +-- package.json | 4 +- test/ios-project-service.ts | 241 +++++++++++++++++++++++- test/mocha.opts | 1 + test/npm-support.ts | 3 +- test/platform-service.ts | 6 +- test/project-changes-service.ts | 33 +++- test/stubs.ts | 3 + test/tns-appstore-upload.ts | 23 ++- 19 files changed, 404 insertions(+), 44 deletions(-) create mode 100644 lib/node/pbxproj-dom-xcode.ts create mode 100644 lib/node/xcode.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index eb70d055e6..25ec914ba8 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -124,6 +124,9 @@ $injector.require("projectChangesService", "./services/project-changes-service") $injector.require("emulatorPlatformService", "./services/emulator-platform-service"); +$injector.require("pbxprojDomXcode", "./node/pbxproj-dom-xcode"); +$injector.require("xcode", "./node/xcode"); + $injector.require("staticConfig", "./config"); $injector.require("requireService", "./services/require-service"); diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index ccb24e46f6..5fb04bf394 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -281,7 +281,7 @@ interface IClean { } interface IProvision { - provision: any; + provision: string; } interface ITeamIdentifier { diff --git a/lib/definitions/project-changes.d.ts b/lib/definitions/project-changes.d.ts index cd5bae3cfa..ceefe2b371 100644 --- a/lib/definitions/project-changes.d.ts +++ b/lib/definitions/project-changes.d.ts @@ -15,8 +15,11 @@ interface IProjectChangesInfo { configChanged: boolean; packageChanged: boolean; nativeChanged: boolean; - hasChanges: boolean; - changesRequireBuild: boolean; + signingChanged: boolean; + + readonly hasChanges: boolean; + readonly changesRequireBuild: boolean; + readonly changesRequirePrepare: boolean; } interface IProjectChangesOptions extends IAppFilesUpdaterOptions, IProvision {} diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index fd44de57ef..50c2e58acc 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -258,6 +258,12 @@ interface IPlatformProjectService extends NodeJS.EventEmitter { * @returns {void} */ cleanProject(projectRoot: string, projectData: IProjectData): Promise + + /** + * Check the current state of the project, and validate against the options. + * If there are parts in the project that are inconsistent with the desired options, marks them in the changeset flags. + */ + checkForChanges(changeset: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): void; } interface IAndroidProjectPropertiesManager { diff --git a/lib/node/pbxproj-dom-xcode.ts b/lib/node/pbxproj-dom-xcode.ts new file mode 100644 index 0000000000..f7bb3a80d7 --- /dev/null +++ b/lib/node/pbxproj-dom-xcode.ts @@ -0,0 +1,7 @@ +import * as pbxprojDomXcodeModule from "pbxproj-dom/xcode"; + +declare global { + type IPbxprojDomXcode = typeof pbxprojDomXcodeModule; +} + +$injector.register("pbxprojDomXcode", pbxprojDomXcodeModule); diff --git a/lib/node/xcode.ts b/lib/node/xcode.ts new file mode 100644 index 0000000000..0e306d8a1a --- /dev/null +++ b/lib/node/xcode.ts @@ -0,0 +1,11 @@ +import * as xcode from "xcode"; + +declare global { + type IXcode = typeof xcode; + export namespace IXcode { + export type project = typeof xcode.project; + export interface Options extends xcode.Options {} // tslint:disable-line + } +} + +$injector.register("xcode", xcode); diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index dd0e3d9dbe..a45f50f2a1 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -438,6 +438,10 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject await adb.executeShellCommand(["rm", "-rf", deviceRootPath]); } + public checkForChanges(changesInfo: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): void { + // Nothing android specific to check yet. + } + private _canUseGradle: boolean; private canUseGradle(projectData: IProjectData, frameworkVersion?: string): boolean { if (!this._canUseGradle) { diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index ab138c670c..c39d6c013d 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -2,7 +2,6 @@ import * as path from "path"; import * as shell from "shelljs"; import * as os from "os"; import * as semver from "semver"; -import * as xcode from "xcode"; import * as constants from "../constants"; import * as helpers from "../common/helpers"; import { attachAwaitDetach } from "../common/helpers"; @@ -11,7 +10,6 @@ import { PlistSession } from "plist-merge-patch"; import { EOL } from "os"; import * as temp from "temp"; import * as plist from "plist"; -import { Xcode } from "pbxproj-dom/xcode"; import { IOSProvisionService } from "./ios-provision-service"; export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService { @@ -42,7 +40,9 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private $pluginVariablesService: IPluginVariablesService, private $xcprojService: IXcprojService, private $iOSProvisionService: IOSProvisionService, - private $sysInfo: ISysInfo) { + private $sysInfo: ISysInfo, + private $pbxprojDomXcode: IPbxprojDomXcode, + private $xcode: IXcode) { super($fs, $projectDataService); } @@ -379,10 +379,9 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ await this.createIpa(projectRoot, projectData, buildConfig); } - private async setupSigningFromProvision(projectRoot: string, projectData: IProjectData, provision?: any): Promise { + private async setupSigningFromProvision(projectRoot: string, projectData: IProjectData, provision?: string): Promise { if (provision) { - const pbxprojPath = path.join(projectRoot, projectData.projectName + ".xcodeproj", "project.pbxproj"); - const xcode = Xcode.open(pbxprojPath); + const xcode = this.$pbxprojDomXcode.Xcode.open(this.getPbxProjPath(projectData)); const signing = xcode.getSigning(projectData.projectName); let shouldUpdateXcode = false; @@ -399,8 +398,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } if (shouldUpdateXcode) { - // This is slow, it read through 260 mobileprovision files on my machine and does quite some checking whether provisioning profiles and devices will match. - // That's why we try to avoid id by checking in the Xcode first. const pickStart = Date.now(); const mobileprovision = await this.$iOSProvisionService.pick(provision, projectData.projectId); const pickEnd = Date.now(); @@ -428,11 +425,16 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } private async setupSigningForDevice(projectRoot: string, buildConfig: IiOSBuildConfig, projectData: IProjectData): Promise { - const pbxprojPath = path.join(projectRoot, projectData.projectName + ".xcodeproj", "project.pbxproj"); - const xcode = Xcode.open(pbxprojPath); + const xcode = this.$pbxprojDomXcode.Xcode.open(this.getPbxProjPath(projectData)); const signing = xcode.getSigning(projectData.projectName); - if ((this.readXCConfigProvisioningProfile(projectData) || this.readXCConfigProvisioningProfileForIPhoneOs(projectData)) && (!signing || signing.style !== "Manual")) { + const hasProvisioningProfileInXCConfig = + this.readXCConfigProvisioningProfileSpecifierForIPhoneOs(projectData) || + this.readXCConfigProvisioningProfileSpecifier(projectData) || + this.readXCConfigProvisioningProfileForIPhoneOs(projectData) || + this.readXCConfigProvisioningProfile(projectData); + + if (hasProvisioningProfileInXCConfig && (!signing || signing.style !== "Manual")) { xcode.setManualSigningStyle(projectData.projectName); xcode.save(); } else if (!buildConfig.provision && !(signing && signing.style === "Manual" && !buildConfig.teamId)) { @@ -490,7 +492,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ let frameworkBinaryPath = path.join(frameworkPath, frameworkName); let isDynamic = _.includes((await this.$childProcess.spawnFromEvent("otool", ["-Vh", frameworkBinaryPath], "close")).stdout, " DYLIB "); - let frameworkAddOptions: xcode.Options = { customFramework: true }; + let frameworkAddOptions: IXcode.Options = { customFramework: true }; if (isDynamic) { frameworkAddOptions["embed"] = true; @@ -623,7 +625,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f if (provision) { let projectRoot = path.join(projectData.platformsDir, "ios"); - await this.setupSigningFromProvision(projectRoot, provision); + await this.setupSigningFromProvision(projectRoot, projectData, provision); } let project = this.createPbxProj(projectData); @@ -787,7 +789,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } private createPbxProj(projectData: IProjectData): any { - let project = new xcode.project(this.getPbxProjPath(projectData)); + let project = new this.$xcode.project(this.getPbxProjPath(projectData)); project.parseSync(); return project; @@ -844,6 +846,35 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f return Promise.resolve(); } + public checkForChanges(changesInfo: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): void { + const provision = options.provision; + if (provision !== undefined) { + // Check if the native project's signing is set to the provided provision... + const pbxprojPath = this.getPbxProjPath(projectData); + + if (this.$fs.exists(pbxprojPath)) { + const xcode = this.$pbxprojDomXcode.Xcode.open(pbxprojPath); + const signing = xcode.getSigning(projectData.projectName); + if (signing && signing.style === "Manual") { + for (let name in signing.configurations) { + let config = signing.configurations[name]; + if (config.uuid !== provision && config.name !== provision) { + changesInfo.signingChanged = true; + break; + } + } + } else { + // Specifying provisioning profile requires "Manual" signing style. + // If the current signing style was not "Manual" it was probably "Automatic" or, + // it was not uniform for the debug and release build configurations. + changesInfo.signingChanged = true; + } + } else { + changesInfo.signingChanged = true; + } + } + } + private getAllLibsForPluginWithFileExtension(pluginData: IPluginData, fileExtension: string): string[] { let filterCallback = (fileName: string, pluginPlatformsFolderPath: string) => path.extname(fileName) === fileExtension; return this.getAllNativeLibrariesForPlugin(pluginData, IOSProjectService.IOS_PLATFORM_NAME, filterCallback); @@ -1186,6 +1217,14 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f return this.readXCConfig("PROVISIONING_PROFILE[sdk=iphoneos*]", projectData); } + private readXCConfigProvisioningProfileSpecifier(projectData: IProjectData): string { + return this.readXCConfig("PROVISIONING_PROFILE_SPECIFIER", projectData); + } + + private readXCConfigProvisioningProfileSpecifierForIPhoneOs(projectData: IProjectData): string { + return this.readXCConfig("PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]", projectData); + } + private async getDevelopmentTeam(projectData: IProjectData, teamId?: string): Promise { teamId = teamId || this.readTeamId(projectData); diff --git a/lib/services/ios-provision-service.ts b/lib/services/ios-provision-service.ts index 3ab4522a53..a8d2db581f 100644 --- a/lib/services/ios-provision-service.ts +++ b/lib/services/ios-provision-service.ts @@ -29,7 +29,7 @@ export class IOSProvisionService { function formatSupportedDeviceCount(prov: mobileprovision.provision.MobileProvision) { if (devices.length > 0 && prov.Type === "Development") { - return prov.ProvisionedDevices.reduce((count, device) => count + (devices.indexOf(device) >= 0 ? 1 : 0), 0) + "/" + devices.length + " targets"; + return prov.ProvisionedDevices.filter(device => devices.indexOf(device) >= 0).length + "/" + devices.length + " targets"; } else { return ""; } diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 633bfd55f9..4ed171abb8 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -290,7 +290,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - if (!changesInfo || changesInfo.appResourcesChanged) { + if (!changesInfo || changesInfo.changesRequirePrepare) { await this.copyAppFiles(platform, appFilesUpdaterOptions, projectData); this.copyAppResources(platform, projectData); await platformData.platformProjectService.prepareProject(projectData, platformSpecificData); diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index befe87b170..c28b4272b8 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -11,13 +11,15 @@ class ProjectChangesInfo implements IProjectChangesInfo { public configChanged: boolean; public packageChanged: boolean; public nativeChanged: boolean; + public signingChanged: boolean; public get hasChanges(): boolean { return this.packageChanged || this.appFilesChanged || this.appResourcesChanged || this.modulesChanged || - this.configChanged; + this.configChanged || + this.signingChanged; } public get changesRequireBuild(): boolean { @@ -25,6 +27,11 @@ class ProjectChangesInfo implements IProjectChangesInfo { this.appResourcesChanged || this.nativeChanged; } + + public get changesRequirePrepare(): boolean { + return this.appResourcesChanged || + this.signingChanged; + } } export class ProjectChangesService implements IProjectChangesService { @@ -75,16 +82,10 @@ export class ProjectChangesService implements IProjectChangesService { ]); } } - if (platform.toLowerCase() === this.$devicePlatformsConstants.iOS.toLowerCase()) { - const nextCommandProvisionUUID = projectChangesOptions.provision; - // We should consider reading here the provisioning profile UUID from the xcodeproj and xcconfig. - const prevProvisionUUID = this._prepareInfo.iOSProvisioningProfileUUID; - if (nextCommandProvisionUUID !== prevProvisionUUID) { - this._changesInfo.nativeChanged = true; - this._changesInfo.configChanged = true; - this._prepareInfo.iOSProvisioningProfileUUID = nextCommandProvisionUUID; - } - } + + let projectService = platformData.platformProjectService; + projectService.checkForChanges(this._changesInfo, projectChangesOptions, projectData); + if (projectChangesOptions.bundle !== this._prepareInfo.bundle || projectChangesOptions.release !== this._prepareInfo.release) { this._changesInfo.appFilesChanged = true; this._changesInfo.appResourcesChanged = true; diff --git a/package.json b/package.json index 9b21ed3f19..38a1455b1f 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "mute-stream": "0.0.5", "open": "0.0.5", "osenv": "0.1.3", - "pbxproj-dom": "1.0.9", + "pbxproj-dom": "1.0.11", "plist": "1.1.0", "plist-merge-patch": "0.0.9", "plistlib": "0.2.1", @@ -98,8 +98,8 @@ "grunt-tslint": "4.0.0", "istanbul": "0.4.5", "mocha": "3.1.2", - "mocha-typescript": "^1.0.4", "should": "7.0.2", + "source-map-support": "^0.4.14", "tslint": "4.3.1", "typescript": "2.1.4" }, diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index abf9288b69..7ab5b0252a 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -24,6 +24,7 @@ import { PluginVariablesHelper } from "../lib/common/plugin-variables-helper"; import { Utils } from "../lib/common/utils"; import { CocoaPodsService } from "../lib/services/cocoapods-service"; import { assert } from "chai"; +import { IOSProvisionService } from "../lib/services/ios-provision-service"; import temp = require("temp"); temp.track(); @@ -85,6 +86,14 @@ function createTestInjector(projectPath: string, projectName: string): IInjector testInjector.register("androidProcessService", {}); testInjector.register("processService", {}); testInjector.register("sysInfo", {}); + testInjector.register("pbxprojDomXcode", {}); + testInjector.register("xcode", { + project: class { + constructor() { /* */ } + parseSync() { /* */ } + pbxGroupByName() { /* */ } + } + }); return testInjector; } @@ -416,7 +425,7 @@ describe("Static libraries support", () => { return; } - let projectName = "projectDirectory"; + let projectName = "TNSApp"; let projectPath = temp.mkdirSync(projectName); let libraryName = "testLibrary1"; let headers = ["TestHeader1.h", "TestHeader2.h"]; @@ -486,3 +495,233 @@ describe("Relative paths", () => { assert.equal(result, path.join("..", "..", "sub", "path")); }); }); + +describe("iOS Project Service Signing", () => { + let testInjector: IInjector; + let projectName: string; + let projectDirName: string; + let projectPath: string; + let files: any; + let iOSProjectService: IPlatformProjectService; + let projectData: any; + let pbxproj: string; + let iOSProvisionService: IOSProvisionService; + let pbxprojDomXcode: IPbxprojDomXcode; + + beforeEach(() => { + files = {}; + projectName = "TNSApp" + Math.ceil(Math.random() * 1000); + projectDirName = projectName + "Dir"; + projectPath = temp.mkdirSync(projectDirName); + testInjector = createTestInjector(projectPath, projectDirName); + testInjector.register("fs", { + files: {}, + readJson(path: string): any { + if (this.exists(path)) { + return JSON.stringify(files[path]); + } else { + return null; + } + }, + exists(path: string): boolean { + return path in files; + } + }); + testInjector.register("pbxprojDomXcode", { Xcode: {} }); + pbxproj = path.join(projectPath, `platforms/ios/${projectDirName}.xcodeproj/project.pbxproj`); + iOSProjectService = testInjector.resolve("iOSProjectService"); + iOSProvisionService = testInjector.resolve("iOSProvisionService"); + pbxprojDomXcode = testInjector.resolve("pbxprojDomXcode"); + projectData = testInjector.resolve("projectData"); + iOSProvisionService.pick = async (uuidOrName: string, projId: string) => { + return ({ + "NativeScriptDev": { + Name: "NativeScriptDev", + CreationDate: null, + ExpirationDate: null, + TeamName: "Telerik AD", + TeamIdentifier: ["TKID101"], + ProvisionedDevices: [], + Entitlements: { + "application-identifier": "*", + "com.apple.developer.team-identifier": "ABC" + }, + UUID: "12345", + ProvisionsAllDevices: false, + ApplicationIdentifierPrefix: null, + DeveloperCertificates: null, + Type: "Development" + }, + "NativeScriptDist": { + Name: "NativeScriptDist", + CreationDate: null, + ExpirationDate: null, + TeamName: "Telerik AD", + TeamIdentifier: ["TKID202"], + ProvisionedDevices: [], + Entitlements: { + "application-identifier": "*", + "com.apple.developer.team-identifier": "ABC" + }, + UUID: "6789", + ProvisionsAllDevices: true, + ApplicationIdentifierPrefix: null, + DeveloperCertificates: null, + Type: "Distribution" + }, + "NativeScriptAdHoc": { + Name: "NativeScriptAdHoc", + CreationDate: null, + ExpirationDate: null, + TeamName: "Telerik AD", + TeamIdentifier: ["TKID303"], + ProvisionedDevices: [], + Entitlements: { + "application-identifier": "*", + "com.apple.developer.team-identifier": "ABC" + }, + UUID: "1010", + ProvisionsAllDevices: true, + ApplicationIdentifierPrefix: null, + DeveloperCertificates: null, + Type: "Distribution" + } + })[uuidOrName]; + }; + }); + + describe("Check for Changes", () => { + it("sets signingChanged if no Xcode project exists", () => { + let changes = {}; + iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev" }, projectData); + assert.isTrue(!!changes.signingChanged); + }); + it("sets signingChanged if the Xcode projects is configured with Automatic signing, but proivsion is specified", () => { + files[pbxproj] = ""; + pbxprojDomXcode.Xcode.open = function(path: string) { + assert.equal(path, pbxproj); + return { + getSigning(x: string) { + return { style: "Automatic" }; + } + }; + }; + let changes = {}; + iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev" }, projectData); + assert.isTrue(!!changes.signingChanged); + }); + it("sets signingChanged if the Xcode projects is configured with Manual signing, but the proivsion specified differs the selected in the pbxproj", () => { + files[pbxproj] = ""; + pbxprojDomXcode.Xcode.open = function(path: string) { + assert.equal(path, pbxproj); + return { + getSigning() { + return { style: "Manual", configurations: { + Debug: { name: "NativeScriptDev2" }, + Release: { name: "NativeScriptDev2" } + }}; + } + }; + }; + let changes = {}; + iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev" }, projectData); + assert.isTrue(!!changes.signingChanged); + }); + it("does not set signingChanged if the Xcode projects is configured with Manual signing and proivsion matches", () => { + files[pbxproj] = ""; + pbxprojDomXcode.Xcode.open = function(path: string) { + assert.equal(path, pbxproj); + return { + getSigning() { + return { style: "Manual", configurations: { + Debug: { name: "NativeScriptDev" }, + Release: { name: "NativeScriptDev" } + }}; + } + }; + }; + let changes = {}; + iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev" }, projectData); + assert.isFalse(!!changes.signingChanged); + }); + }); + + describe("specifying provision", () => { + describe("from Automatic to provision name", () => { + beforeEach(() => { + files[pbxproj] = ""; + pbxprojDomXcode.Xcode.open = function(path: string) { + return { + getSigning(x: string) { + return { style: "Automatic", teamID: "AutoTeam" }; + } + }; + }; + }); + it("fails with proper error if the provision can not be found", async () => { + try { + await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptDev2" }); + } catch (e) { + assert.isTrue(e.toString().indexOf("Failed to find mobile provision with UUID or Name: NativeScriptDev2") >= 0); + } + }); + it("succeeds if the provision name is provided for development cert", async() => { + let stack: any = []; + pbxprojDomXcode.Xcode.open = function(path: string) { + assert.equal(path, pbxproj); + return { + getSigning() { + return { style: "Automatic", teamID: "AutoTeam" }; + }, + save() { + stack.push("save()"); + }, + setManualSigningStyle(targetName: string, manualSigning: any) { + stack.push({ targetName, manualSigning }); + } + }; + }; + await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptDev" }); + assert.deepEqual(stack, [{targetName: projectDirName, manualSigning: { team: "TKID101", uuid: "12345", name: "NativeScriptDev", identity: "iPhone Developer" }}, "save()"]); + }); + it("succeds if the provision name is provided for distribution cert", async () => { + let stack: any = []; + pbxprojDomXcode.Xcode.open = function(path: string) { + assert.equal(path, pbxproj); + return { + getSigning() { + return { style: "Automatic", teamID: "AutoTeam" }; + }, + save() { + stack.push("save()"); + }, + setManualSigningStyle(targetName: string, manualSigning: any) { + stack.push({ targetName, manualSigning }); + } + }; + }; + await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptDist" }); + assert.deepEqual(stack, [{targetName: projectDirName, manualSigning: { team: "TKID202", uuid: "6789", name: "NativeScriptDist", identity: "iPhone Distribution" }}, "save()"]); + }); + it("succeds if the provision name is provided for adhoc cert", async () => { + let stack: any = []; + pbxprojDomXcode.Xcode.open = function(path: string) { + assert.equal(path, pbxproj); + return { + getSigning() { + return { style: "Automatic", teamID: "AutoTeam" }; + }, + save() { + stack.push("save()"); + }, + setManualSigningStyle(targetName: string, manualSigning: any) { + stack.push({ targetName, manualSigning }); + } + }; + }; + await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptAdHoc" }); + assert.deepEqual(stack, [{targetName: projectDirName, manualSigning: { team: "TKID303", uuid: "1010", name: "NativeScriptAdHoc", identity: "iPhone Distribution" }}, "save()"]); + }); + }); + }); +}); diff --git a/test/mocha.opts b/test/mocha.opts index 6541568281..5a8f874618 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,5 +1,6 @@ --recursive --reporter spec +--require source-map-support/register --require test/test-bootstrap.js --timeout 150000 test/ diff --git a/test/npm-support.ts b/test/npm-support.ts index e69d9a4d69..00fe04a5cc 100644 --- a/test/npm-support.ts +++ b/test/npm-support.ts @@ -159,7 +159,8 @@ async function setupProject(dependencies?: any): Promise { ensureConfigurationFileInAppResources: (): any => null, interpolateConfigurationFile: (): void => undefined, isPlatformPrepared: (projectRoot: string) => false, - validatePlugins: (projectData: IProjectData) => Promise.resolve() + validatePlugins: (projectData: IProjectData) => Promise.resolve(), + checkForChanges: () => { /* */ } } }; }; diff --git a/test/platform-service.ts b/test/platform-service.ts index 54df6f0626..a1d95ee1b3 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -418,7 +418,8 @@ describe('Platform Service Tests', () => { ensureConfigurationFileInAppResources: (): any => null, interpolateConfigurationFile: (): void => undefined, isPlatformPrepared: (projectRoot: string) => false, - prepareAppResources: (appResourcesDirectoryPath: string, projectData: IProjectData): void => undefined + prepareAppResources: (appResourcesDirectoryPath: string, projectData: IProjectData): void => undefined, + checkForChanges: () => { /* */ } } }; }; @@ -842,7 +843,8 @@ describe('Platform Service Tests', () => { processConfigurationFilesFromAppResources: () => Promise.resolve(), ensureConfigurationFileInAppResources: (): any => null, interpolateConfigurationFile: (): void => undefined, - isPlatformPrepared: (projectRoot: string) => false + isPlatformPrepared: (projectRoot: string) => false, + checkForChanges: () => { /* */ } }, frameworkPackageName: "tns-ios" }; diff --git a/test/project-changes-service.ts b/test/project-changes-service.ts index ccd3d9b177..5fab8b9598 100644 --- a/test/project-changes-service.ts +++ b/test/project-changes-service.ts @@ -59,9 +59,27 @@ describe("Project Changes Service Tests", () => { serviceTest.platformsData.getPlatformData = (platform: string) => { - return { - projectRoot: path.join(platformsDir, platform) - }; + if (platform.toLowerCase() === "ios") { + return { + projectRoot: path.join(platformsDir, platform), + get platformProjectService(): any { + return { + checkForChanges(changesInfo: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): void { + changesInfo.signingChanged = true; + } + }; + } + }; + } else { + return { + projectRoot: path.join(platformsDir, platform), + get platformProjectService(): any { + return { + checkForChanges(changesInfo: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): void { /* no android changes */ } + }; + } + }; + } }; }); @@ -113,4 +131,13 @@ describe("Project Changes Service Tests", () => { } }); }); + + describe("Accumulates Changes From Project Services", () => { + it("accumulates changes from the project service", () => { + let iOSChanges = serviceTest.projectChangesService.checkForChanges("ios", serviceTest.projectData, { bundle: false, release: false, provision: undefined }); + assert.isTrue(!!iOSChanges.signingChanged, "iOS signingChanged expected to be true"); + let androidChanges = serviceTest.projectChangesService.checkForChanges("android", serviceTest.projectData, { bundle: false, release: false, provision: undefined }); + assert.isFalse(!!androidChanges.signingChanged, "Android signingChanged expected to be false"); + }); + }); }); diff --git a/test/stubs.ts b/test/stubs.ts index 00f09ba5cd..3893ba69f1 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -357,6 +357,9 @@ export class PlatformProjectServiceStub extends EventEmitter implements IPlatfor async cleanProject(projectRoot: string, projectData: IProjectData): Promise { return Promise.resolve(); } + checkForChanges(changesInfo: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): void { + // Nothing yet. + } } export class ProjectDataService implements IProjectDataService { diff --git a/test/tns-appstore-upload.ts b/test/tns-appstore-upload.ts index 09c62c8457..bc930731ce 100644 --- a/test/tns-appstore-upload.ts +++ b/test/tns-appstore-upload.ts @@ -1,10 +1,8 @@ -import { suite, test/*, only*/ } from "mocha-typescript"; import { PublishIOS } from "../lib/commands/appstore-upload"; import { PrompterStub, LoggerStub, ProjectDataStub } from "./stubs"; import * as chai from "chai"; import * as yok from "../lib/common/yok"; -@suite("tns appstore") class AppStore { static itunesconnect = { user: "person@private.com", @@ -142,7 +140,6 @@ class AppStore { }; } - @test("without args, prompts for itunesconnect credentionals, prepares, archives and uploads") async noArgs() { this.expectItunesPrompt(); this.expectPreparePlatform(); @@ -155,7 +152,6 @@ class AppStore { this.assert(); } - @test("with command line itunesconnect credentionals, prepares, archives and uploads") async itunesconnectArgs() { this.expectPreparePlatform(); this.expectArchive(); @@ -167,7 +163,6 @@ class AppStore { this.assert(); } - @test("passes --team-id to xcodebuild exportArchive") async teamIdOption() { this.expectItunesPrompt(); this.expectPreparePlatform(); @@ -182,3 +177,21 @@ class AppStore { this.assert(); } } + +describe("tns appstore", () => { + it("without args, prompts for itunesconnect credentionals, prepares, archives and uploads", async () => { + const instance = new AppStore(); + instance.before(); + await instance.noArgs(); + }); + it("with command line itunesconnect credentionals, prepares, archives and uploads", async () => { + const instance = new AppStore(); + instance.before(); + await instance.itunesconnectArgs(); + }); + it("passes --team-id to xcodebuild exportArchive", async () => { + const instance = new AppStore(); + instance.before(); + await instance.teamIdOption(); + }); +}); From 9895ddeaabd08c15685d28c3454356ddbb0a3daa Mon Sep 17 00:00:00 2001 From: Panayot Cankov Date: Thu, 27 Apr 2017 09:31:25 +0300 Subject: [PATCH 018/212] Freeze mocha-typescript to 1.0.23 (#2746) From 3e8ddcf9a54a18ee5f56c0425309437e82f7a175 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Fri, 28 Apr 2017 10:53:10 +0300 Subject: [PATCH 019/212] Merge release in master (#2749) * Fix debug on iOS simulator with watch (#2721) During `tns debug ios`, in case you make changes, the application must be restarted and the debugger must attached again. However, in many cases we kill the old lldb process and immediately try to start the new one. The childProcess.kill operation finishes, but lldb process does not die immedietely. So in some occasions, the attach of new debugger fails. This leads to multiple errors - you cannot start this application on simulator anymore, you cannot exit CLI's process with `Ctrl + C`, etc. Fix this by attaching to "close" event of the processes and waiting for them to be really finish their execution. * Implement extensibility model for CLI (#2724) Implement extensibilty of CLI that allows anyone to add easily create packages that add new functionality to NativeScript CLI. The packages are installed in a specific directory, so they are persisted through CLI's updated. The directory where extensions are installed contains a package.json and each extension is npm package installed there. The extensions can be mainatined in two different ways: - navigate to the directory where extensions are installed and use `npm` for install/uninstall/update of packages. - use CLI's commands to update them: `tns extension install `, `tns extension uninstall `, `tns extension` Implement extensibilityService that executes all operations and expose it to public API. In {N} CLI the extensions are loaded in the entry point, before parsing command line arguments. This way extensions can add new commands. In Fusion, after CLI is required as a library, the `extensibilityService.loadExtensions` method should be called. It returns array of Promises - one for each installed extension. Add help for the new commands, but do not link the new commands in other commands help for the moment. Add unit tests for the new service. * Fix installation scripts for Mac (#2714) * Fix android sdk commands to use sdkmanager * Replace brew install with brew cask * Fix installation for haxm * Change brew formulae repository before installing android sdk * Fix setting the ENV variable missing cast * Do not start emulator when `--available-devices` is passed (#2736) In case there's no devices attached and no emulators running, trying ` devices --available-devices` will start emulator. In order to fix this, modify the `startEmulatorIfNecessary` method to skip the starting in case `skipInferPlatform` option is passed. This option indicates that we are not concerned of specific platform, so the method does not know which is the target platform for which to start emulator. Add unit test for this behavior. * Install karma peer dependencies on `test init` (#2693) * implicitly install karma-'s peer dependencies on test init command * add exception handling when installing packages already present in the project * log warning instead of throwing errors when a package's name couldn't be determined when installed using the node-package-manager service * Freeze mocha-typescript to 1.0.23 (#2746) * Freeze mocha-typescript to 1.0.23 (#2746) * Fix the six package detection in release (#2748) --- lib/node-package-manager.ts | 2 +- lib/services/doctor-service.ts | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/node-package-manager.ts b/lib/node-package-manager.ts index 179d624a8a..7b9ad7f05e 100644 --- a/lib/node-package-manager.ts +++ b/lib/node-package-manager.ts @@ -77,7 +77,7 @@ export class NodePackageManager implements INodePackageManager { throw err; } } - } + } @exported("npm") public async uninstall(packageName: string, config?: any, path?: string): Promise { diff --git a/lib/services/doctor-service.ts b/lib/services/doctor-service.ts index 4a8662ab8c..a96e206c95 100644 --- a/lib/services/doctor-service.ts +++ b/lib/services/doctor-service.ts @@ -226,17 +226,18 @@ class DoctorService implements IDoctorService { let hasInvalidPackages = false; if (this.$hostInfo.isDarwin) { try { - let queryForSixPackage = await this.$childProcess.exec(`pip freeze | grep '^six=' | wc -l`); - let sixPackagesFoundCount = parseInt(queryForSixPackage); - if (sixPackagesFoundCount === 0) { + await this.$childProcess.exec(`python -c "import six"`); + } catch (error) { + // error.code = 1 so the Python is present, but we don't have six. + if (error.code === 1) { hasInvalidPackages = true; this.$logger.warn("The Python 'six' package not found."); this.$logger.out("This package is required by the Debugger library (LLDB) for iOS. You can install it by running 'pip install six' from the terminal."); + } else { + this.$logger.warn("Couldn't retrieve installed python packages."); + this.$logger.out("We cannot verify your python installation is setup correctly. Please, make sure you have both 'python' and 'pip' installed."); + this.$logger.trace(`Error while validating Python packages. Error is: ${error.message}`); } - } catch (error) { - this.$logger.warn("Couldn't retrieve installed python packages."); - this.$logger.out("We cannot verify your python installation is setup correctly. Please, make sure you have both 'python' and 'pip' installed."); - this.$logger.trace(`Error while validating Python packages. Error is: ${error.message}`); } } return hasInvalidPackages; From 261429d8f396010ad00b2bac52646f7312854cfe Mon Sep 17 00:00:00 2001 From: yyosifov Date: Fri, 28 Apr 2017 11:07:01 +0300 Subject: [PATCH 020/212] Revert "Merge release in master (#2749)" (#2750) This reverts commit 3e8ddcf9a54a18ee5f56c0425309437e82f7a175. --- lib/node-package-manager.ts | 2 +- lib/services/doctor-service.ts | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/node-package-manager.ts b/lib/node-package-manager.ts index 7b9ad7f05e..179d624a8a 100644 --- a/lib/node-package-manager.ts +++ b/lib/node-package-manager.ts @@ -77,7 +77,7 @@ export class NodePackageManager implements INodePackageManager { throw err; } } - } + } @exported("npm") public async uninstall(packageName: string, config?: any, path?: string): Promise { diff --git a/lib/services/doctor-service.ts b/lib/services/doctor-service.ts index a96e206c95..4a8662ab8c 100644 --- a/lib/services/doctor-service.ts +++ b/lib/services/doctor-service.ts @@ -226,18 +226,17 @@ class DoctorService implements IDoctorService { let hasInvalidPackages = false; if (this.$hostInfo.isDarwin) { try { - await this.$childProcess.exec(`python -c "import six"`); - } catch (error) { - // error.code = 1 so the Python is present, but we don't have six. - if (error.code === 1) { + let queryForSixPackage = await this.$childProcess.exec(`pip freeze | grep '^six=' | wc -l`); + let sixPackagesFoundCount = parseInt(queryForSixPackage); + if (sixPackagesFoundCount === 0) { hasInvalidPackages = true; this.$logger.warn("The Python 'six' package not found."); this.$logger.out("This package is required by the Debugger library (LLDB) for iOS. You can install it by running 'pip install six' from the terminal."); - } else { - this.$logger.warn("Couldn't retrieve installed python packages."); - this.$logger.out("We cannot verify your python installation is setup correctly. Please, make sure you have both 'python' and 'pip' installed."); - this.$logger.trace(`Error while validating Python packages. Error is: ${error.message}`); } + } catch (error) { + this.$logger.warn("Couldn't retrieve installed python packages."); + this.$logger.out("We cannot verify your python installation is setup correctly. Please, make sure you have both 'python' and 'pip' installed."); + this.$logger.trace(`Error while validating Python packages. Error is: ${error.message}`); } } return hasInvalidPackages; From b1aeca322857466d998ffcb7304550f7c539429e Mon Sep 17 00:00:00 2001 From: yyosifov Date: Wed, 3 May 2017 18:10:34 +0300 Subject: [PATCH 021/212] Update the mobile-cli-lib to latest version (#2760) --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index 350b00f2ff..b1d69ec132 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 350b00f2fff61d2db04e8817dae5e0bc94dc6e12 +Subproject commit b1d69ec132cd7fdc74ea86c7dcc791be0829107d From dd8f866a80d96cfd72fa41cc90a899ba9abed5c1 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Thu, 11 May 2017 18:05:56 +0300 Subject: [PATCH 022/212] Fix getting production dependencies code (#2789) * Fix getting production dependencies code CLI has logic to find which are the "production" dependencies, i.e. which should be copied from `node_modules` to `platforms` dir. However, when the project has a lot of dependencies (more than 15), on some machines the code leads to error: "Maximum callstack size exceeded". On other machines the code tooks significant time to execute. After investigation, it turned out the recursion inside `node-modules-dependencies-builder` is incorrect and it adds each package many times to the result array. Fix the recursion and change the class NodeModulesDependenciesBuilder to be stateless - instead of using properties in `this` object when calculating the production dependencies, the methods will persist the results through the passed args. This way the whole class can be safely added to `$injector` and used whenever we need the production dependencies. Each time the calculation is started from the beginning, which is the requirement for long living process, where the project may change. Fix the type of the result, which leads to fix in several other services, where the result has been expected as `IDictionary`. However it's never been dictionary, it's always been an array. The code, that expected dictionary has been working because the `_.values` method of lodash (used all over the places where the incorrect type of data has been expected), returns the same array when the passed argument is array. Fix the tests that incorrectly expected dictionary with keys "0", "1", "2", etc. Remove the usage of Node.js's `fs` module from `NodeModulesDependenciesBuilder` - replace it with `$fs` which allows easir writing of tests. Require the `nodeModulesDependenciesBuilder` in bootstrap, so it can be correctly resolved by `$injector`. Add unit tests for `nodeModulesDependenciesBuilder`. * Use breadth-first search for getting production dependencies Replace the recursion with breadth-first search algorithm in order to make the code easier for understanding and debugging. Fix some incorrect code in the tests. * Add checks before adding new elements to queue of production dependencies Add check before adding new elements to queue of production dependencies - do not add elements, which are already added and do not read package.json of elements that are already added to "resolvedModules". --- lib/bootstrap.ts | 2 + lib/common | 2 +- lib/declarations.d.ts | 32 +- lib/definitions/platform.d.ts | 2 +- lib/definitions/project.d.ts | 2 +- lib/services/android-project-service.ts | 2 +- lib/services/livesync/livesync-service.ts | 7 +- .../node-modules/node-modules-builder.ts | 7 +- .../node-modules-dependencies-builder.ts | 155 +++++---- .../node-modules/node-modules-dest-copy.ts | 18 +- test/npm-support.ts | 4 +- test/plugin-prepare.ts | 31 +- test/plugins-service.ts | 15 +- test/stubs.ts | 4 + .../node-modules-dependencies-builder.ts | 319 ++++++++++++++++++ 15 files changed, 477 insertions(+), 125 deletions(-) create mode 100644 test/tools/node-modules/node-modules-dependencies-builder.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 25ec914ba8..d2d81c8b4c 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -135,3 +135,5 @@ $injector.requireCommand("extension|*list", "./commands/extensibility/list-exten $injector.requireCommand("extension|install", "./commands/extensibility/install-extension"); $injector.requireCommand("extension|uninstall", "./commands/extensibility/uninstall-extension"); $injector.requirePublic("extensibilityService", "./services/extensibility-service"); + +$injector.require("nodeModulesDependenciesBuilder", "./tools/node-modules/node-modules-dependencies-builder"); diff --git a/lib/common b/lib/common index 6e4c95ebbf..e66bb06aed 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 6e4c95ebbfbf118c463f1e09f39505d5ceb8e48c +Subproject commit e66bb06aedefe4f493edecc4223d4a9c4d97564c diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 5fb04bf394..bff60a8780 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -202,12 +202,36 @@ interface INpmInstallOptions { dependencyType?: string; } +/** + * Describes npm package installed in node_modules. + */ interface IDependencyData { + /** + * The name of the package. + */ name: string; - version: string; - nativescript: any; - dependencies?: IStringDictionary; - devDependencies?: IStringDictionary; + + /** + * The full path where the package is installed. + */ + directory: string; + + /** + * The depth inside node_modules dir, where the package is located. + * The /node_modules/ is level 0. + * Level 1 is /node_modules//node_modules, etc. + */ + depth: number; + + /** + * Describes the `nativescript` key in package.json of a dependency. + */ + nativescript?: any; + + /** + * Dependencies of the current module. + */ + dependencies?: string[]; } interface IStaticConfig extends Config.IStaticConfig { } diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index d93d820770..8c6a7c5d7d 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -275,7 +275,7 @@ interface INodeModulesBuilder { } interface INodeModulesDependenciesBuilder { - getProductionDependencies(projectPath: string): void; + getProductionDependencies(projectPath: string): IDependencyData[]; } interface IBuildInfo { diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 50c2e58acc..09e9293c6c 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -225,7 +225,7 @@ interface IPlatformProjectService extends NodeJS.EventEmitter { removePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise; afterPrepareAllPlugins(projectData: IProjectData): Promise; - beforePrepareAllPlugins(projectData: IProjectData, dependencies?: IDictionary): Promise; + beforePrepareAllPlugins(projectData: IProjectData, dependencies?: IDependencyData[]): Promise; /** * Gets the path wheren App_Resources should be copied. diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index a45f50f2a1..e2804185e2 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -400,7 +400,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject return; } - public async beforePrepareAllPlugins(projectData: IProjectData, dependencies?: IDictionary): Promise { + public async beforePrepareAllPlugins(projectData: IProjectData, dependencies?: IDependencyData[]): Promise { if (!this.$config.debugLivesync) { if (dependencies) { let platformDir = path.join(projectData.platformsDir, "android"); diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 3e11bf2708..5f57d8dcc8 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -1,7 +1,6 @@ import * as constants from "../../constants"; import * as helpers from "../../common/helpers"; import * as path from "path"; -import { NodeModulesDependenciesBuilder } from "../../tools/node-modules/node-modules-dependencies-builder"; let choki = require("chokidar"); @@ -17,7 +16,8 @@ class LiveSyncService implements ILiveSyncService { private $logger: ILogger, private $dispatcher: IFutureDispatcher, private $hooksService: IHooksService, - private $processService: IProcessService) { } + private $processService: IProcessService, + private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder) { } public get isInitialized(): boolean { // This function is used from https://github.com/NativeScript/nativescript-dev-typescript/blob/master/lib/before-prepare.js#L4 return this._isInitialized; @@ -94,8 +94,7 @@ class LiveSyncService implements ILiveSyncService { private partialSync(syncWorkingDirectory: string, onChangedActions: ((event: string, filePath: string, dispatcher: IFutureDispatcher) => Promise)[], projectData: IProjectData): void { let that = this; - let dependenciesBuilder = this.$injector.resolve(NodeModulesDependenciesBuilder, {}); - let productionDependencies = dependenciesBuilder.getProductionDependencies(projectData.projectDir); + let productionDependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); let pattern = ["app"]; if (this.$options.syncAllFiles) { diff --git a/lib/tools/node-modules/node-modules-builder.ts b/lib/tools/node-modules/node-modules-builder.ts index 52bea13107..ac1556151d 100644 --- a/lib/tools/node-modules/node-modules-builder.ts +++ b/lib/tools/node-modules/node-modules-builder.ts @@ -1,11 +1,11 @@ import * as shelljs from "shelljs"; import { TnsModulesCopy, NpmPluginPrepare } from "./node-modules-dest-copy"; -import { NodeModulesDependenciesBuilder } from "./node-modules-dependencies-builder"; export class NodeModulesBuilder implements INodeModulesBuilder { constructor(private $fs: IFileSystem, private $injector: IInjector, - private $options: IOptions + private $options: IOptions, + private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder ) { } public async prepareNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData): Promise { @@ -14,8 +14,7 @@ export class NodeModulesBuilder implements INodeModulesBuilder { lastModifiedTime = null; } - let dependenciesBuilder = this.$injector.resolve(NodeModulesDependenciesBuilder, {}); - let productionDependencies = dependenciesBuilder.getProductionDependencies(projectData.projectDir); + let productionDependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); if (!this.$options.bundle) { const tnsModulesCopy = this.$injector.resolve(TnsModulesCopy, { diff --git a/lib/tools/node-modules/node-modules-dependencies-builder.ts b/lib/tools/node-modules/node-modules-dependencies-builder.ts index eb946c30ce..396eb7dc34 100644 --- a/lib/tools/node-modules/node-modules-dependencies-builder.ts +++ b/lib/tools/node-modules/node-modules-dependencies-builder.ts @@ -1,111 +1,110 @@ import * as path from "path"; -import * as fs from "fs"; +import { NODE_MODULES_FOLDER_NAME, PACKAGE_JSON_FILE_NAME } from "../../constants"; -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); - - _.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; - } +interface IDependencyDescription { + parentDir: string; + name: string; + depth: number; +} - 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); +export class NodeModulesDependenciesBuilder implements INodeModulesDependenciesBuilder { + public constructor(private $fs: IFileSystem) { } + + public getProductionDependencies(projectPath: string): IDependencyData[] { + const rootNodeModulesPath = path.join(projectPath, NODE_MODULES_FOLDER_NAME); + const projectPackageJsonPath = path.join(projectPath, PACKAGE_JSON_FILE_NAME); + const packageJsonContent = this.$fs.readJson(projectPackageJsonPath); + const dependencies = packageJsonContent && packageJsonContent.dependencies; + + let resolvedDependencies: IDependencyData[] = []; + + let queue: IDependencyDescription[] = _.keys(dependencies) + .map(dependencyName => ({ + parentDir: projectPath, + name: dependencyName, + depth: 0 + })); + + while (queue.length) { + const currentModule = queue.shift(); + const resolvedDependency = this.findModule(rootNodeModulesPath, currentModule.parentDir, currentModule.name, currentModule.depth, resolvedDependencies); + + if (resolvedDependency && !_.some(resolvedDependencies, r => r.directory === resolvedDependency.directory)) { + _.each(resolvedDependency.dependencies, d => { + const dependency: IDependencyDescription = { name: d, parentDir: resolvedDependency.directory, depth: resolvedDependency.depth + 1 }; + + const shouldAdd = !_.some(queue, element => + element.name === dependency.name && + element.parentDir === dependency.parentDir && + element.depth === dependency.depth); + + if (shouldAdd) { + queue.push(dependency); + } + }); + + resolvedDependencies.push(resolvedDependency); + } + } - this.findModule(modulePath, alternativeModulePath, name, depth); + return resolvedDependencies; } - private findModule(modulePath: string, alternativeModulePath: string, name: string, depth: number) { - let exists = this.moduleExists(modulePath); + private findModule(rootNodeModulesPath: string, parentModulePath: string, name: string, depth: number, resolvedDependencies: IDependencyData[]): IDependencyData { + let modulePath = path.join(parentModulePath, NODE_MODULES_FOLDER_NAME, name); // node_modules/parent/node_modules/ + const rootModulesPath = path.join(rootNodeModulesPath, name); + let depthInNodeModules = depth; - if (exists) { - if (this.seen[modulePath]) { - return; + if (!this.moduleExists(modulePath)) { + modulePath = rootModulesPath; // /node_modules/ + if (!this.moduleExists(modulePath)) { + return null; } - let dependency = this.addDependency(name, modulePath, depth + 1); - this.readModuleDependencies(modulePath, depth + 1, dependency); - } else { - modulePath = alternativeModulePath; // /node_modules/ - exists = this.moduleExists(modulePath); + depthInNodeModules = 0; + } - if (!exists) { - return; - } + if (_.some(resolvedDependencies, r => r.name === name && r.directory === modulePath)) { + return null; - if (this.seen[modulePath]) { - return; - } - - let dependency = this.addDependency(name, modulePath, 0); - this.readModuleDependencies(modulePath, 0, dependency); } - this.seen[modulePath] = true; + return this.getDependencyData(name, modulePath, depthInNodeModules); } - private readModuleDependencies(modulePath: string, depth: number, currentModule: any): void { - let packageJsonPath = path.join(modulePath, 'package.json'); - let packageJsonExists = fs.lstatSync(packageJsonPath).isFile(); + private getDependencyData(name: string, directory: string, depth: number): IDependencyData { + const dependency: IDependencyData = { + name, + directory, + depth + }; + + const packageJsonPath = path.join(directory, PACKAGE_JSON_FILE_NAME); + const packageJsonExists = this.$fs.getLsStats(packageJsonPath).isFile(); if (packageJsonExists) { let packageJsonContents = this.$fs.readJson(packageJsonPath); if (!!packageJsonContents.nativescript) { // add `nativescript` property, necessary for resolving plugins - currentModule.nativescript = packageJsonContents.nativescript; + dependency.nativescript = packageJsonContents.nativescript; } - _.keys(packageJsonContents.dependencies).forEach((dependencyName) => { - this.traverseDependency(dependencyName, modulePath, depth); - }); + dependency.dependencies = _.keys(packageJsonContents.dependencies); + return dependency; } - } - private addDependency(name: string, directory: string, depth: number): any { - let dependency: any = { - name, - directory, - depth - }; - - this.resolvedDependencies.push(dependency); - - return dependency; + return null; } private moduleExists(modulePath: string): boolean { try { - let exists = fs.lstatSync(modulePath); - if (exists.isSymbolicLink()) { - exists = fs.lstatSync(fs.realpathSync(modulePath)); + let modulePathLsStat = this.$fs.getLsStats(modulePath); + if (modulePathLsStat.isSymbolicLink()) { + modulePathLsStat = this.$fs.getLsStats(this.$fs.realpath(modulePath)); } - return exists.isDirectory(); + + return modulePathLsStat.isDirectory(); } catch (e) { return false; } diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts index de313c1f66..c5cb4f4864 100644 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ b/lib/tools/node-modules/node-modules-dest-copy.ts @@ -15,7 +15,7 @@ export class TnsModulesCopy { ) { } - public copyModules(dependencies: any[], platform: string): void { + public copyModules(dependencies: IDependencyData[], platform: string): void { for (let entry in dependencies) { let dependency = dependencies[entry]; @@ -34,7 +34,7 @@ export class TnsModulesCopy { } } - private copyDependencyDir(dependency: any): void { + private copyDependencyDir(dependency: IDependencyData): void { if (dependency.depth === 0) { let isScoped = dependency.name.indexOf("@") === 0; let targetDir = this.outputRoot; @@ -61,18 +61,18 @@ export class NpmPluginPrepare { ) { } - protected async beforePrepare(dependencies: IDictionary, platform: string, projectData: IProjectData): Promise { + protected async beforePrepare(dependencies: IDependencyData[], platform: string, projectData: IProjectData): Promise { await this.$platformsData.getPlatformData(platform, projectData).platformProjectService.beforePrepareAllPlugins(projectData, dependencies); } - protected async afterPrepare(dependencies: IDictionary, platform: string, projectData: IProjectData): Promise { + protected async afterPrepare(dependencies: IDependencyData[], platform: string, projectData: IProjectData): Promise { await this.$platformsData.getPlatformData(platform, projectData).platformProjectService.afterPrepareAllPlugins(projectData); this.writePreparedDependencyInfo(dependencies, platform, projectData); } - private writePreparedDependencyInfo(dependencies: IDictionary, platform: string, projectData: IProjectData): void { + private writePreparedDependencyInfo(dependencies: IDependencyData[], platform: string, projectData: IProjectData): void { let prepareData: IDictionary = {}; - _.values(dependencies).forEach(d => { + _.each(dependencies, d => { prepareData[d.name] = true; }); this.$fs.createDirectory(this.preparedPlatformsDir(platform, projectData)); @@ -101,10 +101,10 @@ export class NpmPluginPrepare { return this.$fs.readJson(this.preparedPlatformsFile(platform, projectData), "utf8"); } - private allPrepared(dependencies: IDictionary, platform: string, projectData: IProjectData): boolean { + private allPrepared(dependencies: IDependencyData[], platform: string, projectData: IProjectData): boolean { let result = true; const previouslyPrepared = this.getPreviouslyPreparedDependencies(platform, projectData); - _.values(dependencies).forEach(d => { + _.each(dependencies, d => { if (!previouslyPrepared[d.name]) { result = false; } @@ -112,7 +112,7 @@ export class NpmPluginPrepare { return result; } - public async preparePlugins(dependencies: IDictionary, platform: string, projectData: IProjectData): Promise { + public async preparePlugins(dependencies: IDependencyData[], platform: string, projectData: IProjectData): Promise { if (_.isEmpty(dependencies) || this.allPrepared(dependencies, platform, projectData)) { return; } diff --git a/test/npm-support.ts b/test/npm-support.ts index 00fe04a5cc..2c030101f8 100644 --- a/test/npm-support.ts +++ b/test/npm-support.ts @@ -27,6 +27,7 @@ import { XmlValidator } from "../lib/xml-validator"; import { LockFile } from "../lib/lockfile"; import ProjectChangesLib = require("../lib/services/project-changes-service"); import { Messages } from "../lib/common/messages/messages"; +import { NodeModulesDependenciesBuilder } from "../lib/tools/node-modules/node-modules-dependencies-builder"; import path = require("path"); import temp = require("temp"); @@ -81,9 +82,10 @@ function createTestInjector(): IInjector { testInjector.register("projectChangesService", ProjectChangesLib.ProjectChangesService); testInjector.register("emulatorPlatformService", stubs.EmulatorPlatformService); testInjector.register("analyticsService", { - track: async () => undefined + track: async (): Promise => undefined }); testInjector.register("messages", Messages); + testInjector.register("nodeModulesDependenciesBuilder", NodeModulesDependenciesBuilder); return testInjector; } diff --git a/test/plugin-prepare.ts b/test/plugin-prepare.ts index 73bcde4131..45f3664147 100644 --- a/test/plugin-prepare.ts +++ b/test/plugin-prepare.ts @@ -14,13 +14,13 @@ class TestNpmPluginPrepare extends NpmPluginPrepare { return this.previouslyPrepared; } - protected async beforePrepare(dependencies: IDictionary, platform: string): Promise { - _.values(dependencies).forEach(d => { + protected async beforePrepare(dependencies: IDependencyData[], platform: string): Promise { + _.each(dependencies, d => { this.preparedDependencies[d.name] = true; }); } - protected async afterPrepare(dependencies: IDictionary, platform: string): Promise { + protected async afterPrepare(dependencies: IDependencyData[], platform: string): Promise { // DO NOTHING } } @@ -28,37 +28,40 @@ class TestNpmPluginPrepare extends NpmPluginPrepare { describe("Plugin preparation", () => { it("skips prepare if no plugins", async () => { const pluginPrepare = new TestNpmPluginPrepare({}); - await pluginPrepare.preparePlugins({}, "android", null); + await pluginPrepare.preparePlugins([], "android", null); assert.deepEqual({}, pluginPrepare.preparedDependencies); }); it("skips prepare if every plugin prepared", async () => { const pluginPrepare = new TestNpmPluginPrepare({ "tns-core-modules-widgets": true }); - const testDependencies: IDictionary = { - "0": { + const testDependencies: IDependencyData[] = [ + { name: "tns-core-modules-widgets", - version: "1.0.0", + depth: 0, + directory: "some dir", nativescript: null, } - }; + ]; await pluginPrepare.preparePlugins(testDependencies, "android", null); assert.deepEqual({}, pluginPrepare.preparedDependencies); }); it("saves prepared plugins after preparation", async () => { const pluginPrepare = new TestNpmPluginPrepare({ "tns-core-modules-widgets": true }); - const testDependencies: IDictionary = { - "0": { + const testDependencies: IDependencyData[] = [ + { name: "tns-core-modules-widgets", - version: "1.0.0", + depth: 0, + directory: "some dir", nativescript: null, }, - "1": { + { name: "nativescript-calendar", - version: "1.0.0", + depth: 0, + directory: "some dir", nativescript: null, } - }; + ]; await pluginPrepare.preparePlugins(testDependencies, "android", null); const prepareData = { "tns-core-modules-widgets": true, "nativescript-calendar": true }; assert.deepEqual(prepareData, pluginPrepare.preparedDependencies); diff --git a/test/plugins-service.ts b/test/plugins-service.ts index cc440a608d..2ad19b077a 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -477,14 +477,15 @@ describe("Plugins service", () => { let pluginName = "mySamplePlugin"; let projectFolder = createProjectFile(testInjector); let pluginFolderPath = path.join(projectFolder, pluginName); - let pluginJsonData = { - "name": pluginName, - "version": "0.0.1", - "nativescript": { - "platforms": { - "android": "0.10.0" + let pluginJsonData: IDependencyData = { + name: pluginName, + nativescript: { + platforms: { + android: "0.10.0" } - } + }, + depth: 0, + directory: "some dir" }; let fs = testInjector.resolve("fs"); fs.writeJson(path.join(pluginFolderPath, "package.json"), pluginJsonData); diff --git a/test/stubs.ts b/test/stubs.ts index 3893ba69f1..eabbb21d7c 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -171,6 +171,10 @@ export class FileSystemStub implements IFileSystem { deleteEmptyParents(directory: string): void { } utimes(path: string, atime: Date, mtime: Date): void { } + + realpath(filePath: string): string { + return null; + } } export class ErrorsStub implements IErrors { diff --git a/test/tools/node-modules/node-modules-dependencies-builder.ts b/test/tools/node-modules/node-modules-dependencies-builder.ts new file mode 100644 index 0000000000..fb25057a12 --- /dev/null +++ b/test/tools/node-modules/node-modules-dependencies-builder.ts @@ -0,0 +1,319 @@ +import { Yok } from "../../../lib/common/yok"; +import { assert } from "chai"; +import { NodeModulesDependenciesBuilder } from "../../../lib/tools/node-modules/node-modules-dependencies-builder"; +import * as path from "path"; +import * as constants from "../../../lib/constants"; + +interface IDependencyInfo { + name: string; + version: string; + depth: number; + dependencies?: IDependencyInfo[]; + nativescript?: any; +}; + +// TODO: Add integration tests. +// The tests assumes npm 3 or later is used, so all dependencies (and their dependencies) will be installed at the root node_modules +describe("nodeModulesDependenciesBuilder", () => { + const pathToProject = "some path"; + const getTestInjector = (): IInjector => { + const testInjector = new Yok(); + testInjector.register("fs", { + readJson: (pathToFile: string): any => undefined + }); + + return testInjector; + }; + + describe("getProductionDependencies", () => { + describe("returns empty array", () => { + const validateResultIsEmpty = async (resultOfReadJson: any) => { + const testInjector = getTestInjector(); + const fs = testInjector.resolve("fs"); + fs.readJson = (filename: string, encoding?: string): any => { + return resultOfReadJson; + }; + + const nodeModulesDependenciesBuilder = testInjector.resolve(NodeModulesDependenciesBuilder); + const result = await nodeModulesDependenciesBuilder.getProductionDependencies(pathToProject); + + assert.deepEqual(result, []); + }; + + it("when package.json does not have any data", async () => { + await validateResultIsEmpty(null); + }); + + it("when package.json does not have dependencies section", async () => { + await validateResultIsEmpty({ name: "some name", devDependencies: { a: "1.0.0" } }); + }); + }); + + describe("returns correct dependencies", () => { + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Helper functions for easier writing of consecutive tests in the suite. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + const getPathToDependencyInNodeModules = (dependencyName: string, parentDir?: string): string => { + return path.join(parentDir || pathToProject, constants.NODE_MODULES_FOLDER_NAME, dependencyName); + }; + + const getNodeModuleInfoForExpecteDependency = (name: string, depth: number, nativescript?: any, dependencies?: string[]): IDependencyData => { + let result: IDependencyData = { + name: path.basename(name), + directory: getPathToDependencyInNodeModules(name), + depth, + dependencies: dependencies || [] + }; + + if (nativescript) { + result.nativescript = nativescript; + } + + return result; + }; + + const getPathToPackageJsonOfDependency = (dependencyName: string, parentDir?: string): string => { + return path.join(getPathToDependencyInNodeModules(dependencyName, parentDir), constants.PACKAGE_JSON_FILE_NAME); + }; + + const getDependenciesObjectFromDependencyInfo = (depInfos: IDependencyInfo[], nativescript: any): { dependencies: any, nativescript?: any } => { + const dependencies: any = {}; + _.each(depInfos, innerDependency => { + dependencies[innerDependency.name] = innerDependency.version; + }); + + let result: any = { + dependencies + }; + + if (nativescript) { + result.nativescript = nativescript; + } + + return result; + }; + + const getDependenciesObject = (filename: string, deps: IDependencyInfo[], parentDir: string): { dependencies: any } => { + let result: { dependencies: any } = null; + for (let dependencyInfo of deps) { + const pathToPackageJson = getPathToPackageJsonOfDependency(dependencyInfo.name, parentDir); + if (filename === pathToPackageJson) { + return getDependenciesObjectFromDependencyInfo(dependencyInfo.dependencies, dependencyInfo.nativescript); + } + + if (dependencyInfo.dependencies) { + result = getDependenciesObject(filename, dependencyInfo.dependencies, path.join(parentDir, constants.NODE_MODULES_FOLDER_NAME, dependencyInfo.name)); + if (result) { + break; + } + } + } + + return result; + }; + + const generateTest = (rootDeps: IDependencyInfo[]): INodeModulesDependenciesBuilder => { + const testInjector = getTestInjector(); + const nodeModulesDependenciesBuilder = testInjector.resolve(NodeModulesDependenciesBuilder); + const fs = testInjector.resolve("fs"); + + fs.readJson = (filename: string, encoding?: string): any => { + const innerDependency = getDependenciesObject(filename, rootDeps, pathToProject); + return innerDependency || getDependenciesObjectFromDependencyInfo(rootDeps, null); + }; + + const isDirectory = (searchedPath: string, currentRootPath: string, deps: IDependencyInfo[], currentDepthLevel: number): boolean => { + let result = false; + + for (let dependencyInfo of deps) { + const pathToDependency = path.join(currentRootPath, constants.NODE_MODULES_FOLDER_NAME, dependencyInfo.name); + + if (pathToDependency === searchedPath && currentDepthLevel === dependencyInfo.depth) { + return true; + } + + if (dependencyInfo.dependencies) { + result = isDirectory(searchedPath, pathToDependency, dependencyInfo.dependencies, currentDepthLevel + 1); + if (result) { + break; + } + } + } + + return result; + }; + + const isPackageJsonOfDependency = (searchedPath: string, currentRootPath: string, deps: IDependencyInfo[], currentDepthLevel: number): boolean => { + let result = false; + for (let dependencyInfo of deps) { + const pathToDependency = path.join(currentRootPath, constants.NODE_MODULES_FOLDER_NAME, dependencyInfo.name); + + const pathToPackageJson = path.join(pathToDependency, constants.PACKAGE_JSON_FILE_NAME); + + if (pathToPackageJson === searchedPath && currentDepthLevel === dependencyInfo.depth) { + return true; + } + + if (dependencyInfo.dependencies) { + result = isPackageJsonOfDependency(searchedPath, pathToDependency, dependencyInfo.dependencies, currentDepthLevel + 1); + if (result) { + break; + } + } + } + + return result; + }; + + fs.getLsStats = (pathToStat: string): any => { + return { + isDirectory: (): boolean => isDirectory(pathToStat, pathToProject, rootDeps, 0), + isSymbolicLink: (): boolean => false, + isFile: (): boolean => isPackageJsonOfDependency(pathToStat, pathToProject, rootDeps, 0) + }; + }; + + return nodeModulesDependenciesBuilder; + }; + + const generateDependency = (name: string, version: string, depth: number, dependencies: IDependencyInfo[], nativescript?: any): IDependencyInfo => { + return { + name, + version, + depth, + dependencies, + nativescript + }; + }; + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * END of helper functions for easier writing of consecutive tests in the suite. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + const firstPackage = "firstPackage"; + const secondPackage = "secondPackage"; + const thirdPackage = "thirdPackage"; + + it("when all dependencies are installed at the root level of the project", async () => { + // The test validates the following dependency tree, when npm 3+ is used. + // + // ├── firstPackage@1.0.0 + // ├── secondPackage@1.1.0 + // └── thirdPackage@1.2.0 + + const rootDeps: IDependencyInfo[] = [ + generateDependency(firstPackage, "1.0.0", 0, null), + generateDependency(secondPackage, "1.1.0", 0, null), + generateDependency(thirdPackage, "1.2.0", 0, null) + ]; + + const nodeModulesDependenciesBuilder = generateTest(rootDeps); + const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies(pathToProject); + + const expectedResult: IDependencyData[] = [ + getNodeModuleInfoForExpecteDependency(firstPackage, 0), + getNodeModuleInfoForExpecteDependency(secondPackage, 0), + getNodeModuleInfoForExpecteDependency(thirdPackage, 0) + ]; + + assert.deepEqual(actualResult, expectedResult); + }); + + it("when the project has a dependency to a package and one of the other packages has dependency to other version of this package", async () => { + // The test validates the following dependency tree, when npm 3+ is used. + // + // ├─┬ firstPackage@1.0.0 + // │ └── secondPackage@1.2.0 + // └── secondPackage@1.1.0 + + const rootDeps: IDependencyInfo[] = [ + generateDependency(firstPackage, "1.0.0", 0, [generateDependency(secondPackage, "1.2.0", 1, null)]), + generateDependency(secondPackage, "1.1.0", 0, null) + ]; + + const expectedResult: IDependencyData[] = [ + getNodeModuleInfoForExpecteDependency(firstPackage, 0, null, [secondPackage]), + getNodeModuleInfoForExpecteDependency(secondPackage, 0), + getNodeModuleInfoForExpecteDependency(path.join(firstPackage, constants.NODE_MODULES_FOLDER_NAME, secondPackage), 1) + ]; + + const nodeModulesDependenciesBuilder = generateTest(rootDeps); + const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies(pathToProject); + assert.deepEqual(actualResult, expectedResult); + }); + + it("when several package depend on different versions of other packages", async () => { + // The test validates the following dependency tree, when npm 3+ is used. + // + // ├─┬ firstPackage@1.0.0 + // │ ├─┬ secondPackage@1.1.0 + // │ │ └── thirdPackage@1.2.0 + // │ └── thirdPackage@1.1.0 + // ├── secondPackage@1.0.0 + // └── thirdPackage@1.0.0 + + const rootDeps: IDependencyInfo[] = [ + generateDependency(firstPackage, "1.0.0", 0, [ + generateDependency(secondPackage, "1.1.0", 1, [ + generateDependency(thirdPackage, "1.2.0", 2, null) + ]), + generateDependency(thirdPackage, "1.1.0", 1, null) + ]), + generateDependency(secondPackage, "1.0.0", 0, null), + generateDependency(thirdPackage, "1.0.0", 0, null) + ]; + + const pathToSecondPackageInsideFirstPackage = path.join(firstPackage, constants.NODE_MODULES_FOLDER_NAME, secondPackage); + const expectedResult: IDependencyData[] = [ + getNodeModuleInfoForExpecteDependency(firstPackage, 0, null, [secondPackage, thirdPackage]), + getNodeModuleInfoForExpecteDependency(secondPackage, 0), + getNodeModuleInfoForExpecteDependency(thirdPackage, 0), + getNodeModuleInfoForExpecteDependency(pathToSecondPackageInsideFirstPackage, 1, null, [thirdPackage]), + getNodeModuleInfoForExpecteDependency(path.join(firstPackage, constants.NODE_MODULES_FOLDER_NAME, thirdPackage), 1), + getNodeModuleInfoForExpecteDependency(path.join(pathToSecondPackageInsideFirstPackage, constants.NODE_MODULES_FOLDER_NAME, thirdPackage), 2), + ]; + + const nodeModulesDependenciesBuilder = generateTest(rootDeps); + const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies(pathToProject); + assert.deepEqual(actualResult, expectedResult); + }); + + it("when the installed packages have nativescript data in their package.json", async () => { + // The test validates the following dependency tree, when npm 3+ is used. + // + // ├── firstPackage@1.0.0 + // ├── secondPackage@1.1.0 + // └── thirdPackage@1.2.0 + + const getNativeScriptDataForPlugin = (pluginName: string): any => { + return { + platforms: { + "tns-android": "x.x.x", + "tns-ios": "x.x.x", + }, + + customPropertyUsedForThisTestOnly: pluginName + }; + }; + + const rootDeps: IDependencyInfo[] = [ + generateDependency(firstPackage, "1.0.0", 0, null, getNativeScriptDataForPlugin(firstPackage)), + generateDependency(secondPackage, "1.1.0", 0, null, getNativeScriptDataForPlugin(secondPackage)), + generateDependency(thirdPackage, "1.2.0", 0, null, getNativeScriptDataForPlugin(thirdPackage)) + ]; + + const nodeModulesDependenciesBuilder = generateTest(rootDeps); + const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies(pathToProject); + + const expectedResult: IDependencyData[] = [ + getNodeModuleInfoForExpecteDependency(firstPackage, 0, getNativeScriptDataForPlugin(firstPackage)), + getNodeModuleInfoForExpecteDependency(secondPackage, 0, getNativeScriptDataForPlugin(secondPackage)), + getNodeModuleInfoForExpecteDependency(thirdPackage, 0, getNativeScriptDataForPlugin(thirdPackage)) + ]; + + assert.deepEqual(actualResult, expectedResult); + }); + }); + }); +}); From 0a36bb2841006fc9ae2e0f7296fd912f1e9b3f40 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Thu, 11 May 2017 20:11:49 +0300 Subject: [PATCH 023/212] Use qr-image for qr code generation (#2803) --- lib/common | 2 +- package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/common b/lib/common index e66bb06aed..5f1268ab1f 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit e66bb06aedefe4f493edecc4223d4a9c4d97564c +Subproject commit 5f1268ab1f1d1d1ffceb3610cf3ee1742ef67b9f diff --git a/package.json b/package.json index fb6610091f..b28106c687 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "plistlib": "0.2.1", "progress-stream": "1.1.1", "properties-parser": "0.2.3", - "qrcode-generator": "1.0.0", + "qr-image": "3.2.0", "request": "2.81.0", "semver": "5.3.0", "shelljs": "0.7.6", @@ -84,6 +84,7 @@ "@types/chai-as-promised": "0.0.29", "@types/lodash": "4.14.50", "@types/node": "6.0.61", + "@types/qr-image": "3.2.0", "@types/request": "0.0.42", "@types/semver": "^5.3.31", "@types/source-map": "0.5.0", From 5311af86a729b9cd6b53953b21a953d293f0b326 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 12 May 2017 11:00:10 +0300 Subject: [PATCH 024/212] Update to latest common lib --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index 5f1268ab1f..2388145d3c 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 5f1268ab1f1d1d1ffceb3610cf3ee1742ef67b9f +Subproject commit 2388145d3cca720ad2ab9cff857effa492c4d341 From 29c149bf928935399a88c42033bb77e37617a6ac Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Fri, 12 May 2017 13:28:28 +0300 Subject: [PATCH 025/212] Merge release in master (#2808) * Update Travis CI Scripts (#2799) Set `rc` tag to the publishConfig section for the release branch. * Remove .html from the files included in the fast-livesync to force full reload (#2802) * Update Travis CI Scripts (#2804) * Fix UserId passing to Analytics Service (#2806) * Release notes for 3.0.1 (#2807) * Update to latest common lib --- .travis.yml | 9 ++++++++- .travis/add-publishConfig.js | 24 ++++++++++++------------ CHANGELOG.md | 16 ++++++++++++++++ lib/common | 2 +- lib/providers/livesync-provider.ts | 2 +- package.json | 2 +- 6 files changed, 39 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 795bfd9141..71e3295d54 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ after_success: - mkdir s3-publish - cp nativescript*.tgz s3-publish/nativescript.tgz before_deploy: -- node .travis/add-publishConfig.js next +- node .travis/add-publishConfig.js $TRAVIS_BRANCH deploy: - provider: s3 bucket: nativescript-ci @@ -43,3 +43,10 @@ deploy: branch: master api_key: secure: KzzsvF3eA3j4gRQa8tO//+XWNSR3XiX8Sa18o3PyKyG9/cBZ6PQ3Te74cNS1C3ZiLUOgs5dWA6/TmRVPci4XjvFaWo/B6e2fuVSl5H94Od99bkeBHJsbLSEkLN4ClV/YbGuyKgA5Q2yIFt6p2EJjL90RjbbIk7I4YuyG2Mo3j0Q= + - provider: npm + skip_cleanup: true + email: nativescript@telerik.com + on: + branch: release + api_key: + secure: KzzsvF3eA3j4gRQa8tO//+XWNSR3XiX8Sa18o3PyKyG9/cBZ6PQ3Te74cNS1C3ZiLUOgs5dWA6/TmRVPci4XjvFaWo/B6e2fuVSl5H94Od99bkeBHJsbLSEkLN4ClV/YbGuyKgA5Q2yIFt6p2EJjL90RjbbIk7I4YuyG2Mo3j0Q= diff --git a/.travis/add-publishConfig.js b/.travis/add-publishConfig.js index c6e3c6334b..e11c0dd434 100644 --- a/.travis/add-publishConfig.js +++ b/.travis/add-publishConfig.js @@ -1,25 +1,25 @@ #!/usr/bin/env node -var fsModule = require('fs'); +var fsModule = require("fs"); -//Adds a publishConfig section to the package.json file +// Adds a publishConfig section to the package.json file // and sets a tag to it -var path = './package.json'; +var path = "./package.json"; var fileOptions = {encoding: "utf-8"}; var content = fsModule.readFileSync(path, fileOptions); -var tag = process.argv[2]; -if (!tag) { - console.log('Please pass the tag name as an argument!'); - process.exit(1); -} - var packageDef = JSON.parse(content); if (!packageDef.publishConfig) { packageDef.publishConfig = {}; } -packageDef.publishConfig.tag = tag; -var newContent = JSON.stringify(packageDef, null, ' '); -fsModule.writeFileSync(path, newContent, fileOptions); \ No newline at end of file +var branch = process.argv[2]; +if (!branch) { + console.log("Please pass the branch name as an argument!"); + process.exit(1); +} +packageDef.publishConfig.tag = branch === "release" ? "rc" : "next"; + +var newContent = JSON.stringify(packageDef, null, " "); +fsModule.writeFileSync(path, newContent, fileOptions); diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c355b7f02..39f25cbcc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ NativeScript CLI Changelog ================ +3.0.1 (2017, May 11) +== + +### Fixed + +* [Fix #2780](https://github.com/NativeScript/nativescript-cli/issues/2780): CLI tool doesn't restart app if HTML/CSS file was modified +* [Fix #2732](https://github.com/NativeScript/nativescript-cli/issues/2732): Livesync crashes app every OTHER time on iOS with 3.0.0-rc.2 +* [Fix #2764](https://github.com/NativeScript/nativescript-cli/issues/2764): Error when executing "tns run ios" with 3.0 on a project that is located in a directory path with "spaces" + +2.5.5 (2017, May 11) +== + +### Fixed + +* [Fix #2782](https://github.com/NativeScript/nativescript-cli/issues/2782): [2.5.*] tns run always add latest platform + 3.0.0 (2017, May 3) == diff --git a/lib/common b/lib/common index 5f1268ab1f..2388145d3c 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 5f1268ab1f1d1d1ffceb3610cf3ee1742ef67b9f +Subproject commit 2388145d3cca720ad2ab9cff857effa492c4d341 diff --git a/lib/providers/livesync-provider.ts b/lib/providers/livesync-provider.ts index 0724a34fd0..deb7ca717b 100644 --- a/lib/providers/livesync-provider.ts +++ b/lib/providers/livesync-provider.ts @@ -10,7 +10,7 @@ export class LiveSyncProvider implements ILiveSyncProvider { private $childProcess: IChildProcess, private $options: IOptions) { } - private static FAST_SYNC_FILE_EXTENSIONS = [".css", ".xml", ".html"]; + private static FAST_SYNC_FILE_EXTENSIONS = [".css", ".xml"]; private deviceSpecificLiveSyncServicesCache: IDictionary = {}; public get deviceSpecificLiveSyncServices(): IDictionary { diff --git a/package.json b/package.json index b28106c687..f1b8624aeb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "3.0.0", + "version": "3.0.1", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { From f64d4718f63e98763689cefd80b74761567dc8d9 Mon Sep 17 00:00:00 2001 From: Vasil Chimev Date: Fri, 12 May 2017 14:03:06 +0300 Subject: [PATCH 026/212] Update Travis CI Scripts (#2805) Set `rc` tag to the publishConfig section for the release branch. From 6b9ffebe0f0f24dd7b7243ef787720d09717c4f6 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Mon, 15 May 2017 15:10:12 +0300 Subject: [PATCH 027/212] Fix getting information for installed npm package (#2813) Some npm versions (like 3.9.6) do not report result when package is already installed and `--dry-run` is passed. This breaks the `JSON.parse()` of the result as it is an empty string. In this case just parse the original output of `npm install` and search for a result like: ``` `-- nativescript-barcodescanner@1.0.0 ``` In case this operation also fails to find which is the installed package, try to find the name from the user specified value. For example: ``` $ tns plugin add nativescript-barcodescanner@1.0.0 ``` The name is `nativescript-barcodescanner` and the version is `1.0.0`. In case a scoped package is passed, the parsing should also work correctly. In case a package, that is not NativeScript plugin, is installed via `tns plugin add` command, we have logic to revert the installation. However the promise that should uninstall the app had never been awaited, so the package was still declared and installed. When `--json` is passed to `npm install` command, it generates `/etc` dir in the project where npm command is executed. We have logic to delete it after finishing our command, but it's been executed too early. Fix the code, so command to remove `/etc` will be executed no matter if any of the other operations fails. --- lib/declarations.d.ts | 2 +- lib/node-package-manager.ts | 123 ++++++++++++++++++++++++++++---- lib/services/plugins-service.ts | 2 +- 3 files changed, 113 insertions(+), 14 deletions(-) diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index bff60a8780..d3b566fbb4 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -193,7 +193,7 @@ interface INpmInstallResultInfo { * The original output that npm CLI produced upon installation. * @type {INpmInstallCLIResult} */ - originalOutput: INpmInstallCLIResult; + originalOutput?: INpmInstallCLIResult; } interface INpmInstallOptions { diff --git a/lib/node-package-manager.ts b/lib/node-package-manager.ts index 7b9ad7f05e..c64f2c8236 100644 --- a/lib/node-package-manager.ts +++ b/lib/node-package-manager.ts @@ -2,6 +2,9 @@ import * as path from "path"; import { exported } from "./common/decorators"; export class NodePackageManager implements INodePackageManager { + private static SCOPED_DEPENDENCY_REGEXP = /^(@.+?)(?:@(.+?))?$/; + private static DEPENDENCY_REGEXP = /^(.+?)(?:@(.+?))?$/; + constructor(private $fs: IFileSystem, private $hostInfo: IHostInfo, private $errors: IErrors, @@ -47,10 +50,7 @@ export class NodePackageManager implements INodePackageManager { } try { - let spawnResult: ISpawnResult = await this.$childProcess.spawnFromEvent(this.getNpmExecutableName(), params, "close", { cwd, stdio: "inherit" }); - if (!etcExistsPriorToInstallation) { - this.$fs.deleteDirectory(etcDirectoryLocation); - } + let spawnResult: ISpawnResult = await this.getNpmInstallResult(params, cwd); // Whenever calling npm install without any arguments (hence installing all dependencies) no output is emitted on stdout // Luckily, whenever you call npm install to install all dependencies chances are you won't need the name/version of the package you're installing because there is none. @@ -63,8 +63,8 @@ export class NodePackageManager implements INodePackageManager { // We cannot use the actual install with --json to get the information because of post-install scripts which may print on stdout // dry-run install is quite fast when the dependencies are already installed even for many dependencies (e.g. angular) so we can live with this approach // We need the --prefix here because without it no output is emitted on stdout because all the dependencies are already installed. - spawnResult = await this.$childProcess.spawnFromEvent(this.getNpmExecutableName(), params, "close"); - return this.parseNpmInstallResult(spawnResult.stdout); + let spawnNpmDryRunResult = await this.$childProcess.spawnFromEvent(this.getNpmExecutableName(), params, "close"); + return this.parseNpmInstallResult(spawnNpmDryRunResult.stdout, spawnResult.stdout, packageName); } catch (err) { if (err.message && err.message.indexOf("EPEERINVALID") !== -1) { // Not installed peer dependencies are treated by npm 2 as errors, but npm 3 treats them as warnings. @@ -76,6 +76,10 @@ export class NodePackageManager implements INodePackageManager { this.$fs.writeJson(packageJsonPath, jsonContentBefore); throw err; } + } finally { + if (!etcExistsPriorToInstallation) { + this.$fs.deleteDirectory(etcDirectoryLocation); + } } } @@ -136,16 +140,111 @@ export class NodePackageManager implements INodePackageManager { return array.join(" "); } - private parseNpmInstallResult(npmInstallOutput: string): INpmInstallResultInfo { - const originalOutput: INpmInstallCLIResult = JSON.parse(npmInstallOutput); - const name = _.head(_.keys(originalOutput.dependencies)); - const dependency = _.pick(originalOutput.dependencies, name); + private parseNpmInstallResult(npmDryRunInstallOutput: string, npmInstallOutput: string, userSpecifiedPackageName: string): INpmInstallResultInfo { + // TODO: Add tests for this functionality + try { + const originalOutput: INpmInstallCLIResult = JSON.parse(npmDryRunInstallOutput); + const name = _.head(_.keys(originalOutput.dependencies)); + const dependency = _.pick(originalOutput.dependencies, name); + return { + name, + originalOutput, + version: dependency[name].version + }; + } catch (err) { + this.$logger.trace(`Unable to parse result of npm --dry-run operation. Output is: ${npmDryRunInstallOutput}.`); + this.$logger.trace("Now we'll try to parse the real output of npm install command."); + + let npmOutputMatchRegExp = /^.--\s+(?!UNMET)(.*)@((?:\d+\.){2}\d+)/m; + const match = npmInstallOutput.match(npmOutputMatchRegExp); + if (match) { + return { + name: match[1], + version: match[2] + }; + } + } + + this.$logger.trace("Unable to get information from npm installation, trying to return value specified by user."); + return this.getDependencyInformation(userSpecifiedPackageName); + } + + private getDependencyInformation(dependency: string): INpmInstallResultInfo { + const scopeDependencyMatch = dependency.match(NodePackageManager.SCOPED_DEPENDENCY_REGEXP); + let name: string = null, + version: string = null; + + if (scopeDependencyMatch) { + name = scopeDependencyMatch[1]; + version = scopeDependencyMatch[2]; + } else { + const matches = dependency.match(NodePackageManager.DEPENDENCY_REGEXP); + if (matches) { + name = matches[1]; + version = matches[2]; + } + } + return { name, - originalOutput, - version: dependency[name].version + version }; } + + private async getNpmInstallResult(params: string[], cwd: string): Promise { + return new Promise((resolve, reject) => { + const npmExecutable = this.getNpmExecutableName(); + let childProcess = this.$childProcess.spawn(npmExecutable, params, { cwd, stdio: "pipe" }); + + let isFulfilled = false; + let capturedOut = ""; + let capturedErr = ""; + + childProcess.stdout.on("data", (data: string) => { + this.$logger.write(data.toString()); + capturedOut += data; + }); + + if (childProcess.stderr) { + childProcess.stderr.on("data", (data: string) => { + console.error(data.toString()); + capturedErr += data; + }); + } + + childProcess.on("close", (arg: any) => { + const exitCode = typeof arg === "number" ? arg : arg && arg.code; + + if (exitCode === 0) { + isFulfilled = true; + const result = { + stdout: capturedOut, + stderr: capturedErr, + exitCode + }; + + resolve(result); + } else { + let errorMessage = `Command ${npmExecutable} ${params && params.join(" ")} failed with exit code ${exitCode}`; + if (capturedErr) { + errorMessage += ` Error output: \n ${capturedErr}`; + } + + if (!isFulfilled) { + isFulfilled = true; + reject(new Error(errorMessage)); + } + } + }); + + childProcess.on("error", (err: Error) => { + if (!isFulfilled) { + isFulfilled = true; + reject(err); + } + }); + }); + } } $injector.register("npm", NodePackageManager); diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index 1af98fb70c..c1fae529f9 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -71,7 +71,7 @@ export class PluginsService implements IPluginsService { this.$logger.out(`Successfully installed plugin ${realNpmPackageJson.name}.`); } else { - this.$npm.uninstall(realNpmPackageJson.name, { save: true }, projectData.projectDir); + await this.$npm.uninstall(realNpmPackageJson.name, { save: true }, projectData.projectDir); this.$errors.failWithoutHelp(`${plugin} is not a valid NativeScript plugin. Verify that the plugin package.json file contains a nativescript key and try again.`); } } From a5bd263bc5cee2a4a42409393352c7514d40e6ec Mon Sep 17 00:00:00 2001 From: yyosifov Date: Thu, 18 May 2017 14:19:38 +0300 Subject: [PATCH 028/212] Add support for Custom entitlements files (#2753) * Extract entitlements and xcconfig services. * Fix tests setup with the new services in ios-project-service tests * Adding newline in xcconfig-service to fix lint error * Add Tests for XCConfig-Service * First test for the merging of the Build XCConfig files * trying to add gems to the Travis CI machine setup * Adding patching the xcconfig file with a default entitlements file location if the user hasn't set one * Fix default empty entitlements file - was needless and caused errors. * Extract entitlements and xcconfig services. * Fix tests setup with the new services in ios-project-service tests * Adding newline in xcconfig-service to fix lint error * Add Tests for XCConfig-Service * First test for the merging of the Build XCConfig files * trying to add gems to the Travis CI machine setup * Adding patching the xcconfig file with a default entitlements file location if the user hasn't set one * Fix default empty entitlements file - was needless and caused errors. * Adding merge entitlements tests * Add merge from App_Resources and Plugin integration test * Fix PR comments --- .travis.yml | 2 + lib/bootstrap.ts | 2 + lib/constants.ts | 1 + lib/services/ios-entitlements-service.ts | 72 +++++++++ lib/services/ios-project-service.ts | 70 +++++---- lib/services/xcconfig-service.ts | 40 +++++ test/ios-entitlements-service.ts | 181 ++++++++++++++++++++++ test/ios-project-service.ts | 171 ++++++++++++++++++--- test/xcconfig-service.ts | 188 +++++++++++++++++++++++ 9 files changed, 674 insertions(+), 53 deletions(-) create mode 100644 lib/services/ios-entitlements-service.ts create mode 100644 lib/services/xcconfig-service.ts create mode 100644 test/ios-entitlements-service.ts create mode 100644 test/xcconfig-service.ts diff --git a/.travis.yml b/.travis.yml index 71e3295d54..26b0021abb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,8 @@ node_js: git: submodules: true before_script: +- gem install xcodeproj +- gem install cocoapods - npm install grunt - node_modules/.bin/grunt enableScripts:false - grunt rebuild diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index d2d81c8b4c..439ce535ba 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -9,8 +9,10 @@ $injector.require("projectData", "./project-data"); $injector.require("projectDataService", "./services/project-data-service"); $injector.requirePublic("projectService", "./services/project-service"); $injector.require("androidProjectService", "./services/android-project-service"); +$injector.require("iOSEntitlementsService", "./services/ios-entitlements-service"); $injector.require("iOSProjectService", "./services/ios-project-service"); $injector.require("iOSProvisionService", "./services/ios-provision-service"); +$injector.require("xCConfigService", "./services/xcconfig-service"); $injector.require("cocoapodsService", "./services/cocoapods-service"); diff --git a/lib/constants.ts b/lib/constants.ts index 21a4488697..6750b710a7 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -16,6 +16,7 @@ export const TEST_RUNNER_NAME = "nativescript-unit-test-runner"; export const LIVESYNC_EXCLUDED_FILE_PATTERNS = ["**/*.js.map", "**/*.ts"]; export const XML_FILE_EXTENSION = ".xml"; export const PLATFORMS_DIR_NAME = "platforms"; +export const CODE_SIGN_ENTITLEMENTS = "CODE_SIGN_ENTITLEMENTS"; export class PackageVersion { static NEXT = "next"; diff --git a/lib/services/ios-entitlements-service.ts b/lib/services/ios-entitlements-service.ts new file mode 100644 index 0000000000..e30be51f54 --- /dev/null +++ b/lib/services/ios-entitlements-service.ts @@ -0,0 +1,72 @@ +import * as path from "path"; +import * as constants from "../constants"; +import { PlistSession } from "plist-merge-patch"; + +export class IOSEntitlementsService { + constructor(private $fs: IFileSystem, + private $logger: ILogger, + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + private $mobileHelper: Mobile.IMobileHelper, + private $pluginsService: IPluginsService) { + } + + public static readonly DefaultEntitlementsName: string = "app.entitlements"; + + private getDefaultAppEntitlementsPath(projectData: IProjectData) : string { + const entitlementsName = IOSEntitlementsService.DefaultEntitlementsName; + const entitlementsPath = path.join(projectData.projectDir, + constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME, + this.$mobileHelper.normalizePlatformName(this.$devicePlatformsConstants.iOS), + entitlementsName); + return entitlementsPath; + } + + public getPlatformsEntitlementsPath(projectData: IProjectData) : string { + return path.join(projectData.platformsDir, this.$devicePlatformsConstants.iOS, + projectData.projectName, projectData.projectName + ".entitlements"); + } + public getPlatformsEntitlementsRelativePath(projectData: IProjectData): string { + return path.join(projectData.projectName, projectData.projectName + ".entitlements"); + } + + public async merge(projectData: IProjectData): Promise { + let session = new PlistSession({ log: (txt: string) => this.$logger.trace("App.entitlements: " + txt) }); + + let projectDir = projectData.projectDir; + let makePatch = (plistPath: string) => { + if (!this.$fs.exists(plistPath)) { + this.$logger.trace("No plist found at: " + plistPath); + return; + } + + this.$logger.trace("Schedule merge plist at: " + plistPath); + session.patch({ + name: path.relative(projectDir, plistPath), + read: () => this.$fs.readText(plistPath) + }); + }; + + let allPlugins = await this.getAllInstalledPlugins(projectData); + for (let plugin of allPlugins) { + let pluginInfoPlistPath = path.join(plugin.pluginPlatformsFolderPath(this.$devicePlatformsConstants.iOS), + IOSEntitlementsService.DefaultEntitlementsName); + makePatch(pluginInfoPlistPath); + } + + let appEntitlementsPath = this.getDefaultAppEntitlementsPath(projectData); + if (this.$fs.exists(appEntitlementsPath)) { + makePatch(appEntitlementsPath); + } + + let plistContent = session.build(); + this.$logger.trace("App.entitlements: Write to: " + this.getPlatformsEntitlementsPath(projectData)); + this.$fs.writeFile(this.getPlatformsEntitlementsPath(projectData), plistContent); + return; + } + + private getAllInstalledPlugins(projectData: IProjectData): Promise { + return this.$pluginsService.getAllInstalledPlugins(projectData); + } +} + +$injector.register("iOSEntitlementsService", IOSEntitlementsService); diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index c39d6c013d..4ffe535f31 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -11,6 +11,8 @@ import { EOL } from "os"; import * as temp from "temp"; import * as plist from "plist"; import { IOSProvisionService } from "./ios-provision-service"; +import { IOSEntitlementsService } from "./ios-entitlements-service"; +import { XCConfigService } from "./xcconfig-service"; export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService { private static XCODE_PROJECT_EXT_NAME = ".xcodeproj"; @@ -40,9 +42,11 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private $pluginVariablesService: IPluginVariablesService, private $xcprojService: IXcprojService, private $iOSProvisionService: IOSProvisionService, - private $sysInfo: ISysInfo, private $pbxprojDomXcode: IPbxprojDomXcode, - private $xcode: IXcode) { + private $xcode: IXcode, + private $iOSEntitlementsService: IOSEntitlementsService, + private $sysInfo: ISysInfo, + private $xCConfigService: XCConfigService) { super($fs, $projectDataService); } @@ -668,6 +672,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f public async processConfigurationFilesFromAppResources(release: boolean, projectData: IProjectData): Promise { await this.mergeInfoPlists(projectData); + await this.$iOSEntitlementsService.merge(projectData); await this.mergeProjectXcconfigFiles(release, projectData); for (let pluginData of await this.getAllInstalledPlugins(projectData)) { await this.$pluginVariablesService.interpolatePluginVariables(pluginData, this.getPlatformData(projectData).configurationFilePath, projectData); @@ -1085,20 +1090,33 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } private async mergeProjectXcconfigFiles(release: boolean, projectData: IProjectData): Promise { - this.$fs.deleteFile(release ? this.getPluginsReleaseXcconfigFilePath(projectData) : this.getPluginsDebugXcconfigFilePath(projectData)); + let pluginsXcconfigFilePath = release ? this.getPluginsReleaseXcconfigFilePath(projectData) : this.getPluginsDebugXcconfigFilePath(projectData); + this.$fs.deleteFile(pluginsXcconfigFilePath); let allPlugins: IPluginData[] = await (this.$injector.resolve("pluginsService")).getAllInstalledPlugins(projectData); for (let plugin of allPlugins) { let pluginPlatformsFolderPath = plugin.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME); let pluginXcconfigFilePath = path.join(pluginPlatformsFolderPath, "build.xcconfig"); if (this.$fs.exists(pluginXcconfigFilePath)) { - await this.mergeXcconfigFiles(pluginXcconfigFilePath, release ? this.getPluginsReleaseXcconfigFilePath(projectData) : this.getPluginsDebugXcconfigFilePath(projectData)); + await this.mergeXcconfigFiles(pluginXcconfigFilePath, pluginsXcconfigFilePath); } } let appResourcesXcconfigPath = path.join(projectData.projectDir, constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME, this.getPlatformData(projectData).normalizedPlatformName, "build.xcconfig"); if (this.$fs.exists(appResourcesXcconfigPath)) { - await this.mergeXcconfigFiles(appResourcesXcconfigPath, release ? this.getPluginsReleaseXcconfigFilePath(projectData) : this.getPluginsDebugXcconfigFilePath(projectData)); + await this.mergeXcconfigFiles(appResourcesXcconfigPath, pluginsXcconfigFilePath); + } + + // Set Entitlements Property to point to default file if not set explicitly by the user. + let entitlementsPropertyValue = this.$xCConfigService.readPropertyValue(pluginsXcconfigFilePath, constants.CODE_SIGN_ENTITLEMENTS); + if (entitlementsPropertyValue === null) { + temp.track(); + const tempEntitlementsDir = temp.mkdirSync("entitlements"); + const tempEntitlementsFilePath = path.join(tempEntitlementsDir, "set-entitlements.xcconfig"); + const entitlementsRelativePath = this.$iOSEntitlementsService.getPlatformsEntitlementsRelativePath(projectData); + this.$fs.writeFile(tempEntitlementsFilePath, `CODE_SIGN_ENTITLEMENTS = ${entitlementsRelativePath}${EOL}`); + + await this.mergeXcconfigFiles(tempEntitlementsFilePath, pluginsXcconfigFilePath); } let podFilesRootDirName = path.join("Pods", "Target Support Files", `Pods-${projectData.projectName}`); @@ -1178,51 +1196,37 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f return null; } - private readXCConfig(flag: string, projectData: IProjectData): string { - let xcconfigFile = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, "build.xcconfig"); - if (this.$fs.exists(xcconfigFile)) { - let text = this.$fs.readText(xcconfigFile); - let teamId: string; - text.split(/\r?\n/).forEach((line) => { - line = line.replace(/\/(\/)[^\n]*$/, ""); - if (line.indexOf(flag) >= 0) { - teamId = line.split("=")[1].trim(); - if (teamId[teamId.length - 1] === ';') { - teamId = teamId.slice(0, -1); - } - } - }); - if (teamId) { - return teamId; - } - } + private getBuildXCConfigFilePath(projectData: IProjectData): string { + let buildXCConfig = path.join(projectData.appResourcesDirectoryPath, + this.getPlatformData(projectData).normalizedPlatformName, "build.xcconfig"); + return buildXCConfig; + } + + private readTeamId(projectData: IProjectData): string { + let teamId = this.$xCConfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "DEVELOPMENT_TEAM"); let fileName = path.join(this.getPlatformData(projectData).projectRoot, "teamid"); if (this.$fs.exists(fileName)) { - return this.$fs.readText(fileName); + teamId = this.$fs.readText(fileName); } - return null; - } - - private readTeamId(projectData: IProjectData): string { - return this.readXCConfig("DEVELOPMENT_TEAM", projectData); + return teamId; } private readXCConfigProvisioningProfile(projectData: IProjectData): string { - return this.readXCConfig("PROVISIONING_PROFILE", projectData); + return this.$xCConfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE"); } private readXCConfigProvisioningProfileForIPhoneOs(projectData: IProjectData): string { - return this.readXCConfig("PROVISIONING_PROFILE[sdk=iphoneos*]", projectData); + return this.$xCConfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE[sdk=iphoneos*]"); } private readXCConfigProvisioningProfileSpecifier(projectData: IProjectData): string { - return this.readXCConfig("PROVISIONING_PROFILE_SPECIFIER", projectData); + return this.$xCConfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE_SPECIFIER"); } private readXCConfigProvisioningProfileSpecifierForIPhoneOs(projectData: IProjectData): string { - return this.readXCConfig("PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]", projectData); + return this.$xCConfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]"); } private async getDevelopmentTeam(projectData: IProjectData, teamId?: string): Promise { diff --git a/lib/services/xcconfig-service.ts b/lib/services/xcconfig-service.ts new file mode 100644 index 0000000000..63fb6b2fca --- /dev/null +++ b/lib/services/xcconfig-service.ts @@ -0,0 +1,40 @@ +export class XCConfigService { + constructor(private $fs: IFileSystem) { + } + + /** + * Returns the Value of a Property from a XC Config file. + * @param xcconfigFilePath + * @param propertyName + */ + public readPropertyValue(xcconfigFilePath: string, propertyName: string): string { + if (this.$fs.exists(xcconfigFilePath)) { + let text = this.$fs.readText(xcconfigFilePath); + + let property: string; + let isPropertyParsed: boolean = false; + text.split(/\r?\n/).forEach((line) => { + line = line.replace(/\/(\/)[^\n]*$/, ""); + if (line.indexOf(propertyName) >= 0) { + let parts = line.split("="); + if (parts.length > 1 && parts[1]) { + property = parts[1].trim(); + isPropertyParsed = true; + if (property[property.length - 1] === ';') { + property = property.slice(0, -1); + } + } + } + }); + + if (isPropertyParsed) { + // property can be an empty string, so we don't check for that. + return property; + } + } + + return null; + } +} + +$injector.register("xCConfigService", XCConfigService); diff --git a/test/ios-entitlements-service.ts b/test/ios-entitlements-service.ts new file mode 100644 index 0000000000..23fbc93621 --- /dev/null +++ b/test/ios-entitlements-service.ts @@ -0,0 +1,181 @@ +import temp = require("temp"); +import { EOL } from "os"; +import { assert } from "chai"; +import { IOSEntitlementsService } from "../lib/services/ios-entitlements-service"; +import * as yok from "../lib/common/yok"; +import * as stubs from "./stubs"; +import * as FsLib from "../lib/common/file-system"; +import * as MobilePlatformsCapabilitiesLib from "../lib/common/appbuilder/mobile-platforms-capabilities"; +import * as MobileHelperLib from "../lib/common/mobile/mobile-helper"; +import * as DevicePlatformsConstantsLib from "../lib/common/mobile/device-platforms-constants"; +import * as ErrorsLib from "../lib/common/errors"; +import * as path from "path"; + +// start tracking temporary folders/files +temp.track(); + +describe("IOSEntitlements Service Tests", () => { + const createTestInjector = (): IInjector => { + const testInjector = new yok.Yok(); + + testInjector.register('platformsData', stubs.PlatformsDataStub); + testInjector.register("logger", stubs.LoggerStub); + testInjector.register('iOSEntitlementsService', IOSEntitlementsService); + + testInjector.register("fs", FsLib.FileSystem); + testInjector.register("mobileHelper", MobileHelperLib.MobileHelper); + testInjector.register("devicePlatformsConstants", DevicePlatformsConstantsLib.DevicePlatformsConstants); + testInjector.register("mobilePlatformsCapabilities", MobilePlatformsCapabilitiesLib.MobilePlatformsCapabilities); + testInjector.register("errors", ErrorsLib.Errors); + + testInjector.register("pluginsService", { + getAllInstalledPlugins: async () => [] + }); + + return testInjector; + }; + + let injector: IInjector, + platformsData: any, + projectData: IProjectData, + fs: IFileSystem, + iOSEntitlementsService: IOSEntitlementsService, + destinationFilePath: string; + + beforeEach(() => { + injector = createTestInjector(); + + platformsData = injector.resolve("platformsData"); + projectData = platformsData.getPlatformData(); + projectData.projectName = 'testApp'; + + projectData.platformsDir = temp.mkdirSync("platformsDir"); + projectData.projectDir = temp.mkdirSync("projectDir"); + + fs = injector.resolve("$fs"); + + iOSEntitlementsService = injector.resolve("iOSEntitlementsService"); + destinationFilePath = iOSEntitlementsService.getPlatformsEntitlementsPath(projectData); + }); + + describe("Ensure paths constructed are correct", () => { + it("Ensure destination entitlements relative path is calculated correctly.", () => { + const expected = "testApp/testApp.entitlements"; + let actual = iOSEntitlementsService.getPlatformsEntitlementsRelativePath(projectData); + assert.equal(actual, expected); + }); + }); + + describe("Merge", () => { + const defaultPlistContent = ` + + + +`; + const defaultAppResourcesEntitlementsContent = ` + + + + aps-environment + development + +`; + const defaultPluginEntitlementsContent = ` + + + + aps-environment + production + +`; + const namedAppResourcesEntitlementsContent = ` + + + + nameKey + appResources + +`; + const mergedEntitlementsContent = ` + + + + aps-environment + production + nameKey + appResources + +`; + + function assertContent(actual: string, expected: string) { + let strip = (x: string) => { + return x.replace(EOL, '').trim(); + }; + assert.equal(strip(actual), strip(expected)); + } + + it("Merge creates a default entitlements file.", async () => { + // act + await iOSEntitlementsService.merge(projectData); + + // assert + let actual = fs.readText(destinationFilePath); + assertContent(actual, defaultPlistContent); + }); + + it("Merge uses the entitlements from App_Resources folder", async () => { + let appResourcesEntitlement = (iOSEntitlementsService).getDefaultAppEntitlementsPath(projectData); + fs.writeFile(appResourcesEntitlement, defaultAppResourcesEntitlementsContent); + + // act + await iOSEntitlementsService.merge(projectData); + + // assert + let actual = fs.readText(destinationFilePath); + assertContent(actual, defaultAppResourcesEntitlementsContent); + }); + + it("Merge uses the entitlements file from a Plugin", async () => { + let pluginsService = injector.resolve("pluginsService"); + let testPluginFolderPath = temp.mkdirSync("testPlugin"); + pluginsService.getAllInstalledPlugins = async() => [{ + pluginPlatformsFolderPath: (platform: string) => { + return testPluginFolderPath; + } + }]; + let pluginAppEntitlementsPath = path.join(testPluginFolderPath, IOSEntitlementsService.DefaultEntitlementsName); + fs.writeFile(pluginAppEntitlementsPath, defaultPluginEntitlementsContent); + + // act + await iOSEntitlementsService.merge(projectData); + + // assert + let actual = fs.readText(destinationFilePath); + assertContent(actual, defaultPluginEntitlementsContent); + }); + + it("Merge uses App_Resources and Plugins and merges all keys", async () => { + // setup app resoruces + let appResourcesEntitlement = (iOSEntitlementsService).getDefaultAppEntitlementsPath(projectData); + fs.writeFile(appResourcesEntitlement, namedAppResourcesEntitlementsContent); + + // setup plugin entitlements + let pluginsService = injector.resolve("pluginsService"); + let testPluginFolderPath = temp.mkdirSync("testPlugin"); + pluginsService.getAllInstalledPlugins = async() => [{ + pluginPlatformsFolderPath: (platform: string) => { + return testPluginFolderPath; + } + }]; + let pluginAppEntitlementsPath = path.join(testPluginFolderPath, IOSEntitlementsService.DefaultEntitlementsName); + fs.writeFile(pluginAppEntitlementsPath, defaultPluginEntitlementsContent); + + // act + await iOSEntitlementsService.merge(projectData); + + // assert + let actual = fs.readText(destinationFilePath); + assertContent(actual, mergedEntitlementsContent); + }); + }); +}); diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index 7ab5b0252a..51f45cb151 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -1,4 +1,5 @@ import * as path from "path"; +import { EOL } from "os"; import * as ChildProcessLib from "../lib/common/child-process"; import * as ConfigLib from "../lib/config"; import * as ErrorsLib from "../lib/common/errors"; @@ -6,6 +7,8 @@ import * as FileSystemLib from "../lib/common/file-system"; import * as HostInfoLib from "../lib/common/host-info"; import * as iOSProjectServiceLib from "../lib/services/ios-project-service"; import { IOSProjectService } from "../lib/services/ios-project-service"; +import { IOSEntitlementsService } from "../lib/services/ios-entitlements-service"; +import { XCConfigService } from "../lib/services/xcconfig-service"; import * as LoggerLib from "../lib/common/logger"; import * as OptionsLib from "../lib/options"; import * as yok from "../lib/common/yok"; @@ -20,9 +23,14 @@ import { DeviceDiscovery } from "../lib/common/mobile/mobile-core/device-discove import { IOSDeviceDiscovery } from "../lib/common/mobile/mobile-core/ios-device-discovery"; import { AndroidDeviceDiscovery } from "../lib/common/mobile/mobile-core/android-device-discovery"; import { PluginVariablesService } from "../lib/services/plugin-variables-service"; +import { PluginsService } from "../lib/services/plugins-service"; import { PluginVariablesHelper } from "../lib/common/plugin-variables-helper"; import { Utils } from "../lib/common/utils"; import { CocoaPodsService } from "../lib/services/cocoapods-service"; +import { NpmInstallationManager } from "../lib/npm-installation-manager"; +import { NodePackageManager } from "../lib/node-package-manager"; +import * as constants from "../lib/constants"; + import { assert } from "chai"; import { IOSProvisionService } from "../lib/services/ios-provision-service"; import temp = require("temp"); @@ -52,6 +60,8 @@ function createTestInjector(projectPath: string, projectName: string): IInjector testInjector.register("cocoapodsService", CocoaPodsService); testInjector.register("iOSProjectService", iOSProjectServiceLib.IOSProjectService); testInjector.register("iOSProvisionService", {}); + testInjector.register("xCConfigService", XCConfigService); + testInjector.register("iOSEntitlementsService", IOSEntitlementsService); testInjector.register("logger", LoggerLib.Logger); testInjector.register("options", OptionsLib.Options); testInjector.register("projectData", { @@ -79,10 +89,17 @@ function createTestInjector(projectPath: string, projectName: string): IInjector testInjector.register("loggingLevels", LoggingLevels); testInjector.register("utils", Utils); testInjector.register("iTunesValidator", {}); - testInjector.register("xcprojService", {}); + testInjector.register("xcprojService", { + getXcprojInfo: () => { + return { + shouldUseXcproj: false + }; + } + }); testInjector.register("iosDeviceOperations", {}); testInjector.register("pluginVariablesService", PluginVariablesService); testInjector.register("pluginVariablesHelper", PluginVariablesHelper); + testInjector.register("pluginsService", PluginsService); testInjector.register("androidProcessService", {}); testInjector.register("processService", {}); testInjector.register("sysInfo", {}); @@ -94,6 +111,9 @@ function createTestInjector(projectPath: string, projectName: string): IInjector pbxGroupByName() { /* */ } } }); + testInjector.register("npmInstallationManager", NpmInstallationManager); + testInjector.register("npm", NodePackageManager); + testInjector.register("xCConfigService", XCConfigService); return testInjector; } @@ -598,7 +618,7 @@ describe("iOS Project Service Signing", () => { }); it("sets signingChanged if the Xcode projects is configured with Automatic signing, but proivsion is specified", () => { files[pbxproj] = ""; - pbxprojDomXcode.Xcode.open = function(path: string) { + pbxprojDomXcode.Xcode.open = function (path: string) { assert.equal(path, pbxproj); return { getSigning(x: string) { @@ -612,14 +632,16 @@ describe("iOS Project Service Signing", () => { }); it("sets signingChanged if the Xcode projects is configured with Manual signing, but the proivsion specified differs the selected in the pbxproj", () => { files[pbxproj] = ""; - pbxprojDomXcode.Xcode.open = function(path: string) { + pbxprojDomXcode.Xcode.open = function (path: string) { assert.equal(path, pbxproj); return { getSigning() { - return { style: "Manual", configurations: { - Debug: { name: "NativeScriptDev2" }, - Release: { name: "NativeScriptDev2" } - }}; + return { + style: "Manual", configurations: { + Debug: { name: "NativeScriptDev2" }, + Release: { name: "NativeScriptDev2" } + } + }; } }; }; @@ -629,14 +651,16 @@ describe("iOS Project Service Signing", () => { }); it("does not set signingChanged if the Xcode projects is configured with Manual signing and proivsion matches", () => { files[pbxproj] = ""; - pbxprojDomXcode.Xcode.open = function(path: string) { + pbxprojDomXcode.Xcode.open = function (path: string) { assert.equal(path, pbxproj); return { getSigning() { - return { style: "Manual", configurations: { - Debug: { name: "NativeScriptDev" }, - Release: { name: "NativeScriptDev" } - }}; + return { + style: "Manual", configurations: { + Debug: { name: "NativeScriptDev" }, + Release: { name: "NativeScriptDev" } + } + }; } }; }; @@ -650,7 +674,7 @@ describe("iOS Project Service Signing", () => { describe("from Automatic to provision name", () => { beforeEach(() => { files[pbxproj] = ""; - pbxprojDomXcode.Xcode.open = function(path: string) { + pbxprojDomXcode.Xcode.open = function (path: string) { return { getSigning(x: string) { return { style: "Automatic", teamID: "AutoTeam" }; @@ -665,9 +689,9 @@ describe("iOS Project Service Signing", () => { assert.isTrue(e.toString().indexOf("Failed to find mobile provision with UUID or Name: NativeScriptDev2") >= 0); } }); - it("succeeds if the provision name is provided for development cert", async() => { + it("succeeds if the provision name is provided for development cert", async () => { let stack: any = []; - pbxprojDomXcode.Xcode.open = function(path: string) { + pbxprojDomXcode.Xcode.open = function (path: string) { assert.equal(path, pbxproj); return { getSigning() { @@ -682,11 +706,11 @@ describe("iOS Project Service Signing", () => { }; }; await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptDev" }); - assert.deepEqual(stack, [{targetName: projectDirName, manualSigning: { team: "TKID101", uuid: "12345", name: "NativeScriptDev", identity: "iPhone Developer" }}, "save()"]); + assert.deepEqual(stack, [{ targetName: projectDirName, manualSigning: { team: "TKID101", uuid: "12345", name: "NativeScriptDev", identity: "iPhone Developer" } }, "save()"]); }); it("succeds if the provision name is provided for distribution cert", async () => { let stack: any = []; - pbxprojDomXcode.Xcode.open = function(path: string) { + pbxprojDomXcode.Xcode.open = function (path: string) { assert.equal(path, pbxproj); return { getSigning() { @@ -701,11 +725,11 @@ describe("iOS Project Service Signing", () => { }; }; await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptDist" }); - assert.deepEqual(stack, [{targetName: projectDirName, manualSigning: { team: "TKID202", uuid: "6789", name: "NativeScriptDist", identity: "iPhone Distribution" }}, "save()"]); + assert.deepEqual(stack, [{ targetName: projectDirName, manualSigning: { team: "TKID202", uuid: "6789", name: "NativeScriptDist", identity: "iPhone Distribution" } }, "save()"]); }); it("succeds if the provision name is provided for adhoc cert", async () => { let stack: any = []; - pbxprojDomXcode.Xcode.open = function(path: string) { + pbxprojDomXcode.Xcode.open = function (path: string) { assert.equal(path, pbxproj); return { getSigning() { @@ -720,8 +744,115 @@ describe("iOS Project Service Signing", () => { }; }; await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptAdHoc" }); - assert.deepEqual(stack, [{targetName: projectDirName, manualSigning: { team: "TKID303", uuid: "1010", name: "NativeScriptAdHoc", identity: "iPhone Distribution" }}, "save()"]); + assert.deepEqual(stack, [{ targetName: projectDirName, manualSigning: { team: "TKID303", uuid: "1010", name: "NativeScriptAdHoc", identity: "iPhone Distribution" } }, "save()"]); }); }); }); }); + +describe("Merge Project XCConfig files", () => { + const assertPropertyValues = (expected: any, xcconfigPath: string, injector: IInjector) => { + let service = injector.resolve('xCConfigService'); + _.forOwn(expected, (value, key) => { + let actual = service.readPropertyValue(xcconfigPath, key); + assert.equal(actual, value); + }); + }; + + let projectName: string, + projectPath: string, + testInjector: IInjector, + iOSProjectService: IOSProjectService, + projectData: IProjectData, + fs: IFileSystem, + appResourcesXcconfigPath: string, + appResourceXCConfigContent: string, + iOSEntitlementsService: IOSEntitlementsService; + + beforeEach(() => { + projectName = "projectDirectory"; + projectPath = temp.mkdirSync(projectName); + + testInjector = createTestInjector(projectPath, projectName); + iOSProjectService = testInjector.resolve("iOSProjectService"); + projectData = testInjector.resolve("projectData"); + projectData.projectDir = projectPath; + + iOSEntitlementsService = testInjector.resolve("iOSEntitlementsService"); + + appResourcesXcconfigPath = path.join(projectData.projectDir, constants.APP_FOLDER_NAME, + constants.APP_RESOURCES_FOLDER_NAME, "iOS", "build.xcconfig"); + appResourceXCConfigContent = `CODE_SIGN_IDENTITY = iPhone Distribution + // To build for device with XCode 8 you need to specify your development team. More info: https://developer.apple.com/library/prerelease/content/releasenotes/DeveloperTools/RN-Xcode/Introduction.html + // DEVELOPMENT_TEAM = YOUR_TEAM_ID; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + `; + let testPackageJson = { + "name": "test-project", + "version": "0.0.1" + }; + fs = testInjector.resolve("fs"); + fs.writeJson(path.join(projectPath, "package.json"), testPackageJson); + }); + + it("Uses the build.xcconfig file content from App_Resources", async () => { + // setup app_resource build.xcconfig + fs.writeFile(appResourcesXcconfigPath, appResourceXCConfigContent); + + // run merge for all release: debug|release + for (let release in [true, false]) { + await (iOSProjectService).mergeProjectXcconfigFiles(release, projectData); + + let destinationFilePath = release ? (iOSProjectService).getPluginsReleaseXcconfigFilePath(projectData) + : (iOSProjectService).getPluginsDebugXcconfigFilePath(projectData); + + assert.isTrue(fs.exists(destinationFilePath), 'Target build xcconfig is missing for release: ' + release); + let expected = { + 'ASSETCATALOG_COMPILER_APPICON_NAME': 'AppIcon', + 'ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME': 'LaunchImage', + 'CODE_SIGN_IDENTITY': 'iPhone Distribution' + }; + assertPropertyValues(expected, destinationFilePath, testInjector); + } + }); + + it("Adds the entitlements property if not set by the user", async () => { + for (let release in [true, false]) { + await (iOSProjectService).mergeProjectXcconfigFiles(release, projectData); + + let destinationFilePath = release ? (iOSProjectService).getPluginsReleaseXcconfigFilePath(projectData) + : (iOSProjectService).getPluginsDebugXcconfigFilePath(projectData); + + assert.isTrue(fs.exists(destinationFilePath), 'Target build xcconfig is missing for release: ' + release); + let expected = { + 'CODE_SIGN_ENTITLEMENTS': iOSEntitlementsService.getPlatformsEntitlementsRelativePath(projectData) + }; + assertPropertyValues(expected, destinationFilePath, testInjector); + } + }); + + it("The user specified entitlements property takes precedence", async () => { + // setup app_resource build.xcconfig + const expectedEntitlementsFile = 'user.entitlements'; + let xcconfigEntitlements = appResourceXCConfigContent + `${EOL}CODE_SIGN_ENTITLEMENTS = ${expectedEntitlementsFile}`; + fs.writeFile(appResourcesXcconfigPath, xcconfigEntitlements); + + // run merge for all release: debug|release + for (let release in [true, false]) { + await (iOSProjectService).mergeProjectXcconfigFiles(release, projectData); + + let destinationFilePath = release ? (iOSProjectService).getPluginsReleaseXcconfigFilePath(projectData) + : (iOSProjectService).getPluginsDebugXcconfigFilePath(projectData); + + assert.isTrue(fs.exists(destinationFilePath), 'Target build xcconfig is missing for release: ' + release); + let expected = { + 'ASSETCATALOG_COMPILER_APPICON_NAME': 'AppIcon', + 'ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME': 'LaunchImage', + 'CODE_SIGN_IDENTITY': 'iPhone Distribution', + 'CODE_SIGN_ENTITLEMENTS': expectedEntitlementsFile + }; + assertPropertyValues(expected, destinationFilePath, testInjector); + } + }); +}); diff --git a/test/xcconfig-service.ts b/test/xcconfig-service.ts new file mode 100644 index 0000000000..099ee44b47 --- /dev/null +++ b/test/xcconfig-service.ts @@ -0,0 +1,188 @@ +import temp = require("temp"); +import { assert } from "chai"; +import { XCConfigService } from "../lib/services/xcconfig-service"; +import * as yok from "../lib/common/yok"; + +// start tracking temporary folders/files +temp.track(); + +describe("XCConfig Service Tests", () => { + const createTestInjector = (): IInjector => { + const testInjector = new yok.Yok(); + testInjector.register("fs", { + exists: (path: string) => { + return true; + } + }); + + testInjector.register('xCConfigService', XCConfigService); + + return testInjector; + }; + + const assertPropertyValues = (expected: any, injector: IInjector) => { + let service = getXCConfigService(injector); + _.forOwn(expected, (value, key) => { + let actual = service.readPropertyValue("any", key); + assert.equal(actual, value); + }); + }; + + function getXCConfigService(injector: IInjector): XCConfigService { + return injector.resolve("xCConfigService"); + } + + function getFileSystemMock(injector: IInjector): any { + return injector.resolve('$fs'); + } + + describe("Read Property Value", () => { + it("Return empty value, for unexistent file", () => { + let injector = createTestInjector(); + injector.register("fs", { + exists: (path: string) => { + return false; + } + }); + + let service = getXCConfigService(injector); + let actual = service.readPropertyValue("any", "any"); + + assert.isNull(actual); + }); + + it("Returns correct value for well-formatted document", () => { + let injector = createTestInjector(); + let fs = getFileSystemMock(injector); + fs.readText = (filename: string, options?: IReadFileOptions | string): string => { + return `// You can add custom settings here + // for example you can uncomment the following line to force distribution code signing + CODE_SIGN_IDENTITY = iPhone Distribution + // To build for device with XCode 8 you need to specify your development team. More info: https://developer.apple.com/library/prerelease/content/releasenotes/DeveloperTools/RN-Xcode/Introduction.html + // DEVELOPMENT_TEAM = YOUR_TEAM_ID; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;`; + }; + + let expected = { + 'ASSETCATALOG_COMPILER_APPICON_NAME': 'AppIcon', + 'ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME': 'LaunchImage', + 'CODE_SIGN_IDENTITY': 'iPhone Distribution' + }; + + assertPropertyValues(expected, injector); + }); + + it("Returns correct value for values with missing ; at the end of the line.", () => { + let injector = createTestInjector(); + let fs = getFileSystemMock(injector); + fs.readText = (filename: string, options?: IReadFileOptions | string): string => { + return `// You can add custom settings here + // for example you can uncomment the following line to force distribution code signing + CODE_SIGN_IDENTITY = iPhone Distribution + // To build for device with XCode 8 you need to specify your development team. More info: https://developer.apple.com/library/prerelease/content/releasenotes/DeveloperTools/RN-Xcode/Introduction.html + // DEVELOPMENT_TEAM = YOUR_TEAM_ID + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage`; + }; + + let expected = { + 'ASSETCATALOG_COMPILER_APPICON_NAME': 'AppIcon', + 'ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME': 'LaunchImage', + 'CODE_SIGN_IDENTITY': 'iPhone Distribution' + }; + + assertPropertyValues(expected, injector); + }); + + it("Doesn't read value from a commented-out line.", () => { + let injector = createTestInjector(); + let fs = getFileSystemMock(injector); + fs.readText = (filename: string, options?: IReadFileOptions | string): string => { + return `// DEVELOPMENT_TEAM = YOUR_TEAM_ID; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;`; + }; + + let expected = { + 'ASSETCATALOG_COMPILER_APPICON_NAME': 'AppIcon', + 'DEVELOPMENT_TEAM': null + }; + + assertPropertyValues(expected, injector); + }); + + it("Returns correct empty value.", () => { + let injector = createTestInjector(); + let fs = getFileSystemMock(injector); + fs.readText = (filename: string, options?: IReadFileOptions | string): string => { + return ` + ASSETCATALOG_COMPILER_APPICON_NAME = ; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + `; + }; + + let expected = { + 'ASSETCATALOG_COMPILER_APPICON_NAME': '', + 'ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME': 'LaunchImage' + }; + + assertPropertyValues(expected, injector); + }); + + it("First part only property doesn't break the service.", () => { + let injector = createTestInjector(); + let fs = getFileSystemMock(injector); + fs.readText = (filename: string, options?: IReadFileOptions | string): string => { + return `ASSETCATALOG_COMPILER_APPICON_NAME`; + }; + + let expected = { + 'ASSETCATALOG_COMPILER_APPICON_NAME': null + }; + + assertPropertyValues(expected, injector); + }); + + it("Invalid config property value with = doesn't break the service.", () => { + let injector = createTestInjector(); + let fs = getFileSystemMock(injector); + fs.readText = (filename: string, options?: IReadFileOptions | string): string => { + return `ASSETCATALOG_COMPILER_APPICON_NAME=`; + }; + + let expected = { + 'ASSETCATALOG_COMPILER_APPICON_NAME': null + }; + + assertPropertyValues(expected, injector); + }); + + it("Property with space is read correctly.", () => { + let injector = createTestInjector(); + let fs = getFileSystemMock(injector); + fs.readText = (filename: string, options?: IReadFileOptions | string): string => { + return `ASSETCATALOG_COMPILER_APPICON_NAME= `; + }; + + let expected = { + 'ASSETCATALOG_COMPILER_APPICON_NAME': '' + }; + + assertPropertyValues(expected, injector); + }); + + it("Ensure property can be an empty value.", () => { + let injector = createTestInjector(); + let fs = getFileSystemMock(injector); + fs.readText = (filename: string, options?: IReadFileOptions | string): string => { + return `ASSETCATALOG_COMPILER_APPICON_NAME= ;`; + }; + + let expected = { + 'ASSETCATALOG_COMPILER_APPICON_NAME': '' + }; + + assertPropertyValues(expected, injector); + }); + }); +}); From 649637fad78851ce6db90947639369617193fad2 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Fri, 19 May 2017 09:43:17 +0300 Subject: [PATCH 029/212] Fix posting notification to iOS Simulator (#2824) In case there are spaces in path to the executable (node) or in the args, we are unable to send notification. Replace the usage of childProcess.exec with childProcess.spawn, which handles the spaces correctly. --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index 2388145d3c..13f21c260f 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 2388145d3cca720ad2ab9cff857effa492c4d341 +Subproject commit 13f21c260f2026022ccb43e2eb9ca883243f1a30 From 94a31325913554ba0ae34b1e68d7e4285e5f5511 Mon Sep 17 00:00:00 2001 From: yyosifov Date: Mon, 22 May 2017 09:15:34 +0300 Subject: [PATCH 030/212] Add .html to the fast-sync files (#2823) --- lib/providers/livesync-provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/providers/livesync-provider.ts b/lib/providers/livesync-provider.ts index deb7ca717b..0724a34fd0 100644 --- a/lib/providers/livesync-provider.ts +++ b/lib/providers/livesync-provider.ts @@ -10,7 +10,7 @@ export class LiveSyncProvider implements ILiveSyncProvider { private $childProcess: IChildProcess, private $options: IOptions) { } - private static FAST_SYNC_FILE_EXTENSIONS = [".css", ".xml"]; + private static FAST_SYNC_FILE_EXTENSIONS = [".css", ".xml", ".html"]; private deviceSpecificLiveSyncServicesCache: IDictionary = {}; public get deviceSpecificLiveSyncServices(): IDictionary { From d3f06ab95afadcd3df60493d0a9280bfbc23fbb1 Mon Sep 17 00:00:00 2001 From: yyosifov Date: Mon, 22 May 2017 10:39:21 +0300 Subject: [PATCH 031/212] =?UTF-8?q?Add=20awaitWriteFinish=20option=20to=20?= =?UTF-8?q?choki=20in=20livesync=20to=20wait=20file=20to=20be=20r=E2=80=A6?= =?UTF-8?q?=20(#2817)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add awaitWriteFinish option to choki in livesync to wait file to be ready to be read * set the threshold for 500ms --- lib/services/livesync/livesync-service.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 5f57d8dcc8..7225477b47 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -106,7 +106,11 @@ class LiveSyncService implements ILiveSyncService { } } - let watcher = choki.watch(pattern, { ignoreInitial: true, cwd: syncWorkingDirectory }).on("all", (event: string, filePath: string) => { + let watcher = choki.watch(pattern, { ignoreInitial: true, cwd: syncWorkingDirectory, + awaitWriteFinish: { + stabilityThreshold: 500, + pollInterval: 100 + }, }).on("all", (event: string, filePath: string) => { that.$dispatcher.dispatch(async () => { try { filePath = path.join(syncWorkingDirectory, filePath); From 9c6bf82001284543198a48bf38cf6eca6285c0b1 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Mon, 22 May 2017 10:57:12 +0300 Subject: [PATCH 032/212] Fix unit tests on non-macOS (#2827) Currently unit tests from master branch are failing on Windows and Linux. Fix them by applying the following: - Fix expected path in entitlements test - use `path.join` instead of hardcoded `/` - on Windows the path separator is `\` - Ignore tests for merging xcconfig files as they can work only on macOS, where ruby is installed. The tests will be ran only in case you are executing them on macOS. - Require "colors" module in test-bootstrap. During test execution I've noticed some "undefined" statements printed on the terminal. It turned out these are `logger.warn` statements. However the logic in the logger is to print `.yellow`. When `colors` is not required, the strings do not have `yellow` property, so instead of seeing the real message, the loggger has been printing "undefined". --- test/ios-entitlements-service.ts | 2 +- test/ios-project-service.ts | 6 +++++- test/test-bootstrap.ts | 11 +++++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/test/ios-entitlements-service.ts b/test/ios-entitlements-service.ts index 23fbc93621..b3e99093be 100644 --- a/test/ios-entitlements-service.ts +++ b/test/ios-entitlements-service.ts @@ -60,7 +60,7 @@ describe("IOSEntitlements Service Tests", () => { describe("Ensure paths constructed are correct", () => { it("Ensure destination entitlements relative path is calculated correctly.", () => { - const expected = "testApp/testApp.entitlements"; + const expected = path.join("testApp", "testApp.entitlements"); let actual = iOSEntitlementsService.getPlatformsEntitlementsRelativePath(projectData); assert.equal(actual, expected); }); diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index 51f45cb151..d0c97cde5f 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -751,6 +751,10 @@ describe("iOS Project Service Signing", () => { }); describe("Merge Project XCConfig files", () => { + if (require("os").platform() !== "darwin") { + console.log("Skipping 'Merge Project XCConfig files' tests. They can work only on macOS"); + return; + } const assertPropertyValues = (expected: any, xcconfigPath: string, injector: IInjector) => { let service = injector.resolve('xCConfigService'); _.forOwn(expected, (value, key) => { @@ -782,7 +786,7 @@ describe("Merge Project XCConfig files", () => { appResourcesXcconfigPath = path.join(projectData.projectDir, constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME, "iOS", "build.xcconfig"); - appResourceXCConfigContent = `CODE_SIGN_IDENTITY = iPhone Distribution + appResourceXCConfigContent = `CODE_SIGN_IDENTITY = iPhone Distribution // To build for device with XCode 8 you need to specify your development team. More info: https://developer.apple.com/library/prerelease/content/releasenotes/DeveloperTools/RN-Xcode/Introduction.html // DEVELOPMENT_TEAM = YOUR_TEAM_ID; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; diff --git a/test/test-bootstrap.ts b/test/test-bootstrap.ts index 30f03b8fba..f9cb09e9ac 100644 --- a/test/test-bootstrap.ts +++ b/test/test-bootstrap.ts @@ -9,13 +9,16 @@ const cliGlobal = global; cliGlobal._ = require("lodash"); cliGlobal.$injector = require("../lib/common/yok").injector; +// Requiring colors will modify the prototype of String +// We need it as in some places we use ., which is undefined when colors is not required +// So we sometimes miss warnings in the tests as we receive "undefined". +require("colors"); + use(require("chai-as-promised")); $injector.register("analyticsService", { - trackException: (): { wait(): void } => { - return { - wait: () => undefined - }; + trackException: async (exception: any, message: string): Promise => { + // Intentionally left blank. } }); From 4b8abedeb42a112a72e2036cebbd6ea5440447e1 Mon Sep 17 00:00:00 2001 From: yyosifov Date: Tue, 23 May 2017 13:56:14 +0300 Subject: [PATCH 033/212] Add a section for Entitlements (#2834) * Add a section for Entitlements Adding a small section describing the default behavior of the Entitlements. * Fix the path to the entitlements file --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 88fb5de239..a5c1265eb4 100644 --- a/README.md +++ b/README.md @@ -259,6 +259,12 @@ The NativeScript CLI respects any platform configuration files placed inside `ap Additionaly, you can modify `app/App_Resources/build.xcconfig` and `app/App_Resources/app.gradle` for adding/removing additional build properties for iOS and Android, respectively. +### Modifying Entitlements File (iOS only) + +To specify which Capabilities are required by your App - Maps, Push Notifications, Wallet and etc. you can add or edit the `app.entitlements` file placed inside `app/App_Resources/iOS`. When building the project, the default `app/App_Resources/iOS/app.entitlements` file gets merged with all Plugins entitlement files and a new `yourAppName.entitlements` is created in the platforms directory. The path would be `app/platforms/ios//.entitlements` and will be linked in the `build.xcconfig` file. + +You can always override the generated entitlements file, by pointing to your own entitlements file by setting the `CODE_SIGN_ENTITLEMENTS` property in the `app/App_Resources/iOS/build.xcconfig` file. + [Back to Top][1] ## Prepare for Build From b077a963188152337218641567eae528305c0452 Mon Sep 17 00:00:00 2001 From: yyosifov Date: Tue, 23 May 2017 15:57:32 +0300 Subject: [PATCH 034/212] Prevent starting a simulator when build --forDevice (#2833) * Pass option to build not to launch the Simulator as it's build only * Updating the mobile-cli-lib version --- lib/common | 2 +- lib/services/ios-project-service.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/common b/lib/common index 13f21c260f..ec1350f1f1 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 13f21c260f2026022ccb43e2eb9ca883243f1a30 +Subproject commit ec1350f1f163a2494028c049f923d26c091ce6c3 diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 4ffe535f31..868122d5ff 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -329,7 +329,8 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ // build only for device specific architecture if (!buildConfig.release && !buildConfig.architectures) { - await this.$devicesService.initialize({ platform: this.$devicePlatformsConstants.iOS.toLowerCase(), deviceId: buildConfig.device }); + await this.$devicesService.initialize({ platform: this.$devicePlatformsConstants.iOS.toLowerCase(), deviceId: buildConfig.device, + isBuildForDevice: true }); let instances = this.$devicesService.getDeviceInstances(); let devicesArchitectures = _(instances) .filter(d => this.$mobileHelper.isiOSPlatform(d.deviceInfo.platform) && d.deviceInfo.activeArchitecture) From 31f8c94b3318560064976762525eb9599b5827fc Mon Sep 17 00:00:00 2001 From: yyosifov Date: Tue, 23 May 2017 17:23:59 +0300 Subject: [PATCH 035/212] Don't create/update the bash_profile when turning autocomplete on (#2840) --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index ec1350f1f1..d689e03383 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit ec1350f1f163a2494028c049f923d26c091ce6c3 +Subproject commit d689e033835297c5e03b2457c23d06bc3c999abd From ff88c9b516e5acde8eecc86dd95b85b4a59accdf Mon Sep 17 00:00:00 2001 From: yyosifov Date: Tue, 23 May 2017 18:43:04 +0300 Subject: [PATCH 036/212] After fix .html can again be synced correctly without restart (#2839) --- lib/providers/livesync-provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/providers/livesync-provider.ts b/lib/providers/livesync-provider.ts index 88034a2323..a175688275 100644 --- a/lib/providers/livesync-provider.ts +++ b/lib/providers/livesync-provider.ts @@ -10,7 +10,7 @@ export class LiveSyncProvider implements ILiveSyncProvider { private $childProcess: IChildProcess, private $options: IOptions) { } - private static FAST_SYNC_FILE_EXTENSIONS = [".css", ".xml"]; + private static FAST_SYNC_FILE_EXTENSIONS = [".css", ".xml", ".html"]; private deviceSpecificLiveSyncServicesCache: IDictionary = {}; public get deviceSpecificLiveSyncServices(): IDictionary { From b4ffe76149beedd7f6e5650c006839871e1a841c Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Fri, 26 May 2017 11:00:49 +0300 Subject: [PATCH 037/212] Fix ios logs with logLevel INFO (#2843) Whenever an ios device log is triggered with logLevel INFO `console.log` statements are not seen on the console for ios 8 and ios 9 devices due to a difference in their formats. Fix this and add tests. --- lib/nativescript-cli-lib-bootstrap.ts | 1 - lib/services/ios-log-filter.ts | 8 +- test/services/ios-log-filter.ts | 131 ++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 test/services/ios-log-filter.ts diff --git a/lib/nativescript-cli-lib-bootstrap.ts b/lib/nativescript-cli-lib-bootstrap.ts index 505c1a76fc..ae0721440d 100644 --- a/lib/nativescript-cli-lib-bootstrap.ts +++ b/lib/nativescript-cli-lib-bootstrap.ts @@ -10,7 +10,6 @@ $injector.requirePublicClass("deviceEmitter", "./common/appbuilder/device-emitte $injector.requirePublicClass("deviceLogProvider", "./common/appbuilder/device-log-provider"); $injector.requirePublicClass("localBuildService", "./services/local-build-service"); $injector.requirePublicClass("debugService", "./services/debug-service"); -$injector.require("iOSLogFilter", "./common/mobile/ios/ios-log-filter"); // We need this because some services check if (!$options.justlaunch) to start the device log after some operation. // We don't want this behaviour when the CLI is required as library. diff --git a/lib/services/ios-log-filter.ts b/lib/services/ios-log-filter.ts index 8f105a33ca..55aed86a16 100644 --- a/lib/services/ios-log-filter.ts +++ b/lib/services/ios-log-filter.ts @@ -4,18 +4,18 @@ import { cache } from "../common/decorators"; import * as iOSLogFilterBase from "../common/mobile/ios/ios-log-filter"; export class IOSLogFilter extends iOSLogFilterBase.IOSLogFilter implements Mobile.IPlatformLogFilter { - protected infoFilterRegex = /^.*?(:.*?((CONSOLE LOG|JS ERROR).*?)|(:.*?)|(:.*?))$/im; + protected infoFilterRegex = /^.*?(:.*?(((?:CONSOLE|JS) (?:LOG|ERROR)).*?))$/im; private partialLine: string = null; constructor($loggingLevels: Mobile.ILoggingLevels, private $fs: IFileSystem, private $projectData: IProjectData) { - super($loggingLevels); - } + super($loggingLevels); + } public filterData(data: string, logLevel: string, pid?: string): string { - data = super.filterData(data, logLevel, pid); + data = super.filterData(data, logLevel, pid); if (pid && data && data.indexOf(`[${pid}]`) === -1) { return null; } diff --git a/test/services/ios-log-filter.ts b/test/services/ios-log-filter.ts new file mode 100644 index 0000000000..8b4f317f9f --- /dev/null +++ b/test/services/ios-log-filter.ts @@ -0,0 +1,131 @@ +import { IOSLogFilter } from "../../lib/services/ios-log-filter"; +import { Yok } from "../../lib/common/yok"; +import { LoggingLevels } from "../../lib/common/mobile/logging-levels"; +import * as assert from "assert"; + +function createTestInjector(): IInjector { + const testInjector = new Yok(); + testInjector.register("loggingLevels", LoggingLevels); + testInjector.register("fs", { + exists: () => false + }); + testInjector.register("projectData", { + initializeProjectData: () => { /* empty */ }, + projectDir: "test" + }); + + return testInjector; +} + +describe("iOSLogFilter", () => { + let testInjector: IInjector; + let logFilter: Mobile.IPlatformLogFilter; + const testData = [ + { + version: 9, + originalDataArr: [ + "May 24 15:54:38 Dragons-iPhone backboardd(BaseBoard)[62] : Unable to bootstrap_look_up port with name .gsEvents: unknown error code (1102)", + "May 24 15:54:51 Dragons-iPhone locationd[67] : Client com.apple.springboard disconnected", + "May 24 14:44:59 iPad-90 NativeScript250[790] : CONSOLE LOG file:///app/modules/homeView/homeView.component.js:13:24: CUSTOM CONSOLE LOG", + "May 24 14:44:59 iPad-90 NativeScript250[790] : CONSOLE LOG file:///app/modules/homeView/homeView.component.js:13:24: CUSTOM CONSOLE LOG", + "May 24 14:44:59 iPad-90 mobile_installation_proxy[355] : 0x1f197000 LoadInfoPlist: Failed to create CFBundle from URL file:///private/var/mobile/Containers/Bundle/Application/EB4866CC-25D2-4A3B-AA6C-70FFA08B908E/NativeScript143.app", + "May 24 14:44:59 iPad-90 mobile_installation_proxy[355] : 0x1f197000 LoadInfoPlist: Failed to create CFBundle from URL file:///private/var/mobile/Containers/Bundle/Application/0DA02818-DCAE-407C-979D-D55F4F36F8D2/NativeScript300.app", + " May 24 14:44:59 iPad-90 mobile_installation_proxy[355] : 0x1f197000 LoadInfoPlist: Failed to create CFBundle from URL file:///private/var/mobile/Containers/Bundle/Application/B0EE9362-7BDD-4FF2-868F-857B76D9D8D3/Cordova370.app", + " May 24 14:44:59 iPad-90 NativeScript250[790] : CONSOLE ERROR file:///app/tns_modules/@angular/core/bundles/core.umd.js:3472:32: EXCEPTION: Uncaught (in promise): Error: CUSTOM EXCEPTION", + "" + ], + infoExpectedArr: [ + null, + null, + "CONSOLE LOG file:///app/modules/homeView/homeView.component.js:13:24: CUSTOM CONSOLE LOG", + "CONSOLE LOG file:///app/modules/homeView/homeView.component.js:13:24: CUSTOM CONSOLE LOG", + null, + null, + null, + "CONSOLE ERROR file:///app/tns_modules/@angular/core/bundles/core.umd.js:3472:32: EXCEPTION: Uncaught (in promise): Error: CUSTOM EXCEPTION", + null + ] + }, + { + version: 10, + originalDataArr: [ + "May 24 15:54:52 Dragons-iPhone apsd(PersistentConnection)[90] : 2017-05-24 15:54:52 +0300 apsd[90]: performing call back", + "May 24 15:54:52 Dragons-iPhone NativeScript250(NativeScript)[356] : CONSOLE LOG file:///app/modules/homeView/homeView.component.js:13:24: CUSTOM CONSOLE LOG", + "May 24 15:54:52 Dragons-iPhone NativeScript250(NativeScript)[356] : CONSOLE ERROR file:///app/tns_modules/@angular/core/bundles/core.umd.js:3472:32: EXCEPTION: Uncaught (in promise): Error: CUSTOM EXCEPTION", + " May 24 15:54:52 Dragons-iPhone NativeScript250(NativeScript)[356] : CONSOLE ERROR file:///app/tns_modules/@angular/core/bundles/core.umd.js:3477:36: ORIGINAL STACKTRACE:", + " May 24 15:54:52 Dragons-iPhone NativeScript250(NativeScript)[356] : CONSOLE ERROR file:///app/tns_modules/@angular/core/bundles/core.umd.js:3478:36: resolvePromise@file:///app/tns_modules/nativescript-angular/zone-js/dist/zone-nativescript.js:416:40", + "resolvePromise@file:///app/tns_modules/nativescript-angular/zone-js/dist/zone-nativescript.js:401:31", + "file:///app/tns_modules/nativescript-angular/zone-js/dist/zone-nativescript.js:449:31", + "invokeTask@file:///app/tns_modules/nativescript-angular/zone-js/dist/zone-nativescript.js:223:42", + "onInvokeTask@file:///app/tns_modules/@angular/core/bundles/core.umd.js:4382:51", + "invokeTask@file:///app/tns_modules/nativescript-angular/zone-js/dist/zone-nativescript.js:222:54", + "runTask@file:///app/tns_modules/nativescript-angular/zone-js/dist/zone-nativescript.js:123:57", + "drainMicroTaskQueue@file:///app/tns_modules/nativescript-angular/zone-js/dist/zone-nativescript.js:355:42", + "promiseReactionJob@[native code]", + "UIApplicationMain@[native code]", + "start@file:///app/tns_modules/tns-core-modules/application/application.js:251:26", + "bootstrapApp@file:///app/tns_module", + "" + ], + infoExpectedArr: [ + null, + "CONSOLE LOG file:///app/modules/homeView/homeView.component.js:13:24: CUSTOM CONSOLE LOG", + "CONSOLE ERROR file:///app/tns_modules/@angular/core/bundles/core.umd.js:3472:32: EXCEPTION: Uncaught (in promise): Error: CUSTOM EXCEPTION", + "CONSOLE ERROR file:///app/tns_modules/@angular/core/bundles/core.umd.js:3477:36: ORIGINAL STACKTRACE:", + "CONSOLE ERROR file:///app/tns_modules/@angular/core/bundles/core.umd.js:3478:36: resolvePromise@file:///app/tns_modules/nativescript-angular/zone-js/dist/zone-nativescript.js:416:40", + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + } + ]; + const infoLogLevel = "INFO"; + const fullLogLevel = "FULL"; + + beforeEach(() => { + testInjector = createTestInjector(); + logFilter = testInjector.resolve(IOSLogFilter); + }); + + describe("filterData", () => { + testData.forEach(data => { + it(`returns correct data when logLevel is ${fullLogLevel} on iOS ${data.version} and all data is passed at once`, () => { + const actualData = logFilter.filterData(data.originalDataArr.join("\n"), fullLogLevel, null); + const actualArr = actualData.split("\n").map(line => line.trim()); + const expectedArr = data.originalDataArr.map(line => line.trim()); + assert.deepEqual(actualArr, expectedArr); + }); + + it(`returns correct data when logLevel is ${fullLogLevel} on iOS ${data.version} and data is passed one line at a time`, () => { + data.originalDataArr.forEach(line => { + const actualData = logFilter.filterData(line, fullLogLevel, null); + assert.deepEqual(actualData.trim(), line.trim()); + }); + }); + + it(`parses data incorrectly when logLevel is ${infoLogLevel} on iOS ${data.version} and all data is passed at once`, () => { + const actualData = logFilter.filterData(data.originalDataArr.join("\n"), infoLogLevel, null); + const actualArr = actualData.split("\n").map(line => line.trim()); + const expectedArr = ["CONSOLE LOG file:///app/modules/homeView/homeView.component.js:13:24: CUSTOM CONSOLE LOG", ""]; + assert.deepEqual(actualArr, expectedArr); + }); + + it(`returns correct data when logLevel is ${infoLogLevel} on iOS ${data.version} and data is passed one line at a time`, () => { + data.originalDataArr.forEach((line, index) => { + const actualData = logFilter.filterData(line, infoLogLevel, null); + const expectedData = data.infoExpectedArr[index]; + assert.deepEqual(actualData && actualData.trim(), expectedData && expectedData); + }); + }); + }); + }); +}); From 756af30eea83f146c6e1b4476dd95ff33e1570a1 Mon Sep 17 00:00:00 2001 From: yyosifov Date: Fri, 26 May 2017 13:33:14 +0300 Subject: [PATCH 038/212] Increase version to 3.0.2 and add changelog (#2849) * Fix UserId passing to Analytics Service * Add changelog for 3.0.2 --- CHANGELOG.md | 8 +++++++- package.json | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39f25cbcc6..fb9671f3e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,18 @@ NativeScript CLI Changelog ================ +3.0.2 (2017, May 26) +== + +### Fixed + +* Removed restart of the App if HTML/CSS file was modified. The issue is fixed in the Modules **3.0.1** and we can again just refresh the app on change. + 3.0.1 (2017, May 11) == ### Fixed -* [Fix #2780](https://github.com/NativeScript/nativescript-cli/issues/2780): CLI tool doesn't restart app if HTML/CSS file was modified * [Fix #2732](https://github.com/NativeScript/nativescript-cli/issues/2732): Livesync crashes app every OTHER time on iOS with 3.0.0-rc.2 * [Fix #2764](https://github.com/NativeScript/nativescript-cli/issues/2764): Error when executing "tns run ios" with 3.0 on a project that is located in a directory path with "spaces" diff --git a/package.json b/package.json index 559d82b4d3..926c65c928 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "3.0.1", + "version": "3.0.2", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { From 3ef06f76e96aafaf888ed4124e564215ef246d8c Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Fri, 26 May 2017 15:35:26 +0300 Subject: [PATCH 039/212] Fix deviceLogs emitted for all attached ios devices (#2845) --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index d689e03383..32d475118d 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit d689e033835297c5e03b2457c23d06bc3c999abd +Subproject commit 32d475118d5039d3761a244d7f0991f05a689fb8 From bea33e9e663b3d938654a54ac0a3a6c38038267d Mon Sep 17 00:00:00 2001 From: PetyaSotirova Date: Fri, 26 May 2017 16:19:47 +0300 Subject: [PATCH 040/212] Remove reference to Google groups in README (#2838) Changing it with a link to the forums. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a5c1265eb4..0f86c68351 100644 --- a/README.md +++ b/README.md @@ -383,7 +383,7 @@ tns doctor This command prints warnings about current configuration issues and provides basic information about how to resolve them. -If addressing the configuration issues does not resolve your problem, you can [report an issue](https://github.com/NativeScript/nativescript-cli/blob/master/CONTRIBUTING.md#report-an-issue) or [post in the NativeScript page in Google Groups](https://groups.google.com/forum/#!forum/nativescript). +If addressing the configuration issues does not resolve your problem, you can [report an issue](https://github.com/NativeScript/nativescript-cli/blob/master/CONTRIBUTING.md#report-an-issue) or [post in the NativeScript forums](https://discourse.nativescript.org/). [Back to Top][1] From bfdee171538b0ee7c246f1270ba403150fb6c004 Mon Sep 17 00:00:00 2001 From: yyosifov Date: Mon, 29 May 2017 09:00:09 +0300 Subject: [PATCH 041/212] Fix permissions on ~/.local/share/.nativescript-cli with Sudo runs (#2848) * Fix permissions on ~/.local/share with Sudo runs * Update the common library --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index 32d475118d..298afb9929 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 32d475118d5039d3761a244d7f0991f05a689fb8 +Subproject commit 298afb9929690e83bb929fb4f2487b630084a442 From a1932ce728a0858634544ef1aaa59c1e1cddce65 Mon Sep 17 00:00:00 2001 From: pkoleva Date: Mon, 29 May 2017 11:32:12 +0300 Subject: [PATCH 042/212] Updated md files for the debug command. (#2836) * Updated md files for the debug command. * In Debug docs changed verbs to agreed form and added prerequisites for Android command --- .../project/testing/debug-android.md | 44 ++++++++----------- docs/man_pages/project/testing/debug-ios.md | 44 +++++++++---------- docs/man_pages/project/testing/debug.md | 9 ++-- 3 files changed, 41 insertions(+), 56 deletions(-) diff --git a/docs/man_pages/project/testing/debug-android.md b/docs/man_pages/project/testing/debug-android.md index 0020bb9586..c1c2021fd3 100644 --- a/docs/man_pages/project/testing/debug-android.md +++ b/docs/man_pages/project/testing/debug-android.md @@ -3,54 +3,46 @@ debug android Usage | Synopsis ---|--- -Deploy on device, run the app start Chrome DevTools, and attach the debugger | `$ tns debug android` -Deploy on device, run the app and stop at the first code statement | `$ tns debug android --debug-brk [--device ] [--debug-port ] [--timeout ]` -Deploy in the native emulator, run the app and stop at the first code statement | `$ tns debug android --debug-brk --emulator [] [--timeout ]` -Attach the debug tools to a running app on device | `$ tns debug android --start [--device ] [--debug-port ] [--timeout ]` -Attach the debug tools to a running app in the native emulator | `$ tns debug android --start --emulator [] [--timeout ]` -Detach the debug tools | `$ tns debug android --stop` +Deploy on device/emulator, run the app, follow generated link to use in Chrome Developer Tools, and attach the debugger | `$ tns debug android` +Deploy on device/emulator, run the app and stop at the first code statement | `$ tns debug android --debug-brk [--device ] [--timeout ]` +Deploy in the native emulator, run the app and stop at the first code statement | `$ tns debug android --debug-brk --emulator [--timeout ]` +Attach the debug tools to a running app on device/emulator | `$ tns debug android --start [--device ] [--timeout ]` +Attach the debug tools to a running app in the native emulator | `$ tns debug android --start --emulator [--timeout ]` Prepares, builds and deploys the project when necessary. Debugs your project on a connected device or emulator. -While debugging, prints the output from the application in the console and watches for changes in your code. Once a change is detected, it synchronizes the change with all selected devices and restarts/refreshes the application. +While debugging, prints the output from the application in the console and watches for changes in your code. Once a change is detected, it synchronizes the change with the selected device and restarts the application. ### Options -* `--device` - Specifies a connected device on which to debug the app. -* `--emulator` - Specifies that you want to debug the app in the native Android emulator from the Android SDK. -* `--debug-brk` - Prepares, builds and deploys the application package on a device or in an emulator, launches the Chrome DevTools of your Chrome browser and stops at the first code statement. +* `--device` - Specifies a connected device/emulator on which to debug the app. +* `--emulator` - Specifies that you want to debug the app in the native Android emulator. +* `--debug-brk` - Prepares, builds and deploys the application package on a device/emulator, generates a link for Chrome Developer Tools and stops at the first code statement. * `--start` - Attaches the debug tools to a deployed and running app. -* `--stop` - Detaches the debug tools. -* `--debug-port` - Sets a new port on which to attach the debug tools. -* `--timeout` - Sets the number of seconds that the NativeScript CLI will wait for the debugger to boot. If not set, the default timeout is 90 seconds. +* `--timeout` - Sets the number of seconds that the NativeScript CLI will wait for the emulator/device to boot. If not set, the default timeout is 90 seconds. * `--no-watch` - If set, changes in your code will not be reflected during the execution of this command. * `--clean` - If set, forces rebuilding the native application. ### Attributes -* `` is the index or name of the target device as listed by `$ tns device` -* `` is an accessible port on the device to which you want to attach the debugging tools. -* `` is any valid combination of options as listed by `$ tns help emulate android` +* `` is the device identifier or name of the target device as listed by `$ tns device android` <% if(isHtml) { %> ### Prerequisites -* You must have Chrome installed on your system.
If you are using a non-standard named Chrome app on an OS X system (for example, a nightly Canary update), you need to set this name in the `ANDROID_DEBUG_UI_MAC` setting in the NativeScript [config.json](file:///<%= #{config.getConfigPath(config)} %>). +* You must have Chrome installed on your system.
+ ### Related Commands Command | Description ----------|---------- -[build android](build-android.html) | Builds the project for Android and produces an APK that you can manually deploy on device or in the native emulator. -[build ios](build-ios.html) | Builds the project for iOS and produces an APP or IPA that you can manually deploy in the iOS Simulator or on device, respectively. [build](build.html) | Builds the project for the selected target platform and produces an application package that you can manually deploy on device or in the native emulator. -[debug ios](debug-ios.html) | Debugs your project on a connected iOS device or in a native emulator. +[build android](build-android.html) | Builds the project for Android and produces an APK that you can manually deploy on device or in the native emulator. [debug](debug.html) | Debugs your project on a connected device or in a native emulator. +[debug ios](debug-ios.html) | Debugs your project on a connected iOS device or in a native emulator. [deploy](deploy.html) | Builds and deploys the project to a connected physical or virtual device. -[emulate android](emulate-android.html) | Builds the specified project and runs it in a native Android emulator. -[emulate ios](emulate-ios.html) | Builds the specified project and runs it in the native iOS Simulator. -[emulate](emulate.html) | You must run the emulate command with a related command. -[run android](run-android.html) | Runs your project on a connected Android device or in a native Android emulator, if configured. -[run ios](run-ios.html) | Runs your project on a connected iOS device or in the iOS Simulator, if configured. +[device](../../device/device.html) | Lists all connected devices/emulators. +[device android](../../device/device-android.html) | Lists all connected devices/emulators for android. [run](run.html) | Runs your project on a connected device or in the native emulator for the selected platform. +[run android](run-android.html) | Runs your project on a connected Android device or in a native Android emulator, if configured. [test init](test-init.html) | Configures your project for unit testing with a selected framework. [test android](test-android.html) | Runs the tests in your project on Android devices or native emulators. -[test ios](test-ios.html) | Runs the tests in your project on iOS devices or the iOS Simulator. <% } %> \ No newline at end of file diff --git a/docs/man_pages/project/testing/debug-ios.md b/docs/man_pages/project/testing/debug-ios.md index 59766b276e..cd0fb05da2 100644 --- a/docs/man_pages/project/testing/debug-ios.md +++ b/docs/man_pages/project/testing/debug-ios.md @@ -3,14 +3,14 @@ debug ios Usage | Synopsis ---|--- -Deploy on device, run the app, start Safari Web Inspector and attach the debugger | `$ tns debug ios` -Deploy on device, run the app and stop at the first code statement | `$ tns debug ios --debug-brk [--device ] [--no-client]` -Deploy in the iOS Simulator, run the app and stop at the first code statement | `$ tns debug ios --debug-brk --emulator [] [--no-client]` -Attach the debug tools to a running app on device | `$ tns debug ios --start [--device ] [--no-client]` -Attach the debug tools to a running app in the iOS Simulator | `$ tns debug ios --start --emulator [] [--no-client]` +Deploy on device/simulator, run the app, start Safari Web Inspector and attache the debugger | `$ tns debug ios` +Deploy on device/simulator, run the app and stop at the first code statement | `$ tns debug ios --debug-brk [--device ] [--no-client]` +Deploy in the iOS simulator, run the app and stop at the first code statement | `$ tns debug ios --debug-brk --emulator [--no-client]` +Attach the debug tools to a running app on specified device or simulator| `$ tns debug ios --start [--device ] [--no-client]` +Attach the debug tools to a running app in the iOS simulator | `$ tns debug ios --start --emulator [--no-client]` -Prepares, builds and deploys the project when necessary. Debugs your project on a connected device or in the iOS Simulator. <% if(isHtml) { %>Any debugging traffic is forwarded on port 8080 from the device to the local machine.<% } %> -While debugging, prints the output from the application in the console and watches for changes in your code. Once a change is detected, it synchronizes the change with all selected devices and restarts/refreshes the application. +Prepares, builds and deploys the project when necessary. Debugs your project on a connected device or in the iOS simulator. <% if(isHtml) { %>Any debugging traffic is forwarded to port 8080( or the next available one) from the device to the local machine.<% } %> +While debugging, prints the output from the application in the console and watches for changes in your code. Once a change is detected, it synchronizes the change with the selected device and restarts the application. <% if(isConsole && (isWindows || isLinux)) { %>WARNING: You can run this command only on OS X systems. To view the complete help for this command, run `$ tns help debug ios`<% } %> @@ -18,23 +18,23 @@ While debugging, prints the output from the application in the console and watch <% if(isHtml) { %>> <% } %>IMPORTANT: Before building for iOS device, verify that you have configured a valid pair of certificate and provisioning profile on your OS X system. <% if(isHtml) { %>For more information, see [Obtaining Signing Identities and Downloading Provisioning Profiles](https://developer.apple.com/library/mac/recipes/xcode_help-accounts_preferences/articles/obtain_certificates_and_provisioning_profiles.html).<% } %> ### Options -* `--device` - Specifies a connected device on which to run the app. +* `--device` - Specifies a connected device or iOS simulator on which to run the app. * `--emulator` - Indicates that you want to debug your app in the iOS simulator. -* `--debug-brk` - Prepares, builds and deploys the application package on a device or in an emulator, runs the app, launches the developer tools of your Safari browser and stops at the first code statement. +* `--debug-brk` - Prepares, builds and deploys the application package on a device or in a simulator, runs the app, launches the developer tools of your Safari browser and stops at the first code statement. * `--start` - Attaches the debug tools to a deployed and running app and launches the developer tools of your Safari browser. -* `--no-client` - If set, the NativeScript CLI attaches the debug tools but does not launch the developer tools in Safari. -* `--timeout` - Sets the number of seconds that NativeScript CLI will wait for the debugger to boot. If not set, the default timeout is 90 seconds. +* `--no-client` - If set, the NativeScript CLI attaches the debug tools but does not launch the developer tools in Safari. Could be used on already started Safari Web Inspector. +* `--timeout` - Sets the number of seconds that NativeScript CLI will wait for the simulator/device to boot. If not set, the default timeout is 90 seconds. * `--no-watch` - If set, changes in your code will not be reflected during the execution of this command. * `--clean` - If set, forces rebuilding the native application. +* `--chrome` - Allows debugging in Chrome Developer Tools. If set Safari Web Inspector is not started and debugging is attached to Chrome Developer Tools. ### Attributes -* `` is the index or name of the target device as listed by `$ tns device` -* `` is any valid combination of options as listed by `$ tns help emulate ios` +* `` is the device identifier of the target device as listed by `$ tns device ios` <% } %> <% if(isHtml) { %> ### Prerequisite -* If you want to debug in the iOS Simulator, you must have Xcode 6 or later installed on your system. +* If you want to debug in the iOS simulator, you must have Xcode 6 or later installed on your system. ### Command Limitations @@ -44,19 +44,15 @@ While debugging, prints the output from the application in the console and watch Command | Description ----------|---------- -[build android](build-android.html) | Builds the project for Android and produces an APK that you can manually deploy on device or in the native emulator. -[build ios](build-ios.html) | Builds the project for iOS and produces an APP or IPA that you can manually deploy in the iOS Simulator or on device, respectively. [build](build.html) | Builds the project for the selected target platform and produces an application package that you can manually deploy on device or in the native emulator. -[debug android](debug-android.html) | Debugs your project on a connected Android device or in a native emulator. +[build ios](build-ios.html) | Builds the project for iOS and produces an APP or IPA that you can manually deploy in the iOS simulator or on device, respectively. [debug](debug.html) | Debugs your project on a connected device or in a native emulator. +[debug android](debug-android.html) | Debugs your project on a connected Android device or in a native emulator. [deploy](deploy.html) | Builds and deploys the project to a connected physical or virtual device. -[emulate android](emulate-android.html) | Builds the specified project and runs it in a native Android emulator. -[emulate ios](emulate-ios.html) | Builds the specified project and runs it in the native iOS Simulator. -[emulate](emulate.html) | You must run the emulate command with a related command. -[run android](run-android.html) | Runs your project on a connected Android device or in a native Android emulator, if configured. -[run ios](run-ios.html) | Runs your project on a connected iOS device or in the iOS Simulator, if configured. +[device](../../device/device.html) | Lists all connected devices/emulators. +[device ios](../../device/device-ios.html) | Lists all connected devices/simulators for iOS. [run](run.html) | Runs your project on a connected device or in the native emulator for the selected platform. +[run ios](run-ios.html) | Runs your project on a connected iOS device or in the iOS simulator, if configured. [test init](test-init.html) | Configures your project for unit testing with a selected framework. -[test android](test-android.html) | Runs the tests in your project on Android devices or native emulators. -[test ios](test-ios.html) | Runs the tests in your project on iOS devices or the iOS Simulator. +[test ios](test-ios.html) | Runs the tests in your project on iOS devices or the iOS simulator. <% } %> \ No newline at end of file diff --git a/docs/man_pages/project/testing/debug.md b/docs/man_pages/project/testing/debug.md index f0d8d6bde0..f9a7123d0f 100644 --- a/docs/man_pages/project/testing/debug.md +++ b/docs/man_pages/project/testing/debug.md @@ -5,12 +5,12 @@ Usage | Synopsis ---|--- <% if((isConsole && isMacOS) || isHtml) { %>General | `$ tns debug `<% } %><% if(isConsole && (isLinux || isWindows)) { %>General | `$ tns debug android`<% } %> -Debugs your project on a connected device or in a native emulator. <% if(isMacOS) { %>You must specify the target platform on which you want to debug.<% } %> The command will prepare, build and deploy the app when necessary. By default listens for changes in your code, synchronizes those changes and refreshes the selected device. +Debugs your project on a connected device or in a native emulator. <% if(isMacOS) { %>You must specify the target platform on which you want to debug.<% } %> The command will prepare, build and deploy the app when necessary. By default listens for changes in your code, synchronizes those changes and restarts the app on the targeted device. <% if((isConsole && isMacOS) || isHtml) { %>### Attributes -`` is the target mobile platform for which you want to debug your project. You can set the following target platforms. +`` is the target mobile platform for which you want to debug your project. You can set the following target platforms: * `android` - Debugs your project on a connected Android device or Android emulator. -* `ios` - Debugs your project on a connected iOS device or in a native iOS emulator.<% } %> +* `ios` - Debugs your project on a connected iOS device or in a native iOS simulator.<% } %> <% if(isHtml) { %> ### Command Limitations @@ -27,9 +27,6 @@ Command | Description [debug android](debug-android.html) | Debugs your project on a connected Android device or in a native emulator. [debug ios](debug-ios.html) | Debugs your project on a connected iOS device or in a native emulator. [deploy](deploy.html) | Builds and deploys the project to a connected physical or virtual device. -[emulate android](emulate-android.html) | Builds the specified project and runs it in a native Android emulator. -[emulate ios](emulate-ios.html) | Builds the specified project and runs it in the native iOS Simulator. -[emulate](emulate.html) | You must run the emulate command with a related command. [run android](run-android.html) | Runs your project on a connected Android device or in a native Android emulator, if configured. [run ios](run-ios.html) | Runs your project on a connected iOS device or in the iOS Simulator, if configured. [run](run.html) | Runs your project on a connected device or in the native emulator for the selected platform. From 1cf84407356c098c9e158b680d5d1b4bc80fa0f3 Mon Sep 17 00:00:00 2001 From: Emil Tabakov Date: Mon, 29 May 2017 13:20:42 +0300 Subject: [PATCH 043/212] Remove the emulate command (#2844) * Remove the emulate command. * Remove unused method and interface. --- PLUGINS.md | 2 +- README.md | 2 - docs/man_pages/index.md | 1 - .../project/testing/build-android.md | 3 - docs/man_pages/project/testing/build-ios.md | 3 - docs/man_pages/project/testing/build.md | 3 - docs/man_pages/project/testing/deploy.md | 3 - .../project/testing/emulate-android.md | 59 ---------------- docs/man_pages/project/testing/emulate-ios.md | 53 --------------- docs/man_pages/project/testing/emulate.md | 35 ---------- docs/man_pages/project/testing/run-android.md | 3 - docs/man_pages/project/testing/run-ios.md | 3 - docs/man_pages/project/testing/run.md | 3 - lib/bootstrap.ts | 2 - lib/commands/emulate.ts | 68 ------------------- lib/declarations.d.ts | 3 - lib/definitions/platform.d.ts | 11 --- lib/services/platform-service.ts | 40 +---------- test/stubs.ts | 4 -- 19 files changed, 2 insertions(+), 299 deletions(-) delete mode 100644 docs/man_pages/project/testing/emulate-android.md delete mode 100644 docs/man_pages/project/testing/emulate-ios.md delete mode 100644 docs/man_pages/project/testing/emulate.md delete mode 100644 lib/commands/emulate.ts diff --git a/PLUGINS.md b/PLUGINS.md index 4a4be475a9..80bbc82cfd 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -352,7 +352,7 @@ tns prepare Make sure to run the command for all platforms configured for the project. During this operation, the NativeScript CLI will remove any leftover plugin files from your `platforms\ios` directory. -> **TIP:** Instead of `$ tns prepare` you can run `$ tns build`, `$ tns run`, `$ tns deploy` or `$ tns emulate`. All these commands run `$ tns prepare`. +> **TIP:** Instead of `$ tns prepare` you can run `$ tns build`, `$ tns run` or `$ tns deploy`. All these commands run `$ tns prepare`. Next, open your `platforms\ios\Info.plist` file and remove any leftover entries from the plugin `Info.plist` file. diff --git a/README.md b/README.md index 0f86c68351..06f236e542 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ The NativeScript CLI lets you create, build, and deploy NativeScript-based proje * [Prepare for Build](#prepare-for-build) * [Build Your Project](#build-your-project) * [Deploy Your Project](#deploy-your-project) - * [Emulate Your Project](#emulate-your-project) * [Run Your Project](#run-your-project) * [Extending the CLI](#extending-the-cli) * [Troubleshooting](#troubleshooting) @@ -112,7 +111,6 @@ Quick Start * [Prepare for Build](#prepare-for-build) * [Build Your Project](#build-your-project) * [Deploy Your Project](#deploy-your-project) -* [Emulate Your Project](#emulate-your-project) * [Run Your Project](#run-your-project) ## The Commands diff --git a/docs/man_pages/index.md b/docs/man_pages/index.md index 5a26c9affa..47da22740f 100644 --- a/docs/man_pages/index.md +++ b/docs/man_pages/index.md @@ -29,7 +29,6 @@ Command | Description [prepare ``](project/configuration/prepare.html) | Copies relevant content from the app directory to the subdirectory for the selected target platform to let you build the project. [build ``](project/testing/build.html) | Builds the project for the selected target platform and produces an application package or an emulator package. [deploy ``](project/testing/deploy.html) | Deploys the project to a connected physical or virtual device. -[emulate ``](project/testing/emulate.html) | Deploys the project in the native emulator for the selected target platform. [run ``](project/testing/run.html) | Runs your project on a connected device or in the native emulator, if configured. [debug ``](project/testing/debug.html) | Debugs your project on a connected physical or virtual device. [test init](project/testing/test-init.html) | Configures your project for unit testing with a selected framework. diff --git a/docs/man_pages/project/testing/build-android.md b/docs/man_pages/project/testing/build-android.md index 0226025829..5acd4ca6f4 100644 --- a/docs/man_pages/project/testing/build-android.md +++ b/docs/man_pages/project/testing/build-android.md @@ -34,9 +34,6 @@ Command | Description [debug ios](debug-ios.html) | Debugs your project on a connected iOS device or in a native emulator. [debug](debug.html) | Debugs your project on a connected device or in a native emulator. [deploy](deploy.html) | Builds and deploys the project to a connected physical or virtual device. -[emulate android](emulate-android.html) | Builds the specified project and runs it in a native Android emulator. -[emulate ios](emulate-ios.html) | Builds the specified project and runs it in the native iOS Simulator. -[emulate](emulate.html) | You must run the emulate command with a related command. [run android](run-android.html) | Runs your project on a connected Android device or in a native Android emulator, if configured. [run ios](run-ios.html) | Runs your project on a connected iOS device or in the iOS Simulator, if configured. [run](run.html) | Runs your project on a connected device or in the native emulator for the selected platform. diff --git a/docs/man_pages/project/testing/build-ios.md b/docs/man_pages/project/testing/build-ios.md index dfd914a625..57511c2817 100644 --- a/docs/man_pages/project/testing/build-ios.md +++ b/docs/man_pages/project/testing/build-ios.md @@ -33,9 +33,6 @@ Command | Description [debug ios](debug-ios.html) | Debugs your project on a connected iOS device or in a native emulator. [debug](debug.html) | Debugs your project on a connected device or in a native emulator. [deploy](deploy.html) | Builds and deploys the project to a connected physical or virtual device. -[emulate android](emulate-android.html) | Builds the specified project and runs it in a native Android emulator. -[emulate ios](emulate-ios.html) | Builds the specified project and runs it in the native iOS Simulator. -[emulate](emulate.html) | You must run the emulate command with a related command. [run android](run-android.html) | Runs your project on a connected Android device or in a native Android emulator, if configured. [run ios](run-ios.html) | Runs your project on a connected iOS device or in the iOS Simulator, if configured. [run](run.html) | Runs your project on a connected device or in the native emulator for the selected platform. diff --git a/docs/man_pages/project/testing/build.md b/docs/man_pages/project/testing/build.md index a53ed9d2ee..6d7eb33f32 100644 --- a/docs/man_pages/project/testing/build.md +++ b/docs/man_pages/project/testing/build.md @@ -29,9 +29,6 @@ Command | Description [debug ios](debug-ios.html) | Debugs your project on a connected iOS device or in a native emulator. [debug](debug.html) | Debugs your project on a connected device or in a native emulator. [deploy](deploy.html) | Builds and deploys the project to a connected physical or virtual device. -[emulate android](emulate-android.html) | Builds the specified project and runs it in a native Android emulator. -[emulate ios](emulate-ios.html) | Builds the specified project and runs it in the native iOS Simulator. -[emulate](emulate.html) | You must run the emulate command with a related command. [run android](run-android.html) | Runs your project on a connected Android device or in a native Android emulator, if configured. [run ios](run-ios.html) | Runs your project on a connected iOS device or in the iOS Simulator, if configured. [run](run.html) | Runs your project on a connected device or in the native emulator for the selected platform. diff --git a/docs/man_pages/project/testing/deploy.md b/docs/man_pages/project/testing/deploy.md index 7338bbb0fd..4bb3339a90 100644 --- a/docs/man_pages/project/testing/deploy.md +++ b/docs/man_pages/project/testing/deploy.md @@ -44,9 +44,6 @@ Command | Description [debug android](debug-android.html) | Debugs your project on a connected Android device or in a native emulator. [debug ios](debug-ios.html) | Debugs your project on a connected iOS device or in a native emulator. [debug](debug.html) | Debugs your project on a connected device or in a native emulator. -[emulate android](emulate-android.html) | Builds the specified project and runs it in a native Android emulator. -[emulate ios](emulate-ios.html) | Builds the specified project and runs it in the native iOS Simulator. -[emulate](emulate.html) | You must run the emulate command with a related command. [run android](run-android.html) | Runs your project on a connected Android device or in a native Android emulator, if configured. [run ios](run-ios.html) | Runs your project on a connected iOS device or in the iOS Simulator, if configured. [run](run.html) | Runs your project on a connected device or in the native emulator for the selected platform. diff --git a/docs/man_pages/project/testing/emulate-android.md b/docs/man_pages/project/testing/emulate-android.md deleted file mode 100644 index f5fbcd646b..0000000000 --- a/docs/man_pages/project/testing/emulate-android.md +++ /dev/null @@ -1,59 +0,0 @@ -emulate android -========== - -Usage | Synopsis ----|--- -Run in the native emulator | `$ tns emulate android [--device ] [--path ] [--timeout ] [--key-store-path --key-store-password --key-store-alias --key-store-alias-password ] [--release] [--justlaunch]` -Run in the default Android virtual device or in a currently running emulator | `$ tns emulate android [--path ] [--timeout ] [--key-store-path --key-store-password --key-store-alias --key-store-alias-password ] [--release] [--justlaunch]` - -Builds the specified project and runs it in the native emulator from the Android SDK. While your app is running, prints the output from the application in the console.<% if(isHtml) { %>If you do not select an Android virtual device (AVD) with the `--device` option, your app runs in the default AVD or a currently running emulator, if any. <% } %> - -### Options -* `--available-devices` - Lists all available emulators for Android. -* `--no-watch` - If set, changes in your code will not be reflected during the execution of this command -* `--path` - Specifies the directory that contains the project. If not specified, the project is searched for in the current directory and all directories above it. -* `--device` - Sets the Android virtual device on which you want to run your app. You can set only one device at a time. -* `--timeout` - Sets the number of seconds that the NativeScript CLI will wait for the virtual device to boot before quitting the operation and releasing the console. If not set, the default timeout is 120 seconds. To wait indefinitely, set 0. -* `--release` - If set, produces a release build. Otherwise, produces a debug build. When set, you must also specify the `--key-store-*` options. -* `--key-store-path` - Specifies the file path to the keystore file (P12) which you want to use to code sign your APK. You can use the `--key-store-*` options along with `--release` to produce a signed release build. You need to specify all `--key-store-*` options. -* `--key-store-password` - Provides the password for the keystore file specified with --key-store-path. You can use the `--key-store-*` options along with --release to produce a signed release build. You need to specify all `--key-store-*` options. -* `--key-store-alias` - Provides the alias for the keystore file specified with `--key-store-path`. You can use the `--key-store-*` options along with `--release` to produce a signed release build. You need to specify all `--key-store-*` options. -* `--key-store-alias-password` - Provides the password for the alias specified with `--key-store-alias-password`. You can use the `--key-store-*` options along with `--release` to produce a signed release build. You need to specify all `--key-store-*` options. -* `--justlaunch` - If set, does not print the application output in the console. -* `--clean` - If set, forces rebuilding the native application. - -### Attributes -* `` is the name of the Android virtual device that you want to use as listed by `$ android list avd` - -<% if(isHtml) { %> -### Prerequisites -Before running your app in the Android emulator from the Android SDK, verify that your system meets the following requirements. -* Verify that you have installed the Android SDK. -* Verify that you have added the following Android SDK directories to the `PATH` environment variable: - * `platform-tools` - * `tools` - -### Command Limitations - -* When the `--release` flag is set, you must also specify all `--key-store-*` options. - -### Related Commands - -Command | Description -----------|---------- -[build android](build-android.html) | Builds the project for Android and produces an APK that you can manually deploy on device or in the native emulator. -[build ios](build-ios.html) | Builds the project for iOS and produces an APP or IPA that you can manually deploy in the iOS Simulator or on device, respectively. -[build](build.html) | Builds the project for the selected target platform and produces an application package that you can manually deploy on device or in the native emulator. -[debug android](debug-android.html) | Debugs your project on a connected Android device or in a native emulator. -[debug ios](debug-ios.html) | Debugs your project on a connected iOS device or in a native emulator. -[debug](debug.html) | Debugs your project on a connected device or in a native emulator. -[deploy](deploy.html) | Builds and deploys the project to a connected physical or virtual device. -[emulate ios](emulate-ios.html) | Builds the specified project and runs it in the native iOS Simulator. -[emulate](emulate.html) | You must run the emulate command with a related command. -[run android](run-android.html) | Runs your project on a connected Android device or in a native Android emulator, if configured. -[run ios](run-ios.html) | Runs your project on a connected iOS device or in the iOS Simulator, if configured. -[run](run.html) | Runs your project on a connected device or in the native emulator for the selected platform. -[test init](test-init.html) | Configures your project for unit testing with a selected framework. -[test android](test-android.html) | Runs the tests in your project on Android devices or native emulators. -[test ios](test-ios.html) | Runs the tests in your project on iOS devices or the iOS Simulator. -<% } %> \ No newline at end of file diff --git a/docs/man_pages/project/testing/emulate-ios.md b/docs/man_pages/project/testing/emulate-ios.md deleted file mode 100644 index ad73948968..0000000000 --- a/docs/man_pages/project/testing/emulate-ios.md +++ /dev/null @@ -1,53 +0,0 @@ -emulate ios -========== - -Usage | Synopsis ----|--- -General | `$ tns emulate ios [--path ] [--device ] [--available-devices] [--release] [--timeout]` - -Prepares, builds and deploys the specified project. Runs it in the native iOS Simulator. While your app is running, prints the output from the application in the console and watches for changes in your code. Once a change is detected, it synchronizes the change with all selected devices and restarts/refreshes the application. - -<% if(isConsole && (isLinux || isWindows)) { %>WARNING: You can run this command only on OS X systems. To view the complete help for this command, run `$ tns help emulate ios`<% } %> - -<% if((isConsole && isMacOS) || isHtml) { %>### Options -* `--available-devices` - Lists all available emulators for the current Xcode. -* `--no-watch` - If set, changes in your code will not be reflected during the execution of this command. -* `--release` - If set, produces a release build. Otherwise, produces a debug build. -* `--path` - Specifies the directory that contains the project. If not specified, the project is searched for in the current directory and all directories above it. -* `--device` - Specifies the name of the iOS Simulator device on which you want to run your app. To list the available iOS Simulator devices, run `$ tns emulate ios --available-devices` -* `--timeout` - Sets the number of seconds that the NativeScript CLI will wait for the iOS Simulator to start before quitting the operation and releasing the console. The value must be a positive integer. If not set, the default timeout is 90 seconds. -* `--justlaunch` - If set, does not print the application output in the console. -* `--clean` - If set, forces rebuilding the native application. - -### Attributes -* `` is the name of the iOS Simulator device on which you want to run your app as listed by `$ tns emulate ios --available-devices`<% } %> - -<% if(isHtml) { %> -### Prerequisites -Before running the iOS Simulator, verify that you have met the following requirements. -* You have installed Xcode. The version of Xcode must be compatible with the ios-sim-portable npm package on which the NativeScript CLI depends. For more information, visit [https://www.npmjs.org/package/ios-sim-portable](https://www.npmjs.org/package/ios-sim-portable). - -### Command Limitations - -* You can run `$ tns emulate ios` only on OS X systems. - -### Related Commands - -Command | Description -----------|---------- -[build android](build-android.html) | Builds the project for Android and produces an APK that you can manually deploy on device or in the native emulator. -[build ios](build-ios.html) | Builds the project for iOS and produces an APP or IPA that you can manually deploy in the iOS Simulator or on device, respectively. -[build](build.html) | Builds the project for the selected target platform and produces an application package that you can manually deploy on device or in the native emulator. -[debug android](debug-android.html) | Debugs your project on a connected Android device or in a native emulator. -[debug ios](debug-ios.html) | Debugs your project on a connected iOS device or in a native emulator. -[debug](debug.html) | Debugs your project on a connected device or in a native emulator. -[deploy](deploy.html) | Builds and deploys the project to a connected physical or virtual device. -[emulate android](emulate-android.html) | Builds the specified project and runs it in a native Android emulator. -[emulate](emulate.html) | You must run the emulate command with a related command. -[run android](run-android.html) | Runs your project on a connected Android device or in a native Android emulator, if configured. -[run ios](run-ios.html) | Runs your project on a connected iOS device or in the iOS Simulator, if configured. -[run](run.html) | Runs your project on a connected device or in the native emulator for the selected platform. -[test init](test-init.html) | Configures your project for unit testing with a selected framework. -[test android](test-android.html) | Runs the tests in your project on Android devices or native emulators. -[test ios](test-ios.html) | Runs the tests in your project on iOS devices or the iOS Simulator. -<% } %> \ No newline at end of file diff --git a/docs/man_pages/project/testing/emulate.md b/docs/man_pages/project/testing/emulate.md deleted file mode 100644 index 52ea563ed9..0000000000 --- a/docs/man_pages/project/testing/emulate.md +++ /dev/null @@ -1,35 +0,0 @@ -emulate -========== - -Usage | Synopsis ----|--- -<% if((isConsole && isMacOS) || isHtml) { %>General | `$ tns emulate `<% } %><% if(isConsole && (isLinux || isWindows)) { %>General | `$ tns emulate android`<% } %> - -Runs the project in the native emulator for the selected target platform. <% if(isMacOS) { %>You must specify the target platform for which you want to build your project.<% } %> The command will prepare, build and deploy the app when necessary. By default listens for changes in your code, synchronizes those changes and refreshes all selected emulators. - -<% if((isConsole && isMacOS) || isHtml) { %>### Attributes -`` is the target mobile platform for which you want to emulate your project. You can set the following target platforms. -* `android` - Builds the specified project and runs it in the native Android emulator. -* `ios` - Builds the specified project and runs it in the native iOS Simulator.<% } %> - -<% if(isHtml) { %> -### Related Commands - -Command | Description -----------|---------- -[build android](build-android.html) | Builds the project for Android and produces an APK that you can manually deploy on device or in the native emulator. -[build ios](build-ios.html) | Builds the project for iOS and produces an APP or IPA that you can manually deploy in the iOS Simulator or on device, respectively. -[build](build.html) | Builds the project for the selected target platform and produces an application package that you can manually deploy on device or in the native emulator. -[debug android](debug-android.html) | Debugs your project on a connected Android device or in a native emulator. -[debug ios](debug-ios.html) | Debugs your project on a connected iOS device or in a native emulator. -[debug](debug.html) | Debugs your project on a connected device or in a native emulator. -[deploy](deploy.html) | Builds and deploys the project to a connected physical or virtual device. -[emulate android](emulate-android.html) | Builds the specified project and runs it in a native Android emulator. -[emulate ios](emulate-ios.html) | Builds the specified project and runs it in the native iOS Simulator. -[run android](run-android.html) | Runs your project on a connected Android device or in a native Android emulator, if configured. -[run ios](run-ios.html) | Runs your project on a connected iOS device or in the iOS Simulator, if configured. -[run](run.html) | Runs your project on a connected device or in the native emulator for the selected platform. -[test init](test-init.html) | Configures your project for unit testing with a selected framework. -[test android](test-android.html) | Runs the tests in your project on Android devices or native emulators. -[test ios](test-ios.html) | Runs the tests in your project on iOS devices or the iOS Simulator. -<% } %> \ No newline at end of file diff --git a/docs/man_pages/project/testing/run-android.md b/docs/man_pages/project/testing/run-android.md index 805e55f866..a02135b7b5 100644 --- a/docs/man_pages/project/testing/run-android.md +++ b/docs/man_pages/project/testing/run-android.md @@ -48,9 +48,6 @@ Command | Description [debug ios](debug-ios.html) | Debugs your project on a connected iOS device or in a native emulator. [debug](debug.html) | Debugs your project on a connected device or in a native emulator. [deploy](deploy.html) | Builds and deploys the project to a connected physical or virtual device. -[emulate android](emulate-android.html) | Builds the specified project and runs it in a native Android emulator. -[emulate ios](emulate-ios.html) | Builds the specified project and runs it in the native iOS Simulator. -[emulate](emulate.html) | You must run the emulate command with a related command. [run ios](run-ios.html) | Runs your project on a connected iOS device or in the iOS Simulator, if configured. [run](run.html) | Runs your project on a connected device or in the native emulator for the selected platform. [test init](test-init.html) | Configures your project for unit testing with a selected framework. diff --git a/docs/man_pages/project/testing/run-ios.md b/docs/man_pages/project/testing/run-ios.md index 191bfd9532..c23d638259 100644 --- a/docs/man_pages/project/testing/run-ios.md +++ b/docs/man_pages/project/testing/run-ios.md @@ -47,9 +47,6 @@ Command | Description [debug ios](debug-ios.html) | Debugs your project on a connected iOS device or in a native emulator. [debug](debug.html) | Debugs your project on a connected device or in a native emulator. [deploy](deploy.html) | Builds and deploys the project to a connected physical or virtual device. -[emulate android](emulate-android.html) | Builds the specified project and runs it in a native Android emulator. -[emulate ios](emulate-ios.html) | Builds the specified project and runs it in the native iOS Simulator. -[emulate](emulate.html) | You must run the emulate command with a related command. [run android](run-android.html) | Runs your project on a connected Android device or in a native Android emulator, if configured. [run](run.html) | Runs your project on a connected device or in the native emulator for the selected platform. [test init](test-init.html) | Configures your project for unit testing with a selected framework. diff --git a/docs/man_pages/project/testing/run.md b/docs/man_pages/project/testing/run.md index 1afbff00a3..7d5172706f 100644 --- a/docs/man_pages/project/testing/run.md +++ b/docs/man_pages/project/testing/run.md @@ -30,9 +30,6 @@ Command | Description [debug ios](debug-ios.html) | Debugs your project on a connected iOS device or in a native emulator. [debug](debug.html) | Debugs your project on a connected device or in a native emulator. [deploy](deploy.html) | Builds and deploys the project to a connected physical or virtual device. -[emulate android](emulate-android.html) | Builds the specified project and runs it in a native Android emulator. -[emulate ios](emulate-ios.html) | Builds the specified project and runs it in the native iOS Simulator. -[emulate](emulate.html) | You must run the emulate command with a related command. [run android](run-android.html) | Runs your project on a connected Android device or in a native Android emulator, if configured. [run ios](run-ios.html) | Runs your project on a connected iOS device or in the iOS Simulator, if configured. [test init](test-init.html) | Configures your project for unit testing with a selected framework. diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 439ce535ba..3cc76cc0b0 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -51,8 +51,6 @@ $injector.requireCommand("clean-app|android", "./commands/clean-app"); $injector.requireCommand("build|ios", "./commands/build"); $injector.requireCommand("build|android", "./commands/build"); $injector.requireCommand("deploy", "./commands/deploy"); -$injector.requireCommand("emulate|android", "./commands/emulate"); -$injector.requireCommand("emulate|ios", "./commands/emulate"); $injector.require("testExecutionService", "./services/test-execution-service"); $injector.requireCommand("dev-test|android", "./commands/test"); diff --git a/lib/commands/emulate.ts b/lib/commands/emulate.ts deleted file mode 100644 index 51cb3068ef..0000000000 --- a/lib/commands/emulate.ts +++ /dev/null @@ -1,68 +0,0 @@ -export class EmulateCommandBase { - constructor(private $options: IOptions, - private $projectData: IProjectData, - private $logger: ILogger, - private $platformService: IPlatformService) { - this.$projectData.initializeProjectData(); - } - - public async executeCore(args: string[]): Promise { - this.$logger.warn(`Emulate command is deprecated and will soon be removed. Please use "tns run " instead. All options available for "tns emulate" are present in "tns run" command. To run on all available emulators, use "tns run --emulator".`); - this.$options.emulator = true; - const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; - const emulateOptions: IEmulatePlatformOptions = { - avd: this.$options.avd, - clean: this.$options.clean, - device: this.$options.device, - release: this.$options.release, - emulator: this.$options.emulator, - projectDir: this.$options.path, - justlaunch: this.$options.justlaunch, - availableDevices: this.$options.availableDevices, - platformTemplate: this.$options.platformTemplate, - provision: this.$options.provision, - teamId: this.$options.teamId, - keyStoreAlias: this.$options.keyStoreAlias, - keyStoreAliasPassword: this.$options.keyStoreAliasPassword, - keyStorePassword: this.$options.keyStorePassword, - keyStorePath: this.$options.keyStorePath - }; - return this.$platformService.emulatePlatform(args[0], appFilesUpdaterOptions, emulateOptions, this.$projectData, this.$options); - } -} - -export class EmulateIosCommand extends EmulateCommandBase implements ICommand { - public allowedParameters: ICommandParameter[] = []; - - constructor($options: IOptions, - $projectData: IProjectData, - $logger: ILogger, - $platformService: IPlatformService, - private $platformsData: IPlatformsData) { - super($options, $projectData, $logger, $platformService); - } - - public async execute(args: string[]): Promise { - return this.executeCore([this.$platformsData.availablePlatforms.iOS]); - } -} - -$injector.registerCommand("emulate|ios", EmulateIosCommand); - -export class EmulateAndroidCommand extends EmulateCommandBase implements ICommand { - constructor($options: IOptions, - $projectData: IProjectData, - $logger: ILogger, - $platformService: IPlatformService, - private $platformsData: IPlatformsData) { - super($options, $projectData, $logger, $platformService); - } - - public allowedParameters: ICommandParameter[] = []; - - public async execute(args: string[]): Promise { - return this.executeCore([this.$platformsData.availablePlatforms.Android]); - } -} - -$injector.registerCommand("emulate|android", EmulateAndroidCommand); diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index d3b566fbb4..448c546933 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -369,9 +369,6 @@ interface IDeployPlatformOptions extends IAndroidReleaseOptions, IPlatformTempla forceInstall?: boolean; } -interface IEmulatePlatformOptions extends IJustLaunch, IDeployPlatformOptions, IAvailableDevices, IAvd { -} - interface IUpdatePlatformOptions extends IPlatformTemplate { currentVersion: string; newVersion: string; diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 8c6a7c5d7d..f1c6dd968d 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -120,17 +120,6 @@ interface IPlatformService extends NodeJS.EventEmitter { */ startApplication(platform: string, runOptions: IRunPlatformOptions, projectId: string): Promise; - /** - * The emulate command. In addition to `run --emulator` command, it handles the `--available-devices` option to show the available devices. - * @param {string} platform The platform to emulate. - * @param {IAppFilesUpdaterOptions} appFilesUpdaterOptions Options needed to instantiate AppFilesUpdater class. - * @param {IEmulatePlatformOptions} emulateOptions Various options that can manage the emulate operation. - * @param {IProjectData} projectData DTO with information about the project. - * @param {IAddPlatformCoreOptions} config Options required for project preparation/creation. - * @returns {void} - */ - emulatePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, emulateOptions: IEmulatePlatformOptions, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise; - cleanDestinationApp(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise; validatePlatformInstalled(platform: string, projectData: IProjectData): void; diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 4ed171abb8..5fb41e8fa1 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -39,10 +39,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $deviceAppDataFactory: Mobile.IDeviceAppDataFactory, private $projectChangesService: IProjectChangesService, - private $emulatorPlatformService: IEmulatorPlatformService, - private $analyticsService: IAnalyticsService, - private $messages: IMessages, - private $staticConfig: Config.IStaticConfig) { + private $analyticsService: IAnalyticsService) { super(); } @@ -528,41 +525,6 @@ export class PlatformService extends EventEmitter implements IPlatformService { await this.$devicesService.execute(action, this.getCanExecuteAction(platform, runOptions)); } - public async emulatePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, emulateOptions: IEmulatePlatformOptions, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { - if (emulateOptions.avd) { - this.$logger.warn(`Option --avd is no longer supported. Please use --device instead!`); - return Promise.resolve(); - } - - if (emulateOptions.availableDevices) { - return this.$emulatorPlatformService.listAvailableEmulators(platform); - } - - if (emulateOptions.device) { - let info = await this.$emulatorPlatformService.getEmulatorInfo(platform, emulateOptions.device); - if (info) { - if (!info.isRunning) { - await this.$emulatorPlatformService.startEmulator(info, projectData); - } - - emulateOptions.device = null; - } else { - await this.$devicesService.initialize({ platform: platform, deviceId: emulateOptions.device }); - let found: Mobile.IDeviceInfo[] = []; - if (this.$devicesService.hasDevices) { - found = this.$devicesService.getDevices().filter((deviceInfo: Mobile.IDeviceInfo) => deviceInfo.identifier === emulateOptions.device); - } - - if (found.length === 0) { - this.$errors.fail(this.$messages.Devices.NotFoundDeviceByIdentifierErrorMessage, this.$staticConfig.CLIENT_NAME.toLowerCase()); - } - } - } - - await this.deployPlatform(platform, appFilesUpdaterOptions, emulateOptions, projectData, config); - return this.startApplication(platform, emulateOptions, projectData.projectId); - } - private getBuildOutputPath(platform: string, platformData: IPlatformData, options: IBuildForDevice): string { if (platform.toLowerCase() === this.$devicePlatformsConstants.iOS.toLowerCase()) { return options.buildForDevice ? platformData.deviceBuildOutputPath : platformData.emulatorBuildOutputPath; diff --git a/test/stubs.ts b/test/stubs.ts index eabbb21d7c..2799642aec 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -653,10 +653,6 @@ export class PlatformServiceStub extends EventEmitter implements IPlatformServic return Promise.resolve(); } - public emulatePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, emulateOptions: IEmulatePlatformOptions): Promise { - return Promise.resolve(); - } - public cleanDestinationApp(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string): Promise { return Promise.resolve(); } From c2d844d4e005ed288b7c78db4e0b84de1c9d56af Mon Sep 17 00:00:00 2001 From: Stanimira Vlaeva Date: Mon, 29 May 2017 15:11:43 +0300 Subject: [PATCH 044/212] Fix prepare for android when building with webpack (#2852) When switching between release, debug and bundle builds, the android build artifacts should be cleaned. fixes https://github.com/NativeScript/android-runtime/issues/759 --- lib/services/platform-service.ts | 30 +++++++++++++++++++++--------- test/npm-support.ts | 5 +++-- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 1df21befb2..60d81a4894 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -239,16 +239,9 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$logger.trace("Changes info in prepare platform:", changesInfo); if (changesInfo.hasChanges) { - // android build artifacts need to be cleaned up when switching from release to debug builds - if (platform.toLowerCase() === "android") { - let previousPrepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); - // clean up prepared plugins when not building for release - if (previousPrepareInfo && previousPrepareInfo.release !== appFilesUpdaterOptions.release) { - await platformData.platformProjectService.cleanProject(platformData.projectRoot, projectData); - } - } - + await this.cleanProject(platform, appFilesUpdaterOptions, platformData, projectData); await this.preparePlatformCore(platform, appFilesUpdaterOptions, projectData, platformSpecificData, changesInfo, filesToSync); + this.$projectChangesService.savePrepareInfo(platform, projectData); } else { this.$logger.out("Skipping prepare."); @@ -275,6 +268,25 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } + private async cleanProject(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformData: IPlatformData, projectData: IProjectData): Promise { + // android build artifacts need to be cleaned up + // when switching between debug, release and webpack builds + if (platform.toLowerCase() !== "android") { + return; + } + + const previousPrepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); + if (!previousPrepareInfo) { + return; + } + + const { release: previousWasRelease, bundle: previousWasBundle } = previousPrepareInfo; + const { release: currentIsRelease, bundle: currentIsBundle } = appFilesUpdaterOptions; + if ((previousWasRelease !== currentIsRelease) || (previousWasBundle !== currentIsBundle)) { + await platformData.platformProjectService.cleanProject(platformData.projectRoot, projectData); + } + } + /* Hooks are expected to use "filesToSync" parameter, as to give plugin authors additional information about the sync process.*/ @helpers.hook('prepare') private async preparePlatformCore(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, changesInfo?: IProjectChangesInfo, filesToSync?: Array): Promise { diff --git a/test/npm-support.ts b/test/npm-support.ts index 261cfe9ad0..f345095ca6 100644 --- a/test/npm-support.ts +++ b/test/npm-support.ts @@ -159,7 +159,8 @@ async function setupProject(dependencies?: any): Promise { ensureConfigurationFileInAppResources: (): any => null, interpolateConfigurationFile: (): void => undefined, isPlatformPrepared: (projectRoot: string) => false, - validatePlugins: (projectData: IProjectData) => Promise.resolve() + validatePlugins: (projectData: IProjectData) => Promise.resolve(), + cleanProject: () => Promise.resolve(), } }; }; @@ -316,7 +317,7 @@ describe("Flatten npm modules tests", () => { let gulpJshint = path.join(tnsModulesFolderPath, "gulp-jshint"); assert.isFalse(fs.exists(gulpJshint)); - // Get all gulp dependencies + // Get all gulp dependencies let gulpJsonContent = fs.readJson(path.join(projectFolder, nodeModulesFolderName, "gulp", packageJsonName)); _.each(_.keys(gulpJsonContent.dependencies), dependency => { assert.isFalse(fs.exists(path.join(tnsModulesFolderPath, dependency))); From 67fdb671a38b615cfca78b99c55bc80423398959 Mon Sep 17 00:00:00 2001 From: dtopuzov Date: Tue, 30 May 2017 10:00:17 +0300 Subject: [PATCH 045/212] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb9671f3e7..2b5b9e9803 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,13 @@ NativeScript CLI Changelog ================ -3.0.2 (2017, May 26) +3.0.2 (2017, May 30) == ### Fixed * Removed restart of the App if HTML/CSS file was modified. The issue is fixed in the Modules **3.0.1** and we can again just refresh the app on change. +* [Fix #2852](https://github.com/NativeScript/nativescript-cli/pull/2852): Fix prepare for android when building with webpack 3.0.1 (2017, May 11) == From 106a2e8a5fb13e574fb90f421f5eef52488df8f6 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Tue, 30 May 2017 15:22:23 +0300 Subject: [PATCH 046/212] Expose a way to set projectApiKey for analytics (#2859) --- PublicAPI.md | 15 +++++++++++++++ lib/bootstrap.ts | 2 +- lib/common | 2 +- lib/services/analytics-service.ts | 8 ++++++++ test/nativescript-cli-lib.ts | 1 + 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/PublicAPI.md b/PublicAPI.md index 6eea5ffc55..394d39f390 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -339,6 +339,21 @@ tns.npm.view(["nativescript"], {}).then(result => { }); ``` +## analyticsService +Provides a way to configure analytics. + +### startEqatecMonitor +* Definition: +```TypeScript +/** + * Starts analytics monitor with provided key. + * @param {string} projectApiKey API key with which to start analytics monitor. + * @returns {Promise}. + */ +startEqatecMonitor(projectApiKey: string): Promise; +``` + + ## debugService Provides methods for debugging applications on devices. The service is also event emitter, that raises the following events: * `connectionError` event - this event is raised when the debug operation cannot start on iOS device. The causes can be: diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 3cc76cc0b0..37ffb4c4ef 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -29,7 +29,7 @@ $injector.require("androidDebugService", "./services/android-debug-service"); $injector.require("userSettingsService", "./services/user-settings-service"); $injector.require("analyticsSettingsService", "./services/analytics-settings-service"); -$injector.require("analyticsService", "./services/analytics-service"); +$injector.requirePublic("analyticsService", "./services/analytics-service"); $injector.require("emulatorSettingsService", "./services/emulator-settings-service"); diff --git a/lib/common b/lib/common index 298afb9929..e3a972f749 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 298afb9929690e83bb929fb4f2487b630084a442 +Subproject commit e3a972f7496ccf3924568c133197ed2e308f8540 diff --git a/lib/services/analytics-service.ts b/lib/services/analytics-service.ts index e37ef56004..ba98c7f662 100644 --- a/lib/services/analytics-service.ts +++ b/lib/services/analytics-service.ts @@ -1,4 +1,5 @@ import { AnalyticsServiceBase } from "../common/services/analytics-service-base"; +import { exported } from "../common/decorators"; export class AnalyticsService extends AnalyticsServiceBase implements IAnalyticsService { private static ANALYTICS_FEATURE_USAGE_TRACKING_API_KEY = "9912cff308334c6d9ad9c33f76a983e3"; @@ -14,6 +15,13 @@ export class AnalyticsService extends AnalyticsServiceBase implements IAnalytics super($logger, $options, $staticConfig, $prompter, $userSettingsService, $analyticsSettingsService, $progressIndicator, $osInfo); } + @exported("analyticsService") + public async startEqatecMonitor(projectApiKey: string): Promise { + if (await this.isEnabled(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME) || await this.isEnabled(this.$staticConfig.ERROR_REPORT_SETTING_NAME)) { + await this.restartEqatecMonitor(projectApiKey); + } + } + protected async checkConsentCore(trackFeatureUsage: boolean): Promise { await this.restartEqatecMonitor(AnalyticsService.ANALYTICS_FEATURE_USAGE_TRACKING_API_KEY); await super.checkConsentCore(trackFeatureUsage); diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index d9929cf3d8..118fd54383 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -20,6 +20,7 @@ describe("nativescript-cli-lib", () => { deviceLogProvider: null, npm: ["install", "uninstall", "view", "search"], extensibilityService: ["loadExtensions", "getInstalledExtensions", "installExtension", "uninstallExtension"], + analyticsService: ["startEqatecMonitor"], debugService: ["debug"] }; From 492d24b9004922f2ff86e92e3b43b880b7137a71 Mon Sep 17 00:00:00 2001 From: Vasil Chimev Date: Tue, 30 May 2017 16:35:09 +0300 Subject: [PATCH 047/212] Update Android SDK packages in macOS setup script (#2857) --- setup/native-script.rb | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/setup/native-script.rb b/setup/native-script.rb index b70aa25d31..8bdc7fb51f 100755 --- a/setup/native-script.rb +++ b/setup/native-script.rb @@ -97,8 +97,7 @@ def install_environment_variable(name, value) unless ENV["ANDROID_HOME"] require 'pathname' - # if android-sdk was installed through brew, there should be a symlink in /usr/local/opt/android-sdk pointing to the actual sdk - android_home = "/usr/local/opt/android-sdk" + android_home = "/usr/local/share/android-sdk" unless Pathname.new(android_home).exist? require 'mkmf' # if there's no such symlink then try to find the `android-sdk` directory through the `android` executable @@ -129,32 +128,24 @@ def install_environment_variable(name, value) # the android tool will introduce a --accept-license option in subsequent releases error_msg = "There seem to be some problems with the Android configuration" -android_executable = File.join(ENV["ANDROID_HOME"], "tools", "bin", "sdkmanager") -execute("echo y | #{android_executable} \"platform-tools\"", error_msg) -execute("echo y | #{android_executable} \"tools\"", error_msg) -execute("echo y | #{android_executable} \"build-tools;25.0.2\"", error_msg) -execute("echo y | #{android_executable} \"platforms;android-25\"", error_msg) -execute("echo y | #{android_executable} \"platforms;android-24\"", error_msg) -execute("echo y | #{android_executable} \"platforms;android-23\"", error_msg) -execute("echo y | #{android_executable} \"platforms;android-22\"", error_msg) -execute("echo y | #{android_executable} \"platforms;android-21\"", error_msg) -execute("echo y | #{android_executable} \"platforms;android-19\"", error_msg) -execute("echo y | #{android_executable} \"platforms;android-18\"", error_msg) -execute("echo y | #{android_executable} \"platforms;android-17\"", error_msg) +sdk_manager = File.join(ENV["ANDROID_HOME"], "tools", "bin", "sdkmanager") +execute("echo y | #{sdk_manager} \"platform-tools\"", error_msg) +execute("echo y | #{sdk_manager} \"tools\"", error_msg) +execute("echo y | #{sdk_manager} \"build-tools;25.0.2\"", error_msg) +execute("echo y | #{sdk_manager} \"platforms;android-23\"", error_msg) +execute("echo y | #{sdk_manager} \"extras;android;m2repository\"", error_msg) +execute("echo y | #{sdk_manager} \"extras;google;m2repository\"", error_msg) puts "Do you want to install Android emulator? (y/n)" if gets.chomp.downcase == "y" puts "Do you want to install HAXM (Hardware accelerated Android emulator)? (y/n)" if gets.chomp.downcase == "y" - execute("echo y | #{android_executable} \"extras;intel;Hardware_Accelerated_Execution_Manager\"", error_msg) - + execute("echo y | #{sdk_manager} \"extras;intel;Hardware_Accelerated_Execution_Manager\"", error_msg) haxm_silent_installer = File.join(ENV["ANDROID_HOME"], "extras", "intel", "Hardware_Accelerated_Execution_Manager", "silent_install.sh") execute("sudo #{haxm_silent_installer}", "There seem to be some problems with the Android configuration") - else end - execute("echo y | #{android_executable} \"system-images;android-25;google_apis;x86\"", error_msg) - execute("echo y | #{android_executable} \"system-images;android-24;default;x86\"", error_msg) + execute("echo y | #{sdk_manager} \"system-images;android-23;default;x86\"", error_msg) end puts "The ANDROID_HOME and JAVA_HOME environment variables have been added to your .bash_profile/.zprofile" -puts "Restart the terminal or run `source ~/.bash_profile` to use them." +puts "Restart the terminal or run `source ~/.bash_profile` to use them." \ No newline at end of file From 2e2a8e10cd4046a18716ab377cc4476877a83bff Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Wed, 31 May 2017 10:43:22 +0300 Subject: [PATCH 048/212] Update ios-device-lib (#2861) Update version of ios-device-lib, in order to fix https://github.com/NativeScript/nativescript-cli/issues/2860 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f1b8624aeb..2226ba0a2d 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "glob": "^7.0.3", "iconv-lite": "0.4.11", "inquirer": "0.9.0", - "ios-device-lib": "0.4.1", + "ios-device-lib": "0.4.2", "ios-mobileprovision-finder": "1.0.9", "ios-sim-portable": "~3.0.0", "lockfile": "1.0.1", From 67dafcce5973ae7a8d2e371b0e5108504da03fbb Mon Sep 17 00:00:00 2001 From: Emil Tabakov Date: Wed, 31 May 2017 13:24:07 +0300 Subject: [PATCH 049/212] Add the available devices option to the help #2850 (#2854) --- docs/man_pages/device/device-android.md | 1 + docs/man_pages/device/device-ios.md | 1 + docs/man_pages/device/device.md | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/docs/man_pages/device/device-android.md b/docs/man_pages/device/device-android.md index 194b6280f9..2394853e16 100644 --- a/docs/man_pages/device/device-android.md +++ b/docs/man_pages/device/device-android.md @@ -8,6 +8,7 @@ General | `$ tns device android [--timeout ]` Lists all recognized connected Android physical and running virtual devices with serial number and index. <% if(isHtml) { %>If a connected Android device is not shown in the list, make sure that you have installed the required Android USB drivers on your system and that USB debugging is enabled on the device.<% } %> ### Options +* `--available-devices` - Lists all available emulators for Android. * `--timeout` - Sets the time in milliseconds for the operation to search for connected devices before completing. If not set, the default value is 4000. <% if(isHtml) { %>The operation will continue to wait and listen for newly connected devices and will list them after the specified time expires. ### Related Commands diff --git a/docs/man_pages/device/device-ios.md b/docs/man_pages/device/device-ios.md index 1269f967ee..f219c96546 100644 --- a/docs/man_pages/device/device-ios.md +++ b/docs/man_pages/device/device-ios.md @@ -11,6 +11,7 @@ Lists all recognized connected iOS devices with serial number and index. <% if((isConsole && (isWindows || isMacOS)) || isHtml) { %> ### Options +* `--available-devices` - Lists all available emulators for iOS. * `--timeout` - Sets the time in milliseconds for the operation to search for connected devices before completing. If not set, the default value is 4000. <% } %><% if(isHtml) { %>The operation will continue to wait and listen for newly connected devices and will list them after the specified time expires. ### Command Limitations diff --git a/docs/man_pages/device/device.md b/docs/man_pages/device/device.md index 22cdee2814..fe4b30467f 100644 --- a/docs/man_pages/device/device.md +++ b/docs/man_pages/device/device.md @@ -20,6 +20,10 @@ Lists all recognized connected Android <% if(isWindows || isMacOS) { %>or iOS de * You can run `$ tns device ios` on Windows and OS X systems. +### Aliases + +* You can use `$ tns devices` as an alias for `$ tns device`. + ### Related Commands Command | Description From 2b04e1d3c2137e5cc9380369846bf599d94a8447 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Wed, 31 May 2017 14:01:28 +0300 Subject: [PATCH 050/212] Conform ios inspector code with npm 5 (#2862) --- lib/constants.ts | 1 + lib/npm-installation-manager.ts | 31 +++++++++++++++++++++---------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/constants.ts b/lib/constants.ts index 21a4488697..3e0e03ecfe 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -71,3 +71,4 @@ export const TYPESCRIPT_NAME = "typescript"; export const BUILD_OUTPUT_EVENT_NAME = "buildOutput"; export const CONNECTION_ERROR_EVENT_NAME = "connectionError"; export const VERSION_STRING = "version"; +export const INSPECTOR_CACHE_DIRNAME = "ios-inspector"; diff --git a/lib/npm-installation-manager.ts b/lib/npm-installation-manager.ts index 1fc8015910..a483b5d994 100644 --- a/lib/npm-installation-manager.ts +++ b/lib/npm-installation-manager.ts @@ -47,27 +47,38 @@ export class NpmInstallationManager implements INpmInstallationManager { } public async getInspectorFromCache(inspectorNpmPackageName: string, projectDir: string): Promise { - let inspectorPath = path.join(projectDir, "node_modules", inspectorNpmPackageName); + let inspectorPath = path.join(projectDir, constants.NODE_MODULES_FOLDER_NAME, inspectorNpmPackageName); // local installation takes precedence over cache if (!this.inspectorAlreadyInstalled(inspectorPath)) { - let cachepath = (await this.$childProcess.exec("npm get cache")).trim(); - let version = await this.getLatestCompatibleVersion(inspectorNpmPackageName); - let pathToPackageInCache = path.join(cachepath, inspectorNpmPackageName, version); - let pathToUnzippedInspector = path.join(pathToPackageInCache, "package"); + const cachePath = path.join(this.$options.profileDir, constants.INSPECTOR_CACHE_DIRNAME); + this.prepareCacheDir(cachePath); + const pathToPackageInCache = path.join(cachePath, constants.NODE_MODULES_FOLDER_NAME, inspectorNpmPackageName); if (!this.$fs.exists(pathToPackageInCache)) { - await this.$childProcess.exec(`npm cache add ${inspectorNpmPackageName}@${version}`); - let inspectorTgzPathInCache = path.join(pathToPackageInCache, "package.tgz"); - await this.$childProcess.exec(`tar -xf ${inspectorTgzPathInCache} -C ${pathToPackageInCache}`); - await this.$childProcess.exec(`npm install --prefix ${pathToUnzippedInspector}`); + const version = await this.getLatestCompatibleVersion(inspectorNpmPackageName); + await this.$childProcess.exec(`npm install ${inspectorNpmPackageName}@${version} --prefix ${cachePath}`); } + this.$logger.out("Using inspector from cache."); - return pathToUnzippedInspector; + return pathToPackageInCache; } + return inspectorPath; } + private prepareCacheDir(cacheDirName: string): void { + this.$fs.ensureDirectoryExists(cacheDirName); + + const cacheDirPackageJsonLocation = path.join(cacheDirName, constants.PACKAGE_JSON_FILE_NAME); + if (!this.$fs.exists(cacheDirPackageJsonLocation)) { + this.$fs.writeJson(cacheDirPackageJsonLocation, { + name: constants.INSPECTOR_CACHE_DIRNAME, + version: "0.1.0" + }); + } + } + private inspectorAlreadyInstalled(pathToInspector: string): Boolean { if (this.$fs.exists(pathToInspector)) { return true; From d0f8dc06371bead2e0cd739d3004e70410a728c4 Mon Sep 17 00:00:00 2001 From: Emil Tabakov Date: Wed, 31 May 2017 13:24:07 +0300 Subject: [PATCH 051/212] Add the available devices option to the help #2850 (#2854) --- docs/man_pages/device/device-android.md | 1 + docs/man_pages/device/device-ios.md | 1 + docs/man_pages/device/device.md | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/docs/man_pages/device/device-android.md b/docs/man_pages/device/device-android.md index 194b6280f9..2394853e16 100644 --- a/docs/man_pages/device/device-android.md +++ b/docs/man_pages/device/device-android.md @@ -8,6 +8,7 @@ General | `$ tns device android [--timeout ]` Lists all recognized connected Android physical and running virtual devices with serial number and index. <% if(isHtml) { %>If a connected Android device is not shown in the list, make sure that you have installed the required Android USB drivers on your system and that USB debugging is enabled on the device.<% } %> ### Options +* `--available-devices` - Lists all available emulators for Android. * `--timeout` - Sets the time in milliseconds for the operation to search for connected devices before completing. If not set, the default value is 4000. <% if(isHtml) { %>The operation will continue to wait and listen for newly connected devices and will list them after the specified time expires. ### Related Commands diff --git a/docs/man_pages/device/device-ios.md b/docs/man_pages/device/device-ios.md index 1269f967ee..f219c96546 100644 --- a/docs/man_pages/device/device-ios.md +++ b/docs/man_pages/device/device-ios.md @@ -11,6 +11,7 @@ Lists all recognized connected iOS devices with serial number and index. <% if((isConsole && (isWindows || isMacOS)) || isHtml) { %> ### Options +* `--available-devices` - Lists all available emulators for iOS. * `--timeout` - Sets the time in milliseconds for the operation to search for connected devices before completing. If not set, the default value is 4000. <% } %><% if(isHtml) { %>The operation will continue to wait and listen for newly connected devices and will list them after the specified time expires. ### Command Limitations diff --git a/docs/man_pages/device/device.md b/docs/man_pages/device/device.md index 22cdee2814..fe4b30467f 100644 --- a/docs/man_pages/device/device.md +++ b/docs/man_pages/device/device.md @@ -20,6 +20,10 @@ Lists all recognized connected Android <% if(isWindows || isMacOS) { %>or iOS de * You can run `$ tns device ios` on Windows and OS X systems. +### Aliases + +* You can use `$ tns devices` as an alias for `$ tns device`. + ### Related Commands Command | Description From 30cbdfaf25562f7da2259ff4502ea42e100addc8 Mon Sep 17 00:00:00 2001 From: Vasil Chimev Date: Tue, 30 May 2017 16:35:09 +0300 Subject: [PATCH 052/212] Update Android SDK packages in macOS setup script (#2857) --- setup/native-script.rb | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/setup/native-script.rb b/setup/native-script.rb index b70aa25d31..8bdc7fb51f 100755 --- a/setup/native-script.rb +++ b/setup/native-script.rb @@ -97,8 +97,7 @@ def install_environment_variable(name, value) unless ENV["ANDROID_HOME"] require 'pathname' - # if android-sdk was installed through brew, there should be a symlink in /usr/local/opt/android-sdk pointing to the actual sdk - android_home = "/usr/local/opt/android-sdk" + android_home = "/usr/local/share/android-sdk" unless Pathname.new(android_home).exist? require 'mkmf' # if there's no such symlink then try to find the `android-sdk` directory through the `android` executable @@ -129,32 +128,24 @@ def install_environment_variable(name, value) # the android tool will introduce a --accept-license option in subsequent releases error_msg = "There seem to be some problems with the Android configuration" -android_executable = File.join(ENV["ANDROID_HOME"], "tools", "bin", "sdkmanager") -execute("echo y | #{android_executable} \"platform-tools\"", error_msg) -execute("echo y | #{android_executable} \"tools\"", error_msg) -execute("echo y | #{android_executable} \"build-tools;25.0.2\"", error_msg) -execute("echo y | #{android_executable} \"platforms;android-25\"", error_msg) -execute("echo y | #{android_executable} \"platforms;android-24\"", error_msg) -execute("echo y | #{android_executable} \"platforms;android-23\"", error_msg) -execute("echo y | #{android_executable} \"platforms;android-22\"", error_msg) -execute("echo y | #{android_executable} \"platforms;android-21\"", error_msg) -execute("echo y | #{android_executable} \"platforms;android-19\"", error_msg) -execute("echo y | #{android_executable} \"platforms;android-18\"", error_msg) -execute("echo y | #{android_executable} \"platforms;android-17\"", error_msg) +sdk_manager = File.join(ENV["ANDROID_HOME"], "tools", "bin", "sdkmanager") +execute("echo y | #{sdk_manager} \"platform-tools\"", error_msg) +execute("echo y | #{sdk_manager} \"tools\"", error_msg) +execute("echo y | #{sdk_manager} \"build-tools;25.0.2\"", error_msg) +execute("echo y | #{sdk_manager} \"platforms;android-23\"", error_msg) +execute("echo y | #{sdk_manager} \"extras;android;m2repository\"", error_msg) +execute("echo y | #{sdk_manager} \"extras;google;m2repository\"", error_msg) puts "Do you want to install Android emulator? (y/n)" if gets.chomp.downcase == "y" puts "Do you want to install HAXM (Hardware accelerated Android emulator)? (y/n)" if gets.chomp.downcase == "y" - execute("echo y | #{android_executable} \"extras;intel;Hardware_Accelerated_Execution_Manager\"", error_msg) - + execute("echo y | #{sdk_manager} \"extras;intel;Hardware_Accelerated_Execution_Manager\"", error_msg) haxm_silent_installer = File.join(ENV["ANDROID_HOME"], "extras", "intel", "Hardware_Accelerated_Execution_Manager", "silent_install.sh") execute("sudo #{haxm_silent_installer}", "There seem to be some problems with the Android configuration") - else end - execute("echo y | #{android_executable} \"system-images;android-25;google_apis;x86\"", error_msg) - execute("echo y | #{android_executable} \"system-images;android-24;default;x86\"", error_msg) + execute("echo y | #{sdk_manager} \"system-images;android-23;default;x86\"", error_msg) end puts "The ANDROID_HOME and JAVA_HOME environment variables have been added to your .bash_profile/.zprofile" -puts "Restart the terminal or run `source ~/.bash_profile` to use them." +puts "Restart the terminal or run `source ~/.bash_profile` to use them." \ No newline at end of file From 29e67187a4cc4195e99890515f6d3c924e3839a6 Mon Sep 17 00:00:00 2001 From: pkoleva Date: Mon, 29 May 2017 11:32:12 +0300 Subject: [PATCH 053/212] Updated md files for the debug command. (#2836) * Updated md files for the debug command. * In Debug docs changed verbs to agreed form and added prerequisites for Android command --- .../project/testing/debug-android.md | 44 ++++++++----------- docs/man_pages/project/testing/debug-ios.md | 44 +++++++++---------- docs/man_pages/project/testing/debug.md | 9 ++-- 3 files changed, 41 insertions(+), 56 deletions(-) diff --git a/docs/man_pages/project/testing/debug-android.md b/docs/man_pages/project/testing/debug-android.md index 0020bb9586..c1c2021fd3 100644 --- a/docs/man_pages/project/testing/debug-android.md +++ b/docs/man_pages/project/testing/debug-android.md @@ -3,54 +3,46 @@ debug android Usage | Synopsis ---|--- -Deploy on device, run the app start Chrome DevTools, and attach the debugger | `$ tns debug android` -Deploy on device, run the app and stop at the first code statement | `$ tns debug android --debug-brk [--device ] [--debug-port ] [--timeout ]` -Deploy in the native emulator, run the app and stop at the first code statement | `$ tns debug android --debug-brk --emulator [] [--timeout ]` -Attach the debug tools to a running app on device | `$ tns debug android --start [--device ] [--debug-port ] [--timeout ]` -Attach the debug tools to a running app in the native emulator | `$ tns debug android --start --emulator [] [--timeout ]` -Detach the debug tools | `$ tns debug android --stop` +Deploy on device/emulator, run the app, follow generated link to use in Chrome Developer Tools, and attach the debugger | `$ tns debug android` +Deploy on device/emulator, run the app and stop at the first code statement | `$ tns debug android --debug-brk [--device ] [--timeout ]` +Deploy in the native emulator, run the app and stop at the first code statement | `$ tns debug android --debug-brk --emulator [--timeout ]` +Attach the debug tools to a running app on device/emulator | `$ tns debug android --start [--device ] [--timeout ]` +Attach the debug tools to a running app in the native emulator | `$ tns debug android --start --emulator [--timeout ]` Prepares, builds and deploys the project when necessary. Debugs your project on a connected device or emulator. -While debugging, prints the output from the application in the console and watches for changes in your code. Once a change is detected, it synchronizes the change with all selected devices and restarts/refreshes the application. +While debugging, prints the output from the application in the console and watches for changes in your code. Once a change is detected, it synchronizes the change with the selected device and restarts the application. ### Options -* `--device` - Specifies a connected device on which to debug the app. -* `--emulator` - Specifies that you want to debug the app in the native Android emulator from the Android SDK. -* `--debug-brk` - Prepares, builds and deploys the application package on a device or in an emulator, launches the Chrome DevTools of your Chrome browser and stops at the first code statement. +* `--device` - Specifies a connected device/emulator on which to debug the app. +* `--emulator` - Specifies that you want to debug the app in the native Android emulator. +* `--debug-brk` - Prepares, builds and deploys the application package on a device/emulator, generates a link for Chrome Developer Tools and stops at the first code statement. * `--start` - Attaches the debug tools to a deployed and running app. -* `--stop` - Detaches the debug tools. -* `--debug-port` - Sets a new port on which to attach the debug tools. -* `--timeout` - Sets the number of seconds that the NativeScript CLI will wait for the debugger to boot. If not set, the default timeout is 90 seconds. +* `--timeout` - Sets the number of seconds that the NativeScript CLI will wait for the emulator/device to boot. If not set, the default timeout is 90 seconds. * `--no-watch` - If set, changes in your code will not be reflected during the execution of this command. * `--clean` - If set, forces rebuilding the native application. ### Attributes -* `` is the index or name of the target device as listed by `$ tns device` -* `` is an accessible port on the device to which you want to attach the debugging tools. -* `` is any valid combination of options as listed by `$ tns help emulate android` +* `` is the device identifier or name of the target device as listed by `$ tns device android` <% if(isHtml) { %> ### Prerequisites -* You must have Chrome installed on your system.
If you are using a non-standard named Chrome app on an OS X system (for example, a nightly Canary update), you need to set this name in the `ANDROID_DEBUG_UI_MAC` setting in the NativeScript [config.json](file:///<%= #{config.getConfigPath(config)} %>). +* You must have Chrome installed on your system.
+ ### Related Commands Command | Description ----------|---------- -[build android](build-android.html) | Builds the project for Android and produces an APK that you can manually deploy on device or in the native emulator. -[build ios](build-ios.html) | Builds the project for iOS and produces an APP or IPA that you can manually deploy in the iOS Simulator or on device, respectively. [build](build.html) | Builds the project for the selected target platform and produces an application package that you can manually deploy on device or in the native emulator. -[debug ios](debug-ios.html) | Debugs your project on a connected iOS device or in a native emulator. +[build android](build-android.html) | Builds the project for Android and produces an APK that you can manually deploy on device or in the native emulator. [debug](debug.html) | Debugs your project on a connected device or in a native emulator. +[debug ios](debug-ios.html) | Debugs your project on a connected iOS device or in a native emulator. [deploy](deploy.html) | Builds and deploys the project to a connected physical or virtual device. -[emulate android](emulate-android.html) | Builds the specified project and runs it in a native Android emulator. -[emulate ios](emulate-ios.html) | Builds the specified project and runs it in the native iOS Simulator. -[emulate](emulate.html) | You must run the emulate command with a related command. -[run android](run-android.html) | Runs your project on a connected Android device or in a native Android emulator, if configured. -[run ios](run-ios.html) | Runs your project on a connected iOS device or in the iOS Simulator, if configured. +[device](../../device/device.html) | Lists all connected devices/emulators. +[device android](../../device/device-android.html) | Lists all connected devices/emulators for android. [run](run.html) | Runs your project on a connected device or in the native emulator for the selected platform. +[run android](run-android.html) | Runs your project on a connected Android device or in a native Android emulator, if configured. [test init](test-init.html) | Configures your project for unit testing with a selected framework. [test android](test-android.html) | Runs the tests in your project on Android devices or native emulators. -[test ios](test-ios.html) | Runs the tests in your project on iOS devices or the iOS Simulator. <% } %> \ No newline at end of file diff --git a/docs/man_pages/project/testing/debug-ios.md b/docs/man_pages/project/testing/debug-ios.md index 59766b276e..cd0fb05da2 100644 --- a/docs/man_pages/project/testing/debug-ios.md +++ b/docs/man_pages/project/testing/debug-ios.md @@ -3,14 +3,14 @@ debug ios Usage | Synopsis ---|--- -Deploy on device, run the app, start Safari Web Inspector and attach the debugger | `$ tns debug ios` -Deploy on device, run the app and stop at the first code statement | `$ tns debug ios --debug-brk [--device ] [--no-client]` -Deploy in the iOS Simulator, run the app and stop at the first code statement | `$ tns debug ios --debug-brk --emulator [] [--no-client]` -Attach the debug tools to a running app on device | `$ tns debug ios --start [--device ] [--no-client]` -Attach the debug tools to a running app in the iOS Simulator | `$ tns debug ios --start --emulator [] [--no-client]` +Deploy on device/simulator, run the app, start Safari Web Inspector and attache the debugger | `$ tns debug ios` +Deploy on device/simulator, run the app and stop at the first code statement | `$ tns debug ios --debug-brk [--device ] [--no-client]` +Deploy in the iOS simulator, run the app and stop at the first code statement | `$ tns debug ios --debug-brk --emulator [--no-client]` +Attach the debug tools to a running app on specified device or simulator| `$ tns debug ios --start [--device ] [--no-client]` +Attach the debug tools to a running app in the iOS simulator | `$ tns debug ios --start --emulator [--no-client]` -Prepares, builds and deploys the project when necessary. Debugs your project on a connected device or in the iOS Simulator. <% if(isHtml) { %>Any debugging traffic is forwarded on port 8080 from the device to the local machine.<% } %> -While debugging, prints the output from the application in the console and watches for changes in your code. Once a change is detected, it synchronizes the change with all selected devices and restarts/refreshes the application. +Prepares, builds and deploys the project when necessary. Debugs your project on a connected device or in the iOS simulator. <% if(isHtml) { %>Any debugging traffic is forwarded to port 8080( or the next available one) from the device to the local machine.<% } %> +While debugging, prints the output from the application in the console and watches for changes in your code. Once a change is detected, it synchronizes the change with the selected device and restarts the application. <% if(isConsole && (isWindows || isLinux)) { %>WARNING: You can run this command only on OS X systems. To view the complete help for this command, run `$ tns help debug ios`<% } %> @@ -18,23 +18,23 @@ While debugging, prints the output from the application in the console and watch <% if(isHtml) { %>> <% } %>IMPORTANT: Before building for iOS device, verify that you have configured a valid pair of certificate and provisioning profile on your OS X system. <% if(isHtml) { %>For more information, see [Obtaining Signing Identities and Downloading Provisioning Profiles](https://developer.apple.com/library/mac/recipes/xcode_help-accounts_preferences/articles/obtain_certificates_and_provisioning_profiles.html).<% } %> ### Options -* `--device` - Specifies a connected device on which to run the app. +* `--device` - Specifies a connected device or iOS simulator on which to run the app. * `--emulator` - Indicates that you want to debug your app in the iOS simulator. -* `--debug-brk` - Prepares, builds and deploys the application package on a device or in an emulator, runs the app, launches the developer tools of your Safari browser and stops at the first code statement. +* `--debug-brk` - Prepares, builds and deploys the application package on a device or in a simulator, runs the app, launches the developer tools of your Safari browser and stops at the first code statement. * `--start` - Attaches the debug tools to a deployed and running app and launches the developer tools of your Safari browser. -* `--no-client` - If set, the NativeScript CLI attaches the debug tools but does not launch the developer tools in Safari. -* `--timeout` - Sets the number of seconds that NativeScript CLI will wait for the debugger to boot. If not set, the default timeout is 90 seconds. +* `--no-client` - If set, the NativeScript CLI attaches the debug tools but does not launch the developer tools in Safari. Could be used on already started Safari Web Inspector. +* `--timeout` - Sets the number of seconds that NativeScript CLI will wait for the simulator/device to boot. If not set, the default timeout is 90 seconds. * `--no-watch` - If set, changes in your code will not be reflected during the execution of this command. * `--clean` - If set, forces rebuilding the native application. +* `--chrome` - Allows debugging in Chrome Developer Tools. If set Safari Web Inspector is not started and debugging is attached to Chrome Developer Tools. ### Attributes -* `` is the index or name of the target device as listed by `$ tns device` -* `` is any valid combination of options as listed by `$ tns help emulate ios` +* `` is the device identifier of the target device as listed by `$ tns device ios` <% } %> <% if(isHtml) { %> ### Prerequisite -* If you want to debug in the iOS Simulator, you must have Xcode 6 or later installed on your system. +* If you want to debug in the iOS simulator, you must have Xcode 6 or later installed on your system. ### Command Limitations @@ -44,19 +44,15 @@ While debugging, prints the output from the application in the console and watch Command | Description ----------|---------- -[build android](build-android.html) | Builds the project for Android and produces an APK that you can manually deploy on device or in the native emulator. -[build ios](build-ios.html) | Builds the project for iOS and produces an APP or IPA that you can manually deploy in the iOS Simulator or on device, respectively. [build](build.html) | Builds the project for the selected target platform and produces an application package that you can manually deploy on device or in the native emulator. -[debug android](debug-android.html) | Debugs your project on a connected Android device or in a native emulator. +[build ios](build-ios.html) | Builds the project for iOS and produces an APP or IPA that you can manually deploy in the iOS simulator or on device, respectively. [debug](debug.html) | Debugs your project on a connected device or in a native emulator. +[debug android](debug-android.html) | Debugs your project on a connected Android device or in a native emulator. [deploy](deploy.html) | Builds and deploys the project to a connected physical or virtual device. -[emulate android](emulate-android.html) | Builds the specified project and runs it in a native Android emulator. -[emulate ios](emulate-ios.html) | Builds the specified project and runs it in the native iOS Simulator. -[emulate](emulate.html) | You must run the emulate command with a related command. -[run android](run-android.html) | Runs your project on a connected Android device or in a native Android emulator, if configured. -[run ios](run-ios.html) | Runs your project on a connected iOS device or in the iOS Simulator, if configured. +[device](../../device/device.html) | Lists all connected devices/emulators. +[device ios](../../device/device-ios.html) | Lists all connected devices/simulators for iOS. [run](run.html) | Runs your project on a connected device or in the native emulator for the selected platform. +[run ios](run-ios.html) | Runs your project on a connected iOS device or in the iOS simulator, if configured. [test init](test-init.html) | Configures your project for unit testing with a selected framework. -[test android](test-android.html) | Runs the tests in your project on Android devices or native emulators. -[test ios](test-ios.html) | Runs the tests in your project on iOS devices or the iOS Simulator. +[test ios](test-ios.html) | Runs the tests in your project on iOS devices or the iOS simulator. <% } %> \ No newline at end of file diff --git a/docs/man_pages/project/testing/debug.md b/docs/man_pages/project/testing/debug.md index f0d8d6bde0..f9a7123d0f 100644 --- a/docs/man_pages/project/testing/debug.md +++ b/docs/man_pages/project/testing/debug.md @@ -5,12 +5,12 @@ Usage | Synopsis ---|--- <% if((isConsole && isMacOS) || isHtml) { %>General | `$ tns debug `<% } %><% if(isConsole && (isLinux || isWindows)) { %>General | `$ tns debug android`<% } %> -Debugs your project on a connected device or in a native emulator. <% if(isMacOS) { %>You must specify the target platform on which you want to debug.<% } %> The command will prepare, build and deploy the app when necessary. By default listens for changes in your code, synchronizes those changes and refreshes the selected device. +Debugs your project on a connected device or in a native emulator. <% if(isMacOS) { %>You must specify the target platform on which you want to debug.<% } %> The command will prepare, build and deploy the app when necessary. By default listens for changes in your code, synchronizes those changes and restarts the app on the targeted device. <% if((isConsole && isMacOS) || isHtml) { %>### Attributes -`` is the target mobile platform for which you want to debug your project. You can set the following target platforms. +`` is the target mobile platform for which you want to debug your project. You can set the following target platforms: * `android` - Debugs your project on a connected Android device or Android emulator. -* `ios` - Debugs your project on a connected iOS device or in a native iOS emulator.<% } %> +* `ios` - Debugs your project on a connected iOS device or in a native iOS simulator.<% } %> <% if(isHtml) { %> ### Command Limitations @@ -27,9 +27,6 @@ Command | Description [debug android](debug-android.html) | Debugs your project on a connected Android device or in a native emulator. [debug ios](debug-ios.html) | Debugs your project on a connected iOS device or in a native emulator. [deploy](deploy.html) | Builds and deploys the project to a connected physical or virtual device. -[emulate android](emulate-android.html) | Builds the specified project and runs it in a native Android emulator. -[emulate ios](emulate-ios.html) | Builds the specified project and runs it in the native iOS Simulator. -[emulate](emulate.html) | You must run the emulate command with a related command. [run android](run-android.html) | Runs your project on a connected Android device or in a native Android emulator, if configured. [run ios](run-ios.html) | Runs your project on a connected iOS device or in the iOS Simulator, if configured. [run](run.html) | Runs your project on a connected device or in the native emulator for the selected platform. From 2e5b96305d33b2e73d1a04a9a51e6c4f988e3a66 Mon Sep 17 00:00:00 2001 From: PetyaSotirova Date: Fri, 26 May 2017 16:19:47 +0300 Subject: [PATCH 054/212] Remove reference to Google groups in README (#2838) Changing it with a link to the forums. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 88fb5de239..7f5446954c 100644 --- a/README.md +++ b/README.md @@ -377,7 +377,7 @@ tns doctor This command prints warnings about current configuration issues and provides basic information about how to resolve them. -If addressing the configuration issues does not resolve your problem, you can [report an issue](https://github.com/NativeScript/nativescript-cli/blob/master/CONTRIBUTING.md#report-an-issue) or [post in the NativeScript page in Google Groups](https://groups.google.com/forum/#!forum/nativescript). +If addressing the configuration issues does not resolve your problem, you can [report an issue](https://github.com/NativeScript/nativescript-cli/blob/master/CONTRIBUTING.md#report-an-issue) or [post in the NativeScript forums](https://discourse.nativescript.org/). [Back to Top][1] From d9f2ab461114e4472f029b8a15969db88d21a10c Mon Sep 17 00:00:00 2001 From: dtopuzov Date: Thu, 1 Jun 2017 10:24:02 +0300 Subject: [PATCH 055/212] Bump version and add release notes (#2866) * Update CHANGELOG.md and bump version * Update date --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b5b9e9803..5804e77b03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ NativeScript CLI Changelog ================ +3.0.3 (2017, June 1) +== + +### Fixed + +* [Fix #2855](https://github.com/NativeScript/nativescript-cli/issues/2855): iOS inspector not installed with npm 5 + 3.0.2 (2017, May 30) == diff --git a/package.json b/package.json index 926c65c928..a90724bf89 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "3.0.2", + "version": "3.0.3", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { From a1867c84a10fb7a983d9ff6ed9abb720ca98bd92 Mon Sep 17 00:00:00 2001 From: dtopuzov Date: Thu, 1 Jun 2017 14:04:18 +0300 Subject: [PATCH 056/212] Update CocoaPods.md (#2867) --- CocoaPods.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CocoaPods.md b/CocoaPods.md index 08ec9ce849..51bb940a7b 100644 --- a/CocoaPods.md +++ b/CocoaPods.md @@ -14,6 +14,7 @@ You need to install CocoaPods. If you haven't yet, you can do so by running: ```bash $ sudo gem install cocoapods +$ pod setup ``` > **NOTE:** The minimum required version of CocoaPods is 1.0.0. @@ -27,6 +28,7 @@ To update CocoaPods, just run the installation command again. ``` sudo gem install cocoapods +pod setup ``` ## Create CLI Project From b615149c72f7deeb248dd2bd27044dc231c06047 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Thu, 1 Jun 2017 15:50:57 +0300 Subject: [PATCH 057/212] Expose loadExtension to fix double loading of packages (#2868) * Expose loadExtension to fix double loading of packages In case you install extension, the method automatically loads it. However calling `loadExtensions` after that loads it again, so our `$injector` fails. In order to fix this, remove the loading from the `installExtension` method and expose `loadExtension(name)` method. So in the future in case someone wants to load a new an extension after calling `loadExtensions`, they'll be able to use the new method. * Update ios-device-lib Update `ios-device-lib` to 0.4.3. The new version includes a fix for long living processes where detaching iOS device causes a huge memory consuption and core dump. --- PublicAPI.md | 29 ++++++++- .../extensibility/install-extension.ts | 3 + lib/definitions/extensibility.d.ts | 10 ++- lib/services/extensibility-service.ts | 5 +- package.json | 2 +- test/nativescript-cli-lib.ts | 2 +- test/services/extensibility-service.ts | 64 ++++++++++--------- 7 files changed, 77 insertions(+), 38 deletions(-) diff --git a/PublicAPI.md b/PublicAPI.md index 394d39f390..3e3a89b629 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -94,12 +94,12 @@ interface IExtensionData { ``` ### installExtension -Installs specified extension and loads it in the current process, so the functionality that it adds can be used immediately. +Installs specified extension. * Definition: ```TypeScript /** - * Installs and loads specified extension. + * Installs a specified extension. * @param {string} extensionName Name of the extension to be installed. It may contain version as well, i.e. myPackage, myPackage@1.0.0, myPackage.tgz, https://github.com/myOrganization/myPackage/tarball/master, https://github.com/myOrganization/myPackage etc. * @returns {Promise} Information about installed extensions. */ @@ -180,6 +180,31 @@ for (let promise of loadExtensionsPromises) { } ``` +### loadExtension +Loads a specified extension. + +* Definition +```TypeScript +/** + * Loads a single extension, so its methods and commands can be used from CLI. + * @param {string} extensionName Name of the extension to be installed. It may contain version as well, i.e. myPackage, myPackage@1.0.0 + * A Promise is returned. It will be rejected in case the extension cannot be loaded. + * @returns {Promise} Promise, resolved with IExtensionData. + */ +loadExtension(extensionName: string): Promise; +``` + +* Usage: +```JavaScript +tns.extensibilityService.loadExtension("my-extension") + .then(extensionData => console.log(`Loaded extension: ${extensionData.extensionName}.`), + err => { + console.log(`Failed to load extension: ${err.extensionName}`); + console.log(err); + }); +} +``` + ## settingsService `settingsService` module provides a way to configure various settings. diff --git a/lib/commands/extensibility/install-extension.ts b/lib/commands/extensibility/install-extension.ts index 91bea2a76a..08513d67bc 100644 --- a/lib/commands/extensibility/install-extension.ts +++ b/lib/commands/extensibility/install-extension.ts @@ -6,6 +6,9 @@ export class InstallExtensionCommand implements ICommand { public async execute(args: string[]): Promise { const extensionData = await this.$extensibilityService.installExtension(args[0]); this.$logger.info(`Successfully installed extension ${extensionData.extensionName}.`); + + await this.$extensibilityService.loadExtension(extensionData.extensionName); + this.$logger.info(`Successfully loaded extension ${extensionData.extensionName}.`); } allowedParameters: ICommandParameter[] = [this.$stringParameterBuilder.createMandatoryParameter("You have to provide a valid name for extension that you want to install.")]; diff --git a/lib/definitions/extensibility.d.ts b/lib/definitions/extensibility.d.ts index 3aa9dba5cd..90d2916ef9 100644 --- a/lib/definitions/extensibility.d.ts +++ b/lib/definitions/extensibility.d.ts @@ -13,7 +13,7 @@ interface IExtensionData { */ interface IExtensibilityService { /** - * Installs and loads specified extension. + * Installs a specified extension. * @param {string} extensionName Name of the extension to be installed. It may contain version as well, i.e. myPackage, myPackage@1.0.0, * myPackage.tgz, https://github.com/myOrganization/myPackage/tarball/master, https://github.com/myOrganization/myPackage, etc. * @returns {Promise} Information about installed extensions. @@ -35,6 +35,14 @@ interface IExtensibilityService { */ loadExtensions(): Promise[]; + /** + * Loads a single extension, so its methods and commands can be used from CLI. + * @param {string} extensionName Name of the extension to be installed. It may contain version as well, i.e. myPackage, myPackage@1.0.0 + * A Promise is returned. It will be rejected in case the extension cannot be loaded. + * @returns {Promise} Promise, resolved with IExtensionData. + */ + loadExtension(extensionName: string): Promise; + /** * Gets information about installed dependencies - names and versions. * @returns {IStringDictionary} diff --git a/lib/services/extensibility-service.ts b/lib/services/extensibility-service.ts index 3eaadf6456..9cbed92579 100644 --- a/lib/services/extensibility-service.ts +++ b/lib/services/extensibility-service.ts @@ -35,7 +35,7 @@ export class ExtensibilityService implements IExtensibilityService { const installResultInfo = await this.$npm.install(packageName, this.pathToExtensions, npmOpts); this.$logger.trace(`Finished installation of extension '${extensionName}'. Trying to load it now.`); - return await this.loadExtension(installResultInfo.name); + return { extensionName: installResultInfo.name }; } @exported("extensibilityService") @@ -74,7 +74,8 @@ export class ExtensibilityService implements IExtensibilityService { return null; } - private async loadExtension(extensionName: string): Promise { + @exported("extensibilityService") + public async loadExtension(extensionName: string): Promise { try { await this.assertExtensionIsInstalled(extensionName); diff --git a/package.json b/package.json index 2226ba0a2d..ac14a00071 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "glob": "^7.0.3", "iconv-lite": "0.4.11", "inquirer": "0.9.0", - "ios-device-lib": "0.4.2", + "ios-device-lib": "0.4.3", "ios-mobileprovision-finder": "1.0.9", "ios-sim-portable": "~3.0.0", "lockfile": "1.0.1", diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index 118fd54383..653d440dec 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -19,7 +19,7 @@ describe("nativescript-cli-lib", () => { localBuildService: ["build"], deviceLogProvider: null, npm: ["install", "uninstall", "view", "search"], - extensibilityService: ["loadExtensions", "getInstalledExtensions", "installExtension", "uninstallExtension"], + extensibilityService: ["loadExtensions", "loadExtension", "getInstalledExtensions", "installExtension", "uninstallExtension"], analyticsService: ["startEqatecMonitor"], debugService: ["debug"] }; diff --git a/test/services/extensibility-service.ts b/test/services/extensibility-service.ts index 25d80063a9..ba32641507 100644 --- a/test/services/extensibility-service.ts +++ b/test/services/extensibility-service.ts @@ -136,37 +136,6 @@ describe("extensibilityService", () => { const actualResult = await extensibilityService.installExtension(extensionName); assert.deepEqual(actualResult, { extensionName }); }); - - it("throws error that has extensionName property when unable to load extension", async () => { - const expectedErrorMessage = "Require failed"; - - const extensionName = "extension1"; - const testInjector = getTestInjector(); - const fs: IFileSystem = testInjector.resolve("fs"); - fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) !== extensionName; - - fs.readDirectory = (dir: string): string[] => [extensionName]; - - const npm: INodePackageManager = testInjector.resolve("npm"); - npm.install = async (packageName: string, pathToSave: string, config?: any): Promise => ({ name: extensionName }); - - const requireService: IRequireService = testInjector.resolve("requireService"); - requireService.require = (pathToRequire: string) => { - throw new Error(expectedErrorMessage); - }; - - const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); - let isErrorRaised = false; - try { - await extensibilityService.installExtension(extensionName); - } catch (err) { - isErrorRaised = true; - assert.deepEqual(err.message, `Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds.`); - assert.deepEqual(err.extensionName, extensionName); - } - - assert.isTrue(isErrorRaised); - }); }); describe("loadExtensions", () => { @@ -609,4 +578,37 @@ describe("extensibilityService", () => { assert.deepEqual(extensibilityService.getInstalledExtensions(), dependencies); }); }); + + describe("loadExtension", () => { + it("throws error that has extensionName property when unable to load extension", async () => { + const expectedErrorMessage = "Require failed"; + + const extensionName = "extension1"; + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) !== extensionName; + + fs.readDirectory = (dir: string): string[] => [extensionName]; + + const npm: INodePackageManager = testInjector.resolve("npm"); + npm.install = async (packageName: string, pathToSave: string, config?: any): Promise => ({ name: extensionName }); + + const requireService: IRequireService = testInjector.resolve("requireService"); + requireService.require = (pathToRequire: string) => { + throw new Error(expectedErrorMessage); + }; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + let isErrorRaised = false; + try { + await extensibilityService.loadExtension(extensionName); + } catch (err) { + isErrorRaised = true; + assert.deepEqual(err.message, `Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds.`); + assert.deepEqual(err.extensionName, extensionName); + } + + assert.isTrue(isErrorRaised); + }); + }); }); From b490242396d893082d4c85acb4b3cdaa3897f954 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Fri, 2 Jun 2017 11:37:51 +0300 Subject: [PATCH 058/212] Fix analytics (#2869) Fix analytics, so we'll no longer track information for users, who have not allowed us to do so. There are several problems: * Whenever the terminal is not interactive and we do not know if user allows us to track them, we set the value of TrackExceptions to true. Also we immediately send information to Analytics that we've set the value to true. So all CI builds are tracked as new users, as we start a session for them and send the value of TrackExceptions (enabled). * `setStatus` method almost always sends data to Analytics. This method should not send information. It does not care if we have allowed to be tracked - it always sends data. Remove this logic. * `tns usage-reporting ` and `tns error-reporting ` always send information to Analytics - in fact the commands are calling "setStatus", that's why they send information. In order to track them, use the "trackFeature" method which will track information ONLY when user had allowed this. * Fix `getStatus` method which had incorrect behavior - the if has been using the values of the enum (0,1,2) and we were getting in the body of the if statement in a very inaccurate cases. * Remove unit tests for `setStatus` and add new ones for checkConsent logic. --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index ebfdf06c65..2ef1887fff 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit ebfdf06c656604551bfd4d261185f10cb7c566af +Subproject commit 2ef1887fffc2895959a82bb130867353ef8828e9 From 66a47adebb6129dc0e3f2104fe6bf6dceabffb36 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 2 Jun 2017 16:37:34 +0300 Subject: [PATCH 059/212] Update submodule to latest version --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index e3a972f749..1829df1b87 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit e3a972f7496ccf3924568c133197ed2e308f8540 +Subproject commit 1829df1b871af9da7bba6dd1225480c67ed12698 From d3dbadebc871891fdbb9b5f01083d306b9ce4400 Mon Sep 17 00:00:00 2001 From: Vasil Chimev Date: Tue, 6 Jun 2017 09:37:53 +0300 Subject: [PATCH 060/212] Bump version to 3.1.0 (#2878) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 925bca1ccf..2565baf463 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "3.0.3", + "version": "3.1.0", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { From ab08c918d52e8077d79e38804052c31b0b31478f Mon Sep 17 00:00:00 2001 From: Toma Nikolov Date: Tue, 6 Jun 2017 11:46:15 +0300 Subject: [PATCH 061/212] Add CFBundleURLSchemes when building for iOS in debug. (#2863) --- lib/services/ios-project-service.ts | 34 ++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 868122d5ff..79362f2a91 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -329,8 +329,10 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ // build only for device specific architecture if (!buildConfig.release && !buildConfig.architectures) { - await this.$devicesService.initialize({ platform: this.$devicePlatformsConstants.iOS.toLowerCase(), deviceId: buildConfig.device, - isBuildForDevice: true }); + await this.$devicesService.initialize({ + platform: this.$devicePlatformsConstants.iOS.toLowerCase(), deviceId: buildConfig.device, + isBuildForDevice: true + }); let instances = this.$devicesService.getDeviceInstances(); let devicesArchitectures = _(instances) .filter(d => this.$mobileHelper.isiOSPlatform(d.deviceInfo.platform) && d.deviceInfo.activeArchitecture) @@ -672,7 +674,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } public async processConfigurationFilesFromAppResources(release: boolean, projectData: IProjectData): Promise { - await this.mergeInfoPlists(projectData); + await this.mergeInfoPlists({ release }, projectData); await this.$iOSEntitlementsService.merge(projectData); await this.mergeProjectXcconfigFiles(release, projectData); for (let pluginData of await this.getAllInstalledPlugins(projectData)) { @@ -704,7 +706,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f return Promise.resolve(); } - private async mergeInfoPlists(projectData: IProjectData): Promise { + private async mergeInfoPlists(buildOptions: IRelease, projectData: IProjectData): Promise { let projectDir = projectData.projectDir; let infoPlistPath = path.join(projectDir, constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME, this.getPlatformData(projectData).normalizedPlatformName, this.getPlatformData(projectData).configurationFileName); this.ensureConfigurationFileInAppResources(); @@ -745,7 +747,29 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f CFBundleIdentifier - ${ projectData.projectId} + ${projectData.projectId} + + ` + }); + } + + if (!buildOptions.release && projectData.projectId) { + session.patch({ + name: "CFBundleURLTypes from package.json nativescript.id", + read: () => + ` + + + + CFBundleURLTypes + + + CFBundleURLSchemes + + ${projectData.projectId.replace(/[^A-Za-z0-9]/g, "")} + + + ` }); From c1c3ac429e3005cf0f286a8baacbb1bacc5e3845 Mon Sep 17 00:00:00 2001 From: Peter Kanev Date: Tue, 6 Jun 2017 15:20:36 +0300 Subject: [PATCH 062/212] Flatten scoped module when copying into android/src (#2837) * fix path when removing dir in a scoped dependency during prepare * flatten android plugins with native code when copying into main/src --- lib/services/android-project-service.ts | 5 ++++- lib/tools/node-modules/node-modules-dest-copy.ts | 16 +++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index e2804185e2..b4e3d63f02 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -361,8 +361,11 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject if (this.$fs.exists(pluginPlatformsFolderPath)) { this.$fs.ensureDirectoryExists(pluginConfigurationDirectoryPath); + let isScoped = pluginData.name.indexOf("@") === 0; + let flattenedDependencyName = isScoped ? pluginData.name.replace("/", "_") : pluginData.name; + // Copy all resources from plugin - let resourcesDestinationDirectoryPath = path.join(this.getPlatformData(projectData).projectRoot, "src", pluginData.name); + let resourcesDestinationDirectoryPath = path.join(this.getPlatformData(projectData).projectRoot, "src", flattenedDependencyName); this.$fs.ensureDirectoryExists(resourcesDestinationDirectoryPath); shell.cp("-Rf", path.join(pluginPlatformsFolderPath, "*"), resourcesDestinationDirectoryPath); diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts index c5cb4f4864..c035f3b197 100644 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ b/lib/tools/node-modules/node-modules-dest-copy.ts @@ -36,18 +36,20 @@ export class TnsModulesCopy { private copyDependencyDir(dependency: IDependencyData): void { if (dependency.depth === 0) { + const targetPackageDir = path.join(this.outputRoot, dependency.name); + + shelljs.mkdir("-p", targetPackageDir); + let isScoped = dependency.name.indexOf("@") === 0; - let targetDir = this.outputRoot; if (isScoped) { - targetDir = path.join(this.outputRoot, dependency.name.substring(0, dependency.name.indexOf("/"))); + // copy module into tns_modules/@scope/module instead of tns_modules/module + shelljs.cp("-Rf", dependency.directory, path.join(this.outputRoot, dependency.name.substring(0, dependency.name.indexOf("/")))); + } else { + shelljs.cp("-Rf", dependency.directory, this.outputRoot); } - shelljs.mkdir("-p", targetDir); - shelljs.cp("-Rf", dependency.directory, targetDir); - - //remove platform-specific files (processed separately by plugin services) - const targetPackageDir = path.join(targetDir, dependency.name); + // remove platform-specific files (processed separately by plugin services) shelljs.rm("-rf", path.join(targetPackageDir, "platforms")); } } From 55b170a71f74cf5bd5ff8d24a1e1ff018f38f0d6 Mon Sep 17 00:00:00 2001 From: Panayot Cankov Date: Thu, 8 Jun 2017 16:28:02 +0300 Subject: [PATCH 063/212] Add Content-Length to the itunes request applications service request (#2882) --- lib/services/itmstransporter-service.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/services/itmstransporter-service.ts b/lib/services/itmstransporter-service.ts index c41969ffa0..7405e3f29a 100644 --- a/lib/services/itmstransporter-service.ts +++ b/lib/services/itmstransporter-service.ts @@ -58,7 +58,10 @@ export class ITMSTransporterService implements IITMSTransporterService { contentDeliveryResponse = await this.$httpClient.httpRequest({ url: "https://contentdelivery.itunes.apple.com/WebObjects/MZLabelService.woa/json/MZITunesProducerService", method: "POST", - body: requestBody + body: requestBody, + headers: { + "Content-Length": requestBody.length + } }), contentDeliveryBody: IContentDeliveryBody = JSON.parse(contentDeliveryResponse.body); @@ -170,12 +173,12 @@ export class ITMSTransporterService implements IITMSTransporterService { return this._itmsTransporterPath; } - private getContentDeliveryRequestBody(credentials: ICredentials): string { + private getContentDeliveryRequestBody(credentials: ICredentials): Buffer { // All of those values except credentials are hardcoded // Apple's content delivery API is very picky with handling requests // and if only one of these ends up missing the API returns // a response with 200 status code and an error - return JSON.stringify({ + return Buffer.from(JSON.stringify({ id: "1", // magic number jsonrpc: "2.0", method: "lookupSoftwareApplications", @@ -186,7 +189,7 @@ export class ITMSTransporterService implements IITMSTransporterService { Application: "Application Loader", OSIdentifier: "Mac OS X 10.8.5 (x86_64)" } - }); + }), "utf8"); } private getITMSMetadataXml(appleId: string, ipaFileName: string, ipaFileHash: string, ipaFileSize: number): string { From b99145d5d646f4f0a0515ed9fdf6570d925dafca Mon Sep 17 00:00:00 2001 From: Emil Tabakov Date: Thu, 8 Jun 2017 16:58:09 +0300 Subject: [PATCH 064/212] #2856 Remove non-production dependencies which break npm links (#2880) --- .../node-modules/node-modules-dest-copy.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts index c035f3b197..cf904d28e6 100644 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ b/lib/tools/node-modules/node-modules-dest-copy.ts @@ -29,7 +29,7 @@ export class TnsModulesCopy { let matchPattern = this.$options.release ? "**/*.ts" : "**/*.d.ts"; allFiles.filter(file => minimatch(file, matchPattern, { nocase: true })).map(file => this.$fs.deleteFile(file)); - shelljs.rm("-rf", path.join(tnsCoreModulesResourcePath, "node_modules")); + shelljs.rm("-rf", path.join(tnsCoreModulesResourcePath, constants.NODE_MODULES_FOLDER_NAME)); } } } @@ -51,6 +51,25 @@ export class TnsModulesCopy { // remove platform-specific files (processed separately by plugin services) shelljs.rm("-rf", path.join(targetPackageDir, "platforms")); + + this.removeNonProductionDependencies(dependency, targetPackageDir); + } + } + + private removeNonProductionDependencies(dependency: IDependencyData, targetPackageDir: string): void { + const packageJsonFilePath = path.join(dependency.directory, constants.PACKAGE_JSON_FILE_NAME); + if (!this.$fs.exists(packageJsonFilePath)) { + return; + } + + const packageJsonContent = this.$fs.readJson(packageJsonFilePath); + const productionDependencies = packageJsonContent.dependencies; + + const dependenciesFolder = path.join(targetPackageDir, constants.NODE_MODULES_FOLDER_NAME); + if (this.$fs.exists(dependenciesFolder)) { + const dependencies = this.$fs.readDirectory(dependenciesFolder); + dependencies.filter(dir => !!productionDependencies || !productionDependencies.hasOwnProperty(dir)) + .forEach(dir => shelljs.rm("-rf", path.join(dependenciesFolder, dir))); } } } From 2f3745af54738a95170956654f6692696deef8ef Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Tue, 13 Jun 2017 17:30:56 +0300 Subject: [PATCH 065/212] Fix unit tests (#2886) --- test/project-service.ts | 118 ++++++++++++---------------------------- 1 file changed, 36 insertions(+), 82 deletions(-) diff --git a/test/project-service.ts b/test/project-service.ts index 8a1c7b0ad4..8fd3e9d574 100644 --- a/test/project-service.ts +++ b/test/project-service.ts @@ -29,6 +29,29 @@ let originalIsInteractive = helpers.isInteractive; temp.track(); +async function prepareTestingPath(testInjector: IInjector, packageToInstall: string, packageName: string, options?: INpmInstallOptions): Promise { + options = options || { dependencyType: "save" }; + const fs = testInjector.resolve("fs"); + + let npmInstallationManager = testInjector.resolve("npmInstallationManager"); + let defaultTemplateDir = temp.mkdirSync("project-service"); + fs.writeJson(path.join(defaultTemplateDir, constants.PACKAGE_JSON_FILE_NAME), { + "name": "defaultTemplate", + "version": "1.0.0", + "description": "dummy", + "license": "MIT", + "readme": "dummy", + "repository": "dummy" + }); + + await npmInstallationManager.install(packageToInstall, defaultTemplateDir, options); + const defaultTemplatePath = path.join(defaultTemplateDir, constants.NODE_MODULES_FOLDER_NAME, packageName); + + fs.deleteDirectory(path.join(defaultTemplatePath, constants.NODE_MODULES_FOLDER_NAME)); + + return defaultTemplatePath; +} + class ProjectIntegrationTest { public testInjector: IInjector; @@ -146,85 +169,12 @@ describe("Project Service Tests", () => { before(async () => { let projectIntegrationTest = new ProjectIntegrationTest(); - let fs: IFileSystem = projectIntegrationTest.testInjector.resolve("fs"); - let npmInstallationManager: INpmInstallationManager = projectIntegrationTest.testInjector.resolve("npmInstallationManager"); - - let defaultTemplateDir = temp.mkdirSync("defaultTemplate"); - fs.writeJson(path.join(defaultTemplateDir, "package.json"), { - "name": "defaultTemplate", - "version": "1.0.0", - "description": "dummy", - "license": "MIT", - "readme": "dummy", - "repository": "dummy" - }); - - await npmInstallationManager.install(constants.RESERVED_TEMPLATE_NAMES["default"], defaultTemplateDir, { dependencyType: "save" }); - defaultTemplatePath = path.join(defaultTemplateDir, "node_modules", constants.RESERVED_TEMPLATE_NAMES["default"]); - - fs.deleteDirectory(path.join(defaultTemplatePath, "node_modules")); - - let defaultSpecificVersionTemplateDir = temp.mkdirSync("defaultTemplateSpeciffic"); - fs.writeJson(path.join(defaultSpecificVersionTemplateDir, "package.json"), { - "name": "defaultTemplateSpecialVersion", - "version": "1.0.0", - "description": "dummy", - "license": "MIT", - "readme": "dummy", - "repository": "dummy" - }); - - await npmInstallationManager.install(constants.RESERVED_TEMPLATE_NAMES["default"], defaultSpecificVersionTemplateDir, { version: "1.4.0", dependencyType: "save" }); - defaultSpecificVersionTemplatePath = path.join(defaultSpecificVersionTemplateDir, "node_modules", constants.RESERVED_TEMPLATE_NAMES["default"]); - - fs.deleteDirectory(path.join(defaultSpecificVersionTemplatePath, "node_modules")); - - let angularTemplateDir = temp.mkdirSync("angularTemplate"); - fs.writeJson(path.join(angularTemplateDir, "package.json"), { - "name": "angularTemplate", - "version": "1.0.0", - "description": "dummy", - "license": "MIT", - "readme": "dummy", - "repository": "dummy" - }); - - await npmInstallationManager.install(constants.RESERVED_TEMPLATE_NAMES["angular"], angularTemplateDir, { dependencyType: "save" }); - angularTemplatePath = path.join(angularTemplateDir, "node_modules", constants.RESERVED_TEMPLATE_NAMES["angular"]); - - fs.deleteDirectory(path.join(angularTemplatePath, "node_modules")); - - let typescriptTemplateDir = temp.mkdirSync("typescriptTemplate"); - fs.writeJson(path.join(typescriptTemplateDir, "package.json"), { - "name": "typescriptTemplate", - "version": "1.0.0", - "description": "dummy", - "license": "MIT", - "readme": "dummy", - "repository": "dummy" - }); - - await npmInstallationManager.install(constants.RESERVED_TEMPLATE_NAMES["typescript"], typescriptTemplateDir, { dependencyType: "save" }); - typescriptTemplatePath = path.join(typescriptTemplateDir, "node_modules", constants.RESERVED_TEMPLATE_NAMES["typescript"]); - - fs.deleteDirectory(path.join(typescriptTemplatePath, "node_modules")); - let noAppResourcesTemplateDir = temp.mkdirSync("noAppResources"); - fs.writeJson(path.join(noAppResourcesTemplateDir, "package.json"), { - "name": "blankTemplate", - "version": "1.0.0", - "description": "dummy", - "license": "MIT", - "readme": "dummy", - "repository": "dummy" - }); - - await npmInstallationManager.install(noAppResourcesTemplateName, noAppResourcesTemplateDir, { - dependencyType: "save", - version: "2.0.0" - }); - noAppResourcesTemplatePath = path.join(noAppResourcesTemplateDir, "node_modules", noAppResourcesTemplateName); - fs.deleteDirectory(path.join(noAppResourcesTemplatePath, "node_modules")); + defaultTemplatePath = await prepareTestingPath(projectIntegrationTest.testInjector, constants.RESERVED_TEMPLATE_NAMES["default"], constants.RESERVED_TEMPLATE_NAMES["default"]); + defaultSpecificVersionTemplatePath = await prepareTestingPath(projectIntegrationTest.testInjector, constants.RESERVED_TEMPLATE_NAMES["default"], constants.RESERVED_TEMPLATE_NAMES["default"], { version: "1.4.0", dependencyType: "save" }); + angularTemplatePath = await prepareTestingPath(projectIntegrationTest.testInjector, constants.RESERVED_TEMPLATE_NAMES["angular"], constants.RESERVED_TEMPLATE_NAMES["angular"]); + typescriptTemplatePath = await prepareTestingPath(projectIntegrationTest.testInjector, constants.RESERVED_TEMPLATE_NAMES["typescript"], constants.RESERVED_TEMPLATE_NAMES["typescript"]); + noAppResourcesTemplatePath = await prepareTestingPath(projectIntegrationTest.testInjector, noAppResourcesTemplateName, noAppResourcesTemplateName, { dependencyType: "save", version: "2.0.0" }); }); it("creates valid project from default template", async () => { @@ -340,28 +290,32 @@ describe("Project Service Tests", () => { let projectIntegrationTest = new ProjectIntegrationTest(); let tempFolder = temp.mkdirSync("projectLocalDir"); let projectName = "myapp"; + const template = "https://github.com/NativeScript/template-hello-world/tarball/master"; await projectIntegrationTest.createProject({ projectName: projectName, - template: "https://github.com/NativeScript/template-hello-world/tarball/master", + template, pathToProject: tempFolder }); - await projectIntegrationTest.assertProject(tempFolder, projectName, "org.nativescript.myapp", defaultTemplatePath); + const projectSourceDirectory = await prepareTestingPath(projectIntegrationTest.testInjector, template, constants.RESERVED_TEMPLATE_NAMES["default"]); + await projectIntegrationTest.assertProject(tempFolder, projectName, "org.nativescript.myapp", projectSourceDirectory); }); it("creates valid project from git url", async () => { let projectIntegrationTest = new ProjectIntegrationTest(); let tempFolder = temp.mkdirSync("projectLocalDir"); let projectName = "myapp"; + const template = "https://github.com/NativeScript/template-hello-world.git"; await projectIntegrationTest.createProject({ projectName: projectName, - template: "https://github.com/NativeScript/template-hello-world.git", + template, pathToProject: tempFolder }); - await projectIntegrationTest.assertProject(tempFolder, projectName, "org.nativescript.myapp", defaultTemplatePath); + const projectSourceDirectory = await prepareTestingPath(projectIntegrationTest.testInjector, template, constants.RESERVED_TEMPLATE_NAMES["default"]); + await projectIntegrationTest.assertProject(tempFolder, projectName, "org.nativescript.myapp", projectSourceDirectory); }); it("creates valid project with specified id from default template", async () => { From 9b83cda6fd84f4f06475f034c40286c5e394f7ed Mon Sep 17 00:00:00 2001 From: Nadya Atanasova Date: Wed, 10 May 2017 17:55:20 +0300 Subject: [PATCH 066/212] WIP --- lib/common | 2 +- lib/providers/livesync-provider.ts | 22 ++++--- lib/services/ios-project-service.ts | 37 ++++++----- lib/services/platform-service.ts | 91 ++++++++++++++-------------- package.json | 1 + vendor/file/COPYING | 29 +++++++++ vendor/file/file.exe | Bin 0 -> 21670 bytes 7 files changed, 115 insertions(+), 67 deletions(-) create mode 100644 vendor/file/COPYING create mode 100644 vendor/file/file.exe diff --git a/lib/common b/lib/common index 1829df1b87..28acf919b5 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 1829df1b871af9da7bba6dd1225480c67ed12698 +Subproject commit 28acf919b580efd9a828a3e9e9698d38d5387c0b diff --git a/lib/providers/livesync-provider.ts b/lib/providers/livesync-provider.ts index 0724a34fd0..fcbbdeb1f5 100644 --- a/lib/providers/livesync-provider.ts +++ b/lib/providers/livesync-provider.ts @@ -1,5 +1,6 @@ import * as path from "path"; import * as temp from "temp"; +import { TNS_MODULES_FOLDER_NAME } from "../constants"; export class LiveSyncProvider implements ILiveSyncProvider { constructor(private $androidLiveSyncServiceLocator: { factory: Function }, @@ -7,8 +8,9 @@ export class LiveSyncProvider implements ILiveSyncProvider { private $platformService: IPlatformService, private $platformsData: IPlatformsData, private $logger: ILogger, - private $childProcess: IChildProcess, - private $options: IOptions) { } + private $options: IOptions, + private $mobileHelper: Mobile.IMobileHelper, + private $fs: IFileSystem) { } private static FAST_SYNC_FILE_EXTENSIONS = [".css", ".xml", ".html"]; @@ -63,21 +65,25 @@ export class LiveSyncProvider implements ILiveSyncProvider { } public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise { - if (deviceAppData.platform.toLowerCase() === "android" || !deviceAppData.deviceSyncZipPath || !isFullSync) { + if (this.$mobileHelper.isAndroidPlatform(deviceAppData.platform) || !deviceAppData.deviceSyncZipPath || !isFullSync) { await deviceAppData.device.fileSystem.transferFiles(deviceAppData, localToDevicePaths); } else { temp.track(); let tempZip = temp.path({ prefix: "sync", suffix: ".zip" }); + let tempApp = temp.mkdirSync("app"); this.$logger.trace("Creating zip file: " + tempZip); + this.$fs.copyFile(path.join(path.dirname(projectFilesPath), "app/*"), tempApp); - if (this.$options.syncAllFiles) { - await this.$childProcess.spawnFromEvent("zip", ["-r", "-0", tempZip, "app"], "close", { cwd: path.dirname(projectFilesPath) }); - } else { + if (!this.$options.syncAllFiles) { this.$logger.info("Skipping node_modules folder! Use the syncAllFiles option to sync files from this folder."); - await this.$childProcess.spawnFromEvent("zip", ["-r", "-0", tempZip, "app", "-x", "app/tns_modules/*"], "close", { cwd: path.dirname(projectFilesPath) }); + this.$fs.deleteDirectory(path.join(tempApp, TNS_MODULES_FOLDER_NAME)); } - deviceAppData.device.fileSystem.transferFiles(deviceAppData, [{ + await this.$fs.zipFiles(tempZip, this.$fs.enumerateFilesInDirectorySync(tempApp), (res) => { + return path.join("app", path.relative(tempApp, res)); + }); + + await deviceAppData.device.fileSystem.transferFiles(deviceAppData, [{ getLocalPath: () => tempZip, getDevicePath: () => deviceAppData.deviceSyncZipPath, getRelativeToProjectBasePath: () => "../sync.zip", diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 79362f2a91..851f4caeaa 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -13,6 +13,7 @@ import * as plist from "plist"; import { IOSProvisionService } from "./ios-provision-service"; import { IOSEntitlementsService } from "./ios-entitlements-service"; import { XCConfigService } from "./xcconfig-service"; +const simplePlist = require("simple-plist"); export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService { private static XCODE_PROJECT_EXT_NAME = ".xcodeproj"; @@ -39,6 +40,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $devicesService: Mobile.IDevicesService, private $mobileHelper: Mobile.IMobileHelper, + private $hostInfo: IHostInfo, private $pluginVariablesService: IPluginVariablesService, private $xcprojService: IXcprojService, private $iOSProvisionService: IOSProvisionService, @@ -111,6 +113,10 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } public async validate(): Promise { + if (!this.$hostInfo.isDarwin) { + return; + } + try { await this.$childProcess.exec("which xcodebuild"); } catch (error) { @@ -492,12 +498,12 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } private async addFramework(frameworkPath: string, projectData: IProjectData): Promise { - await this.validateFramework(frameworkPath); + this.validateFramework(frameworkPath); let project = this.createPbxProj(projectData); let frameworkName = path.basename(frameworkPath, path.extname(frameworkPath)); let frameworkBinaryPath = path.join(frameworkPath, frameworkName); - let isDynamic = _.includes((await this.$childProcess.spawnFromEvent("otool", ["-Vh", frameworkBinaryPath], "close")).stdout, " DYLIB "); + let isDynamic = _.includes((await this.$childProcess.spawnFromEvent(path.join(__dirname, "..", "..", "vendor", "file", "file.exe"), [frameworkBinaryPath], "close")).stdout, "dynamically linked"); let frameworkAddOptions: IXcode.Options = { customFramework: true }; @@ -918,17 +924,18 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f return path.join(newModulesDir, constants.PROJECT_FRAMEWORK_FOLDER_NAME, `${IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER}.xcodeproj`, "project.pbxproj"); } - private async validateFramework(libraryPath: string): Promise { - let infoPlistPath = path.join(libraryPath, "Info.plist"); + private validateFramework(libraryPath: string): void { + const infoPlistPath = path.join(libraryPath, "Info.plist"); if (!this.$fs.exists(infoPlistPath)) { this.$errors.failWithoutHelp("The bundle at %s does not contain an Info.plist file.", libraryPath); } - let packageType = (await this.$childProcess.spawnFromEvent("/usr/libexec/PlistBuddy", ["-c", "Print :CFBundlePackageType", infoPlistPath], "close")).stdout.trim(); + const plistJson = simplePlist.readFileSync(infoPlistPath); + const packageType = plistJson["CFBundlePackageType"]; + if (packageType !== "FMWK") { this.$errors.failWithoutHelp("The bundle at %s does not appear to be a dynamic framework.", libraryPath); } - } private async validateStaticLibrary(libraryPath: string): Promise { @@ -997,9 +1004,9 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } private async prepareFrameworks(pluginPlatformsFolderPath: string, pluginData: IPluginData, projectData: IProjectData): Promise { - for (let fileName of this.getAllLibsForPluginWithFileExtension(pluginData, ".framework")) { - await this.addFramework(path.join(pluginPlatformsFolderPath, fileName), projectData); - } + await _.each(this.getAllLibsForPluginWithFileExtension(pluginData, ".framework"), (fileName) => { + this.addFramework(path.join(pluginPlatformsFolderPath, fileName), projectData); + }); } private async prepareStaticLibs(pluginPlatformsFolderPath: string, pluginData: IPluginData, projectData: IProjectData): Promise { @@ -1107,11 +1114,13 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.$fs.writeFile(projectFile, ""); } - await this.checkIfXcodeprojIsRequired(); - let escapedProjectFile = projectFile.replace(/'/g, "\\'"), - escapedPluginFile = pluginFile.replace(/'/g, "\\'"), - mergeScript = `require 'xcodeproj'; Xcodeproj::Config.new('${escapedProjectFile}').merge(Xcodeproj::Config.new('${escapedPluginFile}')).save_as(Pathname.new('${escapedProjectFile}'))`; - await this.$childProcess.exec(`ruby -e "${mergeScript}"`); + if (this.$hostInfo.isDarwin) { + await this.checkIfXcodeprojIsRequired(); + let escapedProjectFile = projectFile.replace(/'/g, "\\'"), + escapedPluginFile = pluginFile.replace(/'/g, "\\'"), + mergeScript = `require 'xcodeproj'; Xcodeproj::Config.new('${escapedProjectFile}').merge(Xcodeproj::Config.new('${escapedPluginFile}')).save_as(Pathname.new('${escapedProjectFile}'))`; + await this.$childProcess.exec(`ruby -e "${mergeScript}"`); + } } private async mergeProjectXcconfigFiles(release: boolean, projectData: IProjectData): Promise { diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 4694896eca..514dfb550e 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -370,32 +370,34 @@ export class PlatformService extends EventEmitter implements IPlatformService { } public async shouldBuild(platform: string, projectData: IProjectData, buildConfig: IBuildConfig): Promise { - if (this.$projectChangesService.currentChanges.changesRequireBuild) { - return true; - } - let platformData = this.$platformsData.getPlatformData(platform, projectData); - let forDevice = !buildConfig || buildConfig.buildForDevice; - let outputPath = forDevice ? platformData.deviceBuildOutputPath : platformData.emulatorBuildOutputPath; - if (!this.$fs.exists(outputPath)) { - return true; - } - let packageNames = platformData.getValidPackageNames({ isForDevice: forDevice }); - let packages = this.getApplicationPackages(outputPath, packageNames); - if (packages.length === 0) { - return true; - } - let prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); - let buildInfo = this.getBuildInfo(platform, platformData, buildConfig); - if (!prepareInfo || !buildInfo) { - return true; - } - if (buildConfig.clean) { - return true; - } - if (prepareInfo.time === buildInfo.prepareTime) { - return false; - } - return prepareInfo.changesRequireBuildTime !== buildInfo.prepareTime; + //TODO: shouldBuild - issue with outputPath - we do not have always the built dir locally + return false; + // if (this.$projectChangesService.currentChanges.changesRequireBuild) { + // return true; + // } + // let platformData = this.$platformsData.getPlatformData(platform, projectData); + // let forDevice = !buildConfig || buildConfig.buildForDevice; + // let outputPath = forDevice ? platformData.deviceBuildOutputPath : platformData.emulatorBuildOutputPath; + // if (!this.$fs.exists(outputPath)) { + // return true; + // } + // let packageNames = platformData.getValidPackageNames({ isForDevice: forDevice }); + // let packages = this.getApplicationPackages(outputPath, packageNames); + // if (packages.length === 0) { + // return true; + // } + // let prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); + // let buildInfo = this.getBuildInfo(platform, platformData, buildConfig); + // if (!prepareInfo || !buildInfo) { + // return true; + // } + // if (buildConfig.clean) { + // return true; + // } + // if (prepareInfo.time === buildInfo.prepareTime) { + // return false; + // } + // return prepareInfo.changesRequireBuildTime !== buildInfo.prepareTime; } public async trackProjectType(projectData: IProjectData): Promise { @@ -462,25 +464,25 @@ export class PlatformService extends EventEmitter implements IPlatformService { public async installApplication(device: Mobile.IDevice, buildConfig: IBuildConfig, projectData: IProjectData): Promise { this.$logger.out("Installing..."); - let platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); - let packageFile = ""; - if (this.$devicesService.isiOSSimulator(device)) { - packageFile = this.getLatestApplicationPackageForEmulator(platformData, buildConfig).packageName; - } else { - packageFile = this.getLatestApplicationPackageForDevice(platformData, buildConfig).packageName; - } + // let platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + // let packageFile = ""; + // if (this.$devicesService.isiOSSimulator(device)) { + // packageFile = this.getLatestApplicationPackageForEmulator(platformData, buildConfig).packageName; + // } else { + // packageFile = this.getLatestApplicationPackageForDevice(platformData, buildConfig).packageName; + // } - await platformData.platformProjectService.cleanDeviceTempFolder(device.deviceInfo.identifier, projectData); + // await platformData.platformProjectService.cleanDeviceTempFolder(device.deviceInfo.identifier, projectData); - await device.applicationManager.reinstallApplication(projectData.projectId, packageFile); + // await device.applicationManager.reinstallApplication(projectData.projectId, packageFile); - if (!buildConfig.release) { - let deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); - let buildInfoFilePath = this.getBuildOutputPath(device.deviceInfo.platform, platformData, { buildForDevice: !device.isEmulator }); - let appIdentifier = projectData.projectId; + // if (!buildConfig.release) { + // let deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); + // let buildInfoFilePath = this.getBuildOutputPath(device.deviceInfo.platform, platformData, { buildForDevice: !device.isEmulator }); + // let appIdentifier = projectData.projectId; - await device.fileSystem.putFile(path.join(buildInfoFilePath, buildInfoFileName), deviceFilePath, appIdentifier); - } + // await device.fileSystem.putFile(path.join(buildInfoFilePath, buildInfoFileName), deviceFilePath, appIdentifier); + // } this.$logger.out(`Successfully installed on device with identifier '${device.deviceInfo.identifier}'.`); } @@ -678,9 +680,10 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$errors.fail("Invalid platform %s. Valid platforms are %s.", platform, helpers.formatListOfNames(this.$platformsData.platformsNames)); } - if (!this.isPlatformSupportedForOS(platform, projectData)) { - this.$errors.fail("Applications for platform %s can not be built on this OS - %s", platform, process.platform); - } + //TODO: move to commands. + // if (!this.isPlatformSupportedForOS(platform, projectData)) { + // this.$errors.fail("Applications for platform %s can not be built on this OS - %s", platform, process.platform); + // } } public validatePlatformInstalled(platform: string, projectData: IProjectData): void { diff --git a/package.json b/package.json index 2565baf463..f9863dbf39 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "request": "2.81.0", "semver": "5.3.0", "shelljs": "0.7.6", + "simple-plist": "0.2.1", "source-map": "0.5.6", "tabtab": "https://github.com/Icenium/node-tabtab/tarball/master", "temp": "0.8.3", diff --git a/vendor/file/COPYING b/vendor/file/COPYING new file mode 100644 index 0000000000..b3db8b23fb --- /dev/null +++ b/vendor/file/COPYING @@ -0,0 +1,29 @@ +$File: COPYING,v 1.1 2008/02/05 19:08:11 christos Exp $ +Copyright (c) Ian F. Darwin 1986, 1987, 1989, 1990, 1991, 1992, 1994, 1995. +Software written by Ian F. Darwin and others; +maintained 1994- Christos Zoulas. + +This software is not subject to any export provision of the United States +Department of Commerce, and may be exported to any country or planet. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice immediately at the beginning of the file, without modification, + this list of conditions, and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/vendor/file/file.exe b/vendor/file/file.exe new file mode 100644 index 0000000000000000000000000000000000000000..a56647b8e21137163fd30fc7365f1ebc21e17f12 GIT binary patch literal 21670 zcmeHveRx~NmG_lx1!FLBAzo4%;1cX&vymoe!fZ_b>VId|sF+^Z~EvsEgTB&i5@CL>APLFsB^e?R)qD6-Eu zf5#cpL#IA{!FG%5=?i)X12H)g4G%;;L$cQs3WeixzbZ$ELvkP_cP?8Y4~2bdO=)RK zg;3qoB}u+pilx&Xr}arLNot4uh;&iGX$1wJl;(m_aFcOm;)Z1wBpF}8vxv1yVCdS0 z8~6uY7HZe1B*_rqqVnt#i7cO&MfqDLY4<6h_eqjY_4Yvns}Sk`#x>_oNqYPv^wh-F zkvQ~Mor6DQ8?~M4&%64hp1vBNC+>kvAzcpK)NC1d-qj{WCTgM_D4>Hr#V?&%>Q} z0oPU&5jfJFpkKiWUB4TnE!}Z5vB#|k9lv00ujgoxeP~B4>V=q%#eGQ3!hJdJyo+#o?8nJJqX(44;VLCnYCQv8JORJ&a!xo8 zIG@m;%S#{3OaFT=tt56=rHS8~cdb;k*VgUw*VxV`+iiB_10^b5&I7KZG;Ef^KcMZEQIn z03{X4D9!PTs#wYBNJh4eT}Z;@=87tpcBs4hoi1m_m1^vUo4Qi=m&n%YV~Vy*(K;)d z^qqKGm84RJ8ulWqT|3yVz4!m=vC_|miS4P*ij4CBpbb;!ZOF-y^Y*KO;1L%%jxQ9m$CwEyVVbp1z=E*a0n zw^QjIPd~*k<@c{BkNu`VNfq7M-8?ybhdvju&LoTwrdC2jxAvAZqen?CSu-BRaOiIr zQghZ;G%3lRG6f~zCayUJ=yT<|&$!pMuWet~{+V`n`YKlJO^b2~`pq`>vkxJXG5i|CFK~ zg=gw4X2@I=fo5$9L1mm z_@ds%fQ>h>a+CU|T)F#Mx%axYBl@!>1^7ZxluwBAChu;8*~(;R1+5E`{w?YV^6tI1 zv0GVHlg>9>sbGb}mGV{8OqKEW@3APBUneaS}8der>x@G1=M?EtrpWiT7u zLB(vH&+4@(l`MA>=D@J3B+K9Y6mn9f5dicvDbqTaGAH6|Fqh1wVyV(qAlgy=ZTJP$ z?Vzqxv^3NAO-ciO361m=;EuXhDm)|lipSM4k zKb`+`^_@)r#nbAaH;q4M8h_?AzGNC-FpWR{+58nZQ+d?H(|Ybaps$2?Wy&A9F(1mh zAnRzNL;hTTC6-aKqHE7$x_Ol1HvM&)`Nm(ijkV$-Q?ytc)~`FUioof=_6bOm)4bAqC@wKPcAX&|mO_<4kKud>~pkN;>?7?!(d`~D^$7(QVF;qt% zk`pSSItGz^o(4qgh#>joB1$G`(**9voU7v8?XS^jCmi7PJJGtlS#TYnPcdo%8*-OFTiRGJl)tc%pxZ{7xsBB{>TTSQ+L#|rj^-Srg!xUX z^g`09Z4>@wr81*APuapfrSx-uU?}Ef8bZ6q!#-x5m4-%B;6A12uw(@O)_6Wk_QQM!2rnAnfr`^#gxrV+l8c)Z&H8n8d3r8`FGgYOIPo8n z$;ri4;D?Am#CaL~O5%5N9uJANmG}oZUk1LC_**z%48D~3LBdakwdQ+!7y%HYSc2#Lr3`FC3n`1MWhLSyTF7kl2A<;2f== z#t=90@x|PvL5f9n3fdp`V(sm$n6K}lB9qqp!Q(;BAl#fk^(4h7SaDP30}w62Q+D@+ zqLuz5FfPrS0GYJjgum!%Nwa#9(tki|5G$fd*MYbF!-VbO2{H_ok0B3jxdgh5#Q>C< zmp2LKB^fGH`NiSQ-T59r)WP%L*ML2BffeZ!<;IA1!=?* zEJjFU1HrdhnaUDVdGH%3D^q!#$w?m`=5s4ZQ|&Qw7#4x{&LeGWDHMK@!q72f@ld!0 zsa%Jc_DeS%eh~E{9BbE}(U1O~ne{ppvFJ>gaxDU zbq|uM(t-<#{J7#aK=p323Lzz?oQLC(BVbh^mp=J&Hg<)mheW@B7O753f%X!mz1C{_ z)0g-)Jh!s~v(H!sJ*_~r83=4ztZPU&01+2rygJ&lStk%qv;6>K+)nH)euXB%TWn)j z7Sk5v#*GU4{Y-2cvRpLJjUX z6?zkUuzlNIrX)Tn8UAGgW?QDl$tH5o^a#8zVZFZ(-kJ^~ebc@o9HFE=NW#<~;>cu2 zD{!u)OtJSP)YY`_=&TrtHz0sV{dbkI`m0~>#=5seJ3_c;fTQ}yVLq>a5B5+W%o=_f z9xxA;>GpT20_FiJ#JOy*cu>C;Qa<>tNF}VZm|f3dx=HLSNI%7<9rUo0L~mLD4=PS? zhivqjg7h{}YE%J?P5%=y)_+5E%i4H1!xW`&15NBJOOJuxMB%X|y@_QNr*8n&zxumO z#>F1lT1c;G6DRhQ-4m z>EGcn2f_aETfjb@gDqvSA%cx^*wqAkj$oO0vfA!rFa#}*ufm^Ck(cR zV09d}k6^bD>|b-R*BA_sq@< z2E*bj=|ABxlIWix*rFWlGCXwc+Yz5g`jF<5|L3Wwc6uzLwM378mBoxw^0<0IJwO*V=WcHb`mz6 z!&XArzCDM1hb&8#l31TZ*p3{wj$zBQdLQRlCt(l20qof%;9hen!(vk?={IoLVuI}< z*pZ`I*fk6mB-oW4wt`^aAlMT*n2*6+1iO&Kf&|-0u={hc%?vh3u=h!tD!rLtJp}t~ z4whuF5W$}3u-ge%O|ae^>}w3Rlwd#Lux}EqfME6@n9PE1x zMhD0G6iHL14-xD#f?c13Wf-i3V7ocYdJ)od2-cB<&4sXiyN_UB=dkk#_SO_&b0H9} z+RR|ETDaf0kcHUyVvxkXqVyiNysfQJ*v4jiA*R@5ey zq^Ut!{ZWw(`6Wz#Q2#qpJ)zvR`$5!}J0NLy?@H_|PG5(bMxU{yR}kGdn!XA&VI4wM zmh@%J3R?4MGR8`ownIWwFbtB?GC}vr={cY`?JFySsFXy%{vd57y1AlFvW;&qp!H!h z&V_7a>>MfSs&MExpxbD5!wOpJqJxsjv5KuwN*ASRQKd^sBI5jwUWYb}PYi!Se-ZPH zD>+!~NDH=){WP)=2!;m1#dd5_JdGoxN1yAJYlw%enyTuI($*kvKRhM(S>6HZMq zhFhLq##U<%*S%2Uf)S^IcAo01=)scTYe|2JLW<3ZS?Jity9dr&U6zhufu~;ke`rejaVnvm zyXhKu?F+OlKK5Es`)O z$9@Q5;#lEupFRetw$nM0*lDqi*HeWke=C(wp1XY&_5EIbg0XD$(%2=7L+5kQ3+WXw zwmo%4=8{6zxwdc6#zeE;iIVB>Qc%>(^iO?5UylUNlAl8_8S9pj*ydc=3nOvIi@tzRW0PSs^wqxLXMTqbhQe()g+aq zL%k=qXBzBdR`kG7Y*WlW;Jx1NTu|c+1||2E8{Es)fj}&-MmvI@SWIQF^Jna@l`+qN z+9FrRGH-mbyGdRK61C=!irxN(hR9fWpIBob6v3ST&=p;#asV%$(*NVTgWZ`cGZ63+8(Sw6 zk!xyd41F-u-hnbER9+^`9aMvnbtTe#S&}NlQf1A>F{v_E8I$IhRI+4?+~Emb6o(CQ zYIS9-1o_kfB~qR!>yl?}&K&Us?8NA`w;;gDU7pz4!@a5ZWaD{Ksf*@_$&8XoFbqik5w z-&ymA;YCA{sESWda#%SYMXSSdFdSYF%VPnbipn_*^)e4Rp$>Z}991LXD9(?lKfpL6k5Jbi4XFM<>Q7nrg=P9E3dTlMj00Ig?i(XZMQ;Z&*7>dT z4yxYuct-5Z2!Oa*=0blo3@*D~K6_I~JoMaRT&N}V)aGf-&S65|ir(ejORr!WX}Dr>cpSPtmYeKYXHs1l z;l7rO@^sNu%GJPMr(MFSmUJ)a;u8qh9Fwc;b~XjGSp{>#$@)XUjTyU|>ZeQ0@H5me zs;BO0r-eZ7Sl-p%yKH%F@3lRwJGhGM(je4GBsMOg!z0JAAPt^i?#PyAj|~;eYn$j_ zLm6nEP1$bAwZ^nANk*~hWsGjf8-?|%8nF+AF>I*PCoMLw)$bV& z#@WD$c|R1k=W7s!xxv%Q=NXDp{lk7#Yi_48xA8pD%jRMc51q=;m9vwjjA@a)o2@x{ z<=C|6;Kinw<4dC_p8xQ%xvPCeM|ZcpbfwFrNR6q{4XPcV9!`H=X!W2x+2i$M9g*XB zq%g;a*h8H^8&l*lL`yO^D8C&P63`qVW0YvU&O$MR4F%${lRaU9kS`E56gnH_sOlYN z>oPA-(>vw>U{p3qpG+OCD~2|?-oaJ@W(ZS6xwSOY{7DP18uYudZlOzLxvp^m3XRBJ zuFGjHgA8B%Qm3gWokK`XWDu)3)Hv&!vTEp)VN7(aS%0Rw7*khYw{SsD-3c^F*K()aOH0q%a5#Tih(%Oyz!PLK508@#t$0-N zu#0@Tsy`U^uCFCESWhx=` z8Q~9L9qQkNH9hZvU`zXn${Vo-1HhLDWIPQN>ElV@SIKe~BFTsiSjP;;RVzvc24Y&L?fp_b6nNKXsja3E$!EKXq=TJPhoosC|vtMskI90h+R zbJ*-Fv$-OMVF5ZA=#NrI^IDCVmr9}0wFfepE1%6|7UN!syB7CFL?QoMxaa&8Z9Prs zDg^!Q4DJ6B#&3jT*9Z*LkY;Y-tk~}eQARu{t ze#zJu5L7ZE1t}Kx$ECO@DzQ)f=KSs2TX3#4pBCuL()p33%SUbMvmr~Ei(T3C~dSCFC*L_`$En$P2b>}oC1 z3Spkv#f&25O_;npCAsIs=S{}BSLF=(8$@}#$T#ppEDec7(+uTr6!{%9~#*jdR3r z&5&=LCw^~+d@HYBdUS?-dSgu2?iupwRWV&p%#cs-i0OKMhJ1QCOxLS3HT;q`U>uQkUj$X7Vi6yrr#!7a6f=_8R#c)KZvvpdI9e5BkcgS&Bf0<2?JV< zdxGSkI_}*_9|8Rx?x&EJ@T;I%xc3tt^b@$BLwXeSd$T_ir%Giv(=T;PBB%Bg zPt4k1bg1A6aHkYWrz|gY6!cjlEL|tkXB10kL`xi}_7z7?>6tZAw7=kxrDT>=Qa8(C z?JJ5D_7qH5&MJ}4vXrym|K@AW+Jyn2wI_R*Sgj~u$Z8-PDUaHKfF_F}8QEM1n?<`I z`uI9Q5AqKHjOEGh{fZ=wKqk>Gu7@Cd{4Rqt(?ddj#K^y!=a&g83+fQG zP0$`e`vi>$Iwq)5%iV&1K+tW1P6+yxpa%uj1wA6@`+}DJ1J@`ES|wu%Uah$kX@M<&~ zjw1hB!PADFdgMC652*2oCq5{x6+GL6BY&O2hxt*zTkv%HN98?&$5}bsaFeWG$c$r# zQpxT1j(FTSg~e-XNtz8F_JqQ~%1EQ`sET*6RJIEIkOyzvajlTt_=Lgjjm90)YRMfK ziVR0pcO*{j*d^h60cLh0R<7EbddrSV>yAtFK`O+>|eVrkv?)d9m77E4b9yRN;u-M9R z0y*In+^g@Cq%Rc_$FD_~jSK~iqxaT%&Kk!&ycXjh`?Stm*?YOYX&zp3V~9QQc(rxj zCN(zivP(-A(I-bk{rKbpIO9^2kCF?JWs4Uv;oJx(RsB-p+&VbYFt#CU;CokY$K@b zB07o02RQwKAj~^?vrKU6EpEUfI%N%b*_S+7lP<8^d-0mSyAvN+(3@jA1_uQ1OY+ci zY&b&S#;|vY*8mdU7?W4wP{f0G?paj=r26nb{&f3V=Y3YiX^OLEexJQbt@GJEbqx)U zy1GVxbG_%UV@K0R+R@;t^Vc=0_4da4{$_haLxa=a?`ihheG8g>4W0(i0=2&BuVhD! z^CK;B`WGx*;9a=D-sDi7_J+FpW_xpEf4{w{-skWy@Hu>*h0GFJyrx|Amk*88@sXA^ zsSf`_kF(C+@9(e2*r-i*j8vn&p~>%SbaS+>%ALd0xpLjarklV=AdN=8{W| zx0c52^fArv0h+d(3UL+!%Fos5cUHTt`4SwJNDl#!!!$#358ydTHzeCkl2wrGH|fDP zT^cdfMIk_+sno5I6q_)#X`78ZSJ!vIZ82dUf`oqPlEXX+iDJV13X%s+k~bhZW-4_I zl2Man77iBXm?ZSG?t|#ITwT?WtTtgT{S-1_7ttQM*31gBx1<7iYU#%m5_!nJ~lPMop5N!6~LvcR_N{Bq1GTrc(5OLd-EqCcyQWBu|4oWYY5@B(KiO zxA_Pp_o4~8Rutk%>dBUx4bDxQH@Dc;xZRs<4LyVw975V#Lju$(@yiVYDIn5nmD5Rki8(vfZHZESmm()>dYQJ8+q;AUqpTwokBI3 z^DJ<*wa-_?&UHjVe(1p&^d>vG$a1KIpIvZ?IAstenR~Jk&hs4c*ycY~rYD(0gLIJe EU)#=5&Hw-a literal 0 HcmV?d00001 From 33d79ab4b7eda2557667e55816df436fa6443750 Mon Sep 17 00:00:00 2001 From: Nadya Atanasova Date: Tue, 16 May 2017 11:25:01 +0300 Subject: [PATCH 067/212] Move OS compatibility check to commands --- lib/commands/appstore-list.ts | 12 +++++++++++- lib/commands/appstore-upload.ts | 5 ++--- lib/commands/build.ts | 18 +++++++++++++++--- lib/commands/clean-app.ts | 12 +++++++++++- lib/commands/debug.ts | 18 ++++++++++++++---- lib/commands/run.ts | 15 +++++++++++++-- lib/definitions/platform.d.ts | 6 +++++- lib/services/platform-service.ts | 7 +------ test/stubs.ts | 4 ++++ 9 files changed, 76 insertions(+), 21 deletions(-) diff --git a/lib/commands/appstore-list.ts b/lib/commands/appstore-list.ts index 2ba7b8a435..62fa219ca9 100644 --- a/lib/commands/appstore-list.ts +++ b/lib/commands/appstore-list.ts @@ -7,9 +7,19 @@ export class ListiOSApps implements ICommand { constructor(private $injector: IInjector, private $itmsTransporterService: IITMSTransporterService, private $logger: ILogger, - private $prompter: IPrompter) { } + private $projectData: IProjectData, + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + private $platformService: IPlatformService, + private $errors: IErrors, + private $prompter: IPrompter) { + this.$projectData.initializeProjectData(); + } public async execute(args: string[]): Promise { + if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { + this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.iOS, process.platform); + } + let username = args[0], password = args[1]; diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index 7576664371..fbeef8e039 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -7,7 +7,6 @@ export class PublishIOS implements ICommand { new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector)]; constructor(private $errors: IErrors, - private $hostInfo: IHostInfo, private $injector: IInjector, private $itmsTransporterService: IITMSTransporterService, private $logger: ILogger, @@ -100,8 +99,8 @@ export class PublishIOS implements ICommand { } public async canExecute(args: string[]): Promise { - if (!this.$hostInfo.isDarwin) { - this.$errors.failWithoutHelp("This command is only available on Mac OS X."); + if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { + this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.iOS, process.platform); } return true; diff --git a/lib/commands/build.ts b/lib/commands/build.ts index 65be9a7ac4..7c09171615 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -2,6 +2,7 @@ export class BuildCommandBase { constructor(protected $options: IOptions, protected $projectData: IProjectData, protected $platformsData: IPlatformsData, + protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, protected $platformService: IPlatformService) { this.$projectData.initializeProjectData(); } @@ -35,10 +36,12 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; constructor(protected $options: IOptions, + private $errors: IErrors, $projectData: IProjectData, $platformsData: IPlatformsData, + $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $platformService: IPlatformService) { - super($options, $projectData, $platformsData, $platformService); + super($options, $projectData, $platformsData, $devicePlatformsConstants, $platformService); } public async execute(args: string[]): Promise { @@ -46,6 +49,10 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { } public canExecute(args: string[]): Promise { + if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { + this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.iOS, process.platform); + } + return args.length === 0 && this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); } } @@ -56,11 +63,12 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; constructor(protected $options: IOptions, + private $errors: IErrors, $projectData: IProjectData, $platformsData: IPlatformsData, - private $errors: IErrors, + $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $platformService: IPlatformService) { - super($options, $projectData, $platformsData, $platformService); + super($options, $projectData, $platformsData, $devicePlatformsConstants, $platformService); } public async execute(args: string[]): Promise { @@ -68,6 +76,10 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { } public async canExecute(args: string[]): Promise { + if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.Android, this.$projectData)) { + this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.Android, process.platform); + } + if (this.$options.release && (!this.$options.keyStorePath || !this.$options.keyStorePassword || !this.$options.keyStoreAlias || !this.$options.keyStoreAliasPassword)) { this.$errors.fail("When producing a release build, you need to specify all --key-store-* options."); } diff --git a/lib/commands/clean-app.ts b/lib/commands/clean-app.ts index ed79c44977..e014eaf62c 100644 --- a/lib/commands/clean-app.ts +++ b/lib/commands/clean-app.ts @@ -1,7 +1,7 @@ export class CleanAppCommandBase { constructor(protected $options: IOptions, protected $projectData: IProjectData, - private $platformService: IPlatformService) { + protected $platformService: IPlatformService) { this.$projectData.initializeProjectData(); } @@ -14,7 +14,9 @@ export class CleanAppCommandBase { export class CleanAppIosCommand extends CleanAppCommandBase implements ICommand { constructor(protected $options: IOptions, + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $platformsData: IPlatformsData, + private $errors: IErrors, $platformService: IPlatformService, $projectData: IProjectData) { super($options, $projectData, $platformService); @@ -23,6 +25,9 @@ export class CleanAppIosCommand extends CleanAppCommandBase implements ICommand public allowedParameters: ICommandParameter[] = []; public async execute(args: string[]): Promise { + if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { + this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.iOS, process.platform); + } return super.execute([this.$platformsData.availablePlatforms.iOS]); } } @@ -33,13 +38,18 @@ export class CleanAppAndroidCommand extends CleanAppCommandBase implements IComm public allowedParameters: ICommandParameter[] = []; constructor(protected $options: IOptions, + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $platformsData: IPlatformsData, + private $errors: IErrors, $platformService: IPlatformService, $projectData: IProjectData) { super($options, $projectData, $platformService); } public async execute(args: string[]): Promise { + if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { + this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.iOS, process.platform); + } return super.execute([this.$platformsData.availablePlatforms.Android]); } } diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 7165a38905..5b380bda24 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -84,11 +84,12 @@ export abstract class DebugPlatformCommand implements ICommand { } export class DebugIOSCommand extends DebugPlatformCommand { - constructor(protected $logger: ILogger, + constructor(private $errors: IErrors, + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + $logger: ILogger, $iOSDebugService: IPlatformDebugService, $devicesService: Mobile.IDevicesService, $injector: IInjector, - $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $config: IConfiguration, $usbLiveSyncService: ILiveSyncService, $debugDataService: IDebugDataService, @@ -106,6 +107,10 @@ export class DebugIOSCommand extends DebugPlatformCommand { } public async canExecute(args: string[]): Promise { + if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { + this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.iOS, process.platform); + } + return await super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); } @@ -119,11 +124,12 @@ export class DebugIOSCommand extends DebugPlatformCommand { $injector.registerCommand("debug|ios", DebugIOSCommand); export class DebugAndroidCommand extends DebugPlatformCommand { - constructor($logger: ILogger, + constructor(private $errors: IErrors, + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + $logger: ILogger, $androidDebugService: IPlatformDebugService, $devicesService: Mobile.IDevicesService, $injector: IInjector, - $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $config: IConfiguration, $usbLiveSyncService: ILiveSyncService, $debugDataService: IDebugDataService, @@ -135,6 +141,10 @@ export class DebugAndroidCommand extends DebugPlatformCommand { } public async canExecute(args: string[]): Promise { + if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.Android, this.$projectData)) { + this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.Android, process.platform); + } + return await super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.Android); } } diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 3da1b60f6b..44d97d332c 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -51,6 +51,8 @@ export class RunIosCommand extends RunCommandBase implements ICommand { constructor($platformService: IPlatformService, private $platformsData: IPlatformsData, + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + private $errors: IErrors, $usbLiveSyncService: ILiveSyncService, $projectData: IProjectData, $options: IOptions, @@ -59,6 +61,10 @@ export class RunIosCommand extends RunCommandBase implements ICommand { } public async execute(args: string[]): Promise { + if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { + this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.iOS, process.platform); + } + return this.executeCore([this.$platformsData.availablePlatforms.iOS]); } @@ -74,11 +80,12 @@ export class RunAndroidCommand extends RunCommandBase implements ICommand { constructor($platformService: IPlatformService, private $platformsData: IPlatformsData, + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + private $errors: IErrors, $usbLiveSyncService: ILiveSyncService, $projectData: IProjectData, $options: IOptions, - $emulatorPlatformService: IEmulatorPlatformService, - private $errors: IErrors) { + $emulatorPlatformService: IEmulatorPlatformService) { super($platformService, $usbLiveSyncService, $projectData, $options, $emulatorPlatformService); } @@ -87,6 +94,10 @@ export class RunAndroidCommand extends RunCommandBase implements ICommand { } public async canExecute(args: string[]): Promise { + if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.Android, this.$projectData)) { + this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.Android, process.platform); + } + if (this.$options.release && (!this.$options.keyStorePath || !this.$options.keyStorePassword || !this.$options.keyStoreAlias || !this.$options.keyStoreAliasPassword)) { this.$errors.fail("When producing a release build, you need to specify all --key-store-* options."); } diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index f1c6dd968d..cf999440b4 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -125,10 +125,14 @@ interface IPlatformService extends NodeJS.EventEmitter { /** * Ensures the passed platform is a valid one (from the supported ones) - * and that it can be built on the current OS */ validatePlatform(platform: string, projectData: IProjectData): void; + /** + * Ensures that passed platform can be built on the current OS + */ + isPlatformSupportedForOS(platform: string, projectData: IProjectData): boolean; + /** * Returns information about the latest built application for device in the current project. * @param {IPlatformData} platformData Data describing the current platform. diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 514dfb550e..d7340ba03c 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -679,11 +679,6 @@ export class PlatformService extends EventEmitter implements IPlatformService { if (!this.isValidPlatform(platform, projectData)) { this.$errors.fail("Invalid platform %s. Valid platforms are %s.", platform, helpers.formatListOfNames(this.$platformsData.platformsNames)); } - - //TODO: move to commands. - // if (!this.isPlatformSupportedForOS(platform, projectData)) { - // this.$errors.fail("Applications for platform %s can not be built on this OS - %s", platform, process.platform); - // } } public validatePlatformInstalled(platform: string, projectData: IProjectData): void { @@ -708,7 +703,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { return this.$platformsData.getPlatformData(platform, projectData); } - private isPlatformSupportedForOS(platform: string, projectData: IProjectData): boolean { + public isPlatformSupportedForOS(platform: string, projectData: IProjectData): boolean { let targetedOS = this.$platformsData.getPlatformData(platform, projectData).targetedOS; let res = !targetedOS || targetedOS.indexOf("*") >= 0 || targetedOS.indexOf(process.platform) >= 0; return res; diff --git a/test/stubs.ts b/test/stubs.ts index 2799642aec..213682c88e 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -665,6 +665,10 @@ export class PlatformServiceStub extends EventEmitter implements IPlatformServic } + isPlatformSupportedForOS(platform: string, projectData: IProjectData): boolean { + return true; + } + public getLatestApplicationPackageForDevice(platformData: IPlatformData): IApplicationPackage { return null; } From 8ccf75f138bbcdf1b3b7458b2434de9749d03fb2 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Thu, 1 Jun 2017 02:18:13 +0300 Subject: [PATCH 068/212] New LiveSync --- lib/bootstrap.ts | 1 - lib/commands/appstore-list.ts | 4 +- lib/commands/debug.ts | 3 +- lib/commands/run.ts | 3 +- lib/declarations.d.ts | 9 +- lib/definitions/platform.d.ts | 6 +- lib/definitions/project.d.ts | 2 + lib/services/android-project-service.ts | 3 +- lib/services/ios-project-service.ts | 3 +- .../android-device-livesync-service.ts | 47 ++- .../livesync/android-livesync-service.ts | 91 +++++ .../livesync/ios-device-livesync-service.ts | 2 +- lib/services/livesync/ios-livesync-service.ts | 124 +++++++ lib/services/livesync/livesync-service.ts | 317 +++++++++++++----- .../livesync/platform-livesync-service.ts | 263 --------------- lib/services/platform-service.ts | 99 +++--- lib/services/project-data-service.ts | 12 +- lib/services/test-execution-service.ts | 6 +- package.json | 3 +- test/services/project-data-service.ts | 2 + test/stubs.ts | 2 + 21 files changed, 569 insertions(+), 433 deletions(-) create mode 100644 lib/services/livesync/android-livesync-service.ts create mode 100644 lib/services/livesync/ios-livesync-service.ts delete mode 100644 lib/services/livesync/platform-livesync-service.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 37ffb4c4ef..ef74955cd1 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -105,7 +105,6 @@ $injector.requireCommand("platform|clean", "./commands/platform-clean"); $injector.require("usbLiveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript $injector.require("iosLiveSyncServiceLocator", "./services/livesync/ios-device-livesync-service"); $injector.require("androidLiveSyncServiceLocator", "./services/livesync/android-device-livesync-service"); -$injector.require("platformLiveSyncService", "./services/livesync/platform-livesync-service"); $injector.require("sysInfo", "./sys-info"); diff --git a/lib/commands/appstore-list.ts b/lib/commands/appstore-list.ts index 62fa219ca9..7ddc8bf90f 100644 --- a/lib/commands/appstore-list.ts +++ b/lib/commands/appstore-list.ts @@ -12,8 +12,8 @@ export class ListiOSApps implements ICommand { private $platformService: IPlatformService, private $errors: IErrors, private $prompter: IPrompter) { - this.$projectData.initializeProjectData(); - } + this.$projectData.initializeProjectData(); + } public async execute(args: string[]): Promise { if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 5b380bda24..2c84580059 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -56,7 +56,8 @@ export abstract class DebugPlatformCommand implements ICommand { this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); }; - return this.$usbLiveSyncService.liveSync(this.$devicesService.platform, this.$projectData, applicationReloadAction); + // TODO: Fix this call + return this.$usbLiveSyncService.liveSync(this.$devicesService.platform, this.$projectData, applicationReloadAction, this.$options); } public async canExecute(args: string[]): Promise { diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 44d97d332c..a8800eb710 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -42,7 +42,8 @@ export class RunCommandBase { return this.$platformService.trackProjectType(this.$projectData); } - return this.$usbLiveSyncService.liveSync(args[0], this.$projectData); + // TODO: Fix this call + return this.$usbLiveSyncService.liveSync(args[0], this.$projectData, null, this.$options); } } diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 448c546933..50316cd3d1 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -257,8 +257,9 @@ interface IOpener { open(target: string, appname: string): void; } +// TODO: Fix interface ILiveSyncService { - liveSync(platform: string, projectData: IProjectData, applicationReloadAction?: (deviceAppData: Mobile.IDeviceAppData) => Promise): Promise; + liveSync(platform: string, projectData: IProjectData, applicationReloadAction?: (deviceAppData: Mobile.IDeviceAppData) => Promise, options?: IOptions): Promise; } interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase { @@ -282,12 +283,6 @@ interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase afterInstallApplicationAction?(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectId: string): Promise; } -interface IPlatformLiveSyncService { - fullSync(projectData: IProjectData, postAction?: (deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => Promise): Promise; - partialSync(event: string, filePath: string, dispatcher: IFutureDispatcher, afterFileSyncAction: (deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => Promise, projectData: IProjectData): Promise; - refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], isFullSync: boolean, projectData: IProjectData): Promise; -} - interface IBundle { bundle: boolean; } diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index cf999440b4..e132a35bda 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -58,7 +58,7 @@ interface IPlatformService extends NodeJS.EventEmitter { * @param {IBuildConfig} buildConfig Indicates whether the build is for device or emulator. * @returns {boolean} true indicates that the platform should be build. */ - shouldBuild(platform: string, projectData: IProjectData, buildConfig?: IBuildConfig): Promise; + shouldBuild(platform: string, projectData: IProjectData, buildConfig?: IBuildConfig, outputPath?: string): Promise; /** * Builds the native project for the specified platform for device or emulator. @@ -79,7 +79,7 @@ interface IPlatformService extends NodeJS.EventEmitter { * @param {IProjectData} projectData DTO with information about the project. * @returns {Promise} true indicates that the application should be installed. */ - shouldInstall(device: Mobile.IDevice, projectData: IProjectData): Promise; + shouldInstall(device: Mobile.IDevice, projectData: IProjectData, outputPath?: string): Promise; /** * Installs the application on specified device. @@ -90,7 +90,7 @@ interface IPlatformService extends NodeJS.EventEmitter { * @param {IProjectData} projectData DTO with information about the project. * @returns {void} */ - installApplication(device: Mobile.IDevice, options: IRelease, projectData: IProjectData): Promise; + installApplication(device: Mobile.IDevice, options: IRelease, projectData: IProjectData, pathToBuiltApp?: string, outputPath?: string): Promise; /** * Gets first chance to validate the options provided as command line arguments. diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 09e9293c6c..a0aa63b25a 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -105,6 +105,8 @@ interface IProjectDataService { * @returns {void} */ removeDependency(projectDir: string, dependencyName: string): void; + + getProjectData(projectDir: string): IProjectData; } /** diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index b4e3d63f02..e04613141e 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -65,7 +65,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject return [ `${packageName}-${buildMode}.apk`, - `${projectData.projectName}-${buildMode}.apk` + `${projectData.projectName}-${buildMode}.apk`, + `${projectData.projectName}.apk` ]; }, frameworkFilesExtensions: [".jar", ".dat", ".so"], diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 851f4caeaa..1a0464d057 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -503,7 +503,8 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ let project = this.createPbxProj(projectData); let frameworkName = path.basename(frameworkPath, path.extname(frameworkPath)); let frameworkBinaryPath = path.join(frameworkPath, frameworkName); - let isDynamic = _.includes((await this.$childProcess.spawnFromEvent(path.join(__dirname, "..", "..", "vendor", "file", "file.exe"), [frameworkBinaryPath], "close")).stdout, "dynamically linked"); + const pathToFileCommand = this.$hostInfo.isWindows ? path.join(__dirname, "..", "..", "vendor", "file", "file.exe") : "file"; + let isDynamic = _.includes((await this.$childProcess.spawnFromEvent(pathToFileCommand, [frameworkBinaryPath], "close")).stdout, "dynamically linked"); let frameworkAddOptions: IXcode.Options = { customFramework: true }; diff --git a/lib/services/livesync/android-device-livesync-service.ts b/lib/services/livesync/android-device-livesync-service.ts index 0ea189229b..10715c4009 100644 --- a/lib/services/livesync/android-device-livesync-service.ts +++ b/lib/services/livesync/android-device-livesync-service.ts @@ -3,31 +3,54 @@ import { AndroidDeviceHashService } from "../../common/mobile/android/android-de import * as helpers from "../../common/helpers"; import * as path from "path"; import * as net from "net"; +import { EOL } from "os"; -class AndroidLiveSyncService implements INativeScriptDeviceLiveSyncService { +export class AndroidLiveSyncService implements INativeScriptDeviceLiveSyncService { private static BACKEND_PORT = 18182; private device: Mobile.IAndroidDevice; constructor(_device: Mobile.IDevice, private $mobileHelper: Mobile.IMobileHelper, private $injector: IInjector, - private $androidDebugService: IDebugService, + private $logger: ILogger, + private $androidDebugService: IPlatformDebugService, private $liveSyncProvider: ILiveSyncProvider) { this.device = (_device); } - public get debugService(): IDebugService { + public get debugService(): IPlatformDebugService { return this.$androidDebugService; } - public async refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], forceExecuteFullSync: boolean, projectData: IProjectData): Promise { + public async refreshApplication(deviceAppData: Mobile.IDeviceAppData, + localToDevicePaths: Mobile.ILocalToDevicePathData[], + forceExecuteFullSync: boolean, + projectData: IProjectData, + outputFilePath?: string, + debugData?: IDebugData, + debugOptions?: IOptions): Promise { + + if (debugData) { + // TODO: Move this outside of here + await this.debugService.debugStop(); + + let applicationId = deviceAppData.appIdentifier; + await deviceAppData.device.applicationManager.stopApplication(applicationId, projectData.projectName); + + debugData.pathToAppPackage = outputFilePath; + const debugInfo = await this.debugService.debug(debugData, debugOptions); + this.printDebugInformation(debugInfo); + + return; + } + await this.device.adb.executeShellCommand( ["chmod", - "777", - await deviceAppData.getDeviceProjectRootPath(), - `/data/local/tmp/${deviceAppData.appIdentifier}`, - `/data/local/tmp/${deviceAppData.appIdentifier}/sync`] - ); + "777", + await deviceAppData.getDeviceProjectRootPath(), + `/data/local/tmp/${deviceAppData.appIdentifier}`, + `/data/local/tmp/${deviceAppData.appIdentifier}/sync`] + ); let canExecuteFastSync = !forceExecuteFullSync && !_.some(localToDevicePaths, (localToDevicePath: any) => !this.$liveSyncProvider.canExecuteFastSync(localToDevicePath.getLocalPath(), projectData, deviceAppData.platform)); @@ -38,6 +61,12 @@ class AndroidLiveSyncService implements INativeScriptDeviceLiveSyncService { return this.restartApplication(deviceAppData); } + protected printDebugInformation(information: string[]): void { + _.each(information, i => { + this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${i}${EOL}`.cyan); + }); + } + private async restartApplication(deviceAppData: Mobile.IDeviceAppData): Promise { let devicePathRoot = `/data/data/${deviceAppData.appIdentifier}/files`; let devicePath = this.$mobileHelper.buildDevicePath(devicePathRoot, "code_cache", "secondary_dexes", "proxyThumb"); diff --git a/lib/services/livesync/android-livesync-service.ts b/lib/services/livesync/android-livesync-service.ts new file mode 100644 index 0000000000..f64ee5c495 --- /dev/null +++ b/lib/services/livesync/android-livesync-service.ts @@ -0,0 +1,91 @@ +import * as deviceAppDataIdentifiers from "../../providers/device-app-data-provider"; +import * as path from "path"; +import * as adls from "./android-device-livesync-service"; + +export class AndroidLiveSyncService { + constructor(private $projectFilesManager: IProjectFilesManager, + private $platformsData: IPlatformsData, + private $logger: ILogger, + private $projectFilesProvider: IProjectFilesProvider, + private $fs: IFileSystem, + private $injector: IInjector) { + } + + public async fullSync(projectData: IProjectData, device: Mobile.IDevice): Promise { + const deviceLiveSyncService = this.$injector.resolve(adls.AndroidLiveSyncService, { _device: device }); + const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + const deviceAppData = this.$injector.resolve(deviceAppDataIdentifiers.AndroidAppIdentifier, + { _appIdentifier: projectData.projectId, device, platform: device.deviceInfo.platform }); + + await deviceLiveSyncService.beforeLiveSyncAction(deviceAppData); + + const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, "app"); + const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, null, []); + await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, true); + } + + public async liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: { projectData: IProjectData, filesToRemove: string[], filesToSync: string[], isRebuilt: boolean }): Promise { + const projectData = liveSyncInfo.projectData; + const deviceAppData = this.$injector.resolve(deviceAppDataIdentifiers.AndroidAppIdentifier, + { _appIdentifier: projectData.projectId, device, platform: device.deviceInfo.platform }); + + let modifiedLocalToDevicePaths: Mobile.ILocalToDevicePathData[] = []; + if (liveSyncInfo.filesToSync.length) { + const filesToSync = liveSyncInfo.filesToSync; + const mappedFiles = _.map(filesToSync, filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)); + + // Some plugins modify platforms dir on afterPrepare (check nativescript-dev-sass) - we want to sync only existing file. + const existingFiles = mappedFiles.filter(m => this.$fs.exists(m)); + this.$logger.trace("Will execute livesync for files: ", existingFiles); + const skippedFiles = _.difference(mappedFiles, existingFiles); + if (skippedFiles.length) { + this.$logger.trace("The following files will not be synced as they do not exist:", skippedFiles); + } + + if (existingFiles.length) { + let platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, "app"); + let localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, + projectFilesPath, mappedFiles, []); + modifiedLocalToDevicePaths.push(...localToDevicePaths); + await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, false); + } + } + + if (liveSyncInfo.filesToRemove.length) { + const filePaths = liveSyncInfo.filesToRemove; + let platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + + const mappedFiles = _.map(filePaths, filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)); + const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, "app"); + let localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, mappedFiles, []); + modifiedLocalToDevicePaths.push(...localToDevicePaths); + + const deviceLiveSyncService = this.$injector.resolve(adls.AndroidLiveSyncService, { _device: device }); + deviceLiveSyncService.removeFiles(projectData.projectId, localToDevicePaths, projectData.projectId); + } + + if (liveSyncInfo.isRebuilt) { + // After application is rebuilt, we should just start it + await device.applicationManager.restartApplication(liveSyncInfo.projectData.projectId, liveSyncInfo.projectData.projectName); + } else if (modifiedLocalToDevicePaths) { + await this.refreshApplication(deviceAppData, modifiedLocalToDevicePaths, false, projectData); + } + } + + public async refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], isFullSync: boolean, projectData: IProjectData): Promise { + let deviceLiveSyncService = this.$injector.resolve(adls.AndroidLiveSyncService, { _device: deviceAppData.device }); + this.$logger.info("Refreshing application..."); + await deviceLiveSyncService.refreshApplication(deviceAppData, localToDevicePaths, isFullSync, projectData); + } + + protected async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, canTransferDirectory: boolean): Promise { + if (canTransferDirectory) { + await deviceAppData.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath); + } else { + await deviceAppData.device.fileSystem.transferFiles(deviceAppData, localToDevicePaths); + } + + console.log("TRANSFEREEDDDDDDD!!!!!!"); + } +} diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index a309233506..24a4c0c5c3 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -5,7 +5,7 @@ import * as net from "net"; let currentPageReloadId = 0; -class IOSLiveSyncService implements INativeScriptDeviceLiveSyncService { +export class IOSLiveSyncService implements INativeScriptDeviceLiveSyncService { private static BACKEND_PORT = 18181; private socket: net.Socket; private device: Mobile.IiOSDevice; diff --git a/lib/services/livesync/ios-livesync-service.ts b/lib/services/livesync/ios-livesync-service.ts new file mode 100644 index 0000000000..2c1e1987df --- /dev/null +++ b/lib/services/livesync/ios-livesync-service.ts @@ -0,0 +1,124 @@ +import * as deviceAppDataIdentifiers from "../../providers/device-app-data-provider"; +import * as path from "path"; +import * as iosdls from "./ios-device-livesync-service"; +import * as temp from "temp"; +// import * as uuid from "uuid"; + +export class IOSLiveSyncService { + constructor( + private $devicesService: Mobile.IDevicesService, + private $options: IOptions, + private $projectFilesManager: IProjectFilesManager, + private $platformsData: IPlatformsData, + private $logger: ILogger, + private $projectFilesProvider: IProjectFilesProvider, + private $fs: IFileSystem, + private $injector: IInjector) { + } + + public async fullSync(projectData: IProjectData, device: Mobile.IDevice): Promise { + const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + const deviceAppData = this.$injector.resolve(deviceAppDataIdentifiers.IOSAppIdentifier, + { _appIdentifier: projectData.projectId, device, platform: device.deviceInfo.platform }); + const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, "app"); + + if (device.isEmulator) { + const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, null, []); + await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, true); + } else { + temp.track(); + let tempZip = temp.path({ prefix: "sync", suffix: ".zip" }); + let tempApp = temp.mkdirSync("app"); + this.$logger.trace("Creating zip file: " + tempZip); + this.$fs.copyFile(path.join(path.dirname(projectFilesPath), "app/*"), tempApp); + + if (!this.$options.syncAllFiles) { + this.$logger.info("Skipping node_modules folder! Use the syncAllFiles option to sync files from this folder."); + this.$fs.deleteDirectory(path.join(tempApp, "tns_modules")); + } + + await this.$fs.zipFiles(tempZip, this.$fs.enumerateFilesInDirectorySync(tempApp), (res) => { + return path.join("app", path.relative(tempApp, res)); + }); + + await device.fileSystem.transferFiles(deviceAppData, [{ + getLocalPath: () => tempZip, + getDevicePath: () => deviceAppData.deviceSyncZipPath, + getRelativeToProjectBasePath: () => "../sync.zip", + deviceProjectRootPath: await deviceAppData.getDeviceProjectRootPath() + }]); + } + } + + public async liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: { projectData: IProjectData, filesToRemove: string[], filesToSync: string[], isRebuilt: boolean }): Promise { + const projectData = liveSyncInfo.projectData; + const deviceAppData = this.$injector.resolve(deviceAppDataIdentifiers.IOSAppIdentifier, + { _appIdentifier: projectData.projectId, device, platform: device.deviceInfo.platform }); + let modifiedLocalToDevicePaths: Mobile.ILocalToDevicePathData[] = []; + + if (liveSyncInfo.isRebuilt) { + // In this case we should execute fullsync: + await this.fullSync(projectData, device); + } else { + if (liveSyncInfo.filesToSync.length) { + const filesToSync = liveSyncInfo.filesToSync; + const mappedFiles = _.map(filesToSync, filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)); + + // Some plugins modify platforms dir on afterPrepare (check nativescript-dev-sass) - we want to sync only existing file. + const existingFiles = mappedFiles.filter(m => this.$fs.exists(m)); + this.$logger.trace("Will execute livesync for files: ", existingFiles); + const skippedFiles = _.difference(mappedFiles, existingFiles); + if (skippedFiles.length) { + this.$logger.trace("The following files will not be synced as they do not exist:", skippedFiles); + } + + if (existingFiles.length) { + const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, "app"); + let localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, + projectFilesPath, mappedFiles, []); + modifiedLocalToDevicePaths.push(...localToDevicePaths); + await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, false); + } + } + + if (liveSyncInfo.filesToRemove.length) { + const filePaths = liveSyncInfo.filesToRemove; + const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + + const mappedFiles = _.map(filePaths, filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)); + const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, "app"); + let localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, mappedFiles, []); + modifiedLocalToDevicePaths.push(...localToDevicePaths); + + const deviceLiveSyncService = this.$injector.resolve(iosdls.IOSLiveSyncService, { _device: device }); + deviceLiveSyncService.removeFiles(projectData.projectId, localToDevicePaths, projectData.projectId); + } + } + + if (liveSyncInfo.isRebuilt) { + // WHAT if we are in debug session? + // After application is rebuilt, we should just start it + await device.applicationManager.restartApplication(liveSyncInfo.projectData.projectId, liveSyncInfo.projectData.projectName); + } else if (modifiedLocalToDevicePaths) { + await this.refreshApplication(deviceAppData, modifiedLocalToDevicePaths, false, projectData); + } + } + + public async refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], isFullSync: boolean, projectData: IProjectData): Promise { + let deviceLiveSyncService = this.$injector.resolve(iosdls.IOSLiveSyncService, { _device: deviceAppData.device }); + this.$logger.info("Refreshing application..."); + await deviceLiveSyncService.refreshApplication(deviceAppData, localToDevicePaths, isFullSync, projectData); + } + + protected async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise { + let canTransferDirectory = isFullSync && this.$devicesService.isiOSDevice(deviceAppData.device); + if (canTransferDirectory) { + await deviceAppData.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath); + } else { + await deviceAppData.device.fileSystem.transferFiles(deviceAppData, localToDevicePaths); + } + + console.log("### ios TRANSFEREEDDDDDDD!!!!!!"); + } +} diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 7225477b47..0ac3bcc54c 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -1,136 +1,273 @@ -import * as constants from "../../constants"; -import * as helpers from "../../common/helpers"; import * as path from "path"; +import * as choki from "chokidar"; +import * as iOSLs from "./ios-livesync-service"; +import * as androidLs from "./android-livesync-service"; +import { EventEmitter } from "events"; +import { exported } from "../../common/decorators"; +import { hook } from "../../common/helpers"; -let choki = require("chokidar"); - -class LiveSyncService implements ILiveSyncService { - private _isInitialized = false; - - constructor(private $errors: IErrors, - private $platformsData: IPlatformsData, - private $platformService: IPlatformService, - private $injector: IInjector, +// TODO: emit events for "successfull livesync", "stoppedLivesync", +export class LiveSyncService extends EventEmitter { + constructor(private $platformService: IPlatformService, + private $projectDataService: IProjectDataService, private $devicesService: Mobile.IDevicesService, - private $options: IOptions, + private $mobileHelper: Mobile.IMobileHelper, + private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, private $logger: ILogger, - private $dispatcher: IFutureDispatcher, - private $hooksService: IHooksService, private $processService: IProcessService, - private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder) { } - - public get isInitialized(): boolean { // This function is used from https://github.com/NativeScript/nativescript-dev-typescript/blob/master/lib/before-prepare.js#L4 - return this._isInitialized; + private $hooksService: IHooksService, + private $projectChangesService: IProjectChangesService, + private $injector: IInjector) { + super(); } - public async liveSync(platform: string, projectData: IProjectData, applicationReloadAction?: (deviceAppData: Mobile.IDeviceAppData) => Promise): Promise { - if (this.$options.justlaunch) { - this.$options.watch = false; + // TODO: Add finishLivesync method in the platform specific services + @exported("liveSyncService") + @hook("liveSync") + public async liveSync( + deviceDescriptors: { identifier: string, buildAction: () => Promise, outputPath?: string }[], + liveSyncData: { projectDir: string, shouldStartWatcher: boolean, syncAllFiles: boolean }): Promise { + + // TODO: Initialize devicesService before that. + const projectData = this.$projectDataService.getProjectData(liveSyncData.projectDir); + await this.initialSync(projectData, deviceDescriptors, liveSyncData); + + // Should be set after prepare + this.$injector.resolve("usbLiveSyncService")._isInitialized = true; + + if (liveSyncData.shouldStartWatcher) { + await this.startWatcher(projectData, deviceDescriptors, liveSyncData); } - let liveSyncData: ILiveSyncData[] = []; - - if (platform) { - await this.$devicesService.initialize({ platform: platform, deviceId: this.$options.device }); - liveSyncData.push(await this.prepareLiveSyncData(platform, projectData)); - } else if (this.$options.device) { - await this.$devicesService.initialize({ platform: platform, deviceId: this.$options.device }); - platform = this.$devicesService.getDeviceByIdentifier(this.$options.device).deviceInfo.platform; - liveSyncData.push(await this.prepareLiveSyncData(platform, projectData)); - } else { - await this.$devicesService.initialize({ skipInferPlatform: true, skipDeviceDetectionInterval: true }); - - for (let installedPlatform of this.$platformService.getInstalledPlatforms(projectData)) { - if (this.$devicesService.getDevicesForPlatform(installedPlatform).length === 0) { - await this.$devicesService.startEmulator(installedPlatform); - } + } - liveSyncData.push(await this.prepareLiveSyncData(installedPlatform, projectData)); + @exported("liveSyncService") + public async stopLiveSync(projectDir: string): Promise { + const liveSyncProcessInfo = _.find(this.liveSyncProcessesInfo, (info, key) => key === projectDir); + + if (liveSyncProcessInfo) { + if (liveSyncProcessInfo.timer) { + clearTimeout(liveSyncProcessInfo.timer); } - } - if (liveSyncData.length === 0) { - this.$errors.fail("There are no platforms installed in this project. Please specify platform or install one by using `tns platform add` command!"); - } + if (liveSyncProcessInfo.watcher) { + liveSyncProcessInfo.watcher.close(); + } - this._isInitialized = true; // If we want before-prepare hooks to work properly, this should be set after preparePlatform function + delete this.liveSyncProcessesInfo[projectDir]; - await this.liveSyncCore(liveSyncData, applicationReloadAction, projectData); + // Kill typescript watcher + await this.$hooksService.executeAfterHooks('watch'); + } } - private async prepareLiveSyncData(platform: string, projectData: IProjectData): Promise { - platform = platform || this.$devicesService.platform; - let platformData = this.$platformsData.getPlatformData(platform.toLowerCase(), projectData); + private currentPromiseChain: Promise = Promise.resolve(); - let liveSyncData: ILiveSyncData = { - platform: platform, - appIdentifier: projectData.projectId, - projectFilesPath: path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME), - syncWorkingDirectory: projectData.projectDir, - excludedProjectDirsAndFiles: this.$options.release ? constants.LIVESYNC_EXCLUDED_FILE_PATTERNS : [] - }; + private liveSyncProcessesInfo: IDictionary<{ timer: NodeJS.Timer, watcher: choki.FSWatcher }> = {}; - return liveSyncData; - } + // TODO: Register both livesync services in injector + private getLiveSyncService(platform: string): any { + if (this.$mobileHelper.isiOSPlatform(platform)) { + return this.$injector.resolve(iOSLs.IOSLiveSyncService); + } else if (this.$mobileHelper.isAndroidPlatform(platform)) { + return this.$injector.resolve(androidLs.AndroidLiveSyncService); + } - @helpers.hook('livesync') - private async liveSyncCore(liveSyncData: ILiveSyncData[], applicationReloadAction: (deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => Promise, projectData: IProjectData): Promise { - await this.$platformService.trackProjectType(projectData); + throw new Error(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); + } - let watchForChangeActions: ((event: string, filePath: string, dispatcher: IFutureDispatcher) => Promise)[] = []; + private async ensureLatestAppPackageIsInstalledOnDevice(device: Mobile.IDevice, + preparedPlatforms: string[], + rebuiltInformation: any[], + projectData: IProjectData, + deviceBuildInfoDescriptor: { identifier: string, buildAction: () => Promise, outputPath?: string }, + modifiedFiles?: string[]): Promise { - for (let dataItem of liveSyncData) { - let service: IPlatformLiveSyncService = this.$injector.resolve("platformLiveSyncService", { _liveSyncData: dataItem }); - watchForChangeActions.push((event: string, filePath: string, dispatcher: IFutureDispatcher) => - service.partialSync(event, filePath, dispatcher, applicationReloadAction, projectData)); + const platform = device.deviceInfo.platform; + if (preparedPlatforms.indexOf(platform) === -1) { + preparedPlatforms.push(platform); + await this.$platformService.preparePlatform(platform, {}, null, projectData, {}, modifiedFiles); + } - await service.fullSync(projectData, applicationReloadAction); + const shouldBuild = await this.$platformService.shouldBuild(platform, projectData, { buildForDevice: !device.isEmulator }, deviceBuildInfoDescriptor.outputPath); + if (shouldBuild) { + const pathToBuildItem = await deviceBuildInfoDescriptor.buildAction(); + // Is it possible to return shouldBuild for two devices? What about android device and android emulator? + rebuiltInformation.push({ isEmulator: device.isEmulator, platform, pathToBuildItem }); } - if (this.$options.watch && !this.$options.justlaunch) { - await this.$hooksService.executeBeforeHooks('watch'); - await this.partialSync(liveSyncData[0].syncWorkingDirectory, watchForChangeActions, projectData); + const rebuildInfo = _.find(rebuiltInformation, info => info.isEmulator === device.isEmulator && info.platform === platform); + + if (rebuildInfo) { + // Case where we have three devices attached, a change that requires build is found, + // we'll rebuild the app only for the first device, but we should install new package on all three devices. + await this.$platformService.installApplication(device, { release: false }, projectData, rebuildInfo.pathToBuildItem, deviceBuildInfoDescriptor.outputPath); } } - private partialSync(syncWorkingDirectory: string, onChangedActions: ((event: string, filePath: string, dispatcher: IFutureDispatcher) => Promise)[], projectData: IProjectData): void { - let that = this; - let productionDependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); + private async initialSync(projectData: IProjectData, deviceDescriptors: { identifier: string, buildAction: () => Promise, outputPath?: string }[], + liveSyncData: { projectDir: string, shouldStartWatcher: boolean, syncAllFiles: boolean }): Promise { + + const preparedPlatforms: string[] = []; + const rebuiltInformation: { platform: string, isEmulator: boolean, pathToBuildItem: string }[] = []; + + // Now fullSync + const deviceAction = async (device: Mobile.IDevice): Promise => { + // TODO: Call androidDeviceLiveSyncService.beforeLiveSyncAction + const platform = device.deviceInfo.platform; + const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, projectData, deviceDescriptor); + + await this.getLiveSyncService(platform).fullSync(projectData, device); + + await device.applicationManager.restartApplication(projectData.projectId, projectData.projectName); + }; + + await this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier)); + } + + private async startWatcher(projectData: IProjectData, + deviceDescriptors: { identifier: string, buildAction: () => Promise, outputPath?: string }[], + liveSyncData: { projectDir: string, shouldStartWatcher: boolean, syncAllFiles: boolean }): Promise { + let pattern = ["app"]; - if (this.$options.syncAllFiles) { + if (liveSyncData.syncAllFiles) { + const productionDependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); pattern.push("package.json"); // watch only production node_module/packages same one prepare uses for (let index in productionDependencies) { - pattern.push("node_modules/" + productionDependencies[index].name); + pattern.push(productionDependencies[index].directory); } } - let watcher = choki.watch(pattern, { ignoreInitial: true, cwd: syncWorkingDirectory, - awaitWriteFinish: { - stabilityThreshold: 500, - pollInterval: 100 - }, }).on("all", (event: string, filePath: string) => { - that.$dispatcher.dispatch(async () => { - try { - filePath = path.join(syncWorkingDirectory, filePath); - for (let i = 0; i < onChangedActions.length; i++) { - that.$logger.trace(`Event '${event}' triggered for path: '${filePath}'`); - await onChangedActions[i](event, filePath, that.$dispatcher); + console.log("now starting watcher!", pattern); + + let filesToSync: string[] = [], + filesToRemove: string[] = []; + let timeoutTimer: NodeJS.Timer; + + const removeDeviceDescriptor = async (deviceId: string) => { + _.remove(deviceDescriptors, descriptor => descriptor.identifier === deviceId); + + if (!deviceDescriptors.length) { + await this.stopLiveSync(liveSyncData.projectDir); + } + }; + + const startTimeout = () => { + timeoutTimer = setTimeout(async () => { + await this.addActionToQueue(async () => { + // TODO: Push consecutive actions to the queue, do not start them simultaneously + if (filesToSync.length || filesToRemove.length) { + try { + let currentFilesToSync = _.cloneDeep(filesToSync); + filesToSync = []; + + let currentFilesToRemove = _.cloneDeep(filesToRemove); + filesToRemove = []; + + const allModifiedFiles = [].concat(currentFilesToSync).concat(currentFilesToRemove); + console.log(this.$projectChangesService.currentChanges); + + const preparedPlatforms: string[] = []; + const rebuiltInformation: { platform: string, isEmulator: boolean, pathToBuildItem: string }[] = []; + await this.$devicesService.execute(async (device: Mobile.IDevice) => { + // const platform = device.deviceInfo.platform; + const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + + await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, + projectData, deviceDescriptor, allModifiedFiles); + + const service = this.getLiveSyncService(device.deviceInfo.platform); + const settings: any = { + projectData, + filesToRemove: currentFilesToRemove, + filesToSync: currentFilesToSync, + isRebuilt: !!_.find(rebuiltInformation, info => info.isEmulator === device.isEmulator && info.platform === device.deviceInfo.platform) + }; + + await service.liveSyncWatchAction(device, settings); + }, + (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier) + ); + } catch (err) { + // TODO: Decide if we should break here. + // this.$logger.info(`Unable to sync file ${filePath}. Error is:${err.message}`.red.bold); + this.$logger.info("Try saving it again or restart the livesync operation."); + // we can remove the descriptor from action: + const allErrors = err.allErrors; + console.log(allErrors); + _.each(allErrors, (deviceError: any) => { + console.log("for error: ", deviceError, " device ID: ", deviceError.deviceIdentifier); + removeDeviceDescriptor(deviceError.deviceIdentifier); + }); + } } - } catch (err) { - that.$logger.info(`Unable to sync file ${filePath}. Error is:${err.message}`.red.bold); - that.$logger.info("Try saving it again or restart the livesync operation."); + }); + }, 250); + + this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; + }; + + await this.$hooksService.executeBeforeHooks('watch'); + + const watcherOptions: choki.WatchOptions = { + ignoreInitial: true, + cwd: liveSyncData.projectDir, + awaitWriteFinish: { + pollInterval: 100, + stabilityThreshold: 500 + }, + ignored: ["**/.*", ".*"] // hidden files + }; + + const watcher = choki.watch(pattern, watcherOptions) + .on("all", async (event: string, filePath: string) => { + clearTimeout(timeoutTimer); + + filePath = path.join(liveSyncData.projectDir, filePath); + + this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); + + if (event === "add" || event === "addDir" || event === "change" /* <--- what to do when change event is raised ? */) { + filesToSync.push(filePath); + } else if (event === "unlink" || event === "unlinkDir") { + filesToRemove.push(filePath); } + + startTimeout(); }); - }); + + this.liveSyncProcessesInfo[liveSyncData.projectDir] = { + watcher, + timer: timeoutTimer + }; this.$processService.attachToProcessExitSignals(this, () => { - watcher.close(pattern); + _.keys(this.liveSyncProcessesInfo).forEach(projectDir => { + // Do not await here, we are in process exit's handler. + this.stopLiveSync(projectDir); + }); }); - this.$dispatcher.run(); + this.$devicesService.on("deviceLost", async (device: Mobile.IDevice) => { + await removeDeviceDescriptor(device.deviceInfo.identifier); + }); } + + private async addActionToQueue(action: () => Promise): Promise { + this.currentPromiseChain = this.currentPromiseChain.then(async () => { + const res = await action(); + // console.log("after ", unique); + return res; + }); + + const result = await this.currentPromiseChain; + return result; + } + } +$injector.register("liveSyncService", LiveSyncService); $injector.register("usbLiveSyncService", LiveSyncService); diff --git a/lib/services/livesync/platform-livesync-service.ts b/lib/services/livesync/platform-livesync-service.ts deleted file mode 100644 index 4b926dfa77..0000000000 --- a/lib/services/livesync/platform-livesync-service.ts +++ /dev/null @@ -1,263 +0,0 @@ -import syncBatchLib = require("../../common/services/livesync/sync-batch"); -import * as path from "path"; -import * as minimatch from "minimatch"; -import * as util from "util"; -import * as helpers from "../../common/helpers"; - -const livesyncInfoFileName = ".nslivesyncinfo"; - -export abstract class PlatformLiveSyncServiceBase implements IPlatformLiveSyncService { - private batch: IDictionary = Object.create(null); - private livesyncData: IDictionary = Object.create(null); - - protected liveSyncData: ILiveSyncData; - - constructor(_liveSyncData: ILiveSyncData, - private $devicesService: Mobile.IDevicesService, - private $mobileHelper: Mobile.IMobileHelper, - private $logger: ILogger, - private $options: IOptions, - private $deviceAppDataFactory: Mobile.IDeviceAppDataFactory, - private $injector: IInjector, - private $projectFilesManager: IProjectFilesManager, - private $projectFilesProvider: IProjectFilesProvider, - private $platformService: IPlatformService, - private $projectChangesService: IProjectChangesService, - private $liveSyncProvider: ILiveSyncProvider, - private $fs: IFileSystem) { - this.liveSyncData = _liveSyncData; - } - - public async fullSync(projectData: IProjectData, postAction?: (deviceAppData: Mobile.IDeviceAppData) => Promise): Promise { - let appIdentifier = this.liveSyncData.appIdentifier; - let platform = this.liveSyncData.platform; - let projectFilesPath = this.liveSyncData.projectFilesPath; - let canExecute = this.getCanExecuteAction(platform, appIdentifier); - let action = async (device: Mobile.IDevice): Promise => { - await this.$platformService.trackActionForPlatform({ action: "LiveSync", platform, isForDevice: !device.isEmulator, deviceOsVersion: device.deviceInfo.version }); - - let deviceAppData = this.$deviceAppDataFactory.create(appIdentifier, this.$mobileHelper.normalizePlatformName(platform), device); - let localToDevicePaths: Mobile.ILocalToDevicePathData[] = null; - if (await this.shouldTransferAllFiles(platform, deviceAppData, projectData)) { - localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, null, this.liveSyncData.excludedProjectDirsAndFiles); - await this.transferFiles(deviceAppData, localToDevicePaths, this.liveSyncData.projectFilesPath, true); - await device.fileSystem.putFile(this.$projectChangesService.getPrepareInfoFilePath(platform, projectData), await this.getLiveSyncInfoFilePath(deviceAppData), appIdentifier); - } - - if (postAction) { - await this.finishLivesync(deviceAppData); - await postAction(deviceAppData); - return; - } - - await this.refreshApplication(deviceAppData, localToDevicePaths, true, projectData); - await this.finishLivesync(deviceAppData); - }; - await this.$devicesService.execute(action, canExecute); - } - - public async partialSync(event: string, filePath: string, dispatcher: IFutureDispatcher, afterFileSyncAction: (deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => Promise, projectData: IProjectData): Promise { - if (this.isFileExcluded(filePath, this.liveSyncData.excludedProjectDirsAndFiles)) { - this.$logger.trace(`Skipping livesync for changed file ${filePath} as it is excluded in the patterns: ${this.liveSyncData.excludedProjectDirsAndFiles.join(", ")}`); - return; - } - - if (event === "add" || event === "addDir" || event === "change") { - this.batchSync(filePath, dispatcher, afterFileSyncAction, projectData); - } else if (event === "unlink" || event === "unlinkDir") { - await this.syncRemovedFile(filePath, afterFileSyncAction, projectData); - } - } - - protected getCanExecuteAction(platform: string, appIdentifier: string): (dev: Mobile.IDevice) => boolean { - let isTheSamePlatformAction = ((device: Mobile.IDevice) => device.deviceInfo.platform.toLowerCase() === platform.toLowerCase()); - if (this.$options.device) { - return (device: Mobile.IDevice): boolean => isTheSamePlatformAction(device) && device.deviceInfo.identifier === this.$devicesService.getDeviceByDeviceOption().deviceInfo.identifier; - } - return isTheSamePlatformAction; - } - - public async refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], isFullSync: boolean, projectData: IProjectData): Promise { - let deviceLiveSyncService = this.resolveDeviceSpecificLiveSyncService(deviceAppData.device.deviceInfo.platform, deviceAppData.device); - this.$logger.info("Refreshing application..."); - await deviceLiveSyncService.refreshApplication(deviceAppData, localToDevicePaths, isFullSync, projectData); - } - - protected async finishLivesync(deviceAppData: Mobile.IDeviceAppData): Promise { - // This message is important because it signals Visual Studio Code that livesync has finished and debugger can be attached. - this.$logger.info(`Successfully synced application ${deviceAppData.appIdentifier} on device ${deviceAppData.device.deviceInfo.identifier}.\n`); - } - - protected async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise { - this.$logger.info("Transferring project files..."); - let canTransferDirectory = isFullSync && (this.$devicesService.isAndroidDevice(deviceAppData.device) || this.$devicesService.isiOSSimulator(deviceAppData.device)); - if (canTransferDirectory) { - await deviceAppData.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath); - } else { - await this.$liveSyncProvider.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, isFullSync); - } - this.logFilesSyncInformation(localToDevicePaths, "Successfully transferred %s.", this.$logger.info); - } - - protected resolveDeviceSpecificLiveSyncService(platform: string, device: Mobile.IDevice): INativeScriptDeviceLiveSyncService { - return this.$injector.resolve(this.$liveSyncProvider.deviceSpecificLiveSyncServices[platform.toLowerCase()], { _device: device }); - } - - private isFileExcluded(filePath: string, excludedPatterns: string[]): boolean { - let isFileExcluded = false; - _.each(excludedPatterns, pattern => { - if (minimatch(filePath, pattern, { nocase: true })) { - isFileExcluded = true; - return false; - } - }); - - // skip hidden files, to prevent reload of the app for hidden files - // created temporarily by the IDEs - if (this.isUnixHiddenPath(filePath)) { - isFileExcluded = true; - } - - return isFileExcluded; - } - - private isUnixHiddenPath(filePath: string): boolean { - return (/(^|\/)\.[^\/\.]/g).test(filePath); - } - - private batchSync(filePath: string, dispatcher: IFutureDispatcher, afterFileSyncAction: (deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => Promise, projectData: IProjectData): void { - let platformBatch: ISyncBatch = this.batch[this.liveSyncData.platform]; - if (!platformBatch || !platformBatch.syncPending) { - let done = async () => { - dispatcher.dispatch(async () => { - try { - for (let platform in this.batch) { - let batch = this.batch[platform]; - await batch.syncFiles(async (filesToSync: string[]) => { - const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; - await this.$platformService.preparePlatform(this.liveSyncData.platform, appFilesUpdaterOptions, this.$options.platformTemplate, projectData, this.$options, filesToSync); - let canExecute = this.getCanExecuteAction(this.liveSyncData.platform, this.liveSyncData.appIdentifier); - let deviceFileAction = (deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => this.transferFiles(deviceAppData, localToDevicePaths, this.liveSyncData.projectFilesPath, !filePath); - let action = this.getSyncAction(filesToSync, deviceFileAction, afterFileSyncAction, projectData); - await this.$devicesService.execute(action, canExecute); - }); - } - } catch (err) { - this.$logger.warn(`Unable to sync files. Error is:`, err.message); - } - }); - }; - - this.batch[this.liveSyncData.platform] = this.$injector.resolve(syncBatchLib.SyncBatch, { done: done }); - this.livesyncData[this.liveSyncData.platform] = this.liveSyncData; - } - - this.batch[this.liveSyncData.platform].addFile(filePath); - } - - private async syncRemovedFile(filePath: string, - afterFileSyncAction: (deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => Promise, projectData: IProjectData): Promise { - let deviceFilesAction = (deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => { - let deviceLiveSyncService = this.resolveDeviceSpecificLiveSyncService(this.liveSyncData.platform, deviceAppData.device); - return deviceLiveSyncService.removeFiles(this.liveSyncData.appIdentifier, localToDevicePaths, projectData.projectId); - }; - let canExecute = this.getCanExecuteAction(this.liveSyncData.platform, this.liveSyncData.appIdentifier); - let action = this.getSyncAction([filePath], deviceFilesAction, afterFileSyncAction, projectData); - await this.$devicesService.execute(action, canExecute); - } - - private getSyncAction( - filesToSync: string[], - fileSyncAction: (deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => Promise, - afterFileSyncAction: (deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => Promise, - projectData: IProjectData): (device: Mobile.IDevice) => Promise { - let action = async (device: Mobile.IDevice): Promise => { - let deviceAppData: Mobile.IDeviceAppData = null; - let localToDevicePaths: Mobile.ILocalToDevicePathData[] = null; - let isFullSync = false; - - if (this.$projectChangesService.currentChanges.changesRequireBuild) { - let buildConfig: IBuildConfig = { - buildForDevice: !device.isEmulator, - projectDir: this.$options.path, - release: this.$options.release, - teamId: this.$options.teamId, - device: this.$options.device, - provision: this.$options.provision, - }; - let platform = device.deviceInfo.platform; - if (this.$platformService.shouldBuild(platform, projectData, buildConfig)) { - await this.$platformService.buildPlatform(platform, buildConfig, projectData); - } - - await this.$platformService.installApplication(device, buildConfig, projectData); - deviceAppData = this.$deviceAppDataFactory.create(this.liveSyncData.appIdentifier, this.$mobileHelper.normalizePlatformName(this.liveSyncData.platform), device); - isFullSync = true; - } else { - deviceAppData = this.$deviceAppDataFactory.create(this.liveSyncData.appIdentifier, this.$mobileHelper.normalizePlatformName(this.liveSyncData.platform), device); - const mappedFiles = filesToSync.map((file: string) => this.$projectFilesProvider.mapFilePath(file, device.deviceInfo.platform, projectData)); - - // Some plugins modify platforms dir on afterPrepare (check nativescript-dev-sass) - we want to sync only existing file. - const existingFiles = mappedFiles.filter(m => this.$fs.exists(m)); - - this.$logger.trace("Will execute livesync for files: ", existingFiles); - - const skippedFiles = _.difference(mappedFiles, existingFiles); - - if (skippedFiles.length) { - this.$logger.trace("The following files will not be synced as they do not exist:", skippedFiles); - } - - localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, this.liveSyncData.projectFilesPath, mappedFiles, this.liveSyncData.excludedProjectDirsAndFiles); - - await fileSyncAction(deviceAppData, localToDevicePaths); - } - - if (!afterFileSyncAction) { - await this.refreshApplication(deviceAppData, localToDevicePaths, isFullSync, projectData); - } - - await device.fileSystem.putFile(this.$projectChangesService.getPrepareInfoFilePath(device.deviceInfo.platform, projectData), await this.getLiveSyncInfoFilePath(deviceAppData), this.liveSyncData.appIdentifier); - - await this.finishLivesync(deviceAppData); - - if (afterFileSyncAction) { - await afterFileSyncAction(deviceAppData, localToDevicePaths); - } - }; - - return action; - } - - private async shouldTransferAllFiles(platform: string, deviceAppData: Mobile.IDeviceAppData, projectData: IProjectData): Promise { - try { - if (this.$options.clean) { - return false; - } - let fileText = await this.$platformService.readFile(deviceAppData.device, await this.getLiveSyncInfoFilePath(deviceAppData), projectData); - let remoteLivesyncInfo: IPrepareInfo = JSON.parse(fileText); - let localPrepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); - return remoteLivesyncInfo.time !== localPrepareInfo.time; - } catch (e) { - return true; - } - } - - private async getLiveSyncInfoFilePath(deviceAppData: Mobile.IDeviceAppData): Promise { - let deviceRootPath = path.dirname(await deviceAppData.getDeviceProjectRootPath()); - let deviceFilePath = helpers.fromWindowsRelativePathToUnix(path.join(deviceRootPath, livesyncInfoFileName)); - return deviceFilePath; - } - - private logFilesSyncInformation(localToDevicePaths: Mobile.ILocalToDevicePathData[], message: string, action: Function): void { - if (localToDevicePaths && localToDevicePaths.length < 10) { - _.each(localToDevicePaths, (file: Mobile.ILocalToDevicePathData) => { - action.call(this.$logger, util.format(message, path.basename(file.getLocalPath()).yellow)); - }); - } else { - action.call(this.$logger, util.format(message, "all files")); - } - } -} - -$injector.register("platformLiveSyncService", PlatformLiveSyncServiceBase); diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index d7340ba03c..0a23d521f7 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -369,35 +369,34 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - public async shouldBuild(platform: string, projectData: IProjectData, buildConfig: IBuildConfig): Promise { + public async shouldBuild(platform: string, projectData: IProjectData, buildConfig: IBuildConfig, outputPath?: string): Promise { //TODO: shouldBuild - issue with outputPath - we do not have always the built dir locally - return false; - // if (this.$projectChangesService.currentChanges.changesRequireBuild) { - // return true; - // } - // let platformData = this.$platformsData.getPlatformData(platform, projectData); - // let forDevice = !buildConfig || buildConfig.buildForDevice; - // let outputPath = forDevice ? platformData.deviceBuildOutputPath : platformData.emulatorBuildOutputPath; - // if (!this.$fs.exists(outputPath)) { - // return true; - // } - // let packageNames = platformData.getValidPackageNames({ isForDevice: forDevice }); - // let packages = this.getApplicationPackages(outputPath, packageNames); - // if (packages.length === 0) { - // return true; - // } - // let prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); - // let buildInfo = this.getBuildInfo(platform, platformData, buildConfig); - // if (!prepareInfo || !buildInfo) { - // return true; - // } - // if (buildConfig.clean) { - // return true; - // } - // if (prepareInfo.time === buildInfo.prepareTime) { - // return false; - // } - // return prepareInfo.changesRequireBuildTime !== buildInfo.prepareTime; + if (this.$projectChangesService.currentChanges.changesRequireBuild) { + return true; + } + let platformData = this.$platformsData.getPlatformData(platform, projectData); + let forDevice = !buildConfig || buildConfig.buildForDevice; + outputPath = outputPath || (forDevice ? platformData.deviceBuildOutputPath : platformData.emulatorBuildOutputPath || platformData.deviceBuildOutputPath); + if (!this.$fs.exists(outputPath)) { + return true; + } + let packageNames = platformData.getValidPackageNames({ isForDevice: forDevice }); + let packages = this.getApplicationPackages(outputPath, packageNames); + if (packages.length === 0) { + return true; + } + let prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); + let buildInfo = this.getBuildInfo(platform, platformData, buildConfig, outputPath); + if (!prepareInfo || !buildInfo) { + return true; + } + if (buildConfig.clean) { + return true; + } + if (prepareInfo.time === buildInfo.prepareTime) { + return false; + } + return prepareInfo.changesRequireBuildTime !== buildInfo.prepareTime; } public async trackProjectType(projectData: IProjectData): Promise { @@ -451,38 +450,39 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$logger.out("Project successfully built."); } - public async shouldInstall(device: Mobile.IDevice, projectData: IProjectData): Promise { + public async shouldInstall(device: Mobile.IDevice, projectData: IProjectData, outputPath?: string): Promise { let platform = device.deviceInfo.platform; let platformData = this.$platformsData.getPlatformData(platform, projectData); if (!(await device.applicationManager.isApplicationInstalled(projectData.projectId))) { return true; } let deviceBuildInfo: IBuildInfo = await this.getDeviceBuildInfo(device, projectData); - let localBuildInfo = this.getBuildInfo(platform, platformData, { buildForDevice: !device.isEmulator }); + let localBuildInfo = this.getBuildInfo(platform, platformData, { buildForDevice: !device.isEmulator }, outputPath); return !localBuildInfo || !deviceBuildInfo || deviceBuildInfo.buildTime !== localBuildInfo.buildTime; } - public async installApplication(device: Mobile.IDevice, buildConfig: IBuildConfig, projectData: IProjectData): Promise { + public async installApplication(device: Mobile.IDevice, buildConfig: IBuildConfig, projectData: IProjectData, packageFile?: string, outputFilePath?: string): Promise { this.$logger.out("Installing..."); - // let platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); - // let packageFile = ""; - // if (this.$devicesService.isiOSSimulator(device)) { - // packageFile = this.getLatestApplicationPackageForEmulator(platformData, buildConfig).packageName; - // } else { - // packageFile = this.getLatestApplicationPackageForDevice(platformData, buildConfig).packageName; - // } + let platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + if (!packageFile) { + if (this.$devicesService.isiOSSimulator(device)) { + packageFile = this.getLatestApplicationPackageForEmulator(platformData, buildConfig).packageName; + } else { + packageFile = this.getLatestApplicationPackageForDevice(platformData, buildConfig).packageName; + } + } - // await platformData.platformProjectService.cleanDeviceTempFolder(device.deviceInfo.identifier, projectData); + await platformData.platformProjectService.cleanDeviceTempFolder(device.deviceInfo.identifier, projectData); - // await device.applicationManager.reinstallApplication(projectData.projectId, packageFile); + await device.applicationManager.reinstallApplication(projectData.projectId, packageFile); - // if (!buildConfig.release) { - // let deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); - // let buildInfoFilePath = this.getBuildOutputPath(device.deviceInfo.platform, platformData, { buildForDevice: !device.isEmulator }); - // let appIdentifier = projectData.projectId; + if (!buildConfig.release) { + let deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); + let buildInfoFilePath = outputFilePath || this.getBuildOutputPath(device.deviceInfo.platform, platformData, { buildForDevice: !device.isEmulator }); + let appIdentifier = projectData.projectId; - // await device.fileSystem.putFile(path.join(buildInfoFilePath, buildInfoFileName), deviceFilePath, appIdentifier); - // } + await device.fileSystem.putFile(path.join(buildInfoFilePath, buildInfoFileName), deviceFilePath, appIdentifier); + } this.$logger.out(`Successfully installed on device with identifier '${device.deviceInfo.identifier}'.`); } @@ -561,9 +561,9 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - private getBuildInfo(platform: string, platformData: IPlatformData, options: IBuildForDevice): IBuildInfo { - let buildInfoFilePath = this.getBuildOutputPath(platform, platformData, options); - let buildInfoFile = path.join(buildInfoFilePath, buildInfoFileName); + private getBuildInfo(platform: string, platformData: IPlatformData, options: IBuildForDevice, buildOutputPath?: string): IBuildInfo { + buildOutputPath = buildOutputPath || this.getBuildOutputPath(platform, platformData, options); + let buildInfoFile = path.join(buildOutputPath, buildInfoFileName); if (this.$fs.exists(buildInfoFile)) { try { let buildInfoTime = this.$fs.readJson(buildInfoFile); @@ -792,6 +792,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$logger.out("Successfully updated to version ", updateOptions.newVersion); } + // TODO: Remove this method from here. It has nothing to do with platform public async readFile(device: Mobile.IDevice, deviceFilePath: string, projectData: IProjectData): Promise { temp.track(); let uniqueFilePath = temp.path({ suffix: ".tmp" }); diff --git a/lib/services/project-data-service.ts b/lib/services/project-data-service.ts index 58b48e84a8..29f1f964c7 100644 --- a/lib/services/project-data-service.ts +++ b/lib/services/project-data-service.ts @@ -1,4 +1,5 @@ import * as path from "path"; +import { ProjectData } from "../project-data"; interface IProjectFileData { projectData: any; @@ -10,7 +11,8 @@ export class ProjectDataService implements IProjectDataService { constructor(private $fs: IFileSystem, private $staticConfig: IStaticConfig, - private $logger: ILogger) { + private $logger: ILogger, + private $injector: IInjector) { } public getNSValue(projectDir: string, propertyName: string): any { @@ -31,6 +33,14 @@ export class ProjectDataService implements IProjectDataService { this.$fs.writeJson(projectFileInfo.projectFilePath, projectFileInfo.projectData); } + // TODO: Add tests + // TODO: Remove $projectData and replace it with $projectDataService.getProjectData + public getProjectData(projectDir: string): IProjectData { + const projectDataInstance = this.$injector.resolve(ProjectData); + projectDataInstance.initializeProjectData(projectDir); + return projectDataInstance; + } + private getValue(projectDir: string, propertyName: string): any { const projectData = this.getProjectFileData(projectDir).projectData; diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index 838fab565a..d97e45f6bb 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -71,7 +71,8 @@ class TestExecutionService implements ITestExecutionService { teamId: this.$options.teamId }; await this.$platformService.deployPlatform(platform, appFilesUpdaterOptions, deployOptions, projectData, this.$options); - await this.$usbLiveSyncService.liveSync(platform, projectData); + // TODO: Fix + await this.$usbLiveSyncService.liveSync(platform, projectData, null, this.$options); if (this.$options.debugBrk) { this.$logger.info('Starting debugger...'); @@ -146,7 +147,8 @@ class TestExecutionService implements ITestExecutionService { await debugService.debug(debugData, this.$options); } else { await this.$platformService.deployPlatform(platform, appFilesUpdaterOptions, deployOptions, projectData, this.$options); - await this.$usbLiveSyncService.liveSync(platform, projectData); + // TODO: Fix + await this.$usbLiveSyncService.liveSync(platform, projectData, null, this.$options); } }; diff --git a/package.json b/package.json index f9863dbf39..cfe922ebd3 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "bufferpack": "0.0.6", "byline": "4.2.1", "chalk": "1.1.0", - "chokidar": "^1.6.1", + "chokidar": "1.7.0", "cli-table": "https://github.com/telerik/cli-table/tarball/v0.3.1.2", "clui": "0.3.1", "colors": "1.1.2", @@ -83,6 +83,7 @@ "devDependencies": { "@types/chai": "3.4.34", "@types/chai-as-promised": "0.0.29", + "@types/chokidar": "1.6.0", "@types/lodash": "4.14.50", "@types/node": "6.0.61", "@types/qr-image": "3.2.0", diff --git a/test/services/project-data-service.ts b/test/services/project-data-service.ts index f68c29d449..71f08eff31 100644 --- a/test/services/project-data-service.ts +++ b/test/services/project-data-service.ts @@ -53,6 +53,8 @@ const createTestInjector = (readTextData?: string): IInjector => { testInjector.register("projectDataService", ProjectDataService); + testInjector.register("injector", testInjector); + return testInjector; }; diff --git a/test/stubs.ts b/test/stubs.ts index 213682c88e..d99f116e21 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -376,6 +376,8 @@ export class ProjectDataService implements IProjectDataService { removeNSProperty(propertyName: string): void { } removeDependency(dependencyName: string): void { } + + getProjectData(projectDir: string): IProjectData { return null; } } export class ProjectHelperStub implements IProjectHelper { From a0f037e90f84dcb00fca99cc0a1d48eb8d859b29 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Mon, 5 Jun 2017 02:25:25 +0300 Subject: [PATCH 069/212] WIP --- lib/bootstrap.ts | 2 + lib/commands/debug.ts | 173 +++++++++++++++--- lib/commands/run.ts | 125 +++++++++---- lib/declarations.d.ts | 125 ++++++++++++- .../android-device-livesync-service.ts | 47 +++-- .../livesync/android-livesync-service.ts | 44 +++-- .../livesync/ios-device-livesync-service.ts | 5 - lib/services/livesync/ios-livesync-service.ts | 48 +++-- lib/services/livesync/livesync-service.ts | 130 ++++++++----- lib/services/test-execution-service.ts | 88 ++++++++- test/debug.ts | 2 +- test/stubs.ts | 6 +- 12 files changed, 608 insertions(+), 187 deletions(-) diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index ef74955cd1..1a41da311a 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -39,6 +39,7 @@ $injector.requireCommand("platform|*list", "./commands/list-platforms"); $injector.requireCommand("platform|add", "./commands/add-platform"); $injector.requireCommand("platform|remove", "./commands/remove-platform"); $injector.requireCommand("platform|update", "./commands/update-platform"); +$injector.requireCommand("run|*all", "./commands/run"); $injector.requireCommand("run|ios", "./commands/run"); $injector.requireCommand("run|android", "./commands/run"); @@ -102,6 +103,7 @@ $injector.require("androidToolsInfo", "./android-tools-info"); $injector.requireCommand("platform|clean", "./commands/platform-clean"); +$injector.require("liveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript $injector.require("usbLiveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript $injector.require("iosLiveSyncServiceLocator", "./services/livesync/ios-device-livesync-service"); $injector.require("androidLiveSyncServiceLocator", "./services/livesync/android-device-livesync-service"); diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 2c84580059..51f3e5f0c3 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -1,13 +1,83 @@ import { EOL } from "os"; +import { LiveSyncService } from "../services/livesync/livesync-service"; +export class DebugLiveSyncService extends LiveSyncService { + constructor(protected $platformService: IPlatformService, + $projectDataService: IProjectDataService, + protected $devicesService: Mobile.IDevicesService, + $mobileHelper: Mobile.IMobileHelper, + $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, + protected $logger: ILogger, + $processService: IProcessService, + $hooksService: IHooksService, + $projectChangesService: IProjectChangesService, + protected $injector: IInjector, + private $options: IOptions, + private $debugDataService: IDebugDataService, + private $projectData: IProjectData, + private debugService: IPlatformDebugService, + private $config: IConfiguration) { + + super($platformService, + $projectDataService, + $devicesService, + $mobileHelper, + $nodeModulesDependenciesBuilder, + $logger, + $processService, + $hooksService, + $projectChangesService, + $injector); + } + + protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo): Promise { + const debugOptions = this.$options; + const deployOptions: IDeployPlatformOptions = { + clean: this.$options.clean, + device: this.$options.device, + emulator: this.$options.emulator, + platformTemplate: this.$options.platformTemplate, + projectDir: this.$options.path, + release: this.$options.release, + provision: this.$options.provision, + teamId: this.$options.teamId + }; + + let debugData = this.$debugDataService.createDebugData(this.$projectData, this.$options); + + await this.$platformService.trackProjectType(this.$projectData); + + if (this.$options.start) { + return this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); + } + + const deviceAppData = liveSyncResultInfo.deviceAppData; + this.$config.debugLivesync = true; + + await this.debugService.debugStop(); + + let applicationId = deviceAppData.appIdentifier; + await deviceAppData.device.applicationManager.stopApplication(applicationId, projectData.projectName); + + const buildConfig: IBuildConfig = _.merge({ buildForDevice: !deviceAppData.device.isEmulator }, deployOptions); + debugData.pathToAppPackage = this.$platformService.lastOutputPath(this.debugService.platform, buildConfig, projectData); + + this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); + } + + protected printDebugInformation(information: string[]): void { + _.each(information, i => { + this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${i}${EOL}`.cyan); + }); + } +} export abstract class DebugPlatformCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; + public platform: string; constructor(private debugService: IPlatformDebugService, private $devicesService: Mobile.IDevicesService, private $injector: IInjector, - private $config: IConfiguration, - private $usbLiveSyncService: ILiveSyncService, private $debugDataService: IDebugDataService, protected $platformService: IPlatformService, protected $projectData: IProjectData, @@ -19,16 +89,16 @@ export abstract class DebugPlatformCommand implements ICommand { public async execute(args: string[]): Promise { const debugOptions = this.$options; - const deployOptions: IDeployPlatformOptions = { - clean: this.$options.clean, - device: this.$options.device, - emulator: this.$options.emulator, - platformTemplate: this.$options.platformTemplate, - projectDir: this.$options.path, - release: this.$options.release, - provision: this.$options.provision, - teamId: this.$options.teamId - }; + // const deployOptions: IDeployPlatformOptions = { + // clean: this.$options.clean, + // device: this.$options.device, + // emulator: this.$options.emulator, + // platformTemplate: this.$options.platformTemplate, + // projectDir: this.$options.path, + // release: this.$options.release, + // provision: this.$options.provision, + // teamId: this.$options.teamId + // }; let debugData = this.$debugDataService.createDebugData(this.$projectData, this.$options); @@ -38,26 +108,67 @@ export abstract class DebugPlatformCommand implements ICommand { return this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); } - const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; + // const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; - await this.$platformService.deployPlatform(this.$devicesService.platform, appFilesUpdaterOptions, deployOptions, this.$projectData, this.$options); - this.$config.debugLivesync = true; - let applicationReloadAction = async (deviceAppData: Mobile.IDeviceAppData): Promise => { - let projectData: IProjectData = this.$injector.resolve("projectData"); + // await this.$platformService.deployPlatform(this.$devicesService.platform, appFilesUpdaterOptions, deployOptions, this.$projectData, this.$options); + // this.$config.debugLivesync = true; + // let applicationReloadAction = async (deviceAppData: Mobile.IDeviceAppData): Promise => { + // let projectData: IProjectData = this.$injector.resolve("projectData"); - await this.debugService.debugStop(); + // await this.debugService.debugStop(); - let applicationId = deviceAppData.appIdentifier; - await deviceAppData.device.applicationManager.stopApplication(applicationId, projectData.projectName); + // let applicationId = deviceAppData.appIdentifier; + // await deviceAppData.device.applicationManager.stopApplication(applicationId, projectData.projectName); - const buildConfig: IBuildConfig = _.merge({ buildForDevice: !deviceAppData.device.isEmulator }, deployOptions); - debugData.pathToAppPackage = this.$platformService.lastOutputPath(this.debugService.platform, buildConfig, projectData); + // const buildConfig: IBuildConfig = _.merge({ buildForDevice: !deviceAppData.device.isEmulator }, deployOptions); + // debugData.pathToAppPackage = this.$platformService.lastOutputPath(this.debugService.platform, buildConfig, projectData); - this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); - }; + // this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); + // }; // TODO: Fix this call - return this.$usbLiveSyncService.liveSync(this.$devicesService.platform, this.$projectData, applicationReloadAction, this.$options); + await this.$devicesService.initialize({ deviceId: this.$options.device, platform: this.platform, skipDeviceDetectionInterval: true, skipInferPlatform: true }); + await this.$devicesService.detectCurrentlyAttachedDevices(); + + const devices = this.$devicesService.getDeviceInstances(); + // Now let's take data for each device: + const deviceDescriptors: ILiveSyncDeviceInfo[] = devices.filter(d => !this.platform || d.deviceInfo.platform === this.platform) + .map(d => { + const info: ILiveSyncDeviceInfo = { + identifier: d.deviceInfo.identifier, + buildAction: async (): Promise => { + const buildConfig: IBuildConfig = { + buildForDevice: !d.isEmulator, // this.$options.forDevice, + projectDir: this.$options.path, + clean: this.$options.clean, + teamId: this.$options.teamId, + device: this.$options.device, + provision: this.$options.provision, + release: this.$options.release, + keyStoreAlias: this.$options.keyStoreAlias, + keyStorePath: this.$options.keyStorePath, + keyStoreAliasPassword: this.$options.keyStoreAliasPassword, + keyStorePassword: this.$options.keyStorePassword + }; + + await this.$platformService.buildPlatform(d.deviceInfo.platform, buildConfig, this.$projectData); + const pathToBuildResult = await this.$platformService.lastOutputPath(d.deviceInfo.platform, buildConfig, this.$projectData); + console.log("3##### return path to buildResult = ", pathToBuildResult); + return pathToBuildResult; + } + } + + return info; + }); + + const liveSyncInfo: ILiveSyncInfo = { + projectDir: this.$projectData.projectDir, + shouldStartWatcher: this.$options.watch, + syncAllFiles: this.$options.syncAllFiles + }; + + const debugLiveSyncService = this.$injector.resolve(DebugLiveSyncService, { debugService: this.debugService }); + await debugLiveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } public async canExecute(args: string[]): Promise { @@ -92,14 +203,14 @@ export class DebugIOSCommand extends DebugPlatformCommand { $devicesService: Mobile.IDevicesService, $injector: IInjector, $config: IConfiguration, - $usbLiveSyncService: ILiveSyncService, + $liveSyncService: ILiveSyncService, $debugDataService: IDebugDataService, $platformService: IPlatformService, $options: IOptions, $projectData: IProjectData, $platformsData: IPlatformsData, $iosDeviceOperations: IIOSDeviceOperations) { - super($iOSDebugService, $devicesService, $injector, $config, $usbLiveSyncService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger); + super($iOSDebugService, $devicesService, $injector, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger); // Do not dispose ios-device-lib, so the process will remain alive and the debug application (NativeScript Inspector or Chrome DevTools) will be able to connect to the socket. // In case we dispose ios-device-lib, the socket will be closed and the code will fail when the debug application tries to read/send data to device socket. // That's why the `$ tns debug ios --justlaunch` command will not release the terminal. @@ -120,6 +231,8 @@ export class DebugIOSCommand extends DebugPlatformCommand { super.printDebugInformation(information); } } + + public platform = "iOS"; } $injector.registerCommand("debug|ios", DebugIOSCommand); @@ -132,13 +245,13 @@ export class DebugAndroidCommand extends DebugPlatformCommand { $devicesService: Mobile.IDevicesService, $injector: IInjector, $config: IConfiguration, - $usbLiveSyncService: ILiveSyncService, + $liveSyncService: ILiveSyncService, $debugDataService: IDebugDataService, $platformService: IPlatformService, $options: IOptions, $projectData: IProjectData, $platformsData: IPlatformsData) { - super($androidDebugService, $devicesService, $injector, $config, $usbLiveSyncService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger); + super($androidDebugService, $devicesService, $injector, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger); } public async canExecute(args: string[]): Promise { @@ -148,6 +261,8 @@ export class DebugAndroidCommand extends DebugPlatformCommand { return await super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.Android); } + + public platform = "Android"; } $injector.registerCommand("debug|android", DebugAndroidCommand); diff --git a/lib/commands/run.ts b/lib/commands/run.ts index a8800eb710..a7ebd99807 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -1,64 +1,104 @@ -export class RunCommandBase { +export class RunCommandBase implements ICommand { + protected platform: string; + constructor(protected $platformService: IPlatformService, - protected $usbLiveSyncService: ILiveSyncService, + protected $liveSyncService: ILiveSyncService, protected $projectData: IProjectData, protected $options: IOptions, - protected $emulatorPlatformService: IEmulatorPlatformService) { + protected $emulatorPlatformService: IEmulatorPlatformService, + protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + private $devicesService: Mobile.IDevicesService, + private $hostInfo: IHostInfo) { this.$projectData.initializeProjectData(); } - public async executeCore(args: string[]): Promise { + public allowedParameters: ICommandParameter[] = [ ]; + public async execute(args: string[]): Promise { + return this.executeCore(args); + } - const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; - const deployOptions: IDeployPlatformOptions = { - clean: this.$options.clean, - device: this.$options.device, - emulator: this.$options.emulator, - projectDir: this.$options.path, - platformTemplate: this.$options.platformTemplate, - release: this.$options.release, - provision: this.$options.provision, - teamId: this.$options.teamId, - keyStoreAlias: this.$options.keyStoreAlias, - keyStoreAliasPassword: this.$options.keyStoreAliasPassword, - keyStorePassword: this.$options.keyStorePassword, - keyStorePath: this.$options.keyStorePath - }; - - await this.$platformService.deployPlatform(args[0], appFilesUpdaterOptions, deployOptions, this.$projectData, this.$options); + public async canExecute(args: string[]): Promise { + if (!this.platform && !this.$hostInfo.isDarwin) { + this.platform = this.$devicePlatformsConstants.Android; + } + return true; + } + + public async executeCore(args: string[]): Promise { if (this.$options.bundle) { this.$options.watch = false; } - if (this.$options.release) { - const deployOpts: IRunPlatformOptions = { - device: this.$options.device, - emulator: this.$options.emulator, - justlaunch: this.$options.justlaunch, - }; - - await this.$platformService.startApplication(args[0], deployOpts, this.$projectData.projectId); - return this.$platformService.trackProjectType(this.$projectData); - } + await this.$devicesService.initialize({ deviceId: this.$options.device, platform: this.platform, skipDeviceDetectionInterval: true, skipInferPlatform: true }); + await this.$devicesService.detectCurrentlyAttachedDevices(); + + const devices = this.$devicesService.getDeviceInstances(); + // Now let's take data for each device: + const deviceDescriptors: ILiveSyncDeviceInfo[] = devices.filter(d => !this.platform || d.deviceInfo.platform === this.platform) + .map(d => { + const info: ILiveSyncDeviceInfo = { + identifier: d.deviceInfo.identifier, + buildAction: async (): Promise => { + const buildConfig: IBuildConfig = { + buildForDevice: !d.isEmulator, // this.$options.forDevice, + projectDir: this.$options.path, + clean: this.$options.clean, + teamId: this.$options.teamId, + device: this.$options.device, + provision: this.$options.provision, + release: this.$options.release, + keyStoreAlias: this.$options.keyStoreAlias, + keyStorePath: this.$options.keyStorePath, + keyStoreAliasPassword: this.$options.keyStoreAliasPassword, + keyStorePassword: this.$options.keyStorePassword + }; + + await this.$platformService.buildPlatform(d.deviceInfo.platform, buildConfig, this.$projectData); + const pathToBuildResult = await this.$platformService.lastOutputPath(d.deviceInfo.platform, buildConfig, this.$projectData); + console.log("3##### return path to buildResult = ", pathToBuildResult); + return pathToBuildResult; + } + } + + return info; + }); + + // if (this.$options.release) { + // const deployOpts: IRunPlatformOptions = { + // device: this.$options.device, + // emulator: this.$options.emulator, + // justlaunch: this.$options.justlaunch, + // }; + + // await this.$platformService.startApplication(args[0], deployOpts, this.$projectData.projectId); + // return this.$platformService.trackProjectType(this.$projectData); + // } // TODO: Fix this call - return this.$usbLiveSyncService.liveSync(args[0], this.$projectData, null, this.$options); + const liveSyncInfo: ILiveSyncInfo = { projectDir: this.$projectData.projectDir, shouldStartWatcher: this.$options.watch, syncAllFiles: this.$options.syncAllFiles }; + await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } } +$injector.registerCommand("run|*all", RunCommandBase); export class RunIosCommand extends RunCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; + public get platform(): string { + return this.$devicePlatformsConstants.iOS; + } constructor($platformService: IPlatformService, private $platformsData: IPlatformsData, - private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $errors: IErrors, - $usbLiveSyncService: ILiveSyncService, + $liveSyncService: ILiveSyncService, $projectData: IProjectData, $options: IOptions, - $emulatorPlatformService: IEmulatorPlatformService) { - super($platformService, $usbLiveSyncService, $projectData, $options, $emulatorPlatformService); + $emulatorPlatformService: IEmulatorPlatformService, + $devicesService: Mobile.IDevicesService, + $hostInfo: IHostInfo) { + super($platformService, $liveSyncService, $projectData, $options, $emulatorPlatformService, $devicePlatformsConstants, $devicesService, $hostInfo); } public async execute(args: string[]): Promise { @@ -78,16 +118,21 @@ $injector.registerCommand("run|ios", RunIosCommand); export class RunAndroidCommand extends RunCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; + public get platform(): string { + return this.$devicePlatformsConstants.Android; + } constructor($platformService: IPlatformService, private $platformsData: IPlatformsData, - private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $errors: IErrors, - $usbLiveSyncService: ILiveSyncService, + $liveSyncService: ILiveSyncService, $projectData: IProjectData, $options: IOptions, - $emulatorPlatformService: IEmulatorPlatformService) { - super($platformService, $usbLiveSyncService, $projectData, $options, $emulatorPlatformService); + $emulatorPlatformService: IEmulatorPlatformService, + $devicesService: Mobile.IDevicesService, + $hostInfo: IHostInfo) { + super($platformService, $liveSyncService, $projectData, $options, $emulatorPlatformService, $devicePlatformsConstants, $devicesService, $hostInfo); } public async execute(args: string[]): Promise { diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 50316cd3d1..e508c46c5a 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -257,9 +257,123 @@ interface IOpener { open(target: string, appname: string): void; } -// TODO: Fix +interface IFSWatcher extends NodeJS.EventEmitter { + // from fs.FSWatcher + close(): void; + + /** + * events.EventEmitter + * 1. change + * 2. error + */ + addListener(event: string, listener: Function): this; + addListener(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; + addListener(event: "error", listener: (code: number, signal: string) => void): this; + + on(event: string, listener: Function): this; + on(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; + on(event: "error", listener: (code: number, signal: string) => void): this; + + once(event: string, listener: Function): this; + once(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; + once(event: "error", listener: (code: number, signal: string) => void): this; + + prependListener(event: string, listener: Function): this; + prependListener(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; + prependListener(event: "error", listener: (code: number, signal: string) => void): this; + + prependOnceListener(event: string, listener: Function): this; + prependOnceListener(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; + prependOnceListener(event: "error", listener: (code: number, signal: string) => void): this; + + // From chokidar FSWatcher + + /** + * Add files, directories, or glob patterns for tracking. Takes an array of strings or just one + * string. + */ + add(paths: string | string[]): void; + + /** + * Stop watching files, directories, or glob patterns. Takes an array of strings or just one + * string. + */ + unwatch(paths: string | string[]): void; + + /** + * Returns an object representing all the paths on the file system being watched by this + * `FSWatcher` instance. The object's keys are all the directories (using absolute paths unless + * the `cwd` option was used), and the values are arrays of the names of the items contained in + * each directory. + */ + getWatched(): IDictionary; + + /** + * Removes all listeners from watched files. + */ + close(): void; +} + +interface ILiveSyncProcessInfo { + timer: NodeJS.Timer; + watcher: IFSWatcher; + actionsChain: Promise; + isStopped: boolean; +} + +interface ILiveSyncDeviceInfo { + identifier: string; + buildAction: () => Promise; + outputPath?: string; +} + +interface ILiveSyncInfo { + projectDir: string; + shouldStartWatcher: boolean; + syncAllFiles?: boolean; +} + +interface ILiveSyncBuildInfo { + platform: string; + isEmulator: boolean; + pathToBuildItem: string; +} + +// interface ILiveSyncDebugInfo { +// outputFilePath?: string; +// debugData: IDebugData; +// debugOptions: IDebugOptions; +// } + interface ILiveSyncService { - liveSync(platform: string, projectData: IProjectData, applicationReloadAction?: (deviceAppData: Mobile.IDeviceAppData) => Promise, options?: IOptions): Promise; + liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise; + stopLiveSync(projectDir: string): Promise; +} + +interface ILiveSyncWatchInfo { + projectData: IProjectData; + filesToRemove: string[]; + filesToSync: string[]; + isRebuilt: boolean; + syncAllFiles: boolean; +} + +interface ILiveSyncResultInfo { + modifiedFilesData: Mobile.ILocalToDevicePathData[]; + isFullSync: boolean; + deviceAppData: Mobile.IDeviceAppData; +} + +interface IFullSyncInfo { + projectData: IProjectData; + device: Mobile.IDevice; + syncAllFiles: boolean; +} + +interface IPlatformLiveSyncService { + fullSync(syncInfo: IFullSyncInfo): Promise; + liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise; + refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise; } interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase { @@ -271,7 +385,11 @@ interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase * @param {IProjectData} projectData Project data. * @return {Promise} */ - refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], forceExecuteFullSync: boolean, projectData: IProjectData): Promise; + refreshApplication(deviceAppData: Mobile.IDeviceAppData, + localToDevicePaths: Mobile.ILocalToDevicePathData[], + forceExecuteFullSync: boolean, + projectData: IProjectData): Promise; + /** * Removes specified files from a connected device * @param {string} appIdentifier Application identifier. @@ -280,7 +398,6 @@ interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase * @return {Promise} */ removeFiles(appIdentifier: string, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectId: string): Promise; - afterInstallApplicationAction?(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectId: string): Promise; } interface IBundle { diff --git a/lib/services/livesync/android-device-livesync-service.ts b/lib/services/livesync/android-device-livesync-service.ts index 10715c4009..3fc810e0d2 100644 --- a/lib/services/livesync/android-device-livesync-service.ts +++ b/lib/services/livesync/android-device-livesync-service.ts @@ -1,11 +1,14 @@ import { DeviceAndroidDebugBridge } from "../../common/mobile/android/device-android-debug-bridge"; import { AndroidDeviceHashService } from "../../common/mobile/android/android-device-hash-service"; import * as helpers from "../../common/helpers"; +import { cache } from "../../common/decorators"; import * as path from "path"; import * as net from "net"; import { EOL } from "os"; export class AndroidLiveSyncService implements INativeScriptDeviceLiveSyncService { + private static FAST_SYNC_FILE_EXTENSIONS = [".css", ".xml", ".html"]; + private static BACKEND_PORT = 18182; private device: Mobile.IAndroidDevice; @@ -14,7 +17,7 @@ export class AndroidLiveSyncService implements INativeScriptDeviceLiveSyncServic private $injector: IInjector, private $logger: ILogger, private $androidDebugService: IPlatformDebugService, - private $liveSyncProvider: ILiveSyncProvider) { + private $platformsData: IPlatformsData) { this.device = (_device); } @@ -25,34 +28,18 @@ export class AndroidLiveSyncService implements INativeScriptDeviceLiveSyncServic public async refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], forceExecuteFullSync: boolean, - projectData: IProjectData, - outputFilePath?: string, - debugData?: IDebugData, - debugOptions?: IOptions): Promise { - - if (debugData) { - // TODO: Move this outside of here - await this.debugService.debugStop(); - - let applicationId = deviceAppData.appIdentifier; - await deviceAppData.device.applicationManager.stopApplication(applicationId, projectData.projectName); - - debugData.pathToAppPackage = outputFilePath; - const debugInfo = await this.debugService.debug(debugData, debugOptions); - this.printDebugInformation(debugInfo); - - return; - } + projectData: IProjectData): Promise { await this.device.adb.executeShellCommand( ["chmod", "777", - await deviceAppData.getDeviceProjectRootPath(), + "/data/local/tmp/", `/data/local/tmp/${deviceAppData.appIdentifier}`, `/data/local/tmp/${deviceAppData.appIdentifier}/sync`] ); - let canExecuteFastSync = !forceExecuteFullSync && !_.some(localToDevicePaths, (localToDevicePath: any) => !this.$liveSyncProvider.canExecuteFastSync(localToDevicePath.getLocalPath(), projectData, deviceAppData.platform)); + let canExecuteFastSync = !forceExecuteFullSync && !_.some(localToDevicePaths, + (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.canExecuteFastSync(localToDevicePath.getLocalPath(), projectData, this.device.deviceInfo.platform)); if (canExecuteFastSync) { return this.reloadPage(deviceAppData, localToDevicePaths); @@ -61,6 +48,19 @@ export class AndroidLiveSyncService implements INativeScriptDeviceLiveSyncServic return this.restartApplication(deviceAppData); } + @cache() + private getFastLiveSyncFileExtensions(platform: string, projectData: IProjectData): string[] { + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const fastSyncFileExtensions = AndroidLiveSyncService.FAST_SYNC_FILE_EXTENSIONS.concat(platformData.fastLivesyncFileExtensions); + return fastSyncFileExtensions; + } + + public canExecuteFastSync(filePath: string, projectData: IProjectData, platform: string): boolean { + console.log("called canExecuteFastSync for file: ", filePath); + const fastSyncFileExtensions = this.getFastLiveSyncFileExtensions(platform, projectData); + return _.includes(fastSyncFileExtensions, path.extname(filePath)); + } + protected printDebugInformation(information: string[]): void { _.each(information, i => { this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${i}${EOL}`.cyan); @@ -112,11 +112,6 @@ export class AndroidLiveSyncService implements INativeScriptDeviceLiveSyncServic await this.getDeviceHashService(projectId).removeHashes(localToDevicePaths); } - public async afterInstallApplicationAction(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectId: string): Promise { - await this.getDeviceHashService(projectId).uploadHashFileToDevice(localToDevicePaths); - return false; - } - private getDeviceRootPath(appIdentifier: string): string { return `/data/local/tmp/${appIdentifier}`; } diff --git a/lib/services/livesync/android-livesync-service.ts b/lib/services/livesync/android-livesync-service.ts index f64ee5c495..2e3e4241ac 100644 --- a/lib/services/livesync/android-livesync-service.ts +++ b/lib/services/livesync/android-livesync-service.ts @@ -2,7 +2,7 @@ import * as deviceAppDataIdentifiers from "../../providers/device-app-data-provi import * as path from "path"; import * as adls from "./android-device-livesync-service"; -export class AndroidLiveSyncService { +export class AndroidLiveSyncService implements IPlatformLiveSyncService { constructor(private $projectFilesManager: IProjectFilesManager, private $platformsData: IPlatformsData, private $logger: ILogger, @@ -11,7 +11,9 @@ export class AndroidLiveSyncService { private $injector: IInjector) { } - public async fullSync(projectData: IProjectData, device: Mobile.IDevice): Promise { + public async fullSync(syncInfo: IFullSyncInfo): Promise { + const projectData = syncInfo.projectData; + const device = syncInfo.device; const deviceLiveSyncService = this.$injector.resolve(adls.AndroidLiveSyncService, { _device: device }); const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); const deviceAppData = this.$injector.resolve(deviceAppDataIdentifiers.AndroidAppIdentifier, @@ -22,12 +24,18 @@ export class AndroidLiveSyncService { const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, "app"); const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, null, []); await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, true); + + return { + modifiedFilesData: localToDevicePaths, + isFullSync: true, + deviceAppData + }; } - public async liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: { projectData: IProjectData, filesToRemove: string[], filesToSync: string[], isRebuilt: boolean }): Promise { + public async liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise { const projectData = liveSyncInfo.projectData; const deviceAppData = this.$injector.resolve(deviceAppDataIdentifiers.AndroidAppIdentifier, - { _appIdentifier: projectData.projectId, device, platform: device.deviceInfo.platform }); + { _appIdentifier: projectData.projectId, device, platform: device.deviceInfo.platform }); let modifiedLocalToDevicePaths: Mobile.ILocalToDevicePathData[] = []; if (liveSyncInfo.filesToSync.length) { @@ -61,22 +69,28 @@ export class AndroidLiveSyncService { let localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, mappedFiles, []); modifiedLocalToDevicePaths.push(...localToDevicePaths); - const deviceLiveSyncService = this.$injector.resolve(adls.AndroidLiveSyncService, { _device: device }); + const deviceLiveSyncService = this.$injector.resolve(adls.AndroidLiveSyncService, { _device: device }); deviceLiveSyncService.removeFiles(projectData.projectId, localToDevicePaths, projectData.projectId); } - if (liveSyncInfo.isRebuilt) { - // After application is rebuilt, we should just start it - await device.applicationManager.restartApplication(liveSyncInfo.projectData.projectId, liveSyncInfo.projectData.projectName); - } else if (modifiedLocalToDevicePaths) { - await this.refreshApplication(deviceAppData, modifiedLocalToDevicePaths, false, projectData); - } + return { + modifiedFilesData: modifiedLocalToDevicePaths, + isFullSync: liveSyncInfo.isRebuilt, + deviceAppData + }; } - public async refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], isFullSync: boolean, projectData: IProjectData): Promise { - let deviceLiveSyncService = this.$injector.resolve(adls.AndroidLiveSyncService, { _device: deviceAppData.device }); - this.$logger.info("Refreshing application..."); - await deviceLiveSyncService.refreshApplication(deviceAppData, localToDevicePaths, isFullSync, projectData); + public async refreshApplication( + projectData: IProjectData, + liveSyncInfo: ILiveSyncResultInfo + ): Promise { + if (liveSyncInfo.isFullSync || liveSyncInfo.modifiedFilesData.length) { + // const deviceAppData = this.$injector.resolve(deviceAppDataIdentifiers.AndroidAppIdentifier, + // { _appIdentifier: projectData.projectId, device, platform: device.deviceInfo.platform }); + let deviceLiveSyncService = this.$injector.resolve(adls.AndroidLiveSyncService, { _device: liveSyncInfo.deviceAppData.device }); + this.$logger.info("Refreshing application..."); + await deviceLiveSyncService.refreshApplication(liveSyncInfo.deviceAppData, liveSyncInfo.modifiedFilesData, liveSyncInfo.isFullSync, projectData); + } } protected async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, canTransferDirectory: boolean): Promise { diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index 24a4c0c5c3..ed96efabe4 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -15,7 +15,6 @@ export class IOSLiveSyncService implements INativeScriptDeviceLiveSyncService { private $iOSNotification: IiOSNotification, private $iOSEmulatorServices: Mobile.IiOSSimulatorService, private $logger: ILogger, - private $options: IOptions, private $iOSDebugService: IDebugService, private $fs: IFileSystem, private $liveSyncProvider: ILiveSyncProvider, @@ -28,10 +27,6 @@ export class IOSLiveSyncService implements INativeScriptDeviceLiveSyncService { return this.$iOSDebugService; } - public async afterInstallApplicationAction(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { - return this.$options.watch; - } - private async setupSocketIfNeeded(projectId: string): Promise { if (this.socket) { return true; diff --git a/lib/services/livesync/ios-livesync-service.ts b/lib/services/livesync/ios-livesync-service.ts index 2c1e1987df..8297b267c2 100644 --- a/lib/services/livesync/ios-livesync-service.ts +++ b/lib/services/livesync/ios-livesync-service.ts @@ -4,10 +4,9 @@ import * as iosdls from "./ios-device-livesync-service"; import * as temp from "temp"; // import * as uuid from "uuid"; -export class IOSLiveSyncService { +export class IOSLiveSyncService implements IPlatformLiveSyncService { constructor( private $devicesService: Mobile.IDevicesService, - private $options: IOptions, private $projectFilesManager: IProjectFilesManager, private $platformsData: IPlatformsData, private $logger: ILogger, @@ -16,7 +15,15 @@ export class IOSLiveSyncService { private $injector: IInjector) { } - public async fullSync(projectData: IProjectData, device: Mobile.IDevice): Promise { + /* + fullSync(projectData: IProjectData, device: Mobile.IDevice): Promise; + liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise; + refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise; + */ + + public async fullSync(syncInfo: IFullSyncInfo): Promise { + const projectData = syncInfo.projectData; + const device = syncInfo.device; const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); const deviceAppData = this.$injector.resolve(deviceAppDataIdentifiers.IOSAppIdentifier, { _appIdentifier: projectData.projectId, device, platform: device.deviceInfo.platform }); @@ -25,6 +32,11 @@ export class IOSLiveSyncService { if (device.isEmulator) { const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, null, []); await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, true); + return { + deviceAppData, + isFullSync: true, + modifiedFilesData: localToDevicePaths + }; } else { temp.track(); let tempZip = temp.path({ prefix: "sync", suffix: ".zip" }); @@ -32,7 +44,7 @@ export class IOSLiveSyncService { this.$logger.trace("Creating zip file: " + tempZip); this.$fs.copyFile(path.join(path.dirname(projectFilesPath), "app/*"), tempApp); - if (!this.$options.syncAllFiles) { + if (!syncInfo.syncAllFiles) { this.$logger.info("Skipping node_modules folder! Use the syncAllFiles option to sync files from this folder."); this.$fs.deleteDirectory(path.join(tempApp, "tns_modules")); } @@ -47,10 +59,16 @@ export class IOSLiveSyncService { getRelativeToProjectBasePath: () => "../sync.zip", deviceProjectRootPath: await deviceAppData.getDeviceProjectRootPath() }]); + + return { + deviceAppData, + isFullSync: true, + modifiedFilesData: [] + }; } } - public async liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: { projectData: IProjectData, filesToRemove: string[], filesToSync: string[], isRebuilt: boolean }): Promise { + public async liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise { const projectData = liveSyncInfo.projectData; const deviceAppData = this.$injector.resolve(deviceAppDataIdentifiers.IOSAppIdentifier, { _appIdentifier: projectData.projectId, device, platform: device.deviceInfo.platform }); @@ -58,7 +76,7 @@ export class IOSLiveSyncService { if (liveSyncInfo.isRebuilt) { // In this case we should execute fullsync: - await this.fullSync(projectData, device); + await this.fullSync({ projectData, device, syncAllFiles: liveSyncInfo.syncAllFiles }); } else { if (liveSyncInfo.filesToSync.length) { const filesToSync = liveSyncInfo.filesToSync; @@ -96,19 +114,17 @@ export class IOSLiveSyncService { } } - if (liveSyncInfo.isRebuilt) { - // WHAT if we are in debug session? - // After application is rebuilt, we should just start it - await device.applicationManager.restartApplication(liveSyncInfo.projectData.projectId, liveSyncInfo.projectData.projectName); - } else if (modifiedLocalToDevicePaths) { - await this.refreshApplication(deviceAppData, modifiedLocalToDevicePaths, false, projectData); - } + return { + modifiedFilesData: modifiedLocalToDevicePaths, + isFullSync: liveSyncInfo.isRebuilt, + deviceAppData + }; } - public async refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], isFullSync: boolean, projectData: IProjectData): Promise { - let deviceLiveSyncService = this.$injector.resolve(iosdls.IOSLiveSyncService, { _device: deviceAppData.device }); + public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { + let deviceLiveSyncService = this.$injector.resolve(iosdls.IOSLiveSyncService, { _device: liveSyncInfo.deviceAppData.device }); this.$logger.info("Refreshing application..."); - await deviceLiveSyncService.refreshApplication(deviceAppData, localToDevicePaths, isFullSync, projectData); + await deviceLiveSyncService.refreshApplication(liveSyncInfo.deviceAppData, liveSyncInfo.modifiedFilesData, liveSyncInfo.isFullSync, projectData); } protected async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise { diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 0ac3bcc54c..7d0855de38 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -6,34 +6,47 @@ import { EventEmitter } from "events"; import { exported } from "../../common/decorators"; import { hook } from "../../common/helpers"; +const LiveSyncEvents = { + liveSyncStarted: "liveSyncStarted", + liveSyncStopped: "liveSyncStopped", + liveSyncError: "liveSyncError", // Do we need this or we can use liveSyncStopped event? + liveSyncWatcherStarted: "liveSyncWatcherStarted", + liveSyncWatcherStopped: "liveSyncWatcherStopped", + liveSyncFileChangedEvent: "liveSyncFileChangedEvent", + liveSyncOperationStartingEvent: "liveSyncOperationStartedEvent" +}; + // TODO: emit events for "successfull livesync", "stoppedLivesync", -export class LiveSyncService extends EventEmitter { - constructor(private $platformService: IPlatformService, +export class LiveSyncService extends EventEmitter implements ILiveSyncService { + // key is projectDir + private liveSyncProcessesInfo: IDictionary = {}; + + constructor(protected $platformService: IPlatformService, private $projectDataService: IProjectDataService, - private $devicesService: Mobile.IDevicesService, + protected $devicesService: Mobile.IDevicesService, private $mobileHelper: Mobile.IMobileHelper, private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, - private $logger: ILogger, + protected $logger: ILogger, private $processService: IProcessService, private $hooksService: IHooksService, private $projectChangesService: IProjectChangesService, - private $injector: IInjector) { + protected $injector: IInjector) { super(); } // TODO: Add finishLivesync method in the platform specific services @exported("liveSyncService") @hook("liveSync") - public async liveSync( - deviceDescriptors: { identifier: string, buildAction: () => Promise, outputPath?: string }[], - liveSyncData: { projectDir: string, shouldStartWatcher: boolean, syncAllFiles: boolean }): Promise { + public async liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], + liveSyncData: ILiveSyncInfo): Promise { + this.emit(LiveSyncEvents.liveSyncStarted, { projectDir: liveSyncData.projectDir, deviceIdentifiers: deviceDescriptors.map(dd => dd.identifier) }); // TODO: Initialize devicesService before that. const projectData = this.$projectDataService.getProjectData(liveSyncData.projectDir); await this.initialSync(projectData, deviceDescriptors, liveSyncData); // Should be set after prepare - this.$injector.resolve("usbLiveSyncService")._isInitialized = true; + this.$injector.resolve("usbLiveSyncService").isInitialized = true; if (liveSyncData.shouldStartWatcher) { await this.startWatcher(projectData, deviceDescriptors, liveSyncData); @@ -60,12 +73,13 @@ export class LiveSyncService extends EventEmitter { } } - private currentPromiseChain: Promise = Promise.resolve(); - - private liveSyncProcessesInfo: IDictionary<{ timer: NodeJS.Timer, watcher: choki.FSWatcher }> = {}; + protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo): Promise { + const platformLiveSyncService = this.getLiveSyncService(liveSyncResultInfo.deviceAppData.platform); + await platformLiveSyncService.refreshApplication(projectData, liveSyncResultInfo); + } // TODO: Register both livesync services in injector - private getLiveSyncService(platform: string): any { + private getLiveSyncService(platform: string): IPlatformLiveSyncService { if (this.$mobileHelper.isiOSPlatform(platform)) { return this.$injector.resolve(iOSLs.IOSLiveSyncService); } else if (this.$mobileHelper.isAndroidPlatform(platform)) { @@ -77,17 +91,19 @@ export class LiveSyncService extends EventEmitter { private async ensureLatestAppPackageIsInstalledOnDevice(device: Mobile.IDevice, preparedPlatforms: string[], - rebuiltInformation: any[], + rebuiltInformation: ILiveSyncBuildInfo[], projectData: IProjectData, - deviceBuildInfoDescriptor: { identifier: string, buildAction: () => Promise, outputPath?: string }, + deviceBuildInfoDescriptor: ILiveSyncDeviceInfo, modifiedFiles?: string[]): Promise { const platform = device.deviceInfo.platform; if (preparedPlatforms.indexOf(platform) === -1) { preparedPlatforms.push(platform); + // TODO: fix args cast to any await this.$platformService.preparePlatform(platform, {}, null, projectData, {}, modifiedFiles); } + // TODO: fix args cast to any const shouldBuild = await this.$platformService.shouldBuild(platform, projectData, { buildForDevice: !device.isEmulator }, deviceBuildInfoDescriptor.outputPath); if (shouldBuild) { const pathToBuildItem = await deviceBuildInfoDescriptor.buildAction(); @@ -102,13 +118,18 @@ export class LiveSyncService extends EventEmitter { // we'll rebuild the app only for the first device, but we should install new package on all three devices. await this.$platformService.installApplication(device, { release: false }, projectData, rebuildInfo.pathToBuildItem, deviceBuildInfoDescriptor.outputPath); } - } - private async initialSync(projectData: IProjectData, deviceDescriptors: { identifier: string, buildAction: () => Promise, outputPath?: string }[], - liveSyncData: { projectDir: string, shouldStartWatcher: boolean, syncAllFiles: boolean }): Promise { + const shouldInstall = await this.$platformService.shouldInstall(device, projectData, deviceBuildInfoDescriptor.outputPath); + if (shouldInstall) { + // device.applicationManager.installApplication() + console.log("TODO!!!!!!"); + // call platformService.installApplication here as well. + } + } + private async initialSync(projectData: IProjectData, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise { const preparedPlatforms: string[] = []; - const rebuiltInformation: { platform: string, isEmulator: boolean, pathToBuildItem: string }[] = []; + const rebuiltInformation: ILiveSyncBuildInfo[] = []; // Now fullSync const deviceAction = async (device: Mobile.IDevice): Promise => { @@ -117,17 +138,17 @@ export class LiveSyncService extends EventEmitter { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, projectData, deviceDescriptor); - await this.getLiveSyncService(platform).fullSync(projectData, device); - - await device.applicationManager.restartApplication(projectData.projectId, projectData.projectName); + const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ projectData, device, syncAllFiles: liveSyncData.syncAllFiles }); + await this.refreshApplication(projectData, liveSyncResultInfo); + //await device.applicationManager.restartApplication(projectData.projectId, projectData.projectName); }; await this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier)); } private async startWatcher(projectData: IProjectData, - deviceDescriptors: { identifier: string, buildAction: () => Promise, outputPath?: string }[], - liveSyncData: { projectDir: string, shouldStartWatcher: boolean, syncAllFiles: boolean }): Promise { + deviceDescriptors: ILiveSyncDeviceInfo[], + liveSyncData: ILiveSyncInfo): Promise { let pattern = ["app"]; @@ -157,7 +178,7 @@ export class LiveSyncService extends EventEmitter { const startTimeout = () => { timeoutTimer = setTimeout(async () => { - await this.addActionToQueue(async () => { + await this.addActionToQueue(projectData.projectDir, async () => { // TODO: Push consecutive actions to the queue, do not start them simultaneously if (filesToSync.length || filesToRemove.length) { try { @@ -171,7 +192,7 @@ export class LiveSyncService extends EventEmitter { console.log(this.$projectChangesService.currentChanges); const preparedPlatforms: string[] = []; - const rebuiltInformation: { platform: string, isEmulator: boolean, pathToBuildItem: string }[] = []; + const rebuiltInformation: ILiveSyncBuildInfo[] = []; await this.$devicesService.execute(async (device: Mobile.IDevice) => { // const platform = device.deviceInfo.platform; const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); @@ -180,19 +201,20 @@ export class LiveSyncService extends EventEmitter { projectData, deviceDescriptor, allModifiedFiles); const service = this.getLiveSyncService(device.deviceInfo.platform); - const settings: any = { + const settings: ILiveSyncWatchInfo = { projectData, filesToRemove: currentFilesToRemove, filesToSync: currentFilesToSync, - isRebuilt: !!_.find(rebuiltInformation, info => info.isEmulator === device.isEmulator && info.platform === device.deviceInfo.platform) + isRebuilt: !!_.find(rebuiltInformation, info => info.isEmulator === device.isEmulator && info.platform === device.deviceInfo.platform), + syncAllFiles: liveSyncData.syncAllFiles }; - await service.liveSyncWatchAction(device, settings); + const liveSyncResultInfo = await service.liveSyncWatchAction(device, settings); + await this.refreshApplication(projectData, liveSyncResultInfo); }, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier) ); } catch (err) { - // TODO: Decide if we should break here. // this.$logger.info(`Unable to sync file ${filePath}. Error is:${err.message}`.red.bold); this.$logger.info("Try saving it again or restart the livesync operation."); // we can remove the descriptor from action: @@ -222,6 +244,8 @@ export class LiveSyncService extends EventEmitter { ignored: ["**/.*", ".*"] // hidden files }; + this.emit(LiveSyncEvents.liveSyncWatcherStarted, { projectDir: liveSyncData.projectDir, deviceIdentifiers: deviceDescriptors.map(dd => dd.identifier), watcherOptions, }); + const watcher = choki.watch(pattern, watcherOptions) .on("all", async (event: string, filePath: string) => { clearTimeout(timeoutTimer); @@ -239,11 +263,17 @@ export class LiveSyncService extends EventEmitter { startTimeout(); }); - this.liveSyncProcessesInfo[liveSyncData.projectDir] = { - watcher, - timer: timeoutTimer + // TODO: Extract to separate method. + this.liveSyncProcessesInfo[liveSyncData.projectDir] = this.liveSyncProcessesInfo[liveSyncData.projectDir] || { + actionsChain: Promise.resolve() }; + this.liveSyncProcessesInfo[liveSyncData.projectDir].watcher = watcher; + this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; + this.liveSyncProcessesInfo[liveSyncData.projectDir].isStopped = false; + + this.liveSyncProcessesInfo[liveSyncData.projectDir].isStopped = false; + this.$processService.attachToProcessExitSignals(this, () => { _.keys(this.liveSyncProcessesInfo).forEach(projectDir => { // Do not await here, we are in process exit's handler. @@ -256,18 +286,34 @@ export class LiveSyncService extends EventEmitter { }); } - private async addActionToQueue(action: () => Promise): Promise { - this.currentPromiseChain = this.currentPromiseChain.then(async () => { - const res = await action(); - // console.log("after ", unique); - return res; - }); + private async addActionToQueue(projectDir: string, action: () => Promise): Promise { + const liveSyncInfo = this.liveSyncProcessesInfo[projectDir]; + if (liveSyncInfo) { + liveSyncInfo.actionsChain = liveSyncInfo.actionsChain.then(async () => { + if (!liveSyncInfo.isStopped) { + const res = await action(); + return res; + } + }); - const result = await this.currentPromiseChain; - return result; + const result = await liveSyncInfo.actionsChain; + return result; + } } } $injector.register("liveSyncService", LiveSyncService); -$injector.register("usbLiveSyncService", LiveSyncService); + +/** + * This class is used only for old versions of nativescript-dev-typescript plugin. + * It should be replaced with liveSyncService.isInitalized. + * Consider adding get and set methods for isInitialized, + * so whenever someone tries to access the value of isInitialized, + * they'll get a warning to update the plugins (like nativescript-dev-typescript). + */ +export class DeprecatedUsbLiveSyncService { + public isInitialized = false; +} + +$injector.register("usbLiveSyncService", DeprecatedUsbLiveSyncService); diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index d97e45f6bb..ead592a485 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -15,7 +15,7 @@ class TestExecutionService implements ITestExecutionService { constructor(private $injector: IInjector, private $platformService: IPlatformService, private $platformsData: IPlatformsData, - private $usbLiveSyncService: ILiveSyncService, + private $liveSyncService: ILiveSyncService, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $debugDataService: IDebugDataService, private $httpClient: Server.IHttpClient, @@ -42,6 +42,7 @@ class TestExecutionService implements ITestExecutionService { let platformData = this.$platformsData.getPlatformData(platform.toLowerCase(), projectData); let projectDir = projectData.projectDir; await this.$devicesService.initialize({ platform: platform, deviceId: this.$options.device }); + await this.$devicesService.detectCurrentlyAttachedDevices(); let projectFilesPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); let configOptions: IKarmaConfigOptions = JSON.parse(launcherConfig); @@ -53,8 +54,8 @@ class TestExecutionService implements ITestExecutionService { let socketIoJsUrl = `http://localhost:${this.$options.port}/socket.io/socket.io.js`; let socketIoJs = (await this.$httpClient.httpRequest(socketIoJsUrl)).body; this.$fs.writeFile(path.join(projectDir, TestExecutionService.SOCKETIO_JS_FILE_NAME), socketIoJs); - const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; + if (!await this.$platformService.preparePlatform(platform, appFilesUpdaterOptions, this.$options.platformTemplate, projectData, this.$options)) { this.$errors.failWithoutHelp("Verify that listed files are well-formed and try again the operation."); } @@ -63,16 +64,56 @@ class TestExecutionService implements ITestExecutionService { const deployOptions: IDeployPlatformOptions = { clean: this.$options.clean, device: this.$options.device, - projectDir: this.$options.path, emulator: this.$options.emulator, + projectDir: this.$options.path, platformTemplate: this.$options.platformTemplate, release: this.$options.release, provision: this.$options.provision, teamId: this.$options.teamId }; - await this.$platformService.deployPlatform(platform, appFilesUpdaterOptions, deployOptions, projectData, this.$options); + + if (this.$options.bundle) { + this.$options.watch = false; + } + + + + const devices = this.$devicesService.getDeviceInstances(); + // Now let's take data for each device: + const deviceDescriptors: ILiveSyncDeviceInfo[] = devices.filter(d => !this.platform || d.deviceInfo.platform === this.platform) + .map(d => { + const info: ILiveSyncDeviceInfo = { + identifier: d.deviceInfo.identifier, + buildAction: async (): Promise => { + const buildConfig: IBuildConfig = { + buildForDevice: !d.isEmulator, // this.$options.forDevice, + projectDir: this.$options.path, + clean: this.$options.clean, + teamId: this.$options.teamId, + device: this.$options.device, + provision: this.$options.provision, + release: this.$options.release, + keyStoreAlias: this.$options.keyStoreAlias, + keyStorePath: this.$options.keyStorePath, + keyStoreAliasPassword: this.$options.keyStoreAliasPassword, + keyStorePassword: this.$options.keyStorePassword + }; + + await this.$platformService.buildPlatform(d.deviceInfo.platform, buildConfig, projectData); + const pathToBuildResult = await this.$platformService.lastOutputPath(d.deviceInfo.platform, buildConfig, projectData); + console.log("3##### return path to buildResult = ", pathToBuildResult); + return pathToBuildResult; + } + } + + return info; + }); + + // TODO: Fix this call + const liveSyncInfo: ILiveSyncInfo = { projectDir: projectData.projectDir, shouldStartWatcher: this.$options.watch, syncAllFiles: this.$options.syncAllFiles }; + await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); // TODO: Fix - await this.$usbLiveSyncService.liveSync(platform, projectData, null, this.$options); + // await this.$liveSyncService.liveSync(platform, projectData, null, this.$options); if (this.$options.debugBrk) { this.$logger.info('Starting debugger...'); @@ -146,9 +187,40 @@ class TestExecutionService implements ITestExecutionService { const debugData = this.getDebugData(platform, projectData, deployOptions); await debugService.debug(debugData, this.$options); } else { - await this.$platformService.deployPlatform(platform, appFilesUpdaterOptions, deployOptions, projectData, this.$options); - // TODO: Fix - await this.$usbLiveSyncService.liveSync(platform, projectData, null, this.$options); + const devices = this.$devicesService.getDeviceInstances(); + // Now let's take data for each device: + const deviceDescriptors: ILiveSyncDeviceInfo[] = devices.filter(d => !this.platform || d.deviceInfo.platform === this.platform) + .map(d => { + const info: ILiveSyncDeviceInfo = { + identifier: d.deviceInfo.identifier, + buildAction: async (): Promise => { + const buildConfig: IBuildConfig = { + buildForDevice: !d.isEmulator, // this.$options.forDevice, + projectDir: this.$options.path, + clean: this.$options.clean, + teamId: this.$options.teamId, + device: this.$options.device, + provision: this.$options.provision, + release: this.$options.release, + keyStoreAlias: this.$options.keyStoreAlias, + keyStorePath: this.$options.keyStorePath, + keyStoreAliasPassword: this.$options.keyStoreAliasPassword, + keyStorePassword: this.$options.keyStorePassword + }; + + await this.$platformService.buildPlatform(d.deviceInfo.platform, buildConfig, projectData); + const pathToBuildResult = await this.$platformService.lastOutputPath(d.deviceInfo.platform, buildConfig, projectData); + console.log("3##### return path to buildResult = ", pathToBuildResult); + return pathToBuildResult; + } + } + + return info; + }); + + // TODO: Fix this call + const liveSyncInfo: ILiveSyncInfo = { projectDir: projectData.projectDir, shouldStartWatcher: this.$options.watch, syncAllFiles: this.$options.syncAllFiles }; + await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } }; diff --git a/test/debug.ts b/test/debug.ts index dc7de422e9..4993b2cc09 100644 --- a/test/debug.ts +++ b/test/debug.ts @@ -30,7 +30,7 @@ function createTestInjector(): IInjector { checkConsent: async () => undefined, trackFeature: async () => undefined }); - testInjector.register("usbLiveSyncService", stubs.LiveSyncServiceStub); + testInjector.register("liveSyncService", stubs.LiveSyncServiceStub); testInjector.register("androidProjectService", AndroidProjectService); testInjector.register("androidToolsInfo", stubs.AndroidToolsInfoStub); testInjector.register("hostInfo", {}); diff --git a/test/stubs.ts b/test/stubs.ts index d99f116e21..b18b26efd2 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -497,7 +497,11 @@ export class DebugServiceStub extends EventEmitter implements IPlatformDebugServ } export class LiveSyncServiceStub implements ILiveSyncService { - public async liveSync(platform: string, projectData: IProjectData, applicationReloadAction?: (deviceAppData: Mobile.IDeviceAppData) => Promise): Promise { + public async liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise { + return; + } + + public async stopLiveSync(projectDir: string): Promise { return; } } From 33371a464e1711389cd29bf6d3db39bcfe338a63 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Mon, 5 Jun 2017 10:54:28 +0300 Subject: [PATCH 070/212] WIP next --- lib/declarations.d.ts | 143 ------------------ lib/definitions/livesync.d.ts | 137 +++++++++++++++++ .../livesync/ios-device-livesync-service.ts | 6 +- lib/services/livesync/livesync-service.ts | 2 - 4 files changed, 142 insertions(+), 146 deletions(-) create mode 100644 lib/definitions/livesync.d.ts diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index e508c46c5a..01c01623f6 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -257,149 +257,6 @@ interface IOpener { open(target: string, appname: string): void; } -interface IFSWatcher extends NodeJS.EventEmitter { - // from fs.FSWatcher - close(): void; - - /** - * events.EventEmitter - * 1. change - * 2. error - */ - addListener(event: string, listener: Function): this; - addListener(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; - addListener(event: "error", listener: (code: number, signal: string) => void): this; - - on(event: string, listener: Function): this; - on(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; - on(event: "error", listener: (code: number, signal: string) => void): this; - - once(event: string, listener: Function): this; - once(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; - once(event: "error", listener: (code: number, signal: string) => void): this; - - prependListener(event: string, listener: Function): this; - prependListener(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; - prependListener(event: "error", listener: (code: number, signal: string) => void): this; - - prependOnceListener(event: string, listener: Function): this; - prependOnceListener(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; - prependOnceListener(event: "error", listener: (code: number, signal: string) => void): this; - - // From chokidar FSWatcher - - /** - * Add files, directories, or glob patterns for tracking. Takes an array of strings or just one - * string. - */ - add(paths: string | string[]): void; - - /** - * Stop watching files, directories, or glob patterns. Takes an array of strings or just one - * string. - */ - unwatch(paths: string | string[]): void; - - /** - * Returns an object representing all the paths on the file system being watched by this - * `FSWatcher` instance. The object's keys are all the directories (using absolute paths unless - * the `cwd` option was used), and the values are arrays of the names of the items contained in - * each directory. - */ - getWatched(): IDictionary; - - /** - * Removes all listeners from watched files. - */ - close(): void; -} - -interface ILiveSyncProcessInfo { - timer: NodeJS.Timer; - watcher: IFSWatcher; - actionsChain: Promise; - isStopped: boolean; -} - -interface ILiveSyncDeviceInfo { - identifier: string; - buildAction: () => Promise; - outputPath?: string; -} - -interface ILiveSyncInfo { - projectDir: string; - shouldStartWatcher: boolean; - syncAllFiles?: boolean; -} - -interface ILiveSyncBuildInfo { - platform: string; - isEmulator: boolean; - pathToBuildItem: string; -} - -// interface ILiveSyncDebugInfo { -// outputFilePath?: string; -// debugData: IDebugData; -// debugOptions: IDebugOptions; -// } - -interface ILiveSyncService { - liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise; - stopLiveSync(projectDir: string): Promise; -} - -interface ILiveSyncWatchInfo { - projectData: IProjectData; - filesToRemove: string[]; - filesToSync: string[]; - isRebuilt: boolean; - syncAllFiles: boolean; -} - -interface ILiveSyncResultInfo { - modifiedFilesData: Mobile.ILocalToDevicePathData[]; - isFullSync: boolean; - deviceAppData: Mobile.IDeviceAppData; -} - -interface IFullSyncInfo { - projectData: IProjectData; - device: Mobile.IDevice; - syncAllFiles: boolean; -} - -interface IPlatformLiveSyncService { - fullSync(syncInfo: IFullSyncInfo): Promise; - liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise; - refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise; -} - -interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase { - /** - * Refreshes the application's content on a device - * @param {Mobile.IDeviceAppData} deviceAppData Information about the application and the device. - * @param {Mobile.ILocalToDevicePathData[]} localToDevicePaths Object containing a mapping of file paths from the system to the device. - * @param {boolean} forceExecuteFullSync If this is passed a full LiveSync is performed instead of an incremental one. - * @param {IProjectData} projectData Project data. - * @return {Promise} - */ - refreshApplication(deviceAppData: Mobile.IDeviceAppData, - localToDevicePaths: Mobile.ILocalToDevicePathData[], - forceExecuteFullSync: boolean, - projectData: IProjectData): Promise; - - /** - * Removes specified files from a connected device - * @param {string} appIdentifier Application identifier. - * @param {Mobile.ILocalToDevicePathData[]} localToDevicePaths Object containing a mapping of file paths from the system to the device. - * @param {string} projectId Project identifier - for example org.nativescript.livesync. - * @return {Promise} - */ - removeFiles(appIdentifier: string, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectId: string): Promise; -} - interface IBundle { bundle: boolean; } diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts new file mode 100644 index 0000000000..75cfb07fcf --- /dev/null +++ b/lib/definitions/livesync.d.ts @@ -0,0 +1,137 @@ +interface IFSWatcher extends NodeJS.EventEmitter { + // from fs.FSWatcher + close(): void; + + /** + * events.EventEmitter + * 1. change + * 2. error + */ + addListener(event: string, listener: Function): this; + addListener(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; + addListener(event: "error", listener: (code: number, signal: string) => void): this; + + on(event: string, listener: Function): this; + on(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; + on(event: "error", listener: (code: number, signal: string) => void): this; + + once(event: string, listener: Function): this; + once(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; + once(event: "error", listener: (code: number, signal: string) => void): this; + + prependListener(event: string, listener: Function): this; + prependListener(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; + prependListener(event: "error", listener: (code: number, signal: string) => void): this; + + prependOnceListener(event: string, listener: Function): this; + prependOnceListener(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; + prependOnceListener(event: "error", listener: (code: number, signal: string) => void): this; + + // From chokidar FSWatcher + + /** + * Add files, directories, or glob patterns for tracking. Takes an array of strings or just one + * string. + */ + add(paths: string | string[]): void; + + /** + * Stop watching files, directories, or glob patterns. Takes an array of strings or just one + * string. + */ + unwatch(paths: string | string[]): void; + + /** + * Returns an object representing all the paths on the file system being watched by this + * `FSWatcher` instance. The object's keys are all the directories (using absolute paths unless + * the `cwd` option was used), and the values are arrays of the names of the items contained in + * each directory. + */ + getWatched(): IDictionary; + + /** + * Removes all listeners from watched files. + */ + close(): void; +} + +interface ILiveSyncProcessInfo { + timer: NodeJS.Timer; + watcher: IFSWatcher; + actionsChain: Promise; + isStopped: boolean; +} + +interface ILiveSyncDeviceInfo { + identifier: string; + buildAction: () => Promise; + outputPath?: string; +} + +interface ILiveSyncInfo { + projectDir: string; + shouldStartWatcher: boolean; + syncAllFiles?: boolean; + useLiveEdit?: boolean; +} + +interface ILiveSyncBuildInfo { + platform: string; + isEmulator: boolean; + pathToBuildItem: string; +} + +interface ILiveSyncService { + liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise; + stopLiveSync(projectDir: string): Promise; +} + +interface ILiveSyncWatchInfo { + projectData: IProjectData; + filesToRemove: string[]; + filesToSync: string[]; + isRebuilt: boolean; + syncAllFiles: boolean; +} + +interface ILiveSyncResultInfo { + modifiedFilesData: Mobile.ILocalToDevicePathData[]; + isFullSync: boolean; + deviceAppData: Mobile.IDeviceAppData; +} + +interface IFullSyncInfo { + projectData: IProjectData; + device: Mobile.IDevice; + syncAllFiles: boolean; +} + +interface IPlatformLiveSyncService { + fullSync(syncInfo: IFullSyncInfo): Promise; + liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise; + refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise; +} + +interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase { + /** + * Refreshes the application's content on a device + * @param {Mobile.IDeviceAppData} deviceAppData Information about the application and the device. + * @param {Mobile.ILocalToDevicePathData[]} localToDevicePaths Object containing a mapping of file paths from the system to the device. + * @param {boolean} forceExecuteFullSync If this is passed a full LiveSync is performed instead of an incremental one. + * @param {IProjectData} projectData Project data. + * @return {Promise} + */ + refreshApplication(deviceAppData: Mobile.IDeviceAppData, + localToDevicePaths: Mobile.ILocalToDevicePathData[], + forceExecuteFullSync: boolean, + projectData: IProjectData): Promise; + + /** + * Removes specified files from a connected device + * @param {string} appIdentifier Application identifier. + * @param {Mobile.ILocalToDevicePathData[]} localToDevicePaths Object containing a mapping of file paths from the system to the device. + * @param {string} projectId Project identifier - for example org.nativescript.livesync. + * @return {Promise} + */ + removeFiles(appIdentifier: string, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectId: string): Promise; +} diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index ed96efabe4..8f84642d0a 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -55,7 +55,11 @@ export class IOSLiveSyncService implements INativeScriptDeviceLiveSyncService { await Promise.all(_.map(localToDevicePaths, localToDevicePathData => this.device.fileSystem.deleteFile(localToDevicePathData.getDevicePath(), appIdentifier))); } - public async refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], forceExecuteFullSync: boolean, projectData: IProjectData): Promise { + public async refreshApplication( + deviceAppData: Mobile.IDeviceAppData, + localToDevicePaths: Mobile.ILocalToDevicePathData[], + forceExecuteFullSync: boolean, + projectData: IProjectData): Promise { if (forceExecuteFullSync) { await this.restartApplication(deviceAppData, projectData.projectName); return; diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 7d0855de38..19bf6de286 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -272,8 +272,6 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; this.liveSyncProcessesInfo[liveSyncData.projectDir].isStopped = false; - this.liveSyncProcessesInfo[liveSyncData.projectDir].isStopped = false; - this.$processService.attachToProcessExitSignals(this, () => { _.keys(this.liveSyncProcessesInfo).forEach(projectDir => { // Do not await here, we are in process exit's handler. From 19b7fca341ac3fed741683d2d5271f4fd926c2f4 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Mon, 5 Jun 2017 11:16:45 +0300 Subject: [PATCH 071/212] Add useLiveEdit option --- lib/definitions/livesync.d.ts | 9 +++++---- .../android-device-livesync-service.ts | 9 ++++----- .../livesync/android-livesync-service.ts | 4 +--- .../livesync/ios-device-livesync-service.ts | 20 +++++++++---------- lib/services/livesync/ios-livesync-service.ts | 6 +++--- lib/services/livesync/livesync-service.ts | 5 +++-- 6 files changed, 25 insertions(+), 28 deletions(-) diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 75cfb07fcf..ef7638d702 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -92,18 +92,21 @@ interface ILiveSyncWatchInfo { filesToSync: string[]; isRebuilt: boolean; syncAllFiles: boolean; + useLiveEdit?: boolean; } interface ILiveSyncResultInfo { modifiedFilesData: Mobile.ILocalToDevicePathData[]; isFullSync: boolean; deviceAppData: Mobile.IDeviceAppData; + useLiveEdit?: boolean; } interface IFullSyncInfo { projectData: IProjectData; device: Mobile.IDevice; syncAllFiles: boolean; + useLiveEdit?: boolean; } interface IPlatformLiveSyncService { @@ -121,10 +124,8 @@ interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase * @param {IProjectData} projectData Project data. * @return {Promise} */ - refreshApplication(deviceAppData: Mobile.IDeviceAppData, - localToDevicePaths: Mobile.ILocalToDevicePathData[], - forceExecuteFullSync: boolean, - projectData: IProjectData): Promise; + refreshApplication(projectData: IProjectData, + liveSyncInfo: ILiveSyncResultInfo): Promise; /** * Removes specified files from a connected device diff --git a/lib/services/livesync/android-device-livesync-service.ts b/lib/services/livesync/android-device-livesync-service.ts index 3fc810e0d2..2f6e34dcbb 100644 --- a/lib/services/livesync/android-device-livesync-service.ts +++ b/lib/services/livesync/android-device-livesync-service.ts @@ -25,10 +25,9 @@ export class AndroidLiveSyncService implements INativeScriptDeviceLiveSyncServic return this.$androidDebugService; } - public async refreshApplication(deviceAppData: Mobile.IDeviceAppData, - localToDevicePaths: Mobile.ILocalToDevicePathData[], - forceExecuteFullSync: boolean, - projectData: IProjectData): Promise { + public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { + const deviceAppData = liveSyncInfo.deviceAppData; + const localToDevicePaths = liveSyncInfo.modifiedFilesData; await this.device.adb.executeShellCommand( ["chmod", @@ -38,7 +37,7 @@ export class AndroidLiveSyncService implements INativeScriptDeviceLiveSyncServic `/data/local/tmp/${deviceAppData.appIdentifier}/sync`] ); - let canExecuteFastSync = !forceExecuteFullSync && !_.some(localToDevicePaths, + let canExecuteFastSync = ! liveSyncInfo.isFullSync && !_.some(localToDevicePaths, (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.canExecuteFastSync(localToDevicePath.getLocalPath(), projectData, this.device.deviceInfo.platform)); if (canExecuteFastSync) { diff --git a/lib/services/livesync/android-livesync-service.ts b/lib/services/livesync/android-livesync-service.ts index 2e3e4241ac..c1ab1d03be 100644 --- a/lib/services/livesync/android-livesync-service.ts +++ b/lib/services/livesync/android-livesync-service.ts @@ -85,11 +85,9 @@ export class AndroidLiveSyncService implements IPlatformLiveSyncService { liveSyncInfo: ILiveSyncResultInfo ): Promise { if (liveSyncInfo.isFullSync || liveSyncInfo.modifiedFilesData.length) { - // const deviceAppData = this.$injector.resolve(deviceAppDataIdentifiers.AndroidAppIdentifier, - // { _appIdentifier: projectData.projectId, device, platform: device.deviceInfo.platform }); let deviceLiveSyncService = this.$injector.resolve(adls.AndroidLiveSyncService, { _device: liveSyncInfo.deviceAppData.device }); this.$logger.info("Refreshing application..."); - await deviceLiveSyncService.refreshApplication(liveSyncInfo.deviceAppData, liveSyncInfo.modifiedFilesData, liveSyncInfo.isFullSync, projectData); + await deviceLiveSyncService.refreshApplication(projectData, liveSyncInfo); } } diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index 8f84642d0a..cd0d840386 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -5,7 +5,7 @@ import * as net from "net"; let currentPageReloadId = 0; -export class IOSLiveSyncService implements INativeScriptDeviceLiveSyncService { +export class IOSDeviceLiveSyncService implements INativeScriptDeviceLiveSyncService { private static BACKEND_PORT = 18181; private socket: net.Socket; private device: Mobile.IiOSDevice; @@ -35,7 +35,7 @@ export class IOSLiveSyncService implements INativeScriptDeviceLiveSyncService { if (this.device.isEmulator) { await this.$iOSEmulatorServices.postDarwinNotification(this.$iOSNotification.getAttachRequest(projectId)); try { - this.socket = await helpers.connectEventuallyUntilTimeout(() => net.connect(IOSLiveSyncService.BACKEND_PORT), 5000); + this.socket = await helpers.connectEventuallyUntilTimeout(() => net.connect(IOSDeviceLiveSyncService.BACKEND_PORT), 5000); } catch (e) { this.$logger.debug(e); return false; @@ -43,7 +43,7 @@ export class IOSLiveSyncService implements INativeScriptDeviceLiveSyncService { } else { let timeout = 9000; await this.$iOSSocketRequestExecutor.executeAttachRequest(this.device, timeout, projectId); - this.socket = await this.device.connectToPort(IOSLiveSyncService.BACKEND_PORT); + this.socket = await this.device.connectToPort(IOSDeviceLiveSyncService.BACKEND_PORT); } this.attachEventHandlers(); @@ -55,12 +55,10 @@ export class IOSLiveSyncService implements INativeScriptDeviceLiveSyncService { await Promise.all(_.map(localToDevicePaths, localToDevicePathData => this.device.fileSystem.deleteFile(localToDevicePathData.getDevicePath(), appIdentifier))); } - public async refreshApplication( - deviceAppData: Mobile.IDeviceAppData, - localToDevicePaths: Mobile.ILocalToDevicePathData[], - forceExecuteFullSync: boolean, - projectData: IProjectData): Promise { - if (forceExecuteFullSync) { + public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { + const deviceAppData = liveSyncInfo.deviceAppData; + const localToDevicePaths = liveSyncInfo.modifiedFilesData; + if (liveSyncInfo.isFullSync) { await this.restartApplication(deviceAppData, projectData.projectName); return; } @@ -72,7 +70,7 @@ export class IOSLiveSyncService implements INativeScriptDeviceLiveSyncService { let otherFiles = _.difference(localToDevicePaths, _.concat(scriptFiles, scriptRelatedFiles)); let shouldRestart = _.some(otherFiles, (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.$liveSyncProvider.canExecuteFastSync(localToDevicePath.getLocalPath(), projectData, deviceAppData.platform)); - if (shouldRestart || (!this.$options.liveEdit && scriptFiles.length)) { + if (shouldRestart || (!liveSyncInfo.useLiveEdit && scriptFiles.length)) { await this.restartApplication(deviceAppData, projectData.projectName); return; } @@ -174,4 +172,4 @@ export class IOSLiveSyncService implements INativeScriptDeviceLiveSyncService { } } } -$injector.register("iosLiveSyncServiceLocator", { factory: IOSLiveSyncService }); +$injector.register("iosLiveSyncServiceLocator", { factory: IOSDeviceLiveSyncService }); diff --git a/lib/services/livesync/ios-livesync-service.ts b/lib/services/livesync/ios-livesync-service.ts index 8297b267c2..f6fe6d9935 100644 --- a/lib/services/livesync/ios-livesync-service.ts +++ b/lib/services/livesync/ios-livesync-service.ts @@ -109,7 +109,7 @@ export class IOSLiveSyncService implements IPlatformLiveSyncService { let localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, mappedFiles, []); modifiedLocalToDevicePaths.push(...localToDevicePaths); - const deviceLiveSyncService = this.$injector.resolve(iosdls.IOSLiveSyncService, { _device: device }); + const deviceLiveSyncService = this.$injector.resolve(iosdls.IOSDeviceLiveSyncService, { _device: device }); deviceLiveSyncService.removeFiles(projectData.projectId, localToDevicePaths, projectData.projectId); } } @@ -122,9 +122,9 @@ export class IOSLiveSyncService implements IPlatformLiveSyncService { } public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { - let deviceLiveSyncService = this.$injector.resolve(iosdls.IOSLiveSyncService, { _device: liveSyncInfo.deviceAppData.device }); + let deviceLiveSyncService = this.$injector.resolve(iosdls.IOSDeviceLiveSyncService, { _device: liveSyncInfo.deviceAppData.device }); this.$logger.info("Refreshing application..."); - await deviceLiveSyncService.refreshApplication(liveSyncInfo.deviceAppData, liveSyncInfo.modifiedFilesData, liveSyncInfo.isFullSync, projectData); + await deviceLiveSyncService.refreshApplication(projectData, liveSyncInfo); } protected async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise { diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 19bf6de286..0d046df1a1 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -138,7 +138,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, projectData, deviceDescriptor); - const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ projectData, device, syncAllFiles: liveSyncData.syncAllFiles }); + const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ projectData, device, syncAllFiles: liveSyncData.syncAllFiles, useLiveEdit: liveSyncData.useLiveEdit }); await this.refreshApplication(projectData, liveSyncResultInfo); //await device.applicationManager.restartApplication(projectData.projectId, projectData.projectName); }; @@ -206,7 +206,8 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { filesToRemove: currentFilesToRemove, filesToSync: currentFilesToSync, isRebuilt: !!_.find(rebuiltInformation, info => info.isEmulator === device.isEmulator && info.platform === device.deviceInfo.platform), - syncAllFiles: liveSyncData.syncAllFiles + syncAllFiles: liveSyncData.syncAllFiles, + useLiveEdit: liveSyncData.useLiveEdit }; const liveSyncResultInfo = await service.liveSyncWatchAction(device, settings); From 17653972e34a306f88d65307d38a9618cc00548e Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Mon, 5 Jun 2017 13:25:56 +0300 Subject: [PATCH 072/212] Update Public API interfaces --- lib/commands/debug.ts | 4 +- lib/commands/run.ts | 2 +- lib/definitions/livesync.d.ts | 57 ++++++++++++++++++++++- lib/services/livesync/livesync-service.ts | 8 ++-- lib/services/test-execution-service.ts | 4 +- 5 files changed, 64 insertions(+), 11 deletions(-) diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 51f3e5f0c3..b9eae48bff 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -163,8 +163,8 @@ export abstract class DebugPlatformCommand implements ICommand { const liveSyncInfo: ILiveSyncInfo = { projectDir: this.$projectData.projectDir, - shouldStartWatcher: this.$options.watch, - syncAllFiles: this.$options.syncAllFiles + skipWatcher: !this.$options.watch || this.$options.justlaunch, + watchAllFiles: this.$options.syncAllFiles }; const debugLiveSyncService = this.$injector.resolve(DebugLiveSyncService, { debugService: this.debugService }); diff --git a/lib/commands/run.ts b/lib/commands/run.ts index a7ebd99807..06e4ccc227 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -76,7 +76,7 @@ export class RunCommandBase implements ICommand { // } // TODO: Fix this call - const liveSyncInfo: ILiveSyncInfo = { projectDir: this.$projectData.projectDir, shouldStartWatcher: this.$options.watch, syncAllFiles: this.$options.syncAllFiles }; + const liveSyncInfo: ILiveSyncInfo = { projectDir: this.$projectData.projectDir, skipWatcher: !this.$options.watch || this.$options.justlaunch, watchAllFiles: this.$options.syncAllFiles }; await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } } diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index ef7638d702..d437c0670e 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -62,16 +62,53 @@ interface ILiveSyncProcessInfo { isStopped: boolean; } +/** + * Describes information for LiveSync on a device. + */ interface ILiveSyncDeviceInfo { + /** + * Device identifier. + */ identifier: string; + + /** + * Action that will rebuild the application. The action must return a Promise, which is resolved with at path to build artifact. + * @returns {Promise} Path to build artifact (.ipa, .apk or .zip). + */ buildAction: () => Promise; + + /** + * Path where the build result is located (directory containing .ipa, .apk or .zip). + * This is required for initial checks where LiveSync will skip the rebuild in case there's already a build result and no change requiring rebuild is made since then. + * In case it is not passed, the default output for local builds will be used. + */ outputPath?: string; } +/** + * Describes a LiveSync operation. + */ interface ILiveSyncInfo { + /** + * Directory of the project that will be synced. + */ projectDir: string; - shouldStartWatcher: boolean; - syncAllFiles?: boolean; + + /** + * Defines if the watcher should be skipped. If not passed, fs.Watcher will be started. + */ + skipWatcher?: boolean; + + /** + * Defines if all project files should be watched for changes. In case it is not passed, only `app` dir of the project will be watched for changes. + * In case it is set to true, the package.json of the project and node_modules directory will also be watched, so any change there will be transferred to device(s). + */ + watchAllFiles?: boolean; + + /** + * Defines if the liveEdit functionality should be used, i.e. LiveSync of .js files without restart. + * NOTE: Currently this is available only for iOS. + */ useLiveEdit?: boolean; } @@ -81,8 +118,23 @@ interface ILiveSyncBuildInfo { pathToBuildItem: string; } +/** + * Describes LiveSync operations. + */ interface ILiveSyncService { + /** + * Starts LiveSync operation by rebuilding the application if necessary and starting watcher. + * @param {ILiveSyncDeviceInfo[]} deviceDescriptors Describes each device for which we would like to sync the application - identifier, outputPath and action to rebuild the app. + * @param {ILiveSyncInfo} liveSyncData Describes the LiveSync operation - for which project directory is the operation and other settings. + * @returns {Promise} + */ liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise; + + /** + * Stops LiveSync operation for specified directory. + * @param {string} projectDir The directory for which to stop the operation. + * @returns {Promise} + */ stopLiveSync(projectDir: string): Promise; } @@ -93,6 +145,7 @@ interface ILiveSyncWatchInfo { isRebuilt: boolean; syncAllFiles: boolean; useLiveEdit?: boolean; + } interface ILiveSyncResultInfo { diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 0d046df1a1..b1645086ce 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -48,7 +48,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { // Should be set after prepare this.$injector.resolve("usbLiveSyncService").isInitialized = true; - if (liveSyncData.shouldStartWatcher) { + if (!liveSyncData.skipWatcher) { await this.startWatcher(projectData, deviceDescriptors, liveSyncData); } } @@ -138,7 +138,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, projectData, deviceDescriptor); - const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ projectData, device, syncAllFiles: liveSyncData.syncAllFiles, useLiveEdit: liveSyncData.useLiveEdit }); + const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ projectData, device, syncAllFiles: liveSyncData.watchAllFiles, useLiveEdit: liveSyncData.useLiveEdit }); await this.refreshApplication(projectData, liveSyncResultInfo); //await device.applicationManager.restartApplication(projectData.projectId, projectData.projectName); }; @@ -152,7 +152,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { let pattern = ["app"]; - if (liveSyncData.syncAllFiles) { + if (liveSyncData.watchAllFiles) { const productionDependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); pattern.push("package.json"); @@ -206,7 +206,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { filesToRemove: currentFilesToRemove, filesToSync: currentFilesToSync, isRebuilt: !!_.find(rebuiltInformation, info => info.isEmulator === device.isEmulator && info.platform === device.deviceInfo.platform), - syncAllFiles: liveSyncData.syncAllFiles, + syncAllFiles: liveSyncData.watchAllFiles, useLiveEdit: liveSyncData.useLiveEdit }; diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index ead592a485..07ebac8cb2 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -110,7 +110,7 @@ class TestExecutionService implements ITestExecutionService { }); // TODO: Fix this call - const liveSyncInfo: ILiveSyncInfo = { projectDir: projectData.projectDir, shouldStartWatcher: this.$options.watch, syncAllFiles: this.$options.syncAllFiles }; + const liveSyncInfo: ILiveSyncInfo = { projectDir: projectData.projectDir, skipWatcher: !this.$options.watch || this.$options.justlaunch, watchAllFiles: this.$options.syncAllFiles }; await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); // TODO: Fix // await this.$liveSyncService.liveSync(platform, projectData, null, this.$options); @@ -219,7 +219,7 @@ class TestExecutionService implements ITestExecutionService { }); // TODO: Fix this call - const liveSyncInfo: ILiveSyncInfo = { projectDir: projectData.projectDir, shouldStartWatcher: this.$options.watch, syncAllFiles: this.$options.syncAllFiles }; + const liveSyncInfo: ILiveSyncInfo = { projectDir: projectData.projectDir, skipWatcher: !this.$options.watch || this.$options.justlaunch, watchAllFiles: this.$options.syncAllFiles }; await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } }; From 823f1c5ff5f7255aabf220a4da673134d56ef187 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Mon, 5 Jun 2017 14:50:36 +0300 Subject: [PATCH 073/212] Add events for LiveSync --- lib/services/livesync/livesync-service.ts | 49 ++++++++++++++++------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index b1645086ce..5a81b5609b 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -7,13 +7,10 @@ import { exported } from "../../common/decorators"; import { hook } from "../../common/helpers"; const LiveSyncEvents = { - liveSyncStarted: "liveSyncStarted", liveSyncStopped: "liveSyncStopped", - liveSyncError: "liveSyncError", // Do we need this or we can use liveSyncStopped event? - liveSyncWatcherStarted: "liveSyncWatcherStarted", - liveSyncWatcherStopped: "liveSyncWatcherStopped", - liveSyncFileChangedEvent: "liveSyncFileChangedEvent", - liveSyncOperationStartingEvent: "liveSyncOperationStartedEvent" + liveSyncError: "error", // Do we need this or we can use liveSyncStopped event? + liveSyncFileChangedEvent: "fileChanged", + liveSyncCompleted: "liveSyncCompleted" }; // TODO: emit events for "successfull livesync", "stoppedLivesync", @@ -39,8 +36,6 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { @hook("liveSync") public async liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise { - - this.emit(LiveSyncEvents.liveSyncStarted, { projectDir: liveSyncData.projectDir, deviceIdentifiers: deviceDescriptors.map(dd => dd.identifier) }); // TODO: Initialize devicesService before that. const projectData = this.$projectDataService.getProjectData(liveSyncData.projectDir); await this.initialSync(projectData, deviceDescriptors, liveSyncData); @@ -66,16 +61,30 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { liveSyncProcessInfo.watcher.close(); } - delete this.liveSyncProcessesInfo[projectDir]; + if (liveSyncProcessInfo.actionsChain) { + await liveSyncProcessInfo.actionsChain; + } + + liveSyncProcessInfo.isStopped = true; // Kill typescript watcher + // TODO: Pass the projectDir in hooks args. await this.$hooksService.executeAfterHooks('watch'); + + this.emit(LiveSyncEvents.liveSyncStopped, { projectDir }); } } protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo): Promise { const platformLiveSyncService = this.getLiveSyncService(liveSyncResultInfo.deviceAppData.platform); await platformLiveSyncService.refreshApplication(projectData, liveSyncResultInfo); + + this.emit(LiveSyncEvents.liveSyncCompleted, { + projectDir: projectData.projectDir, + applicationIdentifier: projectData.projectId, + syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), + deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier + }); } // TODO: Register both livesync services in injector @@ -140,7 +149,6 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ projectData, device, syncAllFiles: liveSyncData.watchAllFiles, useLiveEdit: liveSyncData.useLiveEdit }); await this.refreshApplication(projectData, liveSyncResultInfo); - //await device.applicationManager.restartApplication(projectData.projectId, projectData.projectName); }; await this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier)); @@ -220,9 +228,16 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { this.$logger.info("Try saving it again or restart the livesync operation."); // we can remove the descriptor from action: const allErrors = err.allErrors; - console.log(allErrors); _.each(allErrors, (deviceError: any) => { - console.log("for error: ", deviceError, " device ID: ", deviceError.deviceIdentifier); + this.$logger.warn(`Unable to apply changes for device: ${deviceError.deviceIdentifier}. Error is: ${deviceError.message}.`); + + this.emit(LiveSyncEvents.liveSyncError, { + error: deviceError, + deviceIdentifier: deviceError.deviceIdentifier, + projectDir: projectData.projectDir, + applicationIdentifier: projectData.projectId + }); + removeDeviceDescriptor(deviceError.deviceIdentifier); }); } @@ -245,12 +260,18 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { ignored: ["**/.*", ".*"] // hidden files }; - this.emit(LiveSyncEvents.liveSyncWatcherStarted, { projectDir: liveSyncData.projectDir, deviceIdentifiers: deviceDescriptors.map(dd => dd.identifier), watcherOptions, }); - const watcher = choki.watch(pattern, watcherOptions) .on("all", async (event: string, filePath: string) => { clearTimeout(timeoutTimer); + this.emit(LiveSyncEvents.liveSyncFileChangedEvent, { + projectDir: liveSyncData.projectDir, + applicationIdentifier: projectData.projectId, + deviceIdentifiers: deviceDescriptors.map(dd => dd.identifier), + modifiedFile: filePath, + event + }); + filePath = path.join(liveSyncData.projectDir, filePath); this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); From f45f1e3e4c3f0e0ceedfb3cbf47f777bc9edba3c Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 6 Jun 2017 01:31:23 +0300 Subject: [PATCH 074/212] Add correct events for LiveSync --- lib/commands/debug.ts | 124 ++------------ lib/commands/run.ts | 2 +- lib/definitions/livesync.d.ts | 1 + .../livesync/debug-livesync-service.ts | 74 ++++++++ lib/services/livesync/livesync-service.ts | 160 ++++++++++-------- lib/services/test-execution-service.ts | 6 +- test/debug.ts | 14 +- 7 files changed, 193 insertions(+), 188 deletions(-) create mode 100644 lib/services/livesync/debug-livesync-service.ts diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index b9eae48bff..a64313bef6 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -1,104 +1,24 @@ import { EOL } from "os"; -import { LiveSyncService } from "../services/livesync/livesync-service"; -export class DebugLiveSyncService extends LiveSyncService { - constructor(protected $platformService: IPlatformService, - $projectDataService: IProjectDataService, - protected $devicesService: Mobile.IDevicesService, - $mobileHelper: Mobile.IMobileHelper, - $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, - protected $logger: ILogger, - $processService: IProcessService, - $hooksService: IHooksService, - $projectChangesService: IProjectChangesService, - protected $injector: IInjector, - private $options: IOptions, - private $debugDataService: IDebugDataService, - private $projectData: IProjectData, - private debugService: IPlatformDebugService, - private $config: IConfiguration) { - - super($platformService, - $projectDataService, - $devicesService, - $mobileHelper, - $nodeModulesDependenciesBuilder, - $logger, - $processService, - $hooksService, - $projectChangesService, - $injector); - } - - protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo): Promise { - const debugOptions = this.$options; - const deployOptions: IDeployPlatformOptions = { - clean: this.$options.clean, - device: this.$options.device, - emulator: this.$options.emulator, - platformTemplate: this.$options.platformTemplate, - projectDir: this.$options.path, - release: this.$options.release, - provision: this.$options.provision, - teamId: this.$options.teamId - }; - - let debugData = this.$debugDataService.createDebugData(this.$projectData, this.$options); - - await this.$platformService.trackProjectType(this.$projectData); - - if (this.$options.start) { - return this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); - } - - const deviceAppData = liveSyncResultInfo.deviceAppData; - this.$config.debugLivesync = true; - - await this.debugService.debugStop(); - - let applicationId = deviceAppData.appIdentifier; - await deviceAppData.device.applicationManager.stopApplication(applicationId, projectData.projectName); - - const buildConfig: IBuildConfig = _.merge({ buildForDevice: !deviceAppData.device.isEmulator }, deployOptions); - debugData.pathToAppPackage = this.$platformService.lastOutputPath(this.debugService.platform, buildConfig, projectData); - - this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); - } - - protected printDebugInformation(information: string[]): void { - _.each(information, i => { - this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${i}${EOL}`.cyan); - }); - } -} export abstract class DebugPlatformCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; public platform: string; constructor(private debugService: IPlatformDebugService, private $devicesService: Mobile.IDevicesService, - private $injector: IInjector, private $debugDataService: IDebugDataService, protected $platformService: IPlatformService, protected $projectData: IProjectData, protected $options: IOptions, protected $platformsData: IPlatformsData, - protected $logger: ILogger) { + protected $logger: ILogger, + private $debugLiveSyncService: ILiveSyncService, + private $config: IConfiguration) { this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { const debugOptions = this.$options; - // const deployOptions: IDeployPlatformOptions = { - // clean: this.$options.clean, - // device: this.$options.device, - // emulator: this.$options.emulator, - // platformTemplate: this.$options.platformTemplate, - // projectDir: this.$options.path, - // release: this.$options.release, - // provision: this.$options.provision, - // teamId: this.$options.teamId - // }; let debugData = this.$debugDataService.createDebugData(this.$projectData, this.$options); @@ -108,23 +28,7 @@ export abstract class DebugPlatformCommand implements ICommand { return this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); } - // const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; - - // await this.$platformService.deployPlatform(this.$devicesService.platform, appFilesUpdaterOptions, deployOptions, this.$projectData, this.$options); - // this.$config.debugLivesync = true; - // let applicationReloadAction = async (deviceAppData: Mobile.IDeviceAppData): Promise => { - // let projectData: IProjectData = this.$injector.resolve("projectData"); - - // await this.debugService.debugStop(); - - // let applicationId = deviceAppData.appIdentifier; - // await deviceAppData.device.applicationManager.stopApplication(applicationId, projectData.projectName); - - // const buildConfig: IBuildConfig = _.merge({ buildForDevice: !deviceAppData.device.isEmulator }, deployOptions); - // debugData.pathToAppPackage = this.$platformService.lastOutputPath(this.debugService.platform, buildConfig, projectData); - - // this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); - // }; + this.$config.debugLivesync = true; // TODO: Fix this call await this.$devicesService.initialize({ deviceId: this.$options.device, platform: this.platform, skipDeviceDetectionInterval: true, skipInferPlatform: true }); @@ -153,10 +57,9 @@ export abstract class DebugPlatformCommand implements ICommand { await this.$platformService.buildPlatform(d.deviceInfo.platform, buildConfig, this.$projectData); const pathToBuildResult = await this.$platformService.lastOutputPath(d.deviceInfo.platform, buildConfig, this.$projectData); - console.log("3##### return path to buildResult = ", pathToBuildResult); return pathToBuildResult; } - } + }; return info; }); @@ -167,8 +70,7 @@ export abstract class DebugPlatformCommand implements ICommand { watchAllFiles: this.$options.syncAllFiles }; - const debugLiveSyncService = this.$injector.resolve(DebugLiveSyncService, { debugService: this.debugService }); - await debugLiveSyncService.liveSync(deviceDescriptors, liveSyncInfo); + await this.$debugLiveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } public async canExecute(args: string[]): Promise { @@ -201,16 +103,15 @@ export class DebugIOSCommand extends DebugPlatformCommand { $logger: ILogger, $iOSDebugService: IPlatformDebugService, $devicesService: Mobile.IDevicesService, - $injector: IInjector, $config: IConfiguration, - $liveSyncService: ILiveSyncService, $debugDataService: IDebugDataService, $platformService: IPlatformService, $options: IOptions, $projectData: IProjectData, $platformsData: IPlatformsData, - $iosDeviceOperations: IIOSDeviceOperations) { - super($iOSDebugService, $devicesService, $injector, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger); + $iosDeviceOperations: IIOSDeviceOperations, + $debugLiveSyncService: ILiveSyncService) { + super($iOSDebugService, $devicesService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger, $debugLiveSyncService, $config); // Do not dispose ios-device-lib, so the process will remain alive and the debug application (NativeScript Inspector or Chrome DevTools) will be able to connect to the socket. // In case we dispose ios-device-lib, the socket will be closed and the code will fail when the debug application tries to read/send data to device socket. // That's why the `$ tns debug ios --justlaunch` command will not release the terminal. @@ -243,15 +144,14 @@ export class DebugAndroidCommand extends DebugPlatformCommand { $logger: ILogger, $androidDebugService: IPlatformDebugService, $devicesService: Mobile.IDevicesService, - $injector: IInjector, $config: IConfiguration, - $liveSyncService: ILiveSyncService, $debugDataService: IDebugDataService, $platformService: IPlatformService, $options: IOptions, $projectData: IProjectData, - $platformsData: IPlatformsData) { - super($androidDebugService, $devicesService, $injector, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger); + $platformsData: IPlatformsData, + $debugLiveSyncService: ILiveSyncService) { + super($androidDebugService, $devicesService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger, $debugLiveSyncService, $config); } public async canExecute(args: string[]): Promise { diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 06e4ccc227..98f56dbe96 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -59,7 +59,7 @@ export class RunCommandBase implements ICommand { console.log("3##### return path to buildResult = ", pathToBuildResult); return pathToBuildResult; } - } + }; return info; }); diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index d437c0670e..c953406d78 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -60,6 +60,7 @@ interface ILiveSyncProcessInfo { watcher: IFSWatcher; actionsChain: Promise; isStopped: boolean; + deviceDescriptors: ILiveSyncDeviceInfo[]; } /** diff --git a/lib/services/livesync/debug-livesync-service.ts b/lib/services/livesync/debug-livesync-service.ts new file mode 100644 index 0000000000..3747516e4e --- /dev/null +++ b/lib/services/livesync/debug-livesync-service.ts @@ -0,0 +1,74 @@ +import { EOL } from "os"; +import { LiveSyncService } from "./livesync-service"; + +export class DebugLiveSyncService extends LiveSyncService { + + constructor(protected $platformService: IPlatformService, + $projectDataService: IProjectDataService, + protected $devicesService: Mobile.IDevicesService, + $mobileHelper: Mobile.IMobileHelper, + $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, + protected $logger: ILogger, + $processService: IProcessService, + $hooksService: IHooksService, + protected $injector: IInjector, + private $options: IOptions, + private $debugDataService: IDebugDataService, + private $projectData: IProjectData, + private debugService: IPlatformDebugService, + private $config: IConfiguration) { + + super($platformService, + $projectDataService, + $devicesService, + $mobileHelper, + $nodeModulesDependenciesBuilder, + $logger, + $processService, + $hooksService, + $injector); + } + + protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo): Promise { + const debugOptions = this.$options; + const deployOptions: IDeployPlatformOptions = { + clean: this.$options.clean, + device: this.$options.device, + emulator: this.$options.emulator, + platformTemplate: this.$options.platformTemplate, + projectDir: this.$options.path, + release: this.$options.release, + provision: this.$options.provision, + teamId: this.$options.teamId + }; + + let debugData = this.$debugDataService.createDebugData(this.$projectData, this.$options); + + await this.$platformService.trackProjectType(this.$projectData); + + if (this.$options.start) { + return this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); + } + + const deviceAppData = liveSyncResultInfo.deviceAppData; + this.$config.debugLivesync = true; + + await this.debugService.debugStop(); + + let applicationId = deviceAppData.appIdentifier; + await deviceAppData.device.applicationManager.stopApplication(applicationId, projectData.projectName); + + const buildConfig: IBuildConfig = _.merge({ buildForDevice: !deviceAppData.device.isEmulator }, deployOptions); + debugData.pathToAppPackage = this.$platformService.lastOutputPath(this.debugService.platform, buildConfig, projectData); + + this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); + } + + protected printDebugInformation(information: string[]): void { + _.each(information, i => { + this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${i}${EOL}`.cyan); + }); + } +} + +$injector.register("debugLiveSyncService", DebugLiveSyncService); diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 5a81b5609b..1015b10a4f 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -8,9 +8,10 @@ import { hook } from "../../common/helpers"; const LiveSyncEvents = { liveSyncStopped: "liveSyncStopped", - liveSyncError: "error", // Do we need this or we can use liveSyncStopped event? + liveSyncError: "error", liveSyncFileChangedEvent: "fileChanged", - liveSyncCompleted: "liveSyncCompleted" + liveSyncExecuted: "liveSyncExecuted", + liveSyncStarted: "liveSyncStarted" }; // TODO: emit events for "successfull livesync", "stoppedLivesync", @@ -26,7 +27,6 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { protected $logger: ILogger, private $processService: IProcessService, private $hooksService: IHooksService, - private $projectChangesService: IProjectChangesService, protected $injector: IInjector) { super(); } @@ -40,38 +40,53 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const projectData = this.$projectDataService.getProjectData(liveSyncData.projectDir); await this.initialSync(projectData, deviceDescriptors, liveSyncData); - // Should be set after prepare - this.$injector.resolve("usbLiveSyncService").isInitialized = true; + if (!liveSyncData.skipWatcher && deviceDescriptors && deviceDescriptors.length) { + // Should be set after prepare + this.$injector.resolve("usbLiveSyncService").isInitialized = true; - if (!liveSyncData.skipWatcher) { - await this.startWatcher(projectData, deviceDescriptors, liveSyncData); + await this.startWatcher(projectData, this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors, liveSyncData); } } @exported("liveSyncService") - public async stopLiveSync(projectDir: string): Promise { + public async stopLiveSync(projectDir: string, deviceIdentifiers?: string[], ): Promise { const liveSyncProcessInfo = _.find(this.liveSyncProcessesInfo, (info, key) => key === projectDir); if (liveSyncProcessInfo) { - if (liveSyncProcessInfo.timer) { - clearTimeout(liveSyncProcessInfo.timer); - } + _.each(deviceIdentifiers, deviceId => { + _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => { + const shouldRemove = descriptor.identifier === deviceId; + if (shouldRemove) { + this.emit(LiveSyncEvents.liveSyncStopped, { projectDir, deviceIdentifier: descriptor.identifier }); + } - if (liveSyncProcessInfo.watcher) { - liveSyncProcessInfo.watcher.close(); - } + return shouldRemove; + }); + }); - if (liveSyncProcessInfo.actionsChain) { - await liveSyncProcessInfo.actionsChain; - } + // In case deviceIdentifiers are not passed, we should stop the whole LiveSync. + if (!deviceIdentifiers || !deviceIdentifiers.length || !liveSyncProcessInfo.deviceDescriptors || !liveSyncProcessInfo.deviceDescriptors.length) { + if (liveSyncProcessInfo.timer) { + clearTimeout(liveSyncProcessInfo.timer); + } + + if (liveSyncProcessInfo.watcher) { + liveSyncProcessInfo.watcher.close(); + } - liveSyncProcessInfo.isStopped = true; + if (liveSyncProcessInfo.actionsChain) { + await liveSyncProcessInfo.actionsChain; + } + + liveSyncProcessInfo.isStopped = true; + liveSyncProcessInfo.deviceDescriptors = []; - // Kill typescript watcher - // TODO: Pass the projectDir in hooks args. - await this.$hooksService.executeAfterHooks('watch'); + // Kill typescript watcher + // TODO: Pass the projectDir in hooks args. + await this.$hooksService.executeAfterHooks('watch'); - this.emit(LiveSyncEvents.liveSyncStopped, { projectDir }); + this.emit(LiveSyncEvents.liveSyncStopped, { projectDir }); + } } } @@ -79,7 +94,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const platformLiveSyncService = this.getLiveSyncService(liveSyncResultInfo.deviceAppData.platform); await platformLiveSyncService.refreshApplication(projectData, liveSyncResultInfo); - this.emit(LiveSyncEvents.liveSyncCompleted, { + this.emit(LiveSyncEvents.liveSyncExecuted, { projectDir: projectData.projectDir, applicationIdentifier: projectData.projectId, syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), @@ -142,13 +157,30 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { // Now fullSync const deviceAction = async (device: Mobile.IDevice): Promise => { - // TODO: Call androidDeviceLiveSyncService.beforeLiveSyncAction - const platform = device.deviceInfo.platform; - const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, projectData, deviceDescriptor); + try { + this.liveSyncProcessesInfo[liveSyncData.projectDir] = this.liveSyncProcessesInfo[liveSyncData.projectDir] || { + actionsChain: Promise.resolve() + }; + + this.liveSyncProcessesInfo[liveSyncData.projectDir].isStopped = false; + this.liveSyncProcessesInfo[liveSyncData.projectDir].deviceDescriptors = deviceDescriptors; + + this.emit(LiveSyncEvents.liveSyncStarted, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectId + }); + + // TODO: Call androidDeviceLiveSyncService.beforeLiveSyncAction + const platform = device.deviceInfo.platform; + const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, projectData, deviceDescriptor); - const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ projectData, device, syncAllFiles: liveSyncData.watchAllFiles, useLiveEdit: liveSyncData.useLiveEdit }); - await this.refreshApplication(projectData, liveSyncResultInfo); + const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ projectData, device, syncAllFiles: liveSyncData.watchAllFiles, useLiveEdit: liveSyncData.useLiveEdit }); + await this.refreshApplication(projectData, liveSyncResultInfo); + } catch (err) { + await this.stopLiveSync(projectData.projectDir, [device.deviceInfo.identifier]); + } }; await this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier)); @@ -170,20 +202,10 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { } } - console.log("now starting watcher!", pattern); - let filesToSync: string[] = [], filesToRemove: string[] = []; let timeoutTimer: NodeJS.Timer; - const removeDeviceDescriptor = async (deviceId: string) => { - _.remove(deviceDescriptors, descriptor => descriptor.identifier === deviceId); - - if (!deviceDescriptors.length) { - await this.stopLiveSync(liveSyncData.projectDir); - } - }; - const startTimeout = () => { timeoutTimer = setTimeout(async () => { await this.addActionToQueue(projectData.projectDir, async () => { @@ -197,13 +219,20 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { filesToRemove = []; const allModifiedFiles = [].concat(currentFilesToSync).concat(currentFilesToRemove); - console.log(this.$projectChangesService.currentChanges); - const preparedPlatforms: string[] = []; const rebuiltInformation: ILiveSyncBuildInfo[] = []; + await this.$devicesService.execute(async (device: Mobile.IDevice) => { + + this.emit(LiveSyncEvents.liveSyncStarted, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectId + }); + // const platform = device.deviceInfo.platform; - const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + const liveSyncProcessesInfo = _.find(this.liveSyncProcessesInfo, (info, projectDir) => projectDir === projectDir); + const deviceDescriptor = _.find(liveSyncProcessesInfo.deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, projectData, deviceDescriptor, allModifiedFiles); @@ -221,25 +250,28 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const liveSyncResultInfo = await service.liveSyncWatchAction(device, settings); await this.refreshApplication(projectData, liveSyncResultInfo); }, - (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier) + (device: Mobile.IDevice) => { + const liveSyncProcessesInfo = _.find(this.liveSyncProcessesInfo, (info, projectDir) => projectDir === projectDir); + return liveSyncProcessesInfo && _.some(liveSyncProcessesInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); + } ); } catch (err) { - // this.$logger.info(`Unable to sync file ${filePath}. Error is:${err.message}`.red.bold); - this.$logger.info("Try saving it again or restart the livesync operation."); // we can remove the descriptor from action: - const allErrors = err.allErrors; - _.each(allErrors, (deviceError: any) => { - this.$logger.warn(`Unable to apply changes for device: ${deviceError.deviceIdentifier}. Error is: ${deviceError.message}.`); - - this.emit(LiveSyncEvents.liveSyncError, { - error: deviceError, - deviceIdentifier: deviceError.deviceIdentifier, - projectDir: projectData.projectDir, - applicationIdentifier: projectData.projectId - }); - - removeDeviceDescriptor(deviceError.deviceIdentifier); - }); + const allErrors = (err).allErrors; + if (allErrors && _.isArray(allErrors)) { + for (let deviceError of allErrors) { + this.$logger.warn(`Unable to apply changes for device: ${deviceError.deviceIdentifier}. Error is: ${deviceError.message}.`); + + this.emit(LiveSyncEvents.liveSyncError, { + error: deviceError, + deviceIdentifier: deviceError.deviceIdentifier, + projectDir: projectData.projectDir, + applicationIdentifier: projectData.projectId + }); + + await this.stopLiveSync(projectData.projectDir, [deviceError.deviceIdentifier]); + } + } } } }); @@ -263,11 +295,11 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const watcher = choki.watch(pattern, watcherOptions) .on("all", async (event: string, filePath: string) => { clearTimeout(timeoutTimer); - + const liveSyncProcessesInfo = _.find(this.liveSyncProcessesInfo, (info, projectDir) => projectDir === projectDir); this.emit(LiveSyncEvents.liveSyncFileChangedEvent, { projectDir: liveSyncData.projectDir, applicationIdentifier: projectData.projectId, - deviceIdentifiers: deviceDescriptors.map(dd => dd.identifier), + deviceIdentifiers: liveSyncProcessesInfo.deviceDescriptors.map(dd => dd.identifier), modifiedFile: filePath, event }); @@ -285,14 +317,8 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { startTimeout(); }); - // TODO: Extract to separate method. - this.liveSyncProcessesInfo[liveSyncData.projectDir] = this.liveSyncProcessesInfo[liveSyncData.projectDir] || { - actionsChain: Promise.resolve() - }; - this.liveSyncProcessesInfo[liveSyncData.projectDir].watcher = watcher; this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; - this.liveSyncProcessesInfo[liveSyncData.projectDir].isStopped = false; this.$processService.attachToProcessExitSignals(this, () => { _.keys(this.liveSyncProcessesInfo).forEach(projectDir => { @@ -302,7 +328,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { }); this.$devicesService.on("deviceLost", async (device: Mobile.IDevice) => { - await removeDeviceDescriptor(device.deviceInfo.identifier); + await this.stopLiveSync(projectData.projectDir, [device.deviceInfo.identifier]); }); } diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index 07ebac8cb2..27271535ac 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -76,8 +76,6 @@ class TestExecutionService implements ITestExecutionService { this.$options.watch = false; } - - const devices = this.$devicesService.getDeviceInstances(); // Now let's take data for each device: const deviceDescriptors: ILiveSyncDeviceInfo[] = devices.filter(d => !this.platform || d.deviceInfo.platform === this.platform) @@ -104,7 +102,7 @@ class TestExecutionService implements ITestExecutionService { console.log("3##### return path to buildResult = ", pathToBuildResult); return pathToBuildResult; } - } + }; return info; }); @@ -213,7 +211,7 @@ class TestExecutionService implements ITestExecutionService { console.log("3##### return path to buildResult = ", pathToBuildResult); return pathToBuildResult; } - } + }; return info; }); diff --git a/test/debug.ts b/test/debug.ts index 4993b2cc09..4b1aae6a89 100644 --- a/test/debug.ts +++ b/test/debug.ts @@ -26,11 +26,17 @@ function createTestInjector(): IInjector { testInjector.register('errors', stubs.ErrorsStub); testInjector.register('hostInfo', {}); testInjector.register("analyticsService", { - trackException: async () => undefined, - checkConsent: async () => undefined, - trackFeature: async () => undefined + trackException: async (): Promise => undefined, + checkConsent: async (): Promise => undefined, + trackFeature: async (): Promise => undefined }); - testInjector.register("liveSyncService", stubs.LiveSyncServiceStub); + testInjector.register('devicesService', { + initialize: async () => { /* Intentionally left blank */ }, + detectCurrentlyAttachedDevices: async () => { /* Intentionally left blank */ }, + getDeviceInstances: (): any[] => { return []; }, + execute: async (): Promise => ({}) + }); + testInjector.register("debugLiveSyncService", stubs.LiveSyncServiceStub); testInjector.register("androidProjectService", AndroidProjectService); testInjector.register("androidToolsInfo", stubs.AndroidToolsInfoStub); testInjector.register("hostInfo", {}); From cd7f808710112e1f516892682c3e51813fb160c8 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 6 Jun 2017 14:01:27 +0300 Subject: [PATCH 075/212] Add docs and improve liveSync operations when called multiple times for same dir --- PublicAPI.md | 211 +++++++++++++++++ lib/definitions/livesync.d.ts | 8 +- lib/services/livesync/livesync-service.ts | 269 +++++++++++----------- 3 files changed, 357 insertions(+), 131 deletions(-) diff --git a/PublicAPI.md b/PublicAPI.md index 3e3a89b629..70d920a80f 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -7,6 +7,32 @@ This document describes all methods that can be invoked when NativeScript CLI is const tns = require("nativescript"); ``` +# Contents +* [projectService](#projectservice) + * [createProject](#createproject) + * [isValidNativeScriptProject](#isvalidnativescriptproject) +* [extensibilityService](#extensibilityservice) + * [installExtension](#installextension) + * [uninstallExtension](#uninstallextension) + * [getInstalledExtensions](#getinstalledextensions) + * [loadExtensions](#loadextensions) +* [settingsService](#settingsservice) + * [setSettings](#setsettings) +* [npm](#npm) + * [install](#install) + * [uninstall](#uninstall) + * [search](#search) + * [view](#view) +* [analyticsService](#analyticsservice) + * [startEqatecMonitor](#starteqatecmonitor) +* [debugService](#debugservice) + * [debug](#debug) +* [liveSyncService](#livesyncservice) + * [liveSync](#livesync) + * [stopLiveSync](#stopLiveSync) + * [events](#events) + + ## Module projectService `projectService` modules allow you to create new NativeScript application. @@ -498,6 +524,191 @@ tns.debugService.debug(debugData, debugOptions) .catch(err => console.log(`Unable to start debug operation, reason: ${err.message}.`)); ``` +## liveSyncService +Used to LiveSync changes on devices. The operation can be started for multiple devices and stopped for each of them. During LiveSync operation, the service will emit different events based on the action that's executing. + +### liveSync +Starts a LiveSync operation for specified devices. During the operation, application may have to be rebuilt (for example in case a change in App_Resources is detected). +By default the LiveSync operation will start file system watcher for `/app` directory and any change in it will trigger a LiveSync operation. +After calling the method once, you can add new devices to the same LiveSync operation by calling the method again with the new device identifiers. + +> NOTE: Consecutive calls to `liveSync` method for the same project will execute the initial sync (deploy and fullSync) only for new device identifiers. So in case the first call is for devices with ids [ 'A' , 'B' ] and the second one is for devices with ids [ 'B', 'C' ], the initial sync will be executed only for device with identifier 'C'. + +> NOTE: In case a consecutive call to `liveSync` method requires change in the pattern for watching files (i.e. `liveSyncData.syncAllFiles` option has changed), current watch operation will be stopped and a new one will be started. + +* Definition +```TypeScript +/** + * Starts LiveSync operation by rebuilding the application if necessary and starting watcher. + * @param {ILiveSyncDeviceInfo[]} deviceDescriptors Describes each device for which we would like to sync the application - identifier, outputPath and action to rebuild the app. + * @param {ILiveSyncInfo} liveSyncData Describes the LiveSync operation - for which project directory is the operation and other settings. + * @returns {Promise} + */ +liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise; +``` + +* Usage: +```JavaScript +const projectDir = "myProjectDir"; +const androidDeviceDescriptor = { + identifier: "4df18f307d8a8f1b", + buildAction: () => { + return tns.localBuildService.build("Android", { projectDir, bundle: false, release: false, buildForDevice: true }); + }, + outputPath: null +}; + +const iOSDeviceDescriptor = { + identifier: "12318af23ebc0e25", + buildAction: () => { + return tns.localBuildService.build("iOS", { projectDir, bundle: false, release: false, buildForDevice: true }); + }, + outputPath: null +}; + +const liveSyncData = { + projectDir, + skipWatcher: false, + watchAllFiles: false, + useLiveEdit: false +}; + +tns.liveSyncService.liveSync([ androidDeviceDescriptor, iOSDeviceDescriptor ], liveSyncData) + .then(() => { + console.log("LiveSync operation started."); + }, err => { + console.log("An error occurred during LiveSync", err); + }); +``` + +### stopLiveSync +Stops LiveSync operation. In case deviceIdentifires are passed, the operation will be stopped only for these devices. + +* Definition +```TypeScript +/** + * Stops LiveSync operation for specified directory. + * @param {string} projectDir The directory for which to stop the operation. + * @param {string[]} @optional deviceIdentifiers Device ids for which to stop the application. In case nothing is passed, LiveSync operation will be stopped for all devices. + * @returns {Promise} + */ +stopLiveSync(projectDir: string, deviceIdentifiers?: string[]): Promise; +``` + +* Usage +```JavaScript +const projectDir = "myProjectDir"; +const deviceIdentifiers = [ "4df18f307d8a8f1b", "12318af23ebc0e25" ]; +tns.liveSyncService.stopLiveSync(projectDir, deviceIdentifiers) + .then(() => { + console.log("LiveSync operation stopped."); + }, err => { + console.log("An error occurred during stopage.", err); + }); +``` + +### Events +`liveSyncService` raises several events in order to provide information for current state of the operation. +* liveSyncStarted - raised whenever CLI starts a LiveSync operation for specific device. When `liveSync` method is called, the initial LiveSync operation will emit `liveSyncStarted` for each specified device. After that the event will be emitted only in case when liveSync method is called again with different device instances. The event is raised with the following data: +```TypeScript +{ + projectDir: string; + deviceIdentifier: string; + applicationIdentifier: string; +} +``` + +Example: +```JavaScript +tns.liveSyncService.on("liveSyncStarted", data => { + console.log(`Started LiveSync on ${data.deviceIdentifier} for ${data.applicationIdentifier}.`); +}); +``` + +* liveSyncExecuted - raised whenever CLI finishes a LiveSync operation for specific device. When `liveSync` method is called, the initial LiveSync operation will emit `liveSyncExecuted` for each specified device once it finishes the operation. After that the event will be emitted whenever a change is detected (in case file system watcher is staretd) and the LiveSync operation is executed for each device. The event is raised with the following data: +```TypeScript +{ + projectDir: string; + deviceIdentifier: string; + applicationIdentifier: string; + /** + * Full paths to files synced during the operation. In case the `syncedFiles.length` is 0, the operation is "fullSync" (i.e. all project files are synced). + */ + syncedFiles: string[]; +} +``` + +Example: +```JavaScript +tns.liveSyncService.on("liveSyncExecuted", data => { + console.log(`Executed LiveSync on ${data.deviceIdentifier} for ${data.applicationIdentifier}. Uploaded files are: ${syncedFiles.join(" ")}.`); +}); +``` + +* liveSyncStopped - raised when LiveSync operation is stopped. The event will be raised when the operation is stopped for each device and will be raised when the whole operation is stopped. The event is raised with the following data: +```TypeScript +{ + projectDir: string; + /** + * Passed only when the LiveSync operation is stopped for a specific device. In case it is not passed, the whole LiveSync operation is stopped. + */ + deviceIdentifier?: string; +} +``` + +Example: +```JavaScript +tns.liveSyncService.on("liveSyncStopped", data => { + if (data.deviceIdentifier) { + console.log(`Stopped LiveSync on ${data.deviceIdentifier} for ${data.projectDir}.`); + } else { + console.log(`Stopped LiveSync for ${data.projectDir}.`); + } +}); +``` + +* error - raised whenever an error is detected during LiveSync operation. The event is raised for specific device. Once an error is detected, the event will be raised and the LiveSync operation will be stopped for this device, i.e. `liveSyncStopped` event will be raised for it. The event is raised with the following data: +```TypeScript +{ + projectDir: string; + deviceIdentifier: string; + applicationIdentifier: string; + error: Error; +} +``` + +Example: +```JavaScript +tns.liveSyncService.on("error", data => { + console.log(`Error detected during LiveSync on ${data.deviceIdentifier} for ${data.projectDir}. Error: ${err.message}.`); +}); +``` + +* fileChanged - raised when a watched file is modified. The event is raised witht he following data: +```TypeScript +{ + projectDir: string; + /** + * Device identifiers on which the file will be LiveSynced. + */ + deviceIdentifiers: string[]; + applicationIdentifier: string; + modifiedFile: string; + + /** + * File System event - "add", "addDir", "change", "unlink", "unlinkDir". + */ + event: string; +} +``` + +Example: +```JavaScript +tns.liveSyncService.on("fileChanged", data => { + console.log(`Detected file changed: ${data.modifiedFile} in ${data.projectDir}. Will start LiveSync operation on ${data.deviceIdentifiers.join(", ")}.`); +}); +``` + ## How to add a new method to Public API CLI is designed as command line tool and when it is used as a library, it does not give you access to all of the methods. This is mainly implementation detail. Most of the CLI's code is created to work in command line, not as a library, so before adding method to public API, most probably it will require some modification. For example the `$options` injected module contains information about all `--` options passed on the terminal. When the CLI is used as a library, the options are not populated. Before adding method to public API, make sure its implementation does not rely on `$options`. diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index c953406d78..72b2e3ef63 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -57,7 +57,10 @@ interface IFSWatcher extends NodeJS.EventEmitter { interface ILiveSyncProcessInfo { timer: NodeJS.Timer; - watcher: IFSWatcher; + watcherInfo: { + watcher: IFSWatcher, + pattern: string | string[] + }; actionsChain: Promise; isStopped: boolean; deviceDescriptors: ILiveSyncDeviceInfo[]; @@ -134,9 +137,10 @@ interface ILiveSyncService { /** * Stops LiveSync operation for specified directory. * @param {string} projectDir The directory for which to stop the operation. + * @param {string[]} @optional deviceIdentifiers Device ids for which to stop the application. In case nothing is passed, LiveSync operation will be stopped for all devices. * @returns {Promise} */ - stopLiveSync(projectDir: string): Promise; + stopLiveSync(projectDir: string, deviceIdentifiers?: string[]): Promise; } interface ILiveSyncWatchInfo { diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 1015b10a4f..1bb086aad2 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -9,7 +9,6 @@ import { hook } from "../../common/helpers"; const LiveSyncEvents = { liveSyncStopped: "liveSyncStopped", liveSyncError: "error", - liveSyncFileChangedEvent: "fileChanged", liveSyncExecuted: "liveSyncExecuted", liveSyncStarted: "liveSyncStarted" }; @@ -38,19 +37,22 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { liveSyncData: ILiveSyncInfo): Promise { // TODO: Initialize devicesService before that. const projectData = this.$projectDataService.getProjectData(liveSyncData.projectDir); + + this.setLiveSyncProcessInfo(liveSyncData.projectDir, deviceDescriptors); + await this.initialSync(projectData, deviceDescriptors, liveSyncData); if (!liveSyncData.skipWatcher && deviceDescriptors && deviceDescriptors.length) { // Should be set after prepare this.$injector.resolve("usbLiveSyncService").isInitialized = true; - await this.startWatcher(projectData, this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors, liveSyncData); + await this.startWatcher(projectData, liveSyncData); } } @exported("liveSyncService") public async stopLiveSync(projectDir: string, deviceIdentifiers?: string[], ): Promise { - const liveSyncProcessInfo = _.find(this.liveSyncProcessesInfo, (info, key) => key === projectDir); + const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectDir]; if (liveSyncProcessInfo) { _.each(deviceIdentifiers, deviceId => { @@ -70,10 +72,12 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { clearTimeout(liveSyncProcessInfo.timer); } - if (liveSyncProcessInfo.watcher) { - liveSyncProcessInfo.watcher.close(); + if (liveSyncProcessInfo.watcherInfo && liveSyncProcessInfo.watcherInfo.watcher) { + liveSyncProcessInfo.watcherInfo.watcher.close(); } + liveSyncProcessInfo.watcherInfo = null; + if (liveSyncProcessInfo.actionsChain) { await liveSyncProcessInfo.actionsChain; } @@ -102,6 +106,16 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { }); } + private setLiveSyncProcessInfo(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[]): void { + this.liveSyncProcessesInfo[projectDir] = this.liveSyncProcessesInfo[projectDir] || Object.create(null); + this.liveSyncProcessesInfo[projectDir].actionsChain = this.liveSyncProcessesInfo[projectDir].actionsChain || Promise.resolve(); + this.liveSyncProcessesInfo[projectDir].isStopped = false; + + const currentDeviceDescriptors = this.liveSyncProcessesInfo[projectDir].deviceDescriptors || []; + // Prevent cases where liveSync is called consecutive times with the same device, for example [ A, B, C ] and then [ A, B, D ] - we want to execute initialSync only for D. + this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), "identifier"); + } + // TODO: Register both livesync services in injector private getLiveSyncService(platform: string): IPlatformLiveSyncService { if (this.$mobileHelper.isiOSPlatform(platform)) { @@ -155,39 +169,45 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const preparedPlatforms: string[] = []; const rebuiltInformation: ILiveSyncBuildInfo[] = []; + // Prevent cases where liveSync is called consecutive times with the same device, for example [ A, B, C ] and then [ A, B, D ] - we want to execute initialSync only for D. + const deviceDescriptorsForExecution = _.difference(deviceDescriptors, this.liveSyncProcessesInfo[liveSyncData.projectDir].deviceDescriptors); + // Now fullSync const deviceAction = async (device: Mobile.IDevice): Promise => { try { - this.liveSyncProcessesInfo[liveSyncData.projectDir] = this.liveSyncProcessesInfo[liveSyncData.projectDir] || { - actionsChain: Promise.resolve() - }; - - this.liveSyncProcessesInfo[liveSyncData.projectDir].isStopped = false; - this.liveSyncProcessesInfo[liveSyncData.projectDir].deviceDescriptors = deviceDescriptors; - this.emit(LiveSyncEvents.liveSyncStarted, { projectDir: projectData.projectDir, deviceIdentifier: device.deviceInfo.identifier, applicationIdentifier: projectData.projectId }); - // TODO: Call androidDeviceLiveSyncService.beforeLiveSyncAction const platform = device.deviceInfo.platform; const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, projectData, deviceDescriptor); const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ projectData, device, syncAllFiles: liveSyncData.watchAllFiles, useLiveEdit: liveSyncData.useLiveEdit }); await this.refreshApplication(projectData, liveSyncResultInfo); } catch (err) { + this.$logger.warn(`Unable to apply changes on device: ${err.deviceIdentifier}. Error is: ${err.message}.`); + + this.emit(LiveSyncEvents.liveSyncError, { + error: err, + deviceIdentifier: device.deviceInfo.identifier, + projectDir: projectData.projectDir, + applicationIdentifier: projectData.projectId + }); + await this.stopLiveSync(projectData.projectDir, [device.deviceInfo.identifier]); } }; - await this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier)); + // Execute the action only on the deviceDescriptors passed to initialSync. + // In case where we add deviceDescriptors to already running application, we've already executed initialSync for them. + await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptorsForExecution, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier))); } private async startWatcher(projectData: IProjectData, - deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise { let pattern = ["app"]; @@ -202,137 +222,128 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { } } - let filesToSync: string[] = [], - filesToRemove: string[] = []; - let timeoutTimer: NodeJS.Timer; - - const startTimeout = () => { - timeoutTimer = setTimeout(async () => { - await this.addActionToQueue(projectData.projectDir, async () => { - // TODO: Push consecutive actions to the queue, do not start them simultaneously - if (filesToSync.length || filesToRemove.length) { - try { - let currentFilesToSync = _.cloneDeep(filesToSync); - filesToSync = []; - - let currentFilesToRemove = _.cloneDeep(filesToRemove); - filesToRemove = []; - - const allModifiedFiles = [].concat(currentFilesToSync).concat(currentFilesToRemove); - const preparedPlatforms: string[] = []; - const rebuiltInformation: ILiveSyncBuildInfo[] = []; - - await this.$devicesService.execute(async (device: Mobile.IDevice) => { - - this.emit(LiveSyncEvents.liveSyncStarted, { - projectDir: projectData.projectDir, - deviceIdentifier: device.deviceInfo.identifier, - applicationIdentifier: projectData.projectId - }); - - // const platform = device.deviceInfo.platform; - const liveSyncProcessesInfo = _.find(this.liveSyncProcessesInfo, (info, projectDir) => projectDir === projectDir); - const deviceDescriptor = _.find(liveSyncProcessesInfo.deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - - await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, - projectData, deviceDescriptor, allModifiedFiles); - - const service = this.getLiveSyncService(device.deviceInfo.platform); - const settings: ILiveSyncWatchInfo = { - projectData, - filesToRemove: currentFilesToRemove, - filesToSync: currentFilesToSync, - isRebuilt: !!_.find(rebuiltInformation, info => info.isEmulator === device.isEmulator && info.platform === device.deviceInfo.platform), - syncAllFiles: liveSyncData.watchAllFiles, - useLiveEdit: liveSyncData.useLiveEdit - }; - - const liveSyncResultInfo = await service.liveSyncWatchAction(device, settings); - await this.refreshApplication(projectData, liveSyncResultInfo); - }, - (device: Mobile.IDevice) => { - const liveSyncProcessesInfo = _.find(this.liveSyncProcessesInfo, (info, projectDir) => projectDir === projectDir); - return liveSyncProcessesInfo && _.some(liveSyncProcessesInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); - } - ); - } catch (err) { - // we can remove the descriptor from action: - const allErrors = (err).allErrors; - if (allErrors && _.isArray(allErrors)) { - for (let deviceError of allErrors) { - this.$logger.warn(`Unable to apply changes for device: ${deviceError.deviceIdentifier}. Error is: ${deviceError.message}.`); - - this.emit(LiveSyncEvents.liveSyncError, { - error: deviceError, - deviceIdentifier: deviceError.deviceIdentifier, - projectDir: projectData.projectDir, - applicationIdentifier: projectData.projectId - }); - - await this.stopLiveSync(projectData.projectDir, [deviceError.deviceIdentifier]); + const currentWatcherInfo = this.liveSyncProcessesInfo[liveSyncData.projectDir].watcherInfo; + + if (!currentWatcherInfo || currentWatcherInfo.pattern !== pattern) { + if (currentWatcherInfo) { + currentWatcherInfo.watcher.close(); + } + + let filesToSync: string[] = [], + filesToRemove: string[] = []; + let timeoutTimer: NodeJS.Timer; + + const startTimeout = () => { + timeoutTimer = setTimeout(async () => { + // Push actions to the queue, do not start them simultaneously + await this.addActionToChain(projectData.projectDir, async () => { + if (filesToSync.length || filesToRemove.length) { + try { + let currentFilesToSync = _.cloneDeep(filesToSync); + filesToSync = []; + + let currentFilesToRemove = _.cloneDeep(filesToRemove); + filesToRemove = []; + + const allModifiedFiles = [].concat(currentFilesToSync).concat(currentFilesToRemove); + const preparedPlatforms: string[] = []; + const rebuiltInformation: ILiveSyncBuildInfo[] = []; + + await this.$devicesService.execute(async (device: Mobile.IDevice) => { + const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; + const deviceDescriptor = _.find(liveSyncProcessInfo.deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + + await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, projectData, deviceDescriptor, allModifiedFiles); + + const service = this.getLiveSyncService(device.deviceInfo.platform); + const settings: ILiveSyncWatchInfo = { + projectData, + filesToRemove: currentFilesToRemove, + filesToSync: currentFilesToSync, + isRebuilt: !!_.find(rebuiltInformation, info => info.isEmulator === device.isEmulator && info.platform === device.deviceInfo.platform), + syncAllFiles: liveSyncData.watchAllFiles, + useLiveEdit: liveSyncData.useLiveEdit + }; + + const liveSyncResultInfo = await service.liveSyncWatchAction(device, settings); + await this.refreshApplication(projectData, liveSyncResultInfo); + }, + (device: Mobile.IDevice) => { + const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; + return liveSyncProcessInfo && _.some(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); + } + ); + } catch (err) { + const allErrors = (err).allErrors; + + if (allErrors && _.isArray(allErrors)) { + for (let deviceError of allErrors) { + this.$logger.warn(`Unable to apply changes for device: ${deviceError.deviceIdentifier}. Error is: ${deviceError.message}.`); + + this.emit(LiveSyncEvents.liveSyncError, { + error: deviceError, + deviceIdentifier: deviceError.deviceIdentifier, + projectDir: projectData.projectDir, + applicationIdentifier: projectData.projectId + }); + + await this.stopLiveSync(projectData.projectDir, [deviceError.deviceIdentifier]); + } } } } - } - }); - }, 250); + }); + }, 250); - this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; - }; + this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; + }; - await this.$hooksService.executeBeforeHooks('watch'); + await this.$hooksService.executeBeforeHooks('watch'); - const watcherOptions: choki.WatchOptions = { - ignoreInitial: true, - cwd: liveSyncData.projectDir, - awaitWriteFinish: { - pollInterval: 100, - stabilityThreshold: 500 - }, - ignored: ["**/.*", ".*"] // hidden files - }; + const watcherOptions: choki.WatchOptions = { + ignoreInitial: true, + cwd: liveSyncData.projectDir, + awaitWriteFinish: { + pollInterval: 100, + stabilityThreshold: 500 + }, + ignored: ["**/.*", ".*"] // hidden files + }; - const watcher = choki.watch(pattern, watcherOptions) - .on("all", async (event: string, filePath: string) => { - clearTimeout(timeoutTimer); - const liveSyncProcessesInfo = _.find(this.liveSyncProcessesInfo, (info, projectDir) => projectDir === projectDir); - this.emit(LiveSyncEvents.liveSyncFileChangedEvent, { - projectDir: liveSyncData.projectDir, - applicationIdentifier: projectData.projectId, - deviceIdentifiers: liveSyncProcessesInfo.deviceDescriptors.map(dd => dd.identifier), - modifiedFile: filePath, - event - }); + const watcher = choki.watch(pattern, watcherOptions) + .on("all", async (event: string, filePath: string) => { + clearTimeout(timeoutTimer); - filePath = path.join(liveSyncData.projectDir, filePath); + filePath = path.join(liveSyncData.projectDir, filePath); - this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); + this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); - if (event === "add" || event === "addDir" || event === "change" /* <--- what to do when change event is raised ? */) { - filesToSync.push(filePath); - } else if (event === "unlink" || event === "unlinkDir") { - filesToRemove.push(filePath); - } + if (event === "add" || event === "addDir" || event === "change" /* <--- what to do when change event is raised ? */) { + filesToSync.push(filePath); + } else if (event === "unlink" || event === "unlinkDir") { + filesToRemove.push(filePath); + } - startTimeout(); - }); + startTimeout(); + }); - this.liveSyncProcessesInfo[liveSyncData.projectDir].watcher = watcher; - this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; + this.liveSyncProcessesInfo[liveSyncData.projectDir].watcherInfo = { watcher, pattern }; + this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; - this.$processService.attachToProcessExitSignals(this, () => { - _.keys(this.liveSyncProcessesInfo).forEach(projectDir => { - // Do not await here, we are in process exit's handler. - this.stopLiveSync(projectDir); + this.$processService.attachToProcessExitSignals(this, () => { + _.keys(this.liveSyncProcessesInfo).forEach(projectDir => { + // Do not await here, we are in process exit's handler. + this.stopLiveSync(projectDir); + }); }); - }); - this.$devicesService.on("deviceLost", async (device: Mobile.IDevice) => { - await this.stopLiveSync(projectData.projectDir, [device.deviceInfo.identifier]); - }); + this.$devicesService.on("deviceLost", async (device: Mobile.IDevice) => { + await this.stopLiveSync(projectData.projectDir, [device.deviceInfo.identifier]); + }); + } } - private async addActionToQueue(projectDir: string, action: () => Promise): Promise { + private async addActionToChain(projectDir: string, action: () => Promise): Promise { const liveSyncInfo = this.liveSyncProcessesInfo[projectDir]; if (liveSyncInfo) { liveSyncInfo.actionsChain = liveSyncInfo.actionsChain.then(async () => { From e293ef92d5e1e76ccf832dcef71772b6cd59b391 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 6 Jun 2017 14:52:09 +0300 Subject: [PATCH 076/212] Fix liveSync - add correct check for deviceDescriptors --- lib/services/livesync/livesync-service.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 1bb086aad2..cfe133d4d9 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -37,10 +37,14 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { liveSyncData: ILiveSyncInfo): Promise { // TODO: Initialize devicesService before that. const projectData = this.$projectDataService.getProjectData(liveSyncData.projectDir); - + // In case liveSync is called for a second time for the same projectDir. + const isAlreadyLiveSyncing = this.liveSyncProcessesInfo[projectData.projectDir] && !this.liveSyncProcessesInfo[projectData.projectDir].isStopped; this.setLiveSyncProcessInfo(liveSyncData.projectDir, deviceDescriptors); - await this.initialSync(projectData, deviceDescriptors, liveSyncData); + // TODO: Check if the _.difference actually works. + const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.difference(deviceDescriptors, this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors) : deviceDescriptors; + + await this.initialSync(projectData, deviceDescriptorsForInitialSync, liveSyncData); if (!liveSyncData.skipWatcher && deviceDescriptors && deviceDescriptors.length) { // Should be set after prepare @@ -169,9 +173,6 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const preparedPlatforms: string[] = []; const rebuiltInformation: ILiveSyncBuildInfo[] = []; - // Prevent cases where liveSync is called consecutive times with the same device, for example [ A, B, C ] and then [ A, B, D ] - we want to execute initialSync only for D. - const deviceDescriptorsForExecution = _.difference(deviceDescriptors, this.liveSyncProcessesInfo[liveSyncData.projectDir].deviceDescriptors); - // Now fullSync const deviceAction = async (device: Mobile.IDevice): Promise => { try { @@ -204,7 +205,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { // Execute the action only on the deviceDescriptors passed to initialSync. // In case where we add deviceDescriptors to already running application, we've already executed initialSync for them. - await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptorsForExecution, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier))); + await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier))); } private async startWatcher(projectData: IProjectData, From 2c4d0c9e986c6cdb36e05f600a0fc2513120075c Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Mon, 5 Jun 2017 13:40:20 +0300 Subject: [PATCH 077/212] Delete AndroidAppIdentifier --- lib/bootstrap.ts | 4 +- lib/constants.ts | 4 +- lib/definitions/livesync.d.ts | 10 +++++ lib/providers/device-app-data-provider.ts | 40 ----------------- .../android-device-livesync-service.ts | 38 +++++++--------- .../livesync/android-livesync-service.ts | 45 ++++++++++++------- lib/services/livesync/ios-livesync-service.ts | 3 +- lib/services/livesync/livesync-service.ts | 6 +-- 8 files changed, 64 insertions(+), 86 deletions(-) diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 1a41da311a..6c2d5ded46 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -104,10 +104,10 @@ $injector.require("androidToolsInfo", "./android-tools-info"); $injector.requireCommand("platform|clean", "./commands/platform-clean"); $injector.require("liveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript +$injector.require("androidLiveSyncService", "./services/livesync/android-livesync-service"); +$injector.require("iOSLiveSyncService", "./services/livesync/ios-livesync-service"); $injector.require("usbLiveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript $injector.require("iosLiveSyncServiceLocator", "./services/livesync/ios-device-livesync-service"); -$injector.require("androidLiveSyncServiceLocator", "./services/livesync/android-device-livesync-service"); - $injector.require("sysInfo", "./sys-info"); $injector.require("iOSNotificationService", "./services/ios-notification-service"); diff --git a/lib/constants.ts b/lib/constants.ts index 4345525a4d..cc8d97b967 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -66,7 +66,9 @@ class ItunesConnectApplicationTypesClass implements IiTunesConnectApplicationTyp } export const ItunesConnectApplicationTypes = new ItunesConnectApplicationTypesClass(); - +export const SYNC_DIR_NAME = "sync"; +export const REMOVEDSYNC_DIR_NAME = "removedsync"; +export const FULLSYNC_DIR_NAME = "fullsync"; export const ANGULAR_NAME = "angular"; export const TYPESCRIPT_NAME = "typescript"; export const BUILD_OUTPUT_EVENT_NAME = "buildOutput"; diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 72b2e3ef63..319bee25cc 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -163,6 +163,7 @@ interface ILiveSyncResultInfo { interface IFullSyncInfo { projectData: IProjectData; device: Mobile.IDevice; + watch: boolean; syncAllFiles: boolean; useLiveEdit?: boolean; } @@ -194,3 +195,12 @@ interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase */ removeFiles(appIdentifier: string, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectId: string): Promise; } + +interface IAndroidNativeScriptDeviceLiveSyncService { + /** + * Retrieves the android device's hash service. + * @param {string} appIdentifier Application identifier. + * @return {Promise} The hash service + */ + getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService; +} diff --git a/lib/providers/device-app-data-provider.ts b/lib/providers/device-app-data-provider.ts index 1ee89f013f..5c19d04521 100644 --- a/lib/providers/device-app-data-provider.ts +++ b/lib/providers/device-app-data-provider.ts @@ -1,10 +1,5 @@ import * as deviceAppDataBaseLib from "../common/mobile/device-app-data/device-app-data-base"; import * as path from "path"; -import { AndroidDeviceHashService } from "../common/mobile/android/android-device-hash-service"; -import { DeviceAndroidDebugBridge } from "../common/mobile/android/device-android-debug-bridge"; - -const SYNC_DIR_NAME = "sync"; -const FULLSYNC_DIR_NAME = "fullsync"; export class IOSAppIdentifier extends deviceAppDataBaseLib.DeviceAppDataBase implements Mobile.IDeviceAppData { private static DEVICE_PROJECT_ROOT_PATH = "Library/Application Support/LiveSync/app"; @@ -43,46 +38,11 @@ export class IOSAppIdentifier extends deviceAppDataBaseLib.DeviceAppDataBase imp } } -export class AndroidAppIdentifier extends deviceAppDataBaseLib.DeviceAppDataBase implements Mobile.IDeviceAppData { - constructor(_appIdentifier: string, - public device: Mobile.IDevice, - public platform: string, - private $options: IOptions, - private $injector: IInjector) { - super(_appIdentifier); - } - - private _deviceProjectRootPath: string; - - public async getDeviceProjectRootPath(): Promise { - if (!this._deviceProjectRootPath) { - let syncFolderName = await this.getSyncFolderName(); - this._deviceProjectRootPath = `/data/local/tmp/${this.appIdentifier}/${syncFolderName}`; - } - - return this._deviceProjectRootPath; - } - - public async isLiveSyncSupported(): Promise { - return true; - } - - private async getSyncFolderName(): Promise { - let adb = this.$injector.resolve(DeviceAndroidDebugBridge, { identifier: this.device.deviceInfo.identifier }); - let deviceHashService: AndroidDeviceHashService = this.$injector.resolve(AndroidDeviceHashService, { adb: adb, appIdentifier: this.appIdentifier }); - let hashFile = this.$options.force ? null : await deviceHashService.doesShasumFileExistsOnDevice(); - return this.$options.watch || hashFile ? SYNC_DIR_NAME : FULLSYNC_DIR_NAME; - } -} - export class DeviceAppDataProvider implements Mobile.IDeviceAppDataProvider { public createFactoryRules(): IDictionary { return { iOS: { vanilla: IOSAppIdentifier - }, - Android: { - vanilla: AndroidAppIdentifier } }; } diff --git a/lib/services/livesync/android-device-livesync-service.ts b/lib/services/livesync/android-device-livesync-service.ts index 2f6e34dcbb..a949a2d363 100644 --- a/lib/services/livesync/android-device-livesync-service.ts +++ b/lib/services/livesync/android-device-livesync-service.ts @@ -1,13 +1,14 @@ import { DeviceAndroidDebugBridge } from "../../common/mobile/android/device-android-debug-bridge"; import { AndroidDeviceHashService } from "../../common/mobile/android/android-device-hash-service"; import * as helpers from "../../common/helpers"; +import { SYNC_DIR_NAME, FULLSYNC_DIR_NAME, REMOVEDSYNC_DIR_NAME } from "../../constants"; import { cache } from "../../common/decorators"; import * as path from "path"; import * as net from "net"; import { EOL } from "os"; -export class AndroidLiveSyncService implements INativeScriptDeviceLiveSyncService { - private static FAST_SYNC_FILE_EXTENSIONS = [".css", ".xml", ".html"]; +export class AndroidDeviceLiveSyncService implements IAndroidNativeScriptDeviceLiveSyncService { + private static FAST_SYNC_FILE_EXTENSIONS = [".css", ".xml", ".html"]; private static BACKEND_PORT = 18182; private device: Mobile.IAndroidDevice; @@ -37,7 +38,7 @@ export class AndroidLiveSyncService implements INativeScriptDeviceLiveSyncServic `/data/local/tmp/${deviceAppData.appIdentifier}/sync`] ); - let canExecuteFastSync = ! liveSyncInfo.isFullSync && !_.some(localToDevicePaths, + let canExecuteFastSync = !liveSyncInfo.isFullSync && !_.some(localToDevicePaths, (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.canExecuteFastSync(localToDevicePath.getLocalPath(), projectData, this.device.deviceInfo.platform)); if (canExecuteFastSync) { @@ -50,7 +51,7 @@ export class AndroidLiveSyncService implements INativeScriptDeviceLiveSyncServic @cache() private getFastLiveSyncFileExtensions(platform: string, projectData: IProjectData): string[] { const platformData = this.$platformsData.getPlatformData(platform, projectData); - const fastSyncFileExtensions = AndroidLiveSyncService.FAST_SYNC_FILE_EXTENSIONS.concat(platformData.fastLivesyncFileExtensions); + const fastSyncFileExtensions = AndroidDeviceLiveSyncService.FAST_SYNC_FILE_EXTENSIONS.concat(platformData.fastLivesyncFileExtensions); return fastSyncFileExtensions; } @@ -87,13 +88,13 @@ export class AndroidLiveSyncService implements INativeScriptDeviceLiveSyncServic await this.device.adb.executeShellCommand(["rm", "-f", deviceRootPath]); } - this.device.adb.executeShellCommand(["rm", "-rf", this.$mobileHelper.buildDevicePath(deviceRootPath, "fullsync"), - this.$mobileHelper.buildDevicePath(deviceRootPath, "sync"), - await this.$mobileHelper.buildDevicePath(deviceRootPath, "removedsync")]); + this.device.adb.executeShellCommand(["rm", "-rf", this.$mobileHelper.buildDevicePath(deviceRootPath, FULLSYNC_DIR_NAME), + this.$mobileHelper.buildDevicePath(deviceRootPath, SYNC_DIR_NAME), + await this.$mobileHelper.buildDevicePath(deviceRootPath, REMOVEDSYNC_DIR_NAME)]); } private async reloadPage(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { - await this.device.adb.executeCommand(["forward", `tcp:${AndroidLiveSyncService.BACKEND_PORT.toString()}`, `localabstract:${deviceAppData.appIdentifier}-livesync`]); + await this.device.adb.executeCommand(["forward", `tcp:${AndroidDeviceLiveSyncService.BACKEND_PORT.toString()}`, `localabstract:${deviceAppData.appIdentifier}-livesync`]); if (!await this.sendPageReloadMessage()) { await this.restartApplication(deviceAppData); } @@ -104,13 +105,19 @@ export class AndroidLiveSyncService implements INativeScriptDeviceLiveSyncServic for (let localToDevicePathData of localToDevicePaths) { let relativeUnixPath = _.trimStart(helpers.fromWindowsRelativePathToUnix(localToDevicePathData.getRelativeToProjectBasePath()), "/"); - let deviceFilePath = this.$mobileHelper.buildDevicePath(deviceRootPath, "removedsync", relativeUnixPath); + let deviceFilePath = this.$mobileHelper.buildDevicePath(deviceRootPath, REMOVEDSYNC_DIR_NAME, relativeUnixPath); await this.device.adb.executeShellCommand(["mkdir", "-p", path.dirname(deviceFilePath), " && ", "touch", deviceFilePath]); } await this.getDeviceHashService(projectId).removeHashes(localToDevicePaths); } + @cache() + public getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService { + let adb = this.$injector.resolve(DeviceAndroidDebugBridge, { identifier: this.device.deviceInfo.identifier }); + return this.$injector.resolve(AndroidDeviceHashService, { adb, appIdentifier }); + } + private getDeviceRootPath(appIdentifier: string): string { return `/data/local/tmp/${appIdentifier}`; } @@ -120,7 +127,7 @@ export class AndroidLiveSyncService implements INativeScriptDeviceLiveSyncServic let isResolved = false; let socket = new net.Socket(); - socket.connect(AndroidLiveSyncService.BACKEND_PORT, '127.0.0.1', () => { + socket.connect(AndroidDeviceLiveSyncService.BACKEND_PORT, '127.0.0.1', () => { socket.write(new Buffer([0, 0, 0, 1, 1])); }); socket.on("data", (data: any) => { @@ -141,15 +148,4 @@ export class AndroidLiveSyncService implements INativeScriptDeviceLiveSyncServic }); }); } - - private _deviceHashService: Mobile.IAndroidDeviceHashService; - private getDeviceHashService(projectId: string): Mobile.IAndroidDeviceHashService { - if (!this._deviceHashService) { - let adb = this.$injector.resolve(DeviceAndroidDebugBridge, { identifier: this.device.deviceInfo.identifier }); - this._deviceHashService = this.$injector.resolve(AndroidDeviceHashService, { adb: adb, appIdentifier: projectId }); - } - - return this._deviceHashService; - } } -$injector.register("androidLiveSyncServiceLocator", { factory: AndroidLiveSyncService }); diff --git a/lib/services/livesync/android-livesync-service.ts b/lib/services/livesync/android-livesync-service.ts index c1ab1d03be..2a5bb57472 100644 --- a/lib/services/livesync/android-livesync-service.ts +++ b/lib/services/livesync/android-livesync-service.ts @@ -1,6 +1,6 @@ -import * as deviceAppDataIdentifiers from "../../providers/device-app-data-provider"; import * as path from "path"; -import * as adls from "./android-device-livesync-service"; +import { AndroidDeviceLiveSyncService } from "./android-device-livesync-service"; +import { APP_FOLDER_NAME, SYNC_DIR_NAME, FULLSYNC_DIR_NAME } from "../../constants"; export class AndroidLiveSyncService implements IPlatformLiveSyncService { constructor(private $projectFilesManager: IProjectFilesManager, @@ -14,14 +14,13 @@ export class AndroidLiveSyncService implements IPlatformLiveSyncService { public async fullSync(syncInfo: IFullSyncInfo): Promise { const projectData = syncInfo.projectData; const device = syncInfo.device; - const deviceLiveSyncService = this.$injector.resolve(adls.AndroidLiveSyncService, { _device: device }); + const deviceLiveSyncService = this.$injector.resolve(AndroidDeviceLiveSyncService, { _device: device }); const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); - const deviceAppData = this.$injector.resolve(deviceAppDataIdentifiers.AndroidAppIdentifier, - { _appIdentifier: projectData.projectId, device, platform: device.deviceInfo.platform }); + const deviceAppData = await this.getAppData(syncInfo, deviceLiveSyncService); await deviceLiveSyncService.beforeLiveSyncAction(deviceAppData); - const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, "app"); + const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, null, []); await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, true); @@ -34,8 +33,9 @@ export class AndroidLiveSyncService implements IPlatformLiveSyncService { public async liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise { const projectData = liveSyncInfo.projectData; - const deviceAppData = this.$injector.resolve(deviceAppDataIdentifiers.AndroidAppIdentifier, - { _appIdentifier: projectData.projectId, device, platform: device.deviceInfo.platform }); + const syncInfo = _.merge({ device, watch: true }, liveSyncInfo); + const deviceLiveSyncService = this.$injector.resolve(AndroidDeviceLiveSyncService, { _device: device }); + const deviceAppData = await this.getAppData(syncInfo, deviceLiveSyncService); let modifiedLocalToDevicePaths: Mobile.ILocalToDevicePathData[] = []; if (liveSyncInfo.filesToSync.length) { @@ -52,7 +52,7 @@ export class AndroidLiveSyncService implements IPlatformLiveSyncService { if (existingFiles.length) { let platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); - const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, "app"); + const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); let localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, mappedFiles, []); modifiedLocalToDevicePaths.push(...localToDevicePaths); @@ -65,11 +65,11 @@ export class AndroidLiveSyncService implements IPlatformLiveSyncService { let platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); const mappedFiles = _.map(filePaths, filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)); - const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, "app"); + const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); let localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, mappedFiles, []); modifiedLocalToDevicePaths.push(...localToDevicePaths); - const deviceLiveSyncService = this.$injector.resolve(adls.AndroidLiveSyncService, { _device: device }); + const deviceLiveSyncService = this.$injector.resolve(AndroidDeviceLiveSyncService, { _device: device }); deviceLiveSyncService.removeFiles(projectData.projectId, localToDevicePaths, projectData.projectId); } @@ -80,12 +80,9 @@ export class AndroidLiveSyncService implements IPlatformLiveSyncService { }; } - public async refreshApplication( - projectData: IProjectData, - liveSyncInfo: ILiveSyncResultInfo - ): Promise { + public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { if (liveSyncInfo.isFullSync || liveSyncInfo.modifiedFilesData.length) { - let deviceLiveSyncService = this.$injector.resolve(adls.AndroidLiveSyncService, { _device: liveSyncInfo.deviceAppData.device }); + let deviceLiveSyncService = this.$injector.resolve(AndroidDeviceLiveSyncService, { _device: liveSyncInfo.deviceAppData.device }); this.$logger.info("Refreshing application..."); await deviceLiveSyncService.refreshApplication(projectData, liveSyncInfo); } @@ -97,7 +94,21 @@ export class AndroidLiveSyncService implements IPlatformLiveSyncService { } else { await deviceAppData.device.fileSystem.transferFiles(deviceAppData, localToDevicePaths); } + } - console.log("TRANSFEREEDDDDDDD!!!!!!"); + private async getAppData(syncInfo: IFullSyncInfo, deviceLiveSyncService: IAndroidNativeScriptDeviceLiveSyncService): Promise { + return { + appIdentifier: syncInfo.device.deviceInfo.identifier, + device: syncInfo.device, + platform: syncInfo.device.deviceInfo.platform, + getDeviceProjectRootPath: async () => { + const hashService = deviceLiveSyncService.getDeviceHashService(syncInfo.device.deviceInfo.identifier); + const hashFile = syncInfo.syncAllFiles ? null : await hashService.doesShasumFileExistsOnDevice(); + const syncFolderName = syncInfo.watch || hashFile ? SYNC_DIR_NAME : FULLSYNC_DIR_NAME; + return `/data/local/tmp/${syncInfo.device.deviceInfo.identifier}/${syncFolderName}`; + }, + isLiveSyncSupported: async () => true + }; } } +$injector.register("androidLiveSyncService", AndroidLiveSyncService); diff --git a/lib/services/livesync/ios-livesync-service.ts b/lib/services/livesync/ios-livesync-service.ts index f6fe6d9935..7cfbad5f29 100644 --- a/lib/services/livesync/ios-livesync-service.ts +++ b/lib/services/livesync/ios-livesync-service.ts @@ -76,7 +76,7 @@ export class IOSLiveSyncService implements IPlatformLiveSyncService { if (liveSyncInfo.isRebuilt) { // In this case we should execute fullsync: - await this.fullSync({ projectData, device, syncAllFiles: liveSyncInfo.syncAllFiles }); + await this.fullSync({ projectData, device, syncAllFiles: liveSyncInfo.syncAllFiles, watch: true }); } else { if (liveSyncInfo.filesToSync.length) { const filesToSync = liveSyncInfo.filesToSync; @@ -138,3 +138,4 @@ export class IOSLiveSyncService implements IPlatformLiveSyncService { console.log("### ios TRANSFEREEDDDDDDD!!!!!!"); } } +$injector.register("iOSLiveSyncService", IOSLiveSyncService); diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index cfe133d4d9..7daa165271 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -1,7 +1,5 @@ import * as path from "path"; import * as choki from "chokidar"; -import * as iOSLs from "./ios-livesync-service"; -import * as androidLs from "./android-livesync-service"; import { EventEmitter } from "events"; import { exported } from "../../common/decorators"; import { hook } from "../../common/helpers"; @@ -123,9 +121,9 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { // TODO: Register both livesync services in injector private getLiveSyncService(platform: string): IPlatformLiveSyncService { if (this.$mobileHelper.isiOSPlatform(platform)) { - return this.$injector.resolve(iOSLs.IOSLiveSyncService); + return this.$injector.resolve("iOSLiveSyncService"); } else if (this.$mobileHelper.isAndroidPlatform(platform)) { - return this.$injector.resolve(androidLs.AndroidLiveSyncService); + return this.$injector.resolve("androidLiveSyncService"); } throw new Error(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); From b3b7df8b6ba0d0caf6ac06a7bdb30a23000a53a9 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Tue, 6 Jun 2017 18:20:01 +0300 Subject: [PATCH 078/212] Remove device-path-provider --- lib/bootstrap.ts | 1 + lib/constants.ts | 1 + lib/device-path-provider.ts | 44 ++++++++++++++++ lib/providers/device-app-data-provider.ts | 50 ------------------- .../livesync/android-livesync-service.ts | 29 +++-------- lib/services/livesync/ios-livesync-service.ts | 18 +++---- lib/services/livesync/livesync-service.ts | 7 ++- .../platform-livesync-service-base.ts | 17 +++++++ lib/services/platform-service.ts | 7 +-- test/npm-support.ts | 2 - test/platform-commands.ts | 2 - test/platform-service.ts | 2 - test/plugins-service.ts | 2 - 13 files changed, 90 insertions(+), 92 deletions(-) create mode 100644 lib/device-path-provider.ts delete mode 100644 lib/providers/device-app-data-provider.ts create mode 100644 lib/services/livesync/platform-livesync-service-base.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 6c2d5ded46..44244b6be9 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -100,6 +100,7 @@ $injector.require("infoService", "./services/info-service"); $injector.requireCommand("info", "./commands/info"); $injector.require("androidToolsInfo", "./android-tools-info"); +$injector.require("devicePathProvider", "./device-path-provider"); $injector.requireCommand("platform|clean", "./commands/platform-clean"); diff --git a/lib/constants.ts b/lib/constants.ts index cc8d97b967..3652134ce8 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -69,6 +69,7 @@ export const ItunesConnectApplicationTypes = new ItunesConnectApplicationTypesCl export const SYNC_DIR_NAME = "sync"; export const REMOVEDSYNC_DIR_NAME = "removedsync"; export const FULLSYNC_DIR_NAME = "fullsync"; +export const IOS_DEVICE_PROJECT_ROOT_PATH = "Library/Application Support/LiveSync/app"; export const ANGULAR_NAME = "angular"; export const TYPESCRIPT_NAME = "typescript"; export const BUILD_OUTPUT_EVENT_NAME = "buildOutput"; diff --git a/lib/device-path-provider.ts b/lib/device-path-provider.ts new file mode 100644 index 0000000000..995d322295 --- /dev/null +++ b/lib/device-path-provider.ts @@ -0,0 +1,44 @@ +import { fromWindowsRelativePathToUnix } from "./common/helpers"; +import { IOS_DEVICE_PROJECT_ROOT_PATH, SYNC_DIR_NAME, FULLSYNC_DIR_NAME } from "./constants"; +import { AndroidDeviceLiveSyncService } from "./services/livesync/android-device-livesync-service"; +import * as path from "path"; + +export class DevicePathProvider implements Mobile.IDevicePathProvider { + constructor(private $mobileHelper: Mobile.IMobileHelper, + private $injector: IInjector, + private $iOSSimResolver: Mobile.IiOSSimResolver) { + } + + public async getDeviceBuildInfoDirname(device: Mobile.IDevice, appIdentifier: string): Promise { + let result = ""; + if (this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform)) { + result = path.basename(await this.getDeviceProjectRootPath(device, { appIdentifier })); + } else if (this.$mobileHelper.isAndroidPlatform(device.deviceInfo.platform)) { + result = `/data/local/tmp/${appIdentifier}`; + } + + return result; + } + + public async getDeviceProjectRootPath(device: Mobile.IDevice, options: Mobile.IDeviceProjectRootOptions): Promise { + let projectRoot = ""; + if (this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform)) { + if (device.isEmulator) { + let applicationPath = this.$iOSSimResolver.iOSSim.getApplicationPath(device.deviceInfo.identifier, options.appIdentifier); + projectRoot = path.join(applicationPath, "app"); + } else { + projectRoot = IOS_DEVICE_PROJECT_ROOT_PATH; + } + } else if (this.$mobileHelper.isAndroidPlatform(device.deviceInfo.platform)) { + const deviceLiveSyncService = this.$injector.resolve(AndroidDeviceLiveSyncService, { _device: device }); + const hashService = deviceLiveSyncService.getDeviceHashService(options.appIdentifier); + const hashFile = options.syncAllFiles ? null : await hashService.doesShasumFileExistsOnDevice(); + const syncFolderName = options.watch || hashFile ? SYNC_DIR_NAME : FULLSYNC_DIR_NAME; + projectRoot = `/data/local/tmp/${options.appIdentifier}/${syncFolderName}`; + } + + return fromWindowsRelativePathToUnix(projectRoot); + } +} + +$injector.register("devicePathProvider", DevicePathProvider); diff --git a/lib/providers/device-app-data-provider.ts b/lib/providers/device-app-data-provider.ts deleted file mode 100644 index 5c19d04521..0000000000 --- a/lib/providers/device-app-data-provider.ts +++ /dev/null @@ -1,50 +0,0 @@ -import * as deviceAppDataBaseLib from "../common/mobile/device-app-data/device-app-data-base"; -import * as path from "path"; - -export class IOSAppIdentifier extends deviceAppDataBaseLib.DeviceAppDataBase implements Mobile.IDeviceAppData { - private static DEVICE_PROJECT_ROOT_PATH = "Library/Application Support/LiveSync/app"; - private _deviceProjectRootPath: string = null; - - constructor(_appIdentifier: string, - public device: Mobile.IDevice, - public platform: string, - private $iOSSimResolver: Mobile.IiOSSimResolver) { - super(_appIdentifier); - } - - public async getDeviceProjectRootPath(): Promise { - if (!this._deviceProjectRootPath) { - if (this.device.isEmulator) { - let applicationPath = this.$iOSSimResolver.iOSSim.getApplicationPath(this.device.deviceInfo.identifier, this.appIdentifier); - this._deviceProjectRootPath = path.join(applicationPath, "app"); - } else { - this._deviceProjectRootPath = IOSAppIdentifier.DEVICE_PROJECT_ROOT_PATH; - } - } - - return this._getDeviceProjectRootPath(this._deviceProjectRootPath); - } - - public get deviceSyncZipPath(): string { - if (this.device.isEmulator) { - return undefined; - } else { - return "Library/Application Support/LiveSync/sync.zip"; - } - } - - public async isLiveSyncSupported(): Promise { - return true; - } -} - -export class DeviceAppDataProvider implements Mobile.IDeviceAppDataProvider { - public createFactoryRules(): IDictionary { - return { - iOS: { - vanilla: IOSAppIdentifier - } - }; - } -} -$injector.register("deviceAppDataProvider", DeviceAppDataProvider); diff --git a/lib/services/livesync/android-livesync-service.ts b/lib/services/livesync/android-livesync-service.ts index 2a5bb57472..ac7e31061a 100644 --- a/lib/services/livesync/android-livesync-service.ts +++ b/lib/services/livesync/android-livesync-service.ts @@ -1,14 +1,17 @@ import * as path from "path"; import { AndroidDeviceLiveSyncService } from "./android-device-livesync-service"; -import { APP_FOLDER_NAME, SYNC_DIR_NAME, FULLSYNC_DIR_NAME } from "../../constants"; +import { APP_FOLDER_NAME } from "../../constants"; +import { PlatformLiveSyncServiceBase } from "./platform-livesync-service-base"; -export class AndroidLiveSyncService implements IPlatformLiveSyncService { +export class AndroidLiveSyncService extends PlatformLiveSyncServiceBase implements IPlatformLiveSyncService { constructor(private $projectFilesManager: IProjectFilesManager, private $platformsData: IPlatformsData, private $logger: ILogger, private $projectFilesProvider: IProjectFilesProvider, private $fs: IFileSystem, - private $injector: IInjector) { + private $injector: IInjector, + $devicePathProvider: Mobile.IDevicePathProvider) { + super($devicePathProvider); } public async fullSync(syncInfo: IFullSyncInfo): Promise { @@ -17,7 +20,7 @@ export class AndroidLiveSyncService implements IPlatformLiveSyncService { const deviceLiveSyncService = this.$injector.resolve(AndroidDeviceLiveSyncService, { _device: device }); const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); - const deviceAppData = await this.getAppData(syncInfo, deviceLiveSyncService); + const deviceAppData = await this.getAppData(syncInfo); await deviceLiveSyncService.beforeLiveSyncAction(deviceAppData); const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); @@ -34,8 +37,7 @@ export class AndroidLiveSyncService implements IPlatformLiveSyncService { public async liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise { const projectData = liveSyncInfo.projectData; const syncInfo = _.merge({ device, watch: true }, liveSyncInfo); - const deviceLiveSyncService = this.$injector.resolve(AndroidDeviceLiveSyncService, { _device: device }); - const deviceAppData = await this.getAppData(syncInfo, deviceLiveSyncService); + const deviceAppData = await this.getAppData(syncInfo); let modifiedLocalToDevicePaths: Mobile.ILocalToDevicePathData[] = []; if (liveSyncInfo.filesToSync.length) { @@ -95,20 +97,5 @@ export class AndroidLiveSyncService implements IPlatformLiveSyncService { await deviceAppData.device.fileSystem.transferFiles(deviceAppData, localToDevicePaths); } } - - private async getAppData(syncInfo: IFullSyncInfo, deviceLiveSyncService: IAndroidNativeScriptDeviceLiveSyncService): Promise { - return { - appIdentifier: syncInfo.device.deviceInfo.identifier, - device: syncInfo.device, - platform: syncInfo.device.deviceInfo.platform, - getDeviceProjectRootPath: async () => { - const hashService = deviceLiveSyncService.getDeviceHashService(syncInfo.device.deviceInfo.identifier); - const hashFile = syncInfo.syncAllFiles ? null : await hashService.doesShasumFileExistsOnDevice(); - const syncFolderName = syncInfo.watch || hashFile ? SYNC_DIR_NAME : FULLSYNC_DIR_NAME; - return `/data/local/tmp/${syncInfo.device.deviceInfo.identifier}/${syncFolderName}`; - }, - isLiveSyncSupported: async () => true - }; - } } $injector.register("androidLiveSyncService", AndroidLiveSyncService); diff --git a/lib/services/livesync/ios-livesync-service.ts b/lib/services/livesync/ios-livesync-service.ts index 7cfbad5f29..edd08fab94 100644 --- a/lib/services/livesync/ios-livesync-service.ts +++ b/lib/services/livesync/ios-livesync-service.ts @@ -1,18 +1,19 @@ -import * as deviceAppDataIdentifiers from "../../providers/device-app-data-provider"; import * as path from "path"; import * as iosdls from "./ios-device-livesync-service"; import * as temp from "temp"; +import { PlatformLiveSyncServiceBase } from "./platform-livesync-service-base"; // import * as uuid from "uuid"; -export class IOSLiveSyncService implements IPlatformLiveSyncService { - constructor( - private $devicesService: Mobile.IDevicesService, +export class IOSLiveSyncService extends PlatformLiveSyncServiceBase implements IPlatformLiveSyncService { + constructor(private $devicesService: Mobile.IDevicesService, private $projectFilesManager: IProjectFilesManager, private $platformsData: IPlatformsData, private $logger: ILogger, private $projectFilesProvider: IProjectFilesProvider, private $fs: IFileSystem, - private $injector: IInjector) { + private $injector: IInjector, + $devicePathProvider: Mobile.IDevicePathProvider) { + super($devicePathProvider); } /* @@ -25,8 +26,7 @@ export class IOSLiveSyncService implements IPlatformLiveSyncService { const projectData = syncInfo.projectData; const device = syncInfo.device; const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); - const deviceAppData = this.$injector.resolve(deviceAppDataIdentifiers.IOSAppIdentifier, - { _appIdentifier: projectData.projectId, device, platform: device.deviceInfo.platform }); + const deviceAppData = await this.getAppData(syncInfo); const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, "app"); if (device.isEmulator) { @@ -70,8 +70,8 @@ export class IOSLiveSyncService implements IPlatformLiveSyncService { public async liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise { const projectData = liveSyncInfo.projectData; - const deviceAppData = this.$injector.resolve(deviceAppDataIdentifiers.IOSAppIdentifier, - { _appIdentifier: projectData.projectId, device, platform: device.deviceInfo.platform }); + const syncInfo = _.merge({ device, watch: true }, liveSyncInfo); + const deviceAppData = await this.getAppData(syncInfo); let modifiedLocalToDevicePaths: Mobile.ILocalToDevicePathData[] = []; if (liveSyncInfo.isRebuilt) { diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 7daa165271..b24e42c0ab 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -185,7 +185,12 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, projectData, deviceDescriptor); - const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ projectData, device, syncAllFiles: liveSyncData.watchAllFiles, useLiveEdit: liveSyncData.useLiveEdit }); + const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ + projectData, device, + syncAllFiles: liveSyncData.watchAllFiles, + useLiveEdit: liveSyncData.useLiveEdit, + watch: !liveSyncData.skipWatcher + }); await this.refreshApplication(projectData, liveSyncResultInfo); } catch (err) { this.$logger.warn(`Unable to apply changes on device: ${err.deviceIdentifier}. Error is: ${err.message}.`); diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts new file mode 100644 index 0000000000..6688616e97 --- /dev/null +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -0,0 +1,17 @@ +export class PlatformLiveSyncServiceBase { + constructor( + private $devicePathProvider: Mobile.IDevicePathProvider, + ) { + } + + protected async getAppData(syncInfo: IFullSyncInfo): Promise { + const deviceProjectRootOptions: Mobile.IDeviceProjectRootOptions = _.assign({ appIdentifier: syncInfo.projectData.projectId }, syncInfo); + return { + appIdentifier: syncInfo.projectData.projectId, + device: syncInfo.device, + platform: syncInfo.device.deviceInfo.platform, + getDeviceProjectRootPath: () => this.$devicePathProvider.getDeviceProjectRootPath(syncInfo.device, deviceProjectRootOptions), + isLiveSyncSupported: async () => true + }; + } +} diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 0a23d521f7..a3e490de0b 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -34,10 +34,10 @@ export class PlatformService extends EventEmitter implements IPlatformService { private $projectFilesManager: IProjectFilesManager, private $mobileHelper: Mobile.IMobileHelper, private $hostInfo: IHostInfo, + private $devicePathProvider: Mobile.IDevicePathProvider, private $xmlValidator: IXmlValidator, private $npm: INodePackageManager, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - private $deviceAppDataFactory: Mobile.IDeviceAppDataFactory, private $projectChangesService: IProjectChangesService, private $analyticsService: IAnalyticsService) { super(); @@ -547,8 +547,9 @@ export class PlatformService extends EventEmitter implements IPlatformService { } private async getDeviceBuildInfoFilePath(device: Mobile.IDevice, projectData: IProjectData): Promise { - let deviceAppData = this.$deviceAppDataFactory.create(projectData.projectId, device.deviceInfo.platform, device); - let deviceRootPath = path.dirname(await deviceAppData.getDeviceProjectRootPath()); + // let deviceAppData = this.$deviceAppDataFactory.create(projectData.projectId, device.deviceInfo.platform, device); + // let deviceRootPath = path.dirname(await deviceAppData.getDeviceProjectRootPath()); + const deviceRootPath = await this.$devicePathProvider.getDeviceBuildInfoDirname(device, projectData.projectId); return helpers.fromWindowsRelativePathToUnix(path.join(deviceRootPath, buildInfoFileName)); } diff --git a/test/npm-support.ts b/test/npm-support.ts index 3b36faa958..55e2d1a93d 100644 --- a/test/npm-support.ts +++ b/test/npm-support.ts @@ -20,7 +20,6 @@ import { DeviceAppDataFactory } from "../lib/common/mobile/device-app-data/devic 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"; @@ -74,7 +73,6 @@ function createTestInjector(): IInjector { testInjector.register("localToDevicePathDataFactory", LocalToDevicePathDataFactory); testInjector.register("mobileHelper", MobileHelper); testInjector.register("projectFilesProvider", ProjectFilesProvider); - testInjector.register("deviceAppDataProvider", DeviceAppDataProvider); testInjector.register("mobilePlatformsCapabilities", MobilePlatformsCapabilities); testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); testInjector.register("xmlValidator", XmlValidator); diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 202f0d8c45..668a3a5268 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -15,7 +15,6 @@ import { DeviceAppDataFactory } from "../lib/common/mobile/device-app-data/devic 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"; @@ -131,7 +130,6 @@ function createTestInjector() { testInjector.register("localToDevicePathDataFactory", LocalToDevicePathDataFactory); testInjector.register("mobileHelper", MobileHelper); testInjector.register("projectFilesProvider", ProjectFilesProvider); - testInjector.register("deviceAppDataProvider", DeviceAppDataProvider); testInjector.register("mobilePlatformsCapabilities", MobilePlatformsCapabilities); testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); testInjector.register("xmlValidator", XmlValidator); diff --git a/test/platform-service.ts b/test/platform-service.ts index a1d95ee1b3..d6ce79f0b2 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -13,7 +13,6 @@ import { DeviceAppDataFactory } from "../lib/common/mobile/device-app-data/devic 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"; @@ -69,7 +68,6 @@ function createTestInjector() { testInjector.register("localToDevicePathDataFactory", LocalToDevicePathDataFactory); testInjector.register("mobileHelper", MobileHelper); testInjector.register("projectFilesProvider", ProjectFilesProvider); - testInjector.register("deviceAppDataProvider", DeviceAppDataProvider); testInjector.register("mobilePlatformsCapabilities", MobilePlatformsCapabilities); testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); testInjector.register("xmlValidator", XmlValidator); diff --git a/test/plugins-service.ts b/test/plugins-service.ts index 2ad19b077a..fa21aa9b98 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -27,7 +27,6 @@ import { DeviceAppDataFactory } from "../lib/common/mobile/device-app-data/devic 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"; @@ -90,7 +89,6 @@ function createTestInjector() { testInjector.register("localToDevicePathDataFactory", LocalToDevicePathDataFactory); testInjector.register("mobileHelper", MobileHelper); testInjector.register("projectFilesProvider", ProjectFilesProvider); - testInjector.register("deviceAppDataProvider", DeviceAppDataProvider); testInjector.register("mobilePlatformsCapabilities", MobilePlatformsCapabilities); testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); testInjector.register("projectTemplatesService", { From 4aafecc3396b28c94860c704aeb6b774da579a6f Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 6 Jun 2017 22:08:29 +0300 Subject: [PATCH 079/212] Fix multiple rebuilds for the same platform --- lib/definitions/platform.d.ts | 9 ++++++--- lib/services/ios-project-service.ts | 4 ++-- lib/services/livesync/livesync-service.ts | 22 ++++++++++++---------- lib/services/platform-service.ts | 15 +++++++-------- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index e132a35bda..ee3ee8ad64 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -137,17 +137,19 @@ interface IPlatformService extends NodeJS.EventEmitter { * Returns information about the latest built application for device in the current project. * @param {IPlatformData} platformData Data describing the current platform. * @param {IBuildConfig} buildConfig Defines if the build is for release configuration. + * @param {string} @optional outputPath Directory that should contain the build artifact. * @returns {IApplicationPackage} Information about latest built application. */ - getLatestApplicationPackageForDevice(platformData: IPlatformData, buildConfig: IBuildConfig): IApplicationPackage; + getLatestApplicationPackageForDevice(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage; /** * Returns information about the latest built application for simulator in the current project. * @param {IPlatformData} platformData Data describing the current platform. * @param {IBuildConfig} buildConfig Defines if the build is for release configuration. + * @param {string} @optional outputPath Directory that should contain the build artifact. * @returns {IApplicationPackage} Information about latest built application. */ - getLatestApplicationPackageForEmulator(platformData: IPlatformData, buildConfig: IBuildConfig): IApplicationPackage; + getLatestApplicationPackageForEmulator(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage; /** * Copies latest build output to a specified location. @@ -164,9 +166,10 @@ interface IPlatformService extends NodeJS.EventEmitter { * @param {string} platform Mobile platform - Android, iOS. * @param {IBuildConfig} buildConfig Defines if the searched artifact should be for simulator and is it built for release. * @param {IProjectData} projectData DTO with information about the project. + * @param {string} @optional outputPath Directory that should contain the build artifact. * @returns {string} The path to latest built artifact. */ - lastOutputPath(platform: string, buildConfig: IBuildConfig, projectData: IProjectData): string; + lastOutputPath(platform: string, buildConfig: IBuildConfig, projectData: IProjectData, outputPath?: string): string; /** * Reads contents of a file on device. diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 1a0464d057..41da36848e 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -73,10 +73,10 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ emulatorBuildOutputPath: path.join(projectRoot, "build", "emulator"), getValidPackageNames: (buildOptions: { isReleaseBuild?: boolean, isForDevice?: boolean }): string[] => { if (buildOptions.isForDevice) { - return [projectData.projectName + ".ipa"]; + return [`${projectData.projectName}.ipa`]; } - return [projectData.projectName + ".app"]; + return [`${projectData.projectName}.app`, `${projectData.projectName}.zip`]; }, frameworkFilesExtensions: [".a", ".framework", ".bin"], frameworkDirectoriesExtensions: [".framework"], diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index b24e42c0ab..ea3882b3bd 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -143,27 +143,29 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { await this.$platformService.preparePlatform(platform, {}, null, projectData, {}, modifiedFiles); } + const rebuildInfo = _.find(rebuiltInformation, info => info.isEmulator === device.isEmulator && info.platform === platform); + + if (rebuildInfo) { + // Case where we have three devices attached, a change that requires build is found, + // we'll rebuild the app only for the first device, but we should install new package on all three devices. + await this.$platformService.installApplication(device, { release: false }, projectData, rebuildInfo.pathToBuildItem, deviceBuildInfoDescriptor.outputPath); + return; + } + // TODO: fix args cast to any const shouldBuild = await this.$platformService.shouldBuild(platform, projectData, { buildForDevice: !device.isEmulator }, deviceBuildInfoDescriptor.outputPath); if (shouldBuild) { const pathToBuildItem = await deviceBuildInfoDescriptor.buildAction(); // Is it possible to return shouldBuild for two devices? What about android device and android emulator? rebuiltInformation.push({ isEmulator: device.isEmulator, platform, pathToBuildItem }); - } - - const rebuildInfo = _.find(rebuiltInformation, info => info.isEmulator === device.isEmulator && info.platform === platform); + await this.$platformService.installApplication(device, { release: false }, projectData, pathToBuildItem, deviceBuildInfoDescriptor.outputPath); - if (rebuildInfo) { - // Case where we have three devices attached, a change that requires build is found, - // we'll rebuild the app only for the first device, but we should install new package on all three devices. - await this.$platformService.installApplication(device, { release: false }, projectData, rebuildInfo.pathToBuildItem, deviceBuildInfoDescriptor.outputPath); } const shouldInstall = await this.$platformService.shouldInstall(device, projectData, deviceBuildInfoDescriptor.outputPath); if (shouldInstall) { - // device.applicationManager.installApplication() - console.log("TODO!!!!!!"); - // call platformService.installApplication here as well. + + await this.$platformService.installApplication(device, { release: false }, projectData, null, deviceBuildInfoDescriptor.outputPath); } } diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index a3e490de0b..a1830ff2d3 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -370,7 +370,6 @@ export class PlatformService extends EventEmitter implements IPlatformService { } public async shouldBuild(platform: string, projectData: IProjectData, buildConfig: IBuildConfig, outputPath?: string): Promise { - //TODO: shouldBuild - issue with outputPath - we do not have always the built dir locally if (this.$projectChangesService.currentChanges.changesRequireBuild) { return true; } @@ -587,13 +586,13 @@ export class PlatformService extends EventEmitter implements IPlatformService { appUpdater.cleanDestinationApp(); } - public lastOutputPath(platform: string, buildConfig: IBuildConfig, projectData: IProjectData): string { + public lastOutputPath(platform: string, buildConfig: IBuildConfig, projectData: IProjectData, outputPath?: string): string { let packageFile: string; let platformData = this.$platformsData.getPlatformData(platform, projectData); if (buildConfig.buildForDevice) { - packageFile = this.getLatestApplicationPackageForDevice(platformData, buildConfig).packageName; + packageFile = this.getLatestApplicationPackageForDevice(platformData, buildConfig, outputPath).packageName; } else { - packageFile = this.getLatestApplicationPackageForEmulator(platformData, buildConfig).packageName; + packageFile = this.getLatestApplicationPackageForEmulator(platformData, buildConfig, outputPath).packageName; } if (!packageFile || !this.$fs.exists(packageFile)) { this.$errors.failWithoutHelp("Unable to find built application. Try 'tns build %s'.", platform); @@ -744,12 +743,12 @@ export class PlatformService extends EventEmitter implements IPlatformService { return packages[0]; } - public getLatestApplicationPackageForDevice(platformData: IPlatformData, buildConfig: IBuildConfig): IApplicationPackage { - return this.getLatestApplicationPackage(platformData.deviceBuildOutputPath, platformData.getValidPackageNames({ isForDevice: true, isReleaseBuild: buildConfig.release })); + public getLatestApplicationPackageForDevice(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage { + return this.getLatestApplicationPackage(outputPath || platformData.deviceBuildOutputPath, platformData.getValidPackageNames({ isForDevice: true, isReleaseBuild: buildConfig.release })); } - public getLatestApplicationPackageForEmulator(platformData: IPlatformData, buildConfig: IBuildConfig): IApplicationPackage { - return this.getLatestApplicationPackage(platformData.emulatorBuildOutputPath || platformData.deviceBuildOutputPath, platformData.getValidPackageNames({ isForDevice: false, isReleaseBuild: buildConfig.release })); + public getLatestApplicationPackageForEmulator(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage { + return this.getLatestApplicationPackage(outputPath || platformData.emulatorBuildOutputPath || platformData.deviceBuildOutputPath, platformData.getValidPackageNames({ isForDevice: false, isReleaseBuild: buildConfig.release })); } private async updatePlatform(platform: string, version: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { From 6b3662d93ddc6d34d4014c5817651f7a64f4a1ef Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 6 Jun 2017 22:36:14 +0300 Subject: [PATCH 080/212] Fix unit tests --- lib/definitions/livesync.d.ts | 11 +++++++++++ lib/device-path-provider.ts | 4 ++-- lib/services/livesync/android-livesync-service.ts | 2 +- lib/services/livesync/ios-livesync-service.ts | 2 +- .../livesync/platform-livesync-service-base.ts | 4 ++-- lib/services/platform-service.ts | 2 +- test/npm-support.ts | 2 ++ test/platform-commands.ts | 1 + test/platform-service.ts | 1 + 9 files changed, 22 insertions(+), 7 deletions(-) diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 319bee25cc..bc9ff214b4 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -204,3 +204,14 @@ interface IAndroidNativeScriptDeviceLiveSyncService { */ getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService; } + +interface IDeviceProjectRootOptions { + appIdentifier: string; + syncAllFiles?: boolean; + watch?: boolean; +} + +interface IDevicePathProvider { + getDeviceBuildInfoDirname(device: Mobile.IDevice, appIdentifier: string): Promise; + getDeviceProjectRootPath(device: Mobile.IDevice, options: IDeviceProjectRootOptions): Promise; +} diff --git a/lib/device-path-provider.ts b/lib/device-path-provider.ts index 995d322295..5ad9f7ee77 100644 --- a/lib/device-path-provider.ts +++ b/lib/device-path-provider.ts @@ -3,7 +3,7 @@ import { IOS_DEVICE_PROJECT_ROOT_PATH, SYNC_DIR_NAME, FULLSYNC_DIR_NAME } from " import { AndroidDeviceLiveSyncService } from "./services/livesync/android-device-livesync-service"; import * as path from "path"; -export class DevicePathProvider implements Mobile.IDevicePathProvider { +export class DevicePathProvider implements IDevicePathProvider { constructor(private $mobileHelper: Mobile.IMobileHelper, private $injector: IInjector, private $iOSSimResolver: Mobile.IiOSSimResolver) { @@ -20,7 +20,7 @@ export class DevicePathProvider implements Mobile.IDevicePathProvider { return result; } - public async getDeviceProjectRootPath(device: Mobile.IDevice, options: Mobile.IDeviceProjectRootOptions): Promise { + public async getDeviceProjectRootPath(device: Mobile.IDevice, options: IDeviceProjectRootOptions): Promise { let projectRoot = ""; if (this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform)) { if (device.isEmulator) { diff --git a/lib/services/livesync/android-livesync-service.ts b/lib/services/livesync/android-livesync-service.ts index ac7e31061a..3fe2604c13 100644 --- a/lib/services/livesync/android-livesync-service.ts +++ b/lib/services/livesync/android-livesync-service.ts @@ -10,7 +10,7 @@ export class AndroidLiveSyncService extends PlatformLiveSyncServiceBase implemen private $projectFilesProvider: IProjectFilesProvider, private $fs: IFileSystem, private $injector: IInjector, - $devicePathProvider: Mobile.IDevicePathProvider) { + $devicePathProvider: IDevicePathProvider) { super($devicePathProvider); } diff --git a/lib/services/livesync/ios-livesync-service.ts b/lib/services/livesync/ios-livesync-service.ts index edd08fab94..c2097d5250 100644 --- a/lib/services/livesync/ios-livesync-service.ts +++ b/lib/services/livesync/ios-livesync-service.ts @@ -12,7 +12,7 @@ export class IOSLiveSyncService extends PlatformLiveSyncServiceBase implements I private $projectFilesProvider: IProjectFilesProvider, private $fs: IFileSystem, private $injector: IInjector, - $devicePathProvider: Mobile.IDevicePathProvider) { + $devicePathProvider: IDevicePathProvider) { super($devicePathProvider); } diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index 6688616e97..b05ef258a0 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -1,11 +1,11 @@ export class PlatformLiveSyncServiceBase { constructor( - private $devicePathProvider: Mobile.IDevicePathProvider, + private $devicePathProvider: IDevicePathProvider, ) { } protected async getAppData(syncInfo: IFullSyncInfo): Promise { - const deviceProjectRootOptions: Mobile.IDeviceProjectRootOptions = _.assign({ appIdentifier: syncInfo.projectData.projectId }, syncInfo); + const deviceProjectRootOptions: IDeviceProjectRootOptions = _.assign({ appIdentifier: syncInfo.projectData.projectId }, syncInfo); return { appIdentifier: syncInfo.projectData.projectId, device: syncInfo.device, diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index a1830ff2d3..b961d2fc5b 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -34,7 +34,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { private $projectFilesManager: IProjectFilesManager, private $mobileHelper: Mobile.IMobileHelper, private $hostInfo: IHostInfo, - private $devicePathProvider: Mobile.IDevicePathProvider, + private $devicePathProvider: IDevicePathProvider, private $xmlValidator: IXmlValidator, private $npm: INodePackageManager, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, diff --git a/test/npm-support.ts b/test/npm-support.ts index 55e2d1a93d..3e72a558bb 100644 --- a/test/npm-support.ts +++ b/test/npm-support.ts @@ -85,6 +85,8 @@ function createTestInjector(): IInjector { testInjector.register("messages", Messages); testInjector.register("nodeModulesDependenciesBuilder", NodeModulesDependenciesBuilder); + testInjector.register("devicePathProvider", {}); + return testInjector; } diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 668a3a5268..31a2350f60 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -141,6 +141,7 @@ function createTestInjector() { track: async () => undefined }); testInjector.register("messages", Messages); + testInjector.register("devicePathProvider", {}); return testInjector; } diff --git a/test/platform-service.ts b/test/platform-service.ts index d6ce79f0b2..d8898f0b26 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -83,6 +83,7 @@ function createTestInjector() { track: async () => undefined }); testInjector.register("messages", Messages); + testInjector.register("devicePathProvider", {}); return testInjector; } From 0f9c0aa9669766986239c1d55c77966786f92f6c Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 7 Jun 2017 00:39:36 +0300 Subject: [PATCH 081/212] Extract all common logic of livesync services to platform-livesync-service-base --- .../livesync/android-livesync-service.ts | 102 ++---------- lib/services/livesync/ios-livesync-service.ts | 156 +++++------------- .../platform-livesync-service-base.ts | 99 ++++++++++- 3 files changed, 152 insertions(+), 205 deletions(-) diff --git a/lib/services/livesync/android-livesync-service.ts b/lib/services/livesync/android-livesync-service.ts index 3fe2604c13..bce1431688 100644 --- a/lib/services/livesync/android-livesync-service.ts +++ b/lib/services/livesync/android-livesync-service.ts @@ -1,101 +1,21 @@ -import * as path from "path"; import { AndroidDeviceLiveSyncService } from "./android-device-livesync-service"; -import { APP_FOLDER_NAME } from "../../constants"; import { PlatformLiveSyncServiceBase } from "./platform-livesync-service-base"; export class AndroidLiveSyncService extends PlatformLiveSyncServiceBase implements IPlatformLiveSyncService { - constructor(private $projectFilesManager: IProjectFilesManager, - private $platformsData: IPlatformsData, - private $logger: ILogger, - private $projectFilesProvider: IProjectFilesProvider, - private $fs: IFileSystem, + constructor(protected $platformsData: IPlatformsData, + protected $projectFilesManager: IProjectFilesManager, private $injector: IInjector, - $devicePathProvider: IDevicePathProvider) { - super($devicePathProvider); + $devicePathProvider: IDevicePathProvider, + $fs: IFileSystem, + $logger: ILogger, + $projectFilesProvider: IProjectFilesProvider, + ) { + super($fs, $logger, $platformsData, $projectFilesManager, $devicePathProvider, $projectFilesProvider); } - public async fullSync(syncInfo: IFullSyncInfo): Promise { - const projectData = syncInfo.projectData; - const device = syncInfo.device; - const deviceLiveSyncService = this.$injector.resolve(AndroidDeviceLiveSyncService, { _device: device }); - const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); - - const deviceAppData = await this.getAppData(syncInfo); - await deviceLiveSyncService.beforeLiveSyncAction(deviceAppData); - - const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); - const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, null, []); - await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, true); - - return { - modifiedFilesData: localToDevicePaths, - isFullSync: true, - deviceAppData - }; - } - - public async liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise { - const projectData = liveSyncInfo.projectData; - const syncInfo = _.merge({ device, watch: true }, liveSyncInfo); - const deviceAppData = await this.getAppData(syncInfo); - - let modifiedLocalToDevicePaths: Mobile.ILocalToDevicePathData[] = []; - if (liveSyncInfo.filesToSync.length) { - const filesToSync = liveSyncInfo.filesToSync; - const mappedFiles = _.map(filesToSync, filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)); - - // Some plugins modify platforms dir on afterPrepare (check nativescript-dev-sass) - we want to sync only existing file. - const existingFiles = mappedFiles.filter(m => this.$fs.exists(m)); - this.$logger.trace("Will execute livesync for files: ", existingFiles); - const skippedFiles = _.difference(mappedFiles, existingFiles); - if (skippedFiles.length) { - this.$logger.trace("The following files will not be synced as they do not exist:", skippedFiles); - } - - if (existingFiles.length) { - let platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); - const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); - let localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, - projectFilesPath, mappedFiles, []); - modifiedLocalToDevicePaths.push(...localToDevicePaths); - await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, false); - } - } - - if (liveSyncInfo.filesToRemove.length) { - const filePaths = liveSyncInfo.filesToRemove; - let platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); - - const mappedFiles = _.map(filePaths, filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)); - const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); - let localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, mappedFiles, []); - modifiedLocalToDevicePaths.push(...localToDevicePaths); - - const deviceLiveSyncService = this.$injector.resolve(AndroidDeviceLiveSyncService, { _device: device }); - deviceLiveSyncService.removeFiles(projectData.projectId, localToDevicePaths, projectData.projectId); - } - - return { - modifiedFilesData: modifiedLocalToDevicePaths, - isFullSync: liveSyncInfo.isRebuilt, - deviceAppData - }; - } - - public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { - if (liveSyncInfo.isFullSync || liveSyncInfo.modifiedFilesData.length) { - let deviceLiveSyncService = this.$injector.resolve(AndroidDeviceLiveSyncService, { _device: liveSyncInfo.deviceAppData.device }); - this.$logger.info("Refreshing application..."); - await deviceLiveSyncService.refreshApplication(projectData, liveSyncInfo); - } - } - - protected async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, canTransferDirectory: boolean): Promise { - if (canTransferDirectory) { - await deviceAppData.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath); - } else { - await deviceAppData.device.fileSystem.transferFiles(deviceAppData, localToDevicePaths); - } + public getDeviceLiveSyncService(device: Mobile.IDevice): INativeScriptDeviceLiveSyncService { + const service = this.$injector.resolve(AndroidDeviceLiveSyncService, { _device: device }); + return service; } } $injector.register("androidLiveSyncService", AndroidLiveSyncService); diff --git a/lib/services/livesync/ios-livesync-service.ts b/lib/services/livesync/ios-livesync-service.ts index c2097d5250..6932efee1b 100644 --- a/lib/services/livesync/ios-livesync-service.ts +++ b/lib/services/livesync/ios-livesync-service.ts @@ -1,141 +1,75 @@ import * as path from "path"; -import * as iosdls from "./ios-device-livesync-service"; import * as temp from "temp"; + +import { IOSDeviceLiveSyncService } from "./ios-device-livesync-service"; import { PlatformLiveSyncServiceBase } from "./platform-livesync-service-base"; -// import * as uuid from "uuid"; +import { APP_FOLDER_NAME, TNS_MODULES_FOLDER_NAME } from "../../constants"; export class IOSLiveSyncService extends PlatformLiveSyncServiceBase implements IPlatformLiveSyncService { - constructor(private $devicesService: Mobile.IDevicesService, - private $projectFilesManager: IProjectFilesManager, - private $platformsData: IPlatformsData, - private $logger: ILogger, - private $projectFilesProvider: IProjectFilesProvider, - private $fs: IFileSystem, + constructor(protected $fs: IFileSystem, + protected $platformsData: IPlatformsData, + protected $projectFilesManager: IProjectFilesManager, private $injector: IInjector, - $devicePathProvider: IDevicePathProvider) { - super($devicePathProvider); + $devicePathProvider: IDevicePathProvider, + $logger: ILogger, + $projectFilesProvider: IProjectFilesProvider, + ) { + super($fs, $logger, $platformsData, $projectFilesManager, $devicePathProvider, $projectFilesProvider); } - /* - fullSync(projectData: IProjectData, device: Mobile.IDevice): Promise; - liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise; - refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise; - */ - public async fullSync(syncInfo: IFullSyncInfo): Promise { - const projectData = syncInfo.projectData; const device = syncInfo.device; - const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); - const deviceAppData = await this.getAppData(syncInfo); - const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, "app"); if (device.isEmulator) { - const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, null, []); - await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, true); - return { - deviceAppData, - isFullSync: true, - modifiedFilesData: localToDevicePaths - }; - } else { - temp.track(); - let tempZip = temp.path({ prefix: "sync", suffix: ".zip" }); - let tempApp = temp.mkdirSync("app"); - this.$logger.trace("Creating zip file: " + tempZip); - this.$fs.copyFile(path.join(path.dirname(projectFilesPath), "app/*"), tempApp); - - if (!syncInfo.syncAllFiles) { - this.$logger.info("Skipping node_modules folder! Use the syncAllFiles option to sync files from this folder."); - this.$fs.deleteDirectory(path.join(tempApp, "tns_modules")); - } - - await this.$fs.zipFiles(tempZip, this.$fs.enumerateFilesInDirectorySync(tempApp), (res) => { - return path.join("app", path.relative(tempApp, res)); - }); - - await device.fileSystem.transferFiles(deviceAppData, [{ - getLocalPath: () => tempZip, - getDevicePath: () => deviceAppData.deviceSyncZipPath, - getRelativeToProjectBasePath: () => "../sync.zip", - deviceProjectRootPath: await deviceAppData.getDeviceProjectRootPath() - }]); - - return { - deviceAppData, - isFullSync: true, - modifiedFilesData: [] - }; + return super.fullSync(syncInfo); } - } - public async liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise { - const projectData = liveSyncInfo.projectData; - const syncInfo = _.merge({ device, watch: true }, liveSyncInfo); + const projectData = syncInfo.projectData; + const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); const deviceAppData = await this.getAppData(syncInfo); - let modifiedLocalToDevicePaths: Mobile.ILocalToDevicePathData[] = []; - - if (liveSyncInfo.isRebuilt) { - // In this case we should execute fullsync: - await this.fullSync({ projectData, device, syncAllFiles: liveSyncInfo.syncAllFiles, watch: true }); - } else { - if (liveSyncInfo.filesToSync.length) { - const filesToSync = liveSyncInfo.filesToSync; - const mappedFiles = _.map(filesToSync, filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)); + const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); - // Some plugins modify platforms dir on afterPrepare (check nativescript-dev-sass) - we want to sync only existing file. - const existingFiles = mappedFiles.filter(m => this.$fs.exists(m)); - this.$logger.trace("Will execute livesync for files: ", existingFiles); - const skippedFiles = _.difference(mappedFiles, existingFiles); - if (skippedFiles.length) { - this.$logger.trace("The following files will not be synced as they do not exist:", skippedFiles); - } + temp.track(); + const tempZip = temp.path({ prefix: "sync", suffix: ".zip" }); + const tempApp = temp.mkdirSync("app"); + this.$logger.trace("Creating zip file: " + tempZip); + this.$fs.copyFile(path.join(path.dirname(projectFilesPath), `${APP_FOLDER_NAME}/*`), tempApp); - if (existingFiles.length) { - const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); - const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, "app"); - let localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, - projectFilesPath, mappedFiles, []); - modifiedLocalToDevicePaths.push(...localToDevicePaths); - await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, false); - } - } + if (!syncInfo.syncAllFiles) { + this.$logger.info("Skipping node_modules folder! Use the syncAllFiles option to sync files from this folder."); + this.$fs.deleteDirectory(path.join(tempApp, TNS_MODULES_FOLDER_NAME)); + } - if (liveSyncInfo.filesToRemove.length) { - const filePaths = liveSyncInfo.filesToRemove; - const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + await this.$fs.zipFiles(tempZip, this.$fs.enumerateFilesInDirectorySync(tempApp), (res) => { + return path.join(APP_FOLDER_NAME, path.relative(tempApp, res)); + }); - const mappedFiles = _.map(filePaths, filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)); - const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, "app"); - let localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, mappedFiles, []); - modifiedLocalToDevicePaths.push(...localToDevicePaths); - - const deviceLiveSyncService = this.$injector.resolve(iosdls.IOSDeviceLiveSyncService, { _device: device }); - deviceLiveSyncService.removeFiles(projectData.projectId, localToDevicePaths, projectData.projectId); - } - } + await device.fileSystem.transferFiles(deviceAppData, [{ + getLocalPath: () => tempZip, + getDevicePath: () => deviceAppData.deviceSyncZipPath, + getRelativeToProjectBasePath: () => "../sync.zip", + deviceProjectRootPath: await deviceAppData.getDeviceProjectRootPath() + }]); return { - modifiedFilesData: modifiedLocalToDevicePaths, - isFullSync: liveSyncInfo.isRebuilt, - deviceAppData + deviceAppData, + isFullSync: true, + modifiedFilesData: [] }; } - public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { - let deviceLiveSyncService = this.$injector.resolve(iosdls.IOSDeviceLiveSyncService, { _device: liveSyncInfo.deviceAppData.device }); - this.$logger.info("Refreshing application..."); - await deviceLiveSyncService.refreshApplication(projectData, liveSyncInfo); - } - - protected async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise { - let canTransferDirectory = isFullSync && this.$devicesService.isiOSDevice(deviceAppData.device); - if (canTransferDirectory) { - await deviceAppData.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath); + public liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise { + if (liveSyncInfo.isRebuilt) { + // In this case we should execute fullsync because iOS Runtime requires the full content of app dir to be extracted in the root of sync dir. + return this.fullSync({ projectData: liveSyncInfo.projectData, device, syncAllFiles: liveSyncInfo.syncAllFiles, watch: true }); } else { - await deviceAppData.device.fileSystem.transferFiles(deviceAppData, localToDevicePaths); + return super.liveSyncWatchAction(device, liveSyncInfo); } + } - console.log("### ios TRANSFEREEDDDDDDD!!!!!!"); + public getDeviceLiveSyncService(device: Mobile.IDevice): INativeScriptDeviceLiveSyncService { + const service = this.$injector.resolve(IOSDeviceLiveSyncService, { _device: device }); + return service; } } $injector.register("iOSLiveSyncService", IOSLiveSyncService); diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index b05ef258a0..93a61d006e 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -1,7 +1,100 @@ -export class PlatformLiveSyncServiceBase { - constructor( +import * as path from "path"; +import { APP_FOLDER_NAME } from "../../constants"; + +export abstract class PlatformLiveSyncServiceBase { + constructor(protected $fs: IFileSystem, + protected $logger: ILogger, + protected $platformsData: IPlatformsData, + protected $projectFilesManager: IProjectFilesManager, private $devicePathProvider: IDevicePathProvider, - ) { + private $projectFilesProvider: IProjectFilesProvider) { } + + public abstract getDeviceLiveSyncService(device: Mobile.IDevice): INativeScriptDeviceLiveSyncService; + + public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { + if (liveSyncInfo.isFullSync || liveSyncInfo.modifiedFilesData.length) { + const deviceLiveSyncService = this.getDeviceLiveSyncService(liveSyncInfo.deviceAppData.device); + this.$logger.info("Refreshing application..."); + await deviceLiveSyncService.refreshApplication(projectData, liveSyncInfo); + } + } + + public async fullSync(syncInfo: IFullSyncInfo): Promise { + const projectData = syncInfo.projectData; + const device = syncInfo.device; + const deviceLiveSyncService = this.getDeviceLiveSyncService(device); + const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + const deviceAppData = await this.getAppData(syncInfo); + + if (deviceLiveSyncService.beforeLiveSyncAction) { + await deviceLiveSyncService.beforeLiveSyncAction(deviceAppData); + } + + const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); + const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, null, []); + await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, true); + + return { + modifiedFilesData: localToDevicePaths, + isFullSync: true, + deviceAppData + }; + } + + public async liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise { + const projectData = liveSyncInfo.projectData; + const syncInfo = _.merge({ device, watch: true }, liveSyncInfo); + const deviceAppData = await this.getAppData(syncInfo); + + let modifiedLocalToDevicePaths: Mobile.ILocalToDevicePathData[] = []; + if (liveSyncInfo.filesToSync.length) { + const filesToSync = liveSyncInfo.filesToSync; + const mappedFiles = _.map(filesToSync, filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)); + + // Some plugins modify platforms dir on afterPrepare (check nativescript-dev-sass) - we want to sync only existing file. + const existingFiles = mappedFiles.filter(m => this.$fs.exists(m)); + this.$logger.trace("Will execute livesync for files: ", existingFiles); + const skippedFiles = _.difference(mappedFiles, existingFiles); + if (skippedFiles.length) { + this.$logger.trace("The following files will not be synced as they do not exist:", skippedFiles); + } + + if (existingFiles.length) { + let platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); + let localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, + projectFilesPath, mappedFiles, []); + modifiedLocalToDevicePaths.push(...localToDevicePaths); + await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, false); + } + } + + if (liveSyncInfo.filesToRemove.length) { + const filePaths = liveSyncInfo.filesToRemove; + let platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + + const mappedFiles = _.map(filePaths, filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)); + const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); + let localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, mappedFiles, []); + modifiedLocalToDevicePaths.push(...localToDevicePaths); + + const deviceLiveSyncService = this.getDeviceLiveSyncService(device); + deviceLiveSyncService.removeFiles(projectData.projectId, localToDevicePaths, projectData.projectId); + } + + return { + modifiedFilesData: modifiedLocalToDevicePaths, + isFullSync: liveSyncInfo.isRebuilt, + deviceAppData + }; + } + + protected async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise { + if (isFullSync) { + await deviceAppData.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath); + } else { + await deviceAppData.device.fileSystem.transferFiles(deviceAppData, localToDevicePaths); + } } protected async getAppData(syncInfo: IFullSyncInfo): Promise { From 4dfc040f9bf351e7e2a0e9290234c5f13a327ec4 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 7 Jun 2017 01:00:10 +0300 Subject: [PATCH 082/212] Remove livesync-provider Remove livesyncProvider and introduce a base class for device specific livesync services. --- lib/bootstrap.ts | 1 - lib/providers/livesync-provider.ts | 95 ------------------- .../android-device-livesync-service.ts | 36 +------ .../livesync/device-livesync-service-base.ts | 21 ++++ .../livesync/ios-device-livesync-service.ts | 20 ++-- 5 files changed, 34 insertions(+), 139 deletions(-) delete mode 100644 lib/providers/livesync-provider.ts create mode 100644 lib/services/livesync/device-livesync-service-base.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 44244b6be9..afedccca0f 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -75,7 +75,6 @@ $injector.require("commandsServiceProvider", "./providers/commands-service-provi $injector.require("deviceAppDataProvider", "./providers/device-app-data-provider"); $injector.require("deviceLogProvider", "./common/mobile/device-log-provider"); -$injector.require("liveSyncProvider", "./providers/livesync-provider"); $injector.require("projectFilesProvider", "./providers/project-files-provider"); $injector.require("nodeModulesBuilder", "./tools/node-modules/node-modules-builder"); diff --git a/lib/providers/livesync-provider.ts b/lib/providers/livesync-provider.ts deleted file mode 100644 index fcbbdeb1f5..0000000000 --- a/lib/providers/livesync-provider.ts +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from "path"; -import * as temp from "temp"; -import { TNS_MODULES_FOLDER_NAME } from "../constants"; - -export class LiveSyncProvider implements ILiveSyncProvider { - constructor(private $androidLiveSyncServiceLocator: { factory: Function }, - private $iosLiveSyncServiceLocator: { factory: Function }, - private $platformService: IPlatformService, - private $platformsData: IPlatformsData, - private $logger: ILogger, - private $options: IOptions, - private $mobileHelper: Mobile.IMobileHelper, - private $fs: IFileSystem) { } - - private static FAST_SYNC_FILE_EXTENSIONS = [".css", ".xml", ".html"]; - - private deviceSpecificLiveSyncServicesCache: IDictionary = {}; - public get deviceSpecificLiveSyncServices(): IDictionary { - return { - android: (_device: Mobile.IDevice, $injector: IInjector) => { - if (!this.deviceSpecificLiveSyncServicesCache[_device.deviceInfo.identifier]) { - this.deviceSpecificLiveSyncServicesCache[_device.deviceInfo.identifier] = $injector.resolve(this.$androidLiveSyncServiceLocator.factory, { _device: _device }); - } - - return this.deviceSpecificLiveSyncServicesCache[_device.deviceInfo.identifier]; - }, - ios: (_device: Mobile.IDevice, $injector: IInjector) => { - if (!this.deviceSpecificLiveSyncServicesCache[_device.deviceInfo.identifier]) { - this.deviceSpecificLiveSyncServicesCache[_device.deviceInfo.identifier] = $injector.resolve(this.$iosLiveSyncServiceLocator.factory, { _device: _device }); - } - - return this.deviceSpecificLiveSyncServicesCache[_device.deviceInfo.identifier]; - } - }; - } - - public async buildForDevice(device: Mobile.IDevice, projectData: IProjectData): Promise { - let buildConfig: IBuildConfig = { - buildForDevice: !device.isEmulator, - projectDir: this.$options.path, - release: this.$options.release, - teamId: this.$options.teamId, - device: this.$options.device, - provision: this.$options.provision, - }; - - await this.$platformService.buildPlatform(device.deviceInfo.platform, buildConfig, projectData); - let platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); - if (device.isEmulator) { - return this.$platformService.getLatestApplicationPackageForEmulator(platformData, buildConfig).packageName; - } - - return this.$platformService.getLatestApplicationPackageForDevice(platformData, buildConfig).packageName; - } - - public async preparePlatformForSync(platform: string, provision: any, projectData: IProjectData): Promise { - const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; - await this.$platformService.preparePlatform(platform, appFilesUpdaterOptions, this.$options.platformTemplate, projectData, this.$options); - } - - public canExecuteFastSync(filePath: string, projectData: IProjectData, platform: string): boolean { - let platformData = this.$platformsData.getPlatformData(platform, projectData); - let fastSyncFileExtensions = LiveSyncProvider.FAST_SYNC_FILE_EXTENSIONS.concat(platformData.fastLivesyncFileExtensions); - return _.includes(fastSyncFileExtensions, path.extname(filePath)); - } - - public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise { - if (this.$mobileHelper.isAndroidPlatform(deviceAppData.platform) || !deviceAppData.deviceSyncZipPath || !isFullSync) { - await deviceAppData.device.fileSystem.transferFiles(deviceAppData, localToDevicePaths); - } else { - temp.track(); - let tempZip = temp.path({ prefix: "sync", suffix: ".zip" }); - let tempApp = temp.mkdirSync("app"); - this.$logger.trace("Creating zip file: " + tempZip); - this.$fs.copyFile(path.join(path.dirname(projectFilesPath), "app/*"), tempApp); - - if (!this.$options.syncAllFiles) { - this.$logger.info("Skipping node_modules folder! Use the syncAllFiles option to sync files from this folder."); - this.$fs.deleteDirectory(path.join(tempApp, TNS_MODULES_FOLDER_NAME)); - } - - await this.$fs.zipFiles(tempZip, this.$fs.enumerateFilesInDirectorySync(tempApp), (res) => { - return path.join("app", path.relative(tempApp, res)); - }); - - await deviceAppData.device.fileSystem.transferFiles(deviceAppData, [{ - getLocalPath: () => tempZip, - getDevicePath: () => deviceAppData.deviceSyncZipPath, - getRelativeToProjectBasePath: () => "../sync.zip", - deviceProjectRootPath: await deviceAppData.getDeviceProjectRootPath() - }]); - } - } -} -$injector.register("liveSyncProvider", LiveSyncProvider); diff --git a/lib/services/livesync/android-device-livesync-service.ts b/lib/services/livesync/android-device-livesync-service.ts index a949a2d363..e6c4abdca4 100644 --- a/lib/services/livesync/android-device-livesync-service.ts +++ b/lib/services/livesync/android-device-livesync-service.ts @@ -1,31 +1,24 @@ import { DeviceAndroidDebugBridge } from "../../common/mobile/android/device-android-debug-bridge"; import { AndroidDeviceHashService } from "../../common/mobile/android/android-device-hash-service"; +import { DeviceLiveSyncServiceBase } from "./device-livesync-service-base"; import * as helpers from "../../common/helpers"; import { SYNC_DIR_NAME, FULLSYNC_DIR_NAME, REMOVEDSYNC_DIR_NAME } from "../../constants"; import { cache } from "../../common/decorators"; import * as path from "path"; import * as net from "net"; -import { EOL } from "os"; - -export class AndroidDeviceLiveSyncService implements IAndroidNativeScriptDeviceLiveSyncService { - private static FAST_SYNC_FILE_EXTENSIONS = [".css", ".xml", ".html"]; +export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase implements IAndroidNativeScriptDeviceLiveSyncService { private static BACKEND_PORT = 18182; private device: Mobile.IAndroidDevice; constructor(_device: Mobile.IDevice, private $mobileHelper: Mobile.IMobileHelper, private $injector: IInjector, - private $logger: ILogger, - private $androidDebugService: IPlatformDebugService, - private $platformsData: IPlatformsData) { + protected $platformsData: IPlatformsData) { + super($platformsData); this.device = (_device); } - public get debugService(): IPlatformDebugService { - return this.$androidDebugService; - } - public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { const deviceAppData = liveSyncInfo.deviceAppData; const localToDevicePaths = liveSyncInfo.modifiedFilesData; @@ -38,7 +31,7 @@ export class AndroidDeviceLiveSyncService implements IAndroidNativeScriptDeviceL `/data/local/tmp/${deviceAppData.appIdentifier}/sync`] ); - let canExecuteFastSync = !liveSyncInfo.isFullSync && !_.some(localToDevicePaths, + const canExecuteFastSync = !liveSyncInfo.isFullSync && !_.some(localToDevicePaths, (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.canExecuteFastSync(localToDevicePath.getLocalPath(), projectData, this.device.deviceInfo.platform)); if (canExecuteFastSync) { @@ -48,25 +41,6 @@ export class AndroidDeviceLiveSyncService implements IAndroidNativeScriptDeviceL return this.restartApplication(deviceAppData); } - @cache() - private getFastLiveSyncFileExtensions(platform: string, projectData: IProjectData): string[] { - const platformData = this.$platformsData.getPlatformData(platform, projectData); - const fastSyncFileExtensions = AndroidDeviceLiveSyncService.FAST_SYNC_FILE_EXTENSIONS.concat(platformData.fastLivesyncFileExtensions); - return fastSyncFileExtensions; - } - - public canExecuteFastSync(filePath: string, projectData: IProjectData, platform: string): boolean { - console.log("called canExecuteFastSync for file: ", filePath); - const fastSyncFileExtensions = this.getFastLiveSyncFileExtensions(platform, projectData); - return _.includes(fastSyncFileExtensions, path.extname(filePath)); - } - - protected printDebugInformation(information: string[]): void { - _.each(information, i => { - this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${i}${EOL}`.cyan); - }); - } - private async restartApplication(deviceAppData: Mobile.IDeviceAppData): Promise { let devicePathRoot = `/data/data/${deviceAppData.appIdentifier}/files`; let devicePath = this.$mobileHelper.buildDevicePath(devicePathRoot, "code_cache", "secondary_dexes", "proxyThumb"); diff --git a/lib/services/livesync/device-livesync-service-base.ts b/lib/services/livesync/device-livesync-service-base.ts new file mode 100644 index 0000000000..bb1f44961f --- /dev/null +++ b/lib/services/livesync/device-livesync-service-base.ts @@ -0,0 +1,21 @@ +import { cache } from "../../common/decorators"; +import * as path from "path"; + +export abstract class DeviceLiveSyncServiceBase { + private static FAST_SYNC_FILE_EXTENSIONS = [".css", ".xml", ".html"]; + + constructor(protected $platformsData: IPlatformsData) { } + + public canExecuteFastSync(filePath: string, projectData: IProjectData, platform: string): boolean { + const fastSyncFileExtensions = this.getFastLiveSyncFileExtensions(platform, projectData); + return _.includes(fastSyncFileExtensions, path.extname(filePath)); + } + + @cache() + private getFastLiveSyncFileExtensions(platform: string, projectData: IProjectData): string[] { + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const fastSyncFileExtensions = DeviceLiveSyncServiceBase.FAST_SYNC_FILE_EXTENSIONS.concat(platformData.fastLivesyncFileExtensions); + return fastSyncFileExtensions; + } + +} diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index cd0d840386..23c4d73ffe 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -2,10 +2,11 @@ import * as helpers from "../../common/helpers"; import * as constants from "../../constants"; import * as minimatch from "minimatch"; import * as net from "net"; +import { DeviceLiveSyncServiceBase } from "./device-livesync-service-base"; let currentPageReloadId = 0; -export class IOSDeviceLiveSyncService implements INativeScriptDeviceLiveSyncService { +export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implements INativeScriptDeviceLiveSyncService { private static BACKEND_PORT = 18181; private socket: net.Socket; private device: Mobile.IiOSDevice; @@ -15,18 +16,13 @@ export class IOSDeviceLiveSyncService implements INativeScriptDeviceLiveSyncServ private $iOSNotification: IiOSNotification, private $iOSEmulatorServices: Mobile.IiOSSimulatorService, private $logger: ILogger, - private $iOSDebugService: IDebugService, private $fs: IFileSystem, - private $liveSyncProvider: ILiveSyncProvider, - private $processService: IProcessService) { - + private $processService: IProcessService, + protected $platformsData: IPlatformsData) { + super($platformsData); this.device = _device; } - public get debugService(): IDebugService { - return this.$iOSDebugService; - } - private async setupSocketIfNeeded(projectId: string): Promise { if (this.socket) { return true; @@ -64,11 +60,11 @@ export class IOSDeviceLiveSyncService implements INativeScriptDeviceLiveSyncServ } let scriptRelatedFiles: Mobile.ILocalToDevicePathData[] = []; - let scriptFiles = _.filter(localToDevicePaths, localToDevicePath => _.endsWith(localToDevicePath.getDevicePath(), ".js")); + const scriptFiles = _.filter(localToDevicePaths, localToDevicePath => _.endsWith(localToDevicePath.getDevicePath(), ".js")); constants.LIVESYNC_EXCLUDED_FILE_PATTERNS.forEach(pattern => scriptRelatedFiles = _.concat(scriptRelatedFiles, localToDevicePaths.filter(file => minimatch(file.getDevicePath(), pattern, { nocase: true })))); - let otherFiles = _.difference(localToDevicePaths, _.concat(scriptFiles, scriptRelatedFiles)); - let shouldRestart = _.some(otherFiles, (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.$liveSyncProvider.canExecuteFastSync(localToDevicePath.getLocalPath(), projectData, deviceAppData.platform)); + const otherFiles = _.difference(localToDevicePaths, _.concat(scriptFiles, scriptRelatedFiles)); + const shouldRestart = _.some(otherFiles, (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.canExecuteFastSync(localToDevicePath.getLocalPath(), projectData, deviceAppData.platform)); if (shouldRestart || (!liveSyncInfo.useLiveEdit && scriptFiles.length)) { await this.restartApplication(deviceAppData, projectData.projectName); From 3a38e78e4e907f8b48aeebb29e9a6c507f9ef798 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 7 Jun 2017 01:26:41 +0300 Subject: [PATCH 083/212] Add notify event during LiveSync --- PublicAPI.md | 18 ++++---------- lib/services/livesync/livesync-service.ts | 30 +++++++++++++++++------ 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/PublicAPI.md b/PublicAPI.md index 70d920a80f..402fa468c4 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -684,28 +684,20 @@ tns.liveSyncService.on("error", data => { }); ``` -* fileChanged - raised when a watched file is modified. The event is raised witht he following data: +* notify - raised when LiveSync operation has some data that is important for the user. The event is raised for specific device. The event is raised with the following data: ```TypeScript { projectDir: string; - /** - * Device identifiers on which the file will be LiveSynced. - */ - deviceIdentifiers: string[]; + deviceIdentifier: string; applicationIdentifier: string; - modifiedFile: string; - - /** - * File System event - "add", "addDir", "change", "unlink", "unlinkDir". - */ - event: string; + notification: string; } ``` Example: ```JavaScript -tns.liveSyncService.on("fileChanged", data => { - console.log(`Detected file changed: ${data.modifiedFile} in ${data.projectDir}. Will start LiveSync operation on ${data.deviceIdentifiers.join(", ")}.`); +tns.liveSyncService.on("notify", data => { + console.log(`Notification: ${notification} for LiveSync operation on ${data.deviceIdentifier} for ${data.projectDir}. `); }); ``` diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index ea3882b3bd..ffe02c45dd 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -8,7 +8,8 @@ const LiveSyncEvents = { liveSyncStopped: "liveSyncStopped", liveSyncError: "error", liveSyncExecuted: "liveSyncExecuted", - liveSyncStarted: "liveSyncStarted" + liveSyncStarted: "liveSyncStarted", + liveSyncNotification: "notify" }; // TODO: emit events for "successfull livesync", "stoppedLivesync", @@ -98,7 +99,20 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo): Promise { const platformLiveSyncService = this.getLiveSyncService(liveSyncResultInfo.deviceAppData.platform); - await platformLiveSyncService.refreshApplication(projectData, liveSyncResultInfo); + try { + // TODO: Assure we are able to self-restart iOS apps on Windows. + await platformLiveSyncService.refreshApplication(projectData, liveSyncResultInfo); + } catch (err) { + this.$logger.info(`Error while trying to start application ${projectData.projectId} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Error is: ${err}`); + const msg = `Unable to start application ${projectData.projectId} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Try starting it manually.`; + this.$logger.warn(msg); + this.emit(LiveSyncEvents.liveSyncNotification, { + projectDir: projectData.projectDir, + applicationIdentifier: projectData.projectId, + deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, + notification: msg + }); + } this.emit(LiveSyncEvents.liveSyncExecuted, { projectDir: projectData.projectDir, @@ -187,12 +201,12 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, projectData, deviceDescriptor); - const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ - projectData, device, - syncAllFiles: liveSyncData.watchAllFiles, - useLiveEdit: liveSyncData.useLiveEdit, - watch: !liveSyncData.skipWatcher - }); + const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ + projectData, device, + syncAllFiles: liveSyncData.watchAllFiles, + useLiveEdit: liveSyncData.useLiveEdit, + watch: !liveSyncData.skipWatcher + }); await this.refreshApplication(projectData, liveSyncResultInfo); } catch (err) { this.$logger.warn(`Unable to apply changes on device: ${err.deviceIdentifier}. Error is: ${err.message}.`); From 869c95a9b7fdaa3695b712e3ed1e446b21e72e8d Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 7 Jun 2017 01:36:04 +0300 Subject: [PATCH 084/212] Rename error event to liveSyncError Rename the error event to liveSyncError as EventEmitter expects instance of Error when event named `error` is raised. So it fails to parse our object. Also it automatically raised Uncaught error in CLI when noone has added a handler for error event:`Uncaught, unspecified "error" event. ([object Object])`. Fix incorrect message in an error where deviceIdentifier was not printed correctly. --- PublicAPI.md | 4 ++-- lib/services/livesync/livesync-service.ts | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/PublicAPI.md b/PublicAPI.md index 402fa468c4..da8e027f1a 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -667,7 +667,7 @@ tns.liveSyncService.on("liveSyncStopped", data => { }); ``` -* error - raised whenever an error is detected during LiveSync operation. The event is raised for specific device. Once an error is detected, the event will be raised and the LiveSync operation will be stopped for this device, i.e. `liveSyncStopped` event will be raised for it. The event is raised with the following data: +* liveSyncError - raised whenever an error is detected during LiveSync operation. The event is raised for specific device. Once an error is detected, the event will be raised and the LiveSync operation will be stopped for this device, i.e. `liveSyncStopped` event will be raised for it. The event is raised with the following data: ```TypeScript { projectDir: string; @@ -679,7 +679,7 @@ tns.liveSyncService.on("liveSyncStopped", data => { Example: ```JavaScript -tns.liveSyncService.on("error", data => { +tns.liveSyncService.on("liveSyncError", data => { console.log(`Error detected during LiveSync on ${data.deviceIdentifier} for ${data.projectDir}. Error: ${err.message}.`); }); ``` diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index ffe02c45dd..b3b8acefa3 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -6,7 +6,8 @@ import { hook } from "../../common/helpers"; const LiveSyncEvents = { liveSyncStopped: "liveSyncStopped", - liveSyncError: "error", + // In case we name it error, EventEmitter expects instance of Error to be raised and will also raise uncaught exception in case there's no handler + liveSyncError: "liveSyncError", liveSyncExecuted: "liveSyncExecuted", liveSyncStarted: "liveSyncStarted", liveSyncNotification: "notify" @@ -209,7 +210,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { }); await this.refreshApplication(projectData, liveSyncResultInfo); } catch (err) { - this.$logger.warn(`Unable to apply changes on device: ${err.deviceIdentifier}. Error is: ${err.message}.`); + this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`); this.emit(LiveSyncEvents.liveSyncError, { error: err, From 96af540371115227c228a805d9a444792b1139db Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 7 Jun 2017 11:51:09 +0300 Subject: [PATCH 085/212] Do not kill ios-device-lib in `tns run` --- lib/commands/run.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 98f56dbe96..7c0591f656 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -8,7 +8,9 @@ export class RunCommandBase implements ICommand { protected $emulatorPlatformService: IEmulatorPlatformService, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $devicesService: Mobile.IDevicesService, - private $hostInfo: IHostInfo) { + private $hostInfo: IHostInfo, + private $iosDeviceOperations: IIOSDeviceOperations, + private $mobileHelper: Mobile.IMobileHelper) { this.$projectData.initializeProjectData(); } @@ -56,7 +58,6 @@ export class RunCommandBase implements ICommand { await this.$platformService.buildPlatform(d.deviceInfo.platform, buildConfig, this.$projectData); const pathToBuildResult = await this.$platformService.lastOutputPath(d.deviceInfo.platform, buildConfig, this.$projectData); - console.log("3##### return path to buildResult = ", pathToBuildResult); return pathToBuildResult; } }; @@ -75,8 +76,11 @@ export class RunCommandBase implements ICommand { // return this.$platformService.trackProjectType(this.$projectData); // } - // TODO: Fix this call - const liveSyncInfo: ILiveSyncInfo = { projectDir: this.$projectData.projectDir, skipWatcher: !this.$options.watch || this.$options.justlaunch, watchAllFiles: this.$options.syncAllFiles }; + if ((!this.platform || this.$mobileHelper.isiOSPlatform(this.platform)) && (this.$options.watch || !this.$options.justlaunch)) { + this.$iosDeviceOperations.setShouldDispose(false); + } + + const liveSyncInfo: ILiveSyncInfo = { projectDir: this.$projectData.projectDir, skipWatcher: !this.$options.watch, watchAllFiles: this.$options.syncAllFiles }; await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } } @@ -97,8 +101,10 @@ export class RunIosCommand extends RunCommandBase implements ICommand { $options: IOptions, $emulatorPlatformService: IEmulatorPlatformService, $devicesService: Mobile.IDevicesService, - $hostInfo: IHostInfo) { - super($platformService, $liveSyncService, $projectData, $options, $emulatorPlatformService, $devicePlatformsConstants, $devicesService, $hostInfo); + $hostInfo: IHostInfo, + $iosDeviceOperations: IIOSDeviceOperations, + $mobileHelper: Mobile.IMobileHelper) { + super($platformService, $liveSyncService, $projectData, $options, $emulatorPlatformService, $devicePlatformsConstants, $devicesService, $hostInfo, $iosDeviceOperations, $mobileHelper); } public async execute(args: string[]): Promise { @@ -131,8 +137,10 @@ export class RunAndroidCommand extends RunCommandBase implements ICommand { $options: IOptions, $emulatorPlatformService: IEmulatorPlatformService, $devicesService: Mobile.IDevicesService, - $hostInfo: IHostInfo) { - super($platformService, $liveSyncService, $projectData, $options, $emulatorPlatformService, $devicePlatformsConstants, $devicesService, $hostInfo); + $hostInfo: IHostInfo, + $iosDeviceOperations: IIOSDeviceOperations, + $mobileHelper: Mobile.IMobileHelper) { + super($platformService, $liveSyncService, $projectData, $options, $emulatorPlatformService, $devicePlatformsConstants, $devicesService, $hostInfo, $iosDeviceOperations, $mobileHelper); } public async execute(args: string[]): Promise { From 4d6b9902275bc3b632d9c61bcad8eff118432d58 Mon Sep 17 00:00:00 2001 From: TsvetanMilanov Date: Wed, 7 Jun 2017 15:37:20 +0300 Subject: [PATCH 086/212] WIP --- lib/bootstrap.ts | 2 ++ lib/constants.ts | 1 + lib/definitions/debug.d.ts | 8 ++++++-- lib/definitions/livesync.d.ts | 1 + lib/device-path-provider.ts | 8 ++++++-- lib/device-sockets/ios/socket-proxy-factory.ts | 2 +- lib/nativescript-cli-lib-bootstrap.ts | 1 - lib/services/debug-service.ts | 2 +- lib/services/livesync/debug-livesync-service.ts | 11 ++++++----- .../livesync/platform-livesync-service-base.ts | 1 + test/services/debug-service.ts | 10 +++++----- 11 files changed, 30 insertions(+), 17 deletions(-) diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index afedccca0f..e66d770679 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -24,6 +24,7 @@ $injector.require("platformsData", "./platforms-data"); $injector.require("platformService", "./services/platform-service"); $injector.require("debugDataService", "./services/debug-data-service"); +$injector.requirePublicClass("debugService", "./services/debug-service"); $injector.require("iOSDebugService", "./services/ios-debug-service"); $injector.require("androidDebugService", "./services/android-debug-service"); @@ -104,6 +105,7 @@ $injector.require("devicePathProvider", "./device-path-provider"); $injector.requireCommand("platform|clean", "./commands/platform-clean"); $injector.require("liveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript +$injector.require("debugLiveSyncService", "./services/livesync/debug-livesync-service"); $injector.require("androidLiveSyncService", "./services/livesync/android-livesync-service"); $injector.require("iOSLiveSyncService", "./services/livesync/ios-livesync-service"); $injector.require("usbLiveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript diff --git a/lib/constants.ts b/lib/constants.ts index 3652134ce8..4ef5504a7d 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -70,6 +70,7 @@ export const SYNC_DIR_NAME = "sync"; export const REMOVEDSYNC_DIR_NAME = "removedsync"; export const FULLSYNC_DIR_NAME = "fullsync"; export const IOS_DEVICE_PROJECT_ROOT_PATH = "Library/Application Support/LiveSync/app"; +export const IOS_DEVICE_SYNC_ZIP_PATH = "Library/Application Support/LiveSync/sync.zip"; export const ANGULAR_NAME = "angular"; export const TYPESCRIPT_NAME = "typescript"; export const BUILD_OUTPUT_EVENT_NAME = "buildOutput"; diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 3a1c62a37b..916fd9e391 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -89,7 +89,7 @@ interface IDebugDataService { /** * Describes methods for debug operation. */ -interface IDebugService extends NodeJS.EventEmitter { +interface IDebugServiceBase extends NodeJS.EventEmitter { /** * Starts debug operation based on the specified debug data. * @param {IDebugData} debugData Describes information for device and application that will be debugged. @@ -99,10 +99,14 @@ interface IDebugService extends NodeJS.EventEmitter { debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; } +interface IDebugService { + getDebugService(device: Mobile.IDevice): IPlatformDebugService; +} + /** * Describes actions required for debugging on specific platform (Android or iOS). */ -interface IPlatformDebugService extends IDebugService { +interface IPlatformDebugService extends IDebugServiceBase { /** * Starts debug operation. * @param {IDebugData} debugData Describes information for device and application that will be debugged. diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index bc9ff214b4..605a554c02 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -214,4 +214,5 @@ interface IDeviceProjectRootOptions { interface IDevicePathProvider { getDeviceBuildInfoDirname(device: Mobile.IDevice, appIdentifier: string): Promise; getDeviceProjectRootPath(device: Mobile.IDevice, options: IDeviceProjectRootOptions): Promise; + getDeviceSyncZipPath(device: Mobile.IDevice): string; } diff --git a/lib/device-path-provider.ts b/lib/device-path-provider.ts index 5ad9f7ee77..e4e41c8a58 100644 --- a/lib/device-path-provider.ts +++ b/lib/device-path-provider.ts @@ -1,5 +1,5 @@ import { fromWindowsRelativePathToUnix } from "./common/helpers"; -import { IOS_DEVICE_PROJECT_ROOT_PATH, SYNC_DIR_NAME, FULLSYNC_DIR_NAME } from "./constants"; +import { IOS_DEVICE_SYNC_ZIP_PATH, IOS_DEVICE_PROJECT_ROOT_PATH, SYNC_DIR_NAME, FULLSYNC_DIR_NAME } from "./constants"; import { AndroidDeviceLiveSyncService } from "./services/livesync/android-device-livesync-service"; import * as path from "path"; @@ -12,7 +12,7 @@ export class DevicePathProvider implements IDevicePathProvider { public async getDeviceBuildInfoDirname(device: Mobile.IDevice, appIdentifier: string): Promise { let result = ""; if (this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform)) { - result = path.basename(await this.getDeviceProjectRootPath(device, { appIdentifier })); + result = path.dirname(await this.getDeviceProjectRootPath(device, { appIdentifier })); } else if (this.$mobileHelper.isAndroidPlatform(device.deviceInfo.platform)) { result = `/data/local/tmp/${appIdentifier}`; } @@ -39,6 +39,10 @@ export class DevicePathProvider implements IDevicePathProvider { return fromWindowsRelativePathToUnix(projectRoot); } + + public getDeviceSyncZipPath(device: Mobile.IDevice): string { + return this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform) && !device.isEmulator ? IOS_DEVICE_SYNC_ZIP_PATH : undefined; + } } $injector.register("devicePathProvider", DevicePathProvider); diff --git a/lib/device-sockets/ios/socket-proxy-factory.ts b/lib/device-sockets/ios/socket-proxy-factory.ts index 45f32abf8b..4f67a7a242 100644 --- a/lib/device-sockets/ios/socket-proxy-factory.ts +++ b/lib/device-sockets/ios/socket-proxy-factory.ts @@ -75,7 +75,7 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact public async createWebSocketProxy(factory: () => Promise): Promise { // NOTE: We will try to provide command line options to select ports, at least on the localhost. - const localPort = await this.$net.getAvailablePortInRange(8080); + const localPort = await this.$net.getFreePort(); this.$logger.info("\nSetting up debugger proxy...\nPress Ctrl + C to terminate, or disconnect.\n"); diff --git a/lib/nativescript-cli-lib-bootstrap.ts b/lib/nativescript-cli-lib-bootstrap.ts index ae0721440d..95dcf39e2f 100644 --- a/lib/nativescript-cli-lib-bootstrap.ts +++ b/lib/nativescript-cli-lib-bootstrap.ts @@ -9,7 +9,6 @@ $injector.requirePublic("companionAppsService", "./common/appbuilder/services/li $injector.requirePublicClass("deviceEmitter", "./common/appbuilder/device-emitter"); $injector.requirePublicClass("deviceLogProvider", "./common/appbuilder/device-log-provider"); $injector.requirePublicClass("localBuildService", "./services/local-build-service"); -$injector.requirePublicClass("debugService", "./services/debug-service"); // We need this because some services check if (!$options.justlaunch) to start the device log after some operation. // We don't want this behaviour when the CLI is required as library. diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index afc9b5d81b..dda56ac784 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -67,7 +67,7 @@ export class DebugService extends EventEmitter implements IDebugService { return _.first(result); } - private getDebugService(device: Mobile.IDevice): IPlatformDebugService { + public getDebugService(device: Mobile.IDevice): IPlatformDebugService { if (this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform)) { return this.$iOSDebugService; } else if (this.$mobileHelper.isAndroidPlatform(device.deviceInfo.platform)) { diff --git a/lib/services/livesync/debug-livesync-service.ts b/lib/services/livesync/debug-livesync-service.ts index 3747516e4e..acb0a55fa7 100644 --- a/lib/services/livesync/debug-livesync-service.ts +++ b/lib/services/livesync/debug-livesync-service.ts @@ -15,7 +15,7 @@ export class DebugLiveSyncService extends LiveSyncService { private $options: IOptions, private $debugDataService: IDebugDataService, private $projectData: IProjectData, - private debugService: IPlatformDebugService, + private $debugService: IDebugService, private $config: IConfiguration) { super($platformService, @@ -43,25 +43,26 @@ export class DebugLiveSyncService extends LiveSyncService { }; let debugData = this.$debugDataService.createDebugData(this.$projectData, this.$options); + const debugService = this.$debugService.getDebugService(liveSyncResultInfo.deviceAppData.device); await this.$platformService.trackProjectType(this.$projectData); if (this.$options.start) { - return this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); + return this.printDebugInformation(await debugService.debug(debugData, debugOptions)); } const deviceAppData = liveSyncResultInfo.deviceAppData; this.$config.debugLivesync = true; - await this.debugService.debugStop(); + await debugService.debugStop(); let applicationId = deviceAppData.appIdentifier; await deviceAppData.device.applicationManager.stopApplication(applicationId, projectData.projectName); const buildConfig: IBuildConfig = _.merge({ buildForDevice: !deviceAppData.device.isEmulator }, deployOptions); - debugData.pathToAppPackage = this.$platformService.lastOutputPath(this.debugService.platform, buildConfig, projectData); + debugData.pathToAppPackage = this.$platformService.lastOutputPath(debugService.platform, buildConfig, projectData); - this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); + this.printDebugInformation(await debugService.debug(debugData, debugOptions)); } protected printDebugInformation(information: string[]): void { diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index 93a61d006e..ffd1c95d75 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -104,6 +104,7 @@ export abstract class PlatformLiveSyncServiceBase { device: syncInfo.device, platform: syncInfo.device.deviceInfo.platform, getDeviceProjectRootPath: () => this.$devicePathProvider.getDeviceProjectRootPath(syncInfo.device, deviceProjectRootOptions), + deviceSyncZipPath: this.$devicePathProvider.getDeviceSyncZipPath(syncInfo.device), isLiveSyncSupported: async () => true }; } diff --git a/test/services/debug-service.ts b/test/services/debug-service.ts index ca0f5d086c..eb670443f8 100644 --- a/test/services/debug-service.ts +++ b/test/services/debug-service.ts @@ -100,7 +100,7 @@ describe("debugService", () => { describe("rejects the result promise when", () => { const assertIsRejected = async (testData: IDebugTestData, expectedError: string, userSpecifiedOptions?: IDebugOptions): Promise => { const testInjector = getTestInjectorForTestConfiguration(testData); - const debugService = testInjector.resolve(DebugService); + const debugService = testInjector.resolve(DebugService); const debugData = getDebugData(); await assert.isRejected(debugService.debug(debugData, userSpecifiedOptions), expectedError); @@ -161,7 +161,7 @@ describe("debugService", () => { throw new Error(expectedErrorMessage); }; - const debugService = testInjector.resolve(DebugService); + const debugService = testInjector.resolve(DebugService); const debugData = getDebugData(); await assert.isRejected(debugService.debug(debugData, null), expectedErrorMessage); @@ -192,7 +192,7 @@ describe("debugService", () => { return []; }; - const debugService = testInjector.resolve(DebugService); + const debugService = testInjector.resolve(DebugService); const debugData = getDebugData(); await assert.isFulfilled(debugService.debug(debugData, userSpecifiedOptions)); @@ -255,7 +255,7 @@ describe("debugService", () => { testData.deviceInformation.deviceInfo.platform = platform; const testInjector = getTestInjectorForTestConfiguration(testData); - const debugService = testInjector.resolve(DebugService); + const debugService = testInjector.resolve(DebugService); let dataRaisedForConnectionError: any = null; debugService.on(CONNECTION_ERROR_EVENT_NAME, (data: any) => { dataRaisedForConnectionError = data; @@ -279,7 +279,7 @@ describe("debugService", () => { testData.deviceInformation.deviceInfo.platform = platform; const testInjector = getTestInjectorForTestConfiguration(testData); - const debugService = testInjector.resolve(DebugService); + const debugService = testInjector.resolve(DebugService); const debugData = getDebugData(); const url = await debugService.debug(debugData, null); From f11d405744e4dac9bdb43ad25ca18b0d8e85435f Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Thu, 8 Jun 2017 14:16:33 +0300 Subject: [PATCH 087/212] Expose saveBuildInfoFile Expose * saveBuildInfoFile - method which should be used in cloud builds * livesyncService - which can be used to perform livesync with require-d tns --- lib/bootstrap.ts | 2 +- lib/common | 2 +- lib/definitions/platform.d.ts | 9 ++++++ lib/services/livesync/livesync-service.ts | 36 +++++++++++++++-------- lib/services/platform-service.ts | 20 +++++++++---- test/nativescript-cli-lib.ts | 1 + test/stubs.ts | 4 +++ 7 files changed, 53 insertions(+), 21 deletions(-) diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index e66d770679..48532a369e 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -104,7 +104,7 @@ $injector.require("devicePathProvider", "./device-path-provider"); $injector.requireCommand("platform|clean", "./commands/platform-clean"); -$injector.require("liveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript +$injector.requirePublicClass("liveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript $injector.require("debugLiveSyncService", "./services/livesync/debug-livesync-service"); $injector.require("androidLiveSyncService", "./services/livesync/android-livesync-service"); $injector.require("iOSLiveSyncService", "./services/livesync/ios-livesync-service"); diff --git a/lib/common b/lib/common index 28acf919b5..18d82c1b74 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 28acf919b580efd9a828a3e9e9698d38d5387c0b +Subproject commit 18d82c1b74dd7df92e4d508b021204044db723a7 diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index ee3ee8ad64..944c74a5c8 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -195,6 +195,15 @@ interface IPlatformService extends NodeJS.EventEmitter { * @returns {Promise} */ trackActionForPlatform(actionData: ITrackPlatformAction): Promise; + + /** + * Saves build information in a proprietary file. + * @param {string} platform The build platform. + * @param {string} projectDir The project's directory. + * @param {string} buildInfoFileDirname The directory where the build file should be written to. + * @returns {void} + */ + saveBuildInfoFile(platform: string, projectDir: string, buildInfoFileDirname: string): void } interface IAddPlatformCoreOptions extends IPlatformSpecificData, ICreateProjectOptions { } diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index b3b8acefa3..b02b43f7e7 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -1,8 +1,8 @@ import * as path from "path"; import * as choki from "chokidar"; import { EventEmitter } from "events"; -import { exported } from "../../common/decorators"; import { hook } from "../../common/helpers"; +import { FileExtensions } from "../../common/constants"; const LiveSyncEvents = { liveSyncStopped: "liveSyncStopped", @@ -30,8 +30,6 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { super(); } - // TODO: Add finishLivesync method in the platform specific services - @exported("liveSyncService") @hook("liveSync") public async liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise { @@ -54,7 +52,6 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { } } - @exported("liveSyncService") public async stopLiveSync(projectDir: string, deviceIdentifiers?: string[], ): Promise { const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectDir]; @@ -91,7 +88,12 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { // Kill typescript watcher // TODO: Pass the projectDir in hooks args. - await this.$hooksService.executeAfterHooks('watch'); + const projectData = this.$projectDataService.getProjectData(projectDir); + await this.$hooksService.executeAfterHooks('watch', { + hookArgs: { + projectData + } + }); this.emit(LiveSyncEvents.liveSyncStopped, { projectDir }); } @@ -155,7 +157,10 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { if (preparedPlatforms.indexOf(platform) === -1) { preparedPlatforms.push(platform); // TODO: fix args cast to any - await this.$platformService.preparePlatform(platform, {}, null, projectData, {}, modifiedFiles); + await this.$platformService.preparePlatform(platform, { + bundle: false, + release: false, + }, null, projectData, {}, modifiedFiles); } const rebuildInfo = _.find(rebuiltInformation, info => info.isEmulator === device.isEmulator && info.platform === platform); @@ -169,18 +174,16 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { // TODO: fix args cast to any const shouldBuild = await this.$platformService.shouldBuild(platform, projectData, { buildForDevice: !device.isEmulator }, deviceBuildInfoDescriptor.outputPath); + let pathToBuildItem = null; if (shouldBuild) { - const pathToBuildItem = await deviceBuildInfoDescriptor.buildAction(); + pathToBuildItem = await deviceBuildInfoDescriptor.buildAction(); // Is it possible to return shouldBuild for two devices? What about android device and android emulator? rebuiltInformation.push({ isEmulator: device.isEmulator, platform, pathToBuildItem }); - await this.$platformService.installApplication(device, { release: false }, projectData, pathToBuildItem, deviceBuildInfoDescriptor.outputPath); - } const shouldInstall = await this.$platformService.shouldInstall(device, projectData, deviceBuildInfoDescriptor.outputPath); if (shouldInstall) { - - await this.$platformService.installApplication(device, { release: false }, projectData, null, deviceBuildInfoDescriptor.outputPath); + await this.$platformService.installApplication(device, { release: false }, projectData, pathToBuildItem, deviceBuildInfoDescriptor.outputPath); } } @@ -319,7 +322,11 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; }; - await this.$hooksService.executeBeforeHooks('watch'); + await this.$hooksService.executeBeforeHooks('watch', { + hookArgs: { + projectData + } + }); const watcherOptions: choki.WatchOptions = { ignoreInitial: true, @@ -345,7 +352,10 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { filesToRemove.push(filePath); } - startTimeout(); + // Do not sync typescript files directly - wait for javascript changes to occur in order to restart the app only once + if (path.extname(filePath) !== FileExtensions.TYPESCRIPT_FILE) { + startTimeout(); + } }); this.liveSyncProcessesInfo[liveSyncData.projectDir].watcherInfo = { watcher, pattern }; diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index b961d2fc5b..0ca0d4d87b 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -438,23 +438,31 @@ export class PlatformService extends EventEmitter implements IPlatformService { await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, platformData.platformProjectService, handler, platformData.platformProjectService.buildProject(platformData.projectRoot, projectData, buildConfig)); - let prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); - let buildInfoFilePath = this.getBuildOutputPath(platform, platformData, buildConfig); - let buildInfoFile = path.join(buildInfoFilePath, buildInfoFileName); - let buildInfo: IBuildInfo = { + const buildInfoFilePath = this.getBuildOutputPath(platform, platformData, buildConfig); + this.saveBuildInfoFile(platform, projectData.projectDir, buildInfoFilePath); + + this.$logger.out("Project successfully built."); + } + + public saveBuildInfoFile(platform: string, projectDir: string, buildInfoFileDirname: string): void { + let buildInfoFile = path.join(buildInfoFileDirname, buildInfoFileName); + + let prepareInfo = this.$projectChangesService.getPrepareInfo(platform, this.$projectDataService.getProjectData(projectDir)); + let buildInfo = { prepareTime: prepareInfo.changesRequireBuildTime, buildTime: new Date().toString() }; + this.$fs.writeJson(buildInfoFile, buildInfo); - this.$logger.out("Project successfully built."); } public async shouldInstall(device: Mobile.IDevice, projectData: IProjectData, outputPath?: string): Promise { let platform = device.deviceInfo.platform; - let platformData = this.$platformsData.getPlatformData(platform, projectData); if (!(await device.applicationManager.isApplicationInstalled(projectData.projectId))) { return true; } + + let platformData = this.$platformsData.getPlatformData(platform, projectData); let deviceBuildInfo: IBuildInfo = await this.getDeviceBuildInfo(device, projectData); let localBuildInfo = this.getBuildInfo(platform, platformData, { buildForDevice: !device.isEmulator }, outputPath); return !localBuildInfo || !deviceBuildInfo || deviceBuildInfo.buildTime !== localBuildInfo.buildTime; diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index 653d440dec..3bcc81522c 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -20,6 +20,7 @@ describe("nativescript-cli-lib", () => { deviceLogProvider: null, npm: ["install", "uninstall", "view", "search"], extensibilityService: ["loadExtensions", "loadExtension", "getInstalledExtensions", "installExtension", "uninstallExtension"], + liveSyncService: ["liveSync", "stopLiveSync"], analyticsService: ["startEqatecMonitor"], debugService: ["debug"] }; diff --git a/test/stubs.ts b/test/stubs.ts index b18b26efd2..d96520608e 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -623,6 +623,10 @@ export class PlatformServiceStub extends EventEmitter implements IPlatformServic return []; } + public saveBuildInfoFile(platform: string, projectDir: string, buildInfoFileDirname: string): void { + return; + } + public async removePlatforms(platforms: string[]): Promise { } From 4eb8d7f070563a023fdc1fb77c0c12a8a5ced333 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 9 Jun 2017 12:45:20 +0300 Subject: [PATCH 088/212] Fix always reinstalling apps during livesync In case the app is not installed on a device when `tns run ` is started, each change reinstalls the application. The problem is in the "isApplicationInstalled" check which was relying on the result of the first execution (when the app has not been installed). Fix this by always checking if the app is installed. --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index 18d82c1b74..9775b07376 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 18d82c1b74dd7df92e4d508b021204044db723a7 +Subproject commit 9775b07376d2da64c1628a7d70c39de99b589b0f From 4bb03307b666e18b51321c9331ed4b38cd78522d Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Fri, 9 Jun 2017 12:57:25 +0300 Subject: [PATCH 089/212] Minor bugfixes --- lib/common | 2 +- lib/declarations.d.ts | 2 +- lib/services/ios-notification-service.ts | 4 ++-- lib/services/livesync/livesync-service.ts | 2 +- lib/services/platform-service.ts | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/common b/lib/common index 9775b07376..73b1f5cbe9 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 9775b07376d2da64c1628a7d70c39de99b589b0f +Subproject commit 73b1f5cbe9cb96e904c02d27300897a1e33e6456 diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 01c01623f6..146dfead38 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -487,7 +487,7 @@ interface IiOSNotification { interface IiOSNotificationService { awaitNotification(deviceIdentifier: string, socket: number, timeout: number): Promise; - postNotification(deviceIdentifier: string, notification: string, commandType?: string): Promise; + postNotification(deviceIdentifier: string, notification: string, commandType?: string): Promise; } interface IiOSSocketRequestExecutor { diff --git a/lib/services/ios-notification-service.ts b/lib/services/ios-notification-service.ts index 36b8f6dc87..7ac5687554 100644 --- a/lib/services/ios-notification-service.ts +++ b/lib/services/ios-notification-service.ts @@ -15,10 +15,10 @@ export class IOSNotificationService implements IiOSNotificationService { return _.first(notificationResponse[deviceIdentifier]).response; } - public async postNotification(deviceIdentifier: string, notification: string, commandType?: string): Promise { + public async postNotification(deviceIdentifier: string, notification: string, commandType?: string): Promise { commandType = commandType || constants.IOS_POST_NOTIFICATION_COMMAND_TYPE; const response = await this.$iosDeviceOperations.postNotification([{ deviceId: deviceIdentifier, commandType: commandType, notificationName: notification }]); - return _.first(response[deviceIdentifier]).response; + return +_.first(response[deviceIdentifier]).response; } } diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index b02b43f7e7..23939cade7 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -106,7 +106,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { // TODO: Assure we are able to self-restart iOS apps on Windows. await platformLiveSyncService.refreshApplication(projectData, liveSyncResultInfo); } catch (err) { - this.$logger.info(`Error while trying to start application ${projectData.projectId} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Error is: ${err}`); + this.$logger.info(`Error while trying to start application ${projectData.projectId} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Error is: ${err.message || err}`); const msg = `Unable to start application ${projectData.projectId} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Try starting it manually.`; this.$logger.warn(msg); this.emit(LiveSyncEvents.liveSyncNotification, { diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 0ca0d4d87b..715e0ebe2c 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -473,9 +473,9 @@ export class PlatformService extends EventEmitter implements IPlatformService { let platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); if (!packageFile) { if (this.$devicesService.isiOSSimulator(device)) { - packageFile = this.getLatestApplicationPackageForEmulator(platformData, buildConfig).packageName; + packageFile = this.getLatestApplicationPackageForEmulator(platformData, buildConfig, outputFilePath).packageName; } else { - packageFile = this.getLatestApplicationPackageForDevice(platformData, buildConfig).packageName; + packageFile = this.getLatestApplicationPackageForDevice(platformData, buildConfig, outputFilePath).packageName; } } From e6c2821672a755c7284a817d0365b1b7bed2f413 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Fri, 9 Jun 2017 13:12:04 +0300 Subject: [PATCH 090/212] Move notification service to common --- lib/common | 2 +- lib/declarations.d.ts | 5 ----- lib/services/ios-notification-service.ts | 25 ------------------------ 3 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 lib/services/ios-notification-service.ts diff --git a/lib/common b/lib/common index 73b1f5cbe9..f37528aeed 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 73b1f5cbe9cb96e904c02d27300897a1e33e6456 +Subproject commit f37528aeeda54e9f73f3fc5986aab5f226767d14 diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 146dfead38..2d9637d780 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -485,11 +485,6 @@ interface IiOSNotification { getAttachAvailable(projectId: string): string; } -interface IiOSNotificationService { - awaitNotification(deviceIdentifier: string, socket: number, timeout: number): Promise; - postNotification(deviceIdentifier: string, notification: string, commandType?: string): Promise; -} - interface IiOSSocketRequestExecutor { executeLaunchRequest(deviceIdentifier: string, timeout: number, readyForAttachTimeout: number, projectId: string, shouldBreak?: boolean): Promise; executeAttachRequest(device: Mobile.IiOSDevice, timeout: number, projectId: string): Promise; diff --git a/lib/services/ios-notification-service.ts b/lib/services/ios-notification-service.ts deleted file mode 100644 index 7ac5687554..0000000000 --- a/lib/services/ios-notification-service.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as constants from "../common/constants"; - -export class IOSNotificationService implements IiOSNotificationService { - constructor(private $iosDeviceOperations: IIOSDeviceOperations) { } - - public async awaitNotification(deviceIdentifier: string, socket: number, timeout: number): Promise { - const notificationResponse = await this.$iosDeviceOperations.awaitNotificationResponse([{ - deviceId: deviceIdentifier, - socket: socket, - timeout: timeout, - responseCommandType: constants.IOS_RELAY_NOTIFICATION_COMMAND_TYPE, - responsePropertyName: "Name" - }]); - - return _.first(notificationResponse[deviceIdentifier]).response; - } - - public async postNotification(deviceIdentifier: string, notification: string, commandType?: string): Promise { - commandType = commandType || constants.IOS_POST_NOTIFICATION_COMMAND_TYPE; - const response = await this.$iosDeviceOperations.postNotification([{ deviceId: deviceIdentifier, commandType: commandType, notificationName: notification }]); - return +_.first(response[deviceIdentifier]).response; - } -} - -$injector.register("iOSNotificationService", IOSNotificationService); From 4e4197e7ae0a197568d26a4c0c1a7c381890a800 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Fri, 9 Jun 2017 13:55:14 +0300 Subject: [PATCH 091/212] Fix Success livesync and run fail messages --- lib/commands/run.ts | 44 +++++++++++++---------- lib/common | 2 +- lib/services/livesync/livesync-service.ts | 3 +- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 7c0591f656..2bca1d0817 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -1,3 +1,5 @@ +import { ERROR_NO_VALID_SUBCOMMAND_FORMAT } from "../common/constants"; + export class RunCommandBase implements ICommand { protected platform: string; @@ -7,19 +9,24 @@ export class RunCommandBase implements ICommand { protected $options: IOptions, protected $emulatorPlatformService: IEmulatorPlatformService, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + protected $errors: IErrors, private $devicesService: Mobile.IDevicesService, private $hostInfo: IHostInfo, private $iosDeviceOperations: IIOSDeviceOperations, private $mobileHelper: Mobile.IMobileHelper) { - this.$projectData.initializeProjectData(); } - public allowedParameters: ICommandParameter[] = [ ]; + public allowedParameters: ICommandParameter[] = []; public async execute(args: string[]): Promise { return this.executeCore(args); } public async canExecute(args: string[]): Promise { + if (args.length) { + this.$errors.fail(ERROR_NO_VALID_SUBCOMMAND_FORMAT, "run"); + } + + this.$projectData.initializeProjectData(); if (!this.platform && !this.$hostInfo.isDarwin) { this.platform = this.$devicePlatformsConstants.Android; } @@ -43,7 +50,7 @@ export class RunCommandBase implements ICommand { identifier: d.deviceInfo.identifier, buildAction: async (): Promise => { const buildConfig: IBuildConfig = { - buildForDevice: !d.isEmulator, // this.$options.forDevice, + buildForDevice: !d.isEmulator, projectDir: this.$options.path, clean: this.$options.clean, teamId: this.$options.teamId, @@ -65,25 +72,26 @@ export class RunCommandBase implements ICommand { return info; }); - // if (this.$options.release) { - // const deployOpts: IRunPlatformOptions = { - // device: this.$options.device, - // emulator: this.$options.emulator, - // justlaunch: this.$options.justlaunch, - // }; - - // await this.$platformService.startApplication(args[0], deployOpts, this.$projectData.projectId); - // return this.$platformService.trackProjectType(this.$projectData); - // } - if ((!this.platform || this.$mobileHelper.isiOSPlatform(this.platform)) && (this.$options.watch || !this.$options.justlaunch)) { this.$iosDeviceOperations.setShouldDispose(false); } + if (this.$options.release) { + const deployOpts: IRunPlatformOptions = { + device: this.$options.device, + emulator: this.$options.emulator, + justlaunch: this.$options.justlaunch, + }; + + await this.$platformService.startApplication(args[0], deployOpts, this.$projectData.projectId); + return this.$platformService.trackProjectType(this.$projectData); + } + const liveSyncInfo: ILiveSyncInfo = { projectDir: this.$projectData.projectDir, skipWatcher: !this.$options.watch, watchAllFiles: this.$options.syncAllFiles }; await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } } + $injector.registerCommand("run|*all", RunCommandBase); export class RunIosCommand extends RunCommandBase implements ICommand { @@ -95,7 +103,7 @@ export class RunIosCommand extends RunCommandBase implements ICommand { constructor($platformService: IPlatformService, private $platformsData: IPlatformsData, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - private $errors: IErrors, + protected $errors: IErrors, $liveSyncService: ILiveSyncService, $projectData: IProjectData, $options: IOptions, @@ -104,7 +112,7 @@ export class RunIosCommand extends RunCommandBase implements ICommand { $hostInfo: IHostInfo, $iosDeviceOperations: IIOSDeviceOperations, $mobileHelper: Mobile.IMobileHelper) { - super($platformService, $liveSyncService, $projectData, $options, $emulatorPlatformService, $devicePlatformsConstants, $devicesService, $hostInfo, $iosDeviceOperations, $mobileHelper); + super($platformService, $liveSyncService, $projectData, $options, $emulatorPlatformService, $devicePlatformsConstants, $errors, $devicesService, $hostInfo, $iosDeviceOperations, $mobileHelper); } public async execute(args: string[]): Promise { @@ -131,7 +139,7 @@ export class RunAndroidCommand extends RunCommandBase implements ICommand { constructor($platformService: IPlatformService, private $platformsData: IPlatformsData, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - private $errors: IErrors, + protected $errors: IErrors, $liveSyncService: ILiveSyncService, $projectData: IProjectData, $options: IOptions, @@ -140,7 +148,7 @@ export class RunAndroidCommand extends RunCommandBase implements ICommand { $hostInfo: IHostInfo, $iosDeviceOperations: IIOSDeviceOperations, $mobileHelper: Mobile.IMobileHelper) { - super($platformService, $liveSyncService, $projectData, $options, $emulatorPlatformService, $devicePlatformsConstants, $devicesService, $hostInfo, $iosDeviceOperations, $mobileHelper); + super($platformService, $liveSyncService, $projectData, $options, $emulatorPlatformService, $devicePlatformsConstants, $errors, $devicesService, $hostInfo, $iosDeviceOperations, $mobileHelper); } public async execute(args: string[]): Promise { diff --git a/lib/common b/lib/common index f37528aeed..1cc1c65ec7 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit f37528aeeda54e9f73f3fc5986aab5f226767d14 +Subproject commit 1cc1c65ec710938c6decf3c380956bd65e55f3a5 diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 23939cade7..8b49de9e2d 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -103,7 +103,6 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo): Promise { const platformLiveSyncService = this.getLiveSyncService(liveSyncResultInfo.deviceAppData.platform); try { - // TODO: Assure we are able to self-restart iOS apps on Windows. await platformLiveSyncService.refreshApplication(projectData, liveSyncResultInfo); } catch (err) { this.$logger.info(`Error while trying to start application ${projectData.projectId} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Error is: ${err.message || err}`); @@ -123,6 +122,8 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier }); + + this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); } private setLiveSyncProcessInfo(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[]): void { From e3773401e99c4860e4d41ea33f585523e4a00a70 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Fri, 9 Jun 2017 17:39:58 +0300 Subject: [PATCH 092/212] Implement tracking --- lib/commands/run.ts | 5 +- lib/common | 2 +- lib/constants.ts | 7 +++ lib/definitions/livesync.d.ts | 6 +++ lib/services/emulator-platform-service.ts | 3 +- .../livesync/debug-livesync-service.ts | 2 + lib/services/livesync/livesync-service.ts | 48 ++++++++++++++++--- 7 files changed, 63 insertions(+), 10 deletions(-) diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 2bca1d0817..ce50ab2358 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -124,7 +124,7 @@ export class RunIosCommand extends RunCommandBase implements ICommand { } public async canExecute(args: string[]): Promise { - return args.length === 0 && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); + return super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); } } @@ -156,6 +156,7 @@ export class RunAndroidCommand extends RunCommandBase implements ICommand { } public async canExecute(args: string[]): Promise { + super.canExecute(args); if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.Android, this.$projectData)) { this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.Android, process.platform); } @@ -163,7 +164,7 @@ export class RunAndroidCommand extends RunCommandBase implements ICommand { if (this.$options.release && (!this.$options.keyStorePath || !this.$options.keyStorePassword || !this.$options.keyStoreAlias || !this.$options.keyStoreAliasPassword)) { this.$errors.fail("When producing a release build, you need to specify all --key-store-* options."); } - return args.length === 0 && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.Android); + return this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.Android); } } diff --git a/lib/common b/lib/common index 1cc1c65ec7..333ee88c7b 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 1cc1c65ec710938c6decf3c380956bd65e55f3a5 +Subproject commit 333ee88c7b2963d76ed44e016bbf0ad54e9f9b88 diff --git a/lib/constants.ts b/lib/constants.ts index 4ef5504a7d..8a5b31cf3f 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -23,6 +23,13 @@ export class PackageVersion { static LATEST = "latest"; } +const liveSyncOperation = "LiveSync Operation"; +export class LiveSyncTrackActionNames { + static LIVESYNC_OPERATION = liveSyncOperation; + static LIVESYNC_OPERATION_BUILD = `${liveSyncOperation} - Build`; + static DEVICE_INFO = `Device Info for ${liveSyncOperation}`; +} + export const PackageJsonKeysToKeep: Array = ["name", "main", "android", "version"]; export class SaveOptions { diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 605a554c02..6c55c78c33 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -116,6 +116,12 @@ interface ILiveSyncInfo { useLiveEdit?: boolean; } +interface ILatestAppPackageInstalledSettings { + [key: string]: { + [key: string]: boolean; + } +} + interface ILiveSyncBuildInfo { platform: string; isEmulator: boolean; diff --git a/lib/services/emulator-platform-service.ts b/lib/services/emulator-platform-service.ts index 6bcd24de6f..ead601fa6d 100644 --- a/lib/services/emulator-platform-service.ts +++ b/lib/services/emulator-platform-service.ts @@ -1,4 +1,5 @@ import { createTable, deferPromise } from "../common/helpers"; +import { DeviceTypes } from "../common/constants"; export class EmulatorPlatformService implements IEmulatorPlatformService { @@ -70,7 +71,7 @@ export class EmulatorPlatformService implements IEmulatorPlatformService { name: device.deviceInfo.displayName, version: device.deviceInfo.version, platform: "Android", - type: "emulator", + type: DeviceTypes.Emulator, isRunning: true }; } diff --git a/lib/services/livesync/debug-livesync-service.ts b/lib/services/livesync/debug-livesync-service.ts index acb0a55fa7..a6d63b2378 100644 --- a/lib/services/livesync/debug-livesync-service.ts +++ b/lib/services/livesync/debug-livesync-service.ts @@ -7,6 +7,7 @@ export class DebugLiveSyncService extends LiveSyncService { $projectDataService: IProjectDataService, protected $devicesService: Mobile.IDevicesService, $mobileHelper: Mobile.IMobileHelper, + $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, protected $logger: ILogger, $processService: IProcessService, @@ -22,6 +23,7 @@ export class DebugLiveSyncService extends LiveSyncService { $projectDataService, $devicesService, $mobileHelper, + $devicePlatformsConstants, $nodeModulesDependenciesBuilder, $logger, $processService, diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 8b49de9e2d..a00ab15366 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -2,7 +2,8 @@ import * as path from "path"; import * as choki from "chokidar"; import { EventEmitter } from "events"; import { hook } from "../../common/helpers"; -import { FileExtensions } from "../../common/constants"; +import { APP_FOLDER_NAME, PACKAGE_JSON_FILE_NAME, LiveSyncTrackActionNames } from "../../constants"; +import { FileExtensions, DeviceTypes } from "../../common/constants"; const LiveSyncEvents = { liveSyncStopped: "liveSyncStopped", @@ -22,6 +23,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { private $projectDataService: IProjectDataService, protected $devicesService: Mobile.IDevicesService, private $mobileHelper: Mobile.IMobileHelper, + protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, protected $logger: ILogger, private $processService: IProcessService, @@ -152,8 +154,8 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { rebuiltInformation: ILiveSyncBuildInfo[], projectData: IProjectData, deviceBuildInfoDescriptor: ILiveSyncDeviceInfo, + settings: ILatestAppPackageInstalledSettings, modifiedFiles?: string[]): Promise { - const platform = device.deviceInfo.platform; if (preparedPlatforms.indexOf(platform) === -1) { preparedPlatforms.push(platform); @@ -176,12 +178,29 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { // TODO: fix args cast to any const shouldBuild = await this.$platformService.shouldBuild(platform, projectData, { buildForDevice: !device.isEmulator }, deviceBuildInfoDescriptor.outputPath); let pathToBuildItem = null; + let action = LiveSyncTrackActionNames.LIVESYNC_OPERATION; if (shouldBuild) { pathToBuildItem = await deviceBuildInfoDescriptor.buildAction(); // Is it possible to return shouldBuild for two devices? What about android device and android emulator? rebuiltInformation.push({ isEmulator: device.isEmulator, platform, pathToBuildItem }); + action = LiveSyncTrackActionNames.LIVESYNC_OPERATION_BUILD; + } + + if (!settings[platform][device.deviceInfo.type]) { + let isForDevice = !device.isEmulator; + settings[platform][device.deviceInfo.type] = true; + if (this.$mobileHelper.isAndroidPlatform(platform)) { + settings[platform][DeviceTypes.Emulator] = true; + settings[platform][DeviceTypes.Device] = true; + isForDevice = null; + } + + await this.$platformService.trackActionForPlatform({ action, platform, isForDevice }); } + await this.$platformService.trackActionForPlatform({ action: LiveSyncTrackActionNames.DEVICE_INFO, platform, isForDevice: !device.isEmulator, deviceOsVersion: device.deviceInfo.version }); + + const shouldInstall = await this.$platformService.shouldInstall(device, projectData, deviceBuildInfoDescriptor.outputPath); if (shouldInstall) { await this.$platformService.installApplication(device, { release: false }, projectData, pathToBuildItem, deviceBuildInfoDescriptor.outputPath); @@ -192,6 +211,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const preparedPlatforms: string[] = []; const rebuiltInformation: ILiveSyncBuildInfo[] = []; + const settings = this.getDefaultLatestAppPackageInstalledSettings(); // Now fullSync const deviceAction = async (device: Mobile.IDevice): Promise => { try { @@ -204,7 +224,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const platform = device.deviceInfo.platform; const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, projectData, deviceDescriptor); + await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, projectData, deviceDescriptor, settings); const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ projectData, device, @@ -212,6 +232,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { useLiveEdit: liveSyncData.useLiveEdit, watch: !liveSyncData.skipWatcher }); + await this.$platformService.trackActionForPlatform({ action: "LiveSync", platform: device.deviceInfo.platform, isForDevice: !device.isEmulator, deviceOsVersion: device.deviceInfo.version }); await this.refreshApplication(projectData, liveSyncResultInfo); } catch (err) { this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`); @@ -232,14 +253,27 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier))); } + private getDefaultLatestAppPackageInstalledSettings(): ILatestAppPackageInstalledSettings { + return { + [this.$devicePlatformsConstants.Android]: { + [DeviceTypes.Device]: false, + [DeviceTypes.Emulator]: false + }, + [this.$devicePlatformsConstants.iOS]: { + [DeviceTypes.Device]: false, + [DeviceTypes.Emulator]: false + } + } + } + private async startWatcher(projectData: IProjectData, liveSyncData: ILiveSyncInfo): Promise { - let pattern = ["app"]; + let pattern = [APP_FOLDER_NAME]; if (liveSyncData.watchAllFiles) { const productionDependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); - pattern.push("package.json"); + pattern.push(PACKAGE_JSON_FILE_NAME); // watch only production node_module/packages same one prepare uses for (let index in productionDependencies) { @@ -274,11 +308,13 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const preparedPlatforms: string[] = []; const rebuiltInformation: ILiveSyncBuildInfo[] = []; + const latestAppPackageInstalledSettings = this.getDefaultLatestAppPackageInstalledSettings(); + await this.$devicesService.execute(async (device: Mobile.IDevice) => { const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; const deviceDescriptor = _.find(liveSyncProcessInfo.deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, projectData, deviceDescriptor, allModifiedFiles); + await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, projectData, deviceDescriptor, latestAppPackageInstalledSettings, allModifiedFiles); const service = this.getLiveSyncService(device.deviceInfo.platform); const settings: ILiveSyncWatchInfo = { From 75578ea1a24acf6948fade7d46a73f5f2aa322bd Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Fri, 9 Jun 2017 18:02:50 +0300 Subject: [PATCH 093/212] Fix lint errors --- lib/definitions/livesync.d.ts | 6 +----- lib/services/livesync/livesync-service.ts | 3 +-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 6c55c78c33..74203f95e1 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -116,11 +116,7 @@ interface ILiveSyncInfo { useLiveEdit?: boolean; } -interface ILatestAppPackageInstalledSettings { - [key: string]: { - [key: string]: boolean; - } -} +interface ILatestAppPackageInstalledSettings extends IDictionary> { /* empty */ } interface ILiveSyncBuildInfo { platform: string; diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index a00ab15366..3d886687be 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -200,7 +200,6 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { await this.$platformService.trackActionForPlatform({ action: LiveSyncTrackActionNames.DEVICE_INFO, platform, isForDevice: !device.isEmulator, deviceOsVersion: device.deviceInfo.version }); - const shouldInstall = await this.$platformService.shouldInstall(device, projectData, deviceBuildInfoDescriptor.outputPath); if (shouldInstall) { await this.$platformService.installApplication(device, { release: false }, projectData, pathToBuildItem, deviceBuildInfoDescriptor.outputPath); @@ -263,7 +262,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { [DeviceTypes.Device]: false, [DeviceTypes.Emulator]: false } - } + }; } private async startWatcher(projectData: IProjectData, From 03f0537c68a7bc94c1395443d53eef0e8f80a856 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Mon, 12 Jun 2017 10:55:19 +0300 Subject: [PATCH 094/212] Fix message for tests --- .../livesync/platform-livesync-service-base.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index ffd1c95d75..6f678aa970 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -1,4 +1,5 @@ import * as path from "path"; +import * as util from "util"; import { APP_FOLDER_NAME } from "../../constants"; export abstract class PlatformLiveSyncServiceBase { @@ -95,6 +96,8 @@ export abstract class PlatformLiveSyncServiceBase { } else { await deviceAppData.device.fileSystem.transferFiles(deviceAppData, localToDevicePaths); } + + this.logFilesSyncInformation(localToDevicePaths, "Successfully transferred %s.", this.$logger.info); } protected async getAppData(syncInfo: IFullSyncInfo): Promise { @@ -108,4 +111,15 @@ export abstract class PlatformLiveSyncServiceBase { isLiveSyncSupported: async () => true }; } + + private logFilesSyncInformation(localToDevicePaths: Mobile.ILocalToDevicePathData[], message: string, action: Function): void { + if (localToDevicePaths && localToDevicePaths.length < 10) { + _.each(localToDevicePaths, (file: Mobile.ILocalToDevicePathData) => { + action.call(this.$logger, util.format(message, path.basename(file.getLocalPath()).yellow)); + }); + } else { + action.call(this.$logger, util.format(message, "all files")); + } + } + } From e2205c65325fb298fa62f1639bfa7fff12464812 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Mon, 12 Jun 2017 13:10:41 +0300 Subject: [PATCH 095/212] Fix comments vol. I --- PublicAPI.md | 8 +-- lib/bootstrap.ts | 3 +- lib/commands/build.ts | 25 +++++---- lib/commands/debug.ts | 7 +-- lib/commands/run.ts | 4 +- lib/constants.ts | 12 ++-- lib/definitions/livesync.d.ts | 9 ++- lib/definitions/platform.d.ts | 11 +++- lib/definitions/simple-plist.d.ts | 4 ++ lib/device-path-provider.ts | 36 ++++++------ lib/services/ios-project-service.ts | 8 +-- .../android-device-livesync-service.ts | 55 +++++++++++-------- .../livesync/ios-device-livesync-service.ts | 5 +- lib/services/livesync/livesync-service.ts | 20 +++---- .../platform-livesync-service-base.ts | 10 ++-- lib/services/platform-service.ts | 13 ++++- 16 files changed, 123 insertions(+), 107 deletions(-) create mode 100644 lib/definitions/simple-plist.d.ts diff --git a/PublicAPI.md b/PublicAPI.md index da8e027f1a..ce9e13e09c 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -625,7 +625,7 @@ tns.liveSyncService.on("liveSyncStarted", data => { }); ``` -* liveSyncExecuted - raised whenever CLI finishes a LiveSync operation for specific device. When `liveSync` method is called, the initial LiveSync operation will emit `liveSyncExecuted` for each specified device once it finishes the operation. After that the event will be emitted whenever a change is detected (in case file system watcher is staretd) and the LiveSync operation is executed for each device. The event is raised with the following data: +* liveSyncExecuted - raised whenever CLI finishes a LiveSync operation for specific device. When `liveSync` method is called, the initial LiveSync operation will emit `liveSyncExecuted` for each specified device once it finishes the operation. After that the event will be emitted whenever a change is detected (in case file system watcher is started) and the LiveSync operation is executed for each device. The event is raised with the following data: ```TypeScript { projectDir: string; @@ -641,7 +641,7 @@ tns.liveSyncService.on("liveSyncStarted", data => { Example: ```JavaScript tns.liveSyncService.on("liveSyncExecuted", data => { - console.log(`Executed LiveSync on ${data.deviceIdentifier} for ${data.applicationIdentifier}. Uploaded files are: ${syncedFiles.join(" ")}.`); + console.log(`Executed LiveSync on ${data.deviceIdentifier} for ${data.applicationIdentifier}. Uploaded files are: ${data.syncedFiles.join(" ")}.`); }); ``` @@ -680,7 +680,7 @@ tns.liveSyncService.on("liveSyncStopped", data => { Example: ```JavaScript tns.liveSyncService.on("liveSyncError", data => { - console.log(`Error detected during LiveSync on ${data.deviceIdentifier} for ${data.projectDir}. Error: ${err.message}.`); + console.log(`Error detected during LiveSync on ${data.deviceIdentifier} for ${data.projectDir}. Error: ${data.error.message}.`); }); ``` @@ -697,7 +697,7 @@ tns.liveSyncService.on("liveSyncError", data => { Example: ```JavaScript tns.liveSyncService.on("notify", data => { - console.log(`Notification: ${notification} for LiveSync operation on ${data.deviceIdentifier} for ${data.projectDir}. `); + console.log(`Notification: ${data.notification} for LiveSync operation on ${data.deviceIdentifier} for ${data.projectDir}. `); }); ``` diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 48532a369e..0ebcc14815 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -104,12 +104,11 @@ $injector.require("devicePathProvider", "./device-path-provider"); $injector.requireCommand("platform|clean", "./commands/platform-clean"); -$injector.requirePublicClass("liveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript +$injector.requirePublicClass("liveSyncService", "./services/livesync/livesync-service"); $injector.require("debugLiveSyncService", "./services/livesync/debug-livesync-service"); $injector.require("androidLiveSyncService", "./services/livesync/android-livesync-service"); $injector.require("iOSLiveSyncService", "./services/livesync/ios-livesync-service"); $injector.require("usbLiveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript -$injector.require("iosLiveSyncServiceLocator", "./services/livesync/ios-device-livesync-service"); $injector.require("sysInfo", "./sys-info"); $injector.require("iOSNotificationService", "./services/ios-notification-service"); diff --git a/lib/commands/build.ts b/lib/commands/build.ts index 7c09171615..83825a5859 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -1,5 +1,6 @@ export class BuildCommandBase { constructor(protected $options: IOptions, + protected $errors: IErrors, protected $projectData: IProjectData, protected $platformsData: IPlatformsData, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, @@ -30,18 +31,24 @@ export class BuildCommandBase { this.$platformService.copyLastOutput(platform, this.$options.copyTo, buildConfig, this.$projectData); } } + + protected validatePlatform(platform: string): void { + if (!this.$platformService.isPlatformSupportedForOS(platform, this.$projectData)) { + this.$errors.fail(`Applications for platform ${platform} can not be built on this OS - ${process.platform}`); + } + } } export class BuildIosCommand extends BuildCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; constructor(protected $options: IOptions, - private $errors: IErrors, + $errors: IErrors, $projectData: IProjectData, $platformsData: IPlatformsData, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $platformService: IPlatformService) { - super($options, $projectData, $platformsData, $devicePlatformsConstants, $platformService); + super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformService); } public async execute(args: string[]): Promise { @@ -49,10 +56,7 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { } public canExecute(args: string[]): Promise { - if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { - this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.iOS, process.platform); - } - + super.validatePlatform(this.$devicePlatformsConstants.iOS); return args.length === 0 && this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); } } @@ -63,12 +67,12 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; constructor(protected $options: IOptions, - private $errors: IErrors, + protected $errors: IErrors, $projectData: IProjectData, $platformsData: IPlatformsData, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $platformService: IPlatformService) { - super($options, $projectData, $platformsData, $devicePlatformsConstants, $platformService); + super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformService); } public async execute(args: string[]): Promise { @@ -76,10 +80,7 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { } public async canExecute(args: string[]): Promise { - if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.Android, this.$projectData)) { - this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.Android, process.platform); - } - + super.validatePlatform(this.$devicePlatformsConstants.Android); if (this.$options.release && (!this.$options.keyStorePath || !this.$options.keyStorePassword || !this.$options.keyStoreAlias || !this.$options.keyStoreAliasPassword)) { this.$errors.fail("When producing a release build, you need to specify all --key-store-* options."); } diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index a64313bef6..d1fe45c1f2 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -30,7 +30,6 @@ export abstract class DebugPlatformCommand implements ICommand { this.$config.debugLivesync = true; - // TODO: Fix this call await this.$devicesService.initialize({ deviceId: this.$options.device, platform: this.platform, skipDeviceDetectionInterval: true, skipInferPlatform: true }); await this.$devicesService.detectCurrentlyAttachedDevices(); @@ -42,7 +41,7 @@ export abstract class DebugPlatformCommand implements ICommand { identifier: d.deviceInfo.identifier, buildAction: async (): Promise => { const buildConfig: IBuildConfig = { - buildForDevice: !d.isEmulator, // this.$options.forDevice, + buildForDevice: !d.isEmulator, projectDir: this.$options.path, clean: this.$options.clean, teamId: this.$options.teamId, @@ -133,7 +132,7 @@ export class DebugIOSCommand extends DebugPlatformCommand { } } - public platform = "iOS"; + public platform = this.$devicePlatformsConstants.iOS; } $injector.registerCommand("debug|ios", DebugIOSCommand); @@ -162,7 +161,7 @@ export class DebugAndroidCommand extends DebugPlatformCommand { return await super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.Android); } - public platform = "Android"; + public platform = this.$devicePlatformsConstants.Android; } $injector.registerCommand("debug|android", DebugAndroidCommand); diff --git a/lib/commands/run.ts b/lib/commands/run.ts index ce50ab2358..0bca94dc18 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -72,7 +72,9 @@ export class RunCommandBase implements ICommand { return info; }); - if ((!this.platform || this.$mobileHelper.isiOSPlatform(this.platform)) && (this.$options.watch || !this.$options.justlaunch)) { + const workingWithiOSDevices = !this.platform || this.$mobileHelper.isiOSPlatform(this.platform); + const shouldKeepProcessAlive = this.$options.watch || !this.$options.justlaunch; + if (workingWithiOSDevices && shouldKeepProcessAlive) { this.$iosDeviceOperations.setShouldDispose(false); } diff --git a/lib/constants.ts b/lib/constants.ts index 8a5b31cf3f..984accc309 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -73,11 +73,13 @@ class ItunesConnectApplicationTypesClass implements IiTunesConnectApplicationTyp } export const ItunesConnectApplicationTypes = new ItunesConnectApplicationTypesClass(); -export const SYNC_DIR_NAME = "sync"; -export const REMOVEDSYNC_DIR_NAME = "removedsync"; -export const FULLSYNC_DIR_NAME = "fullsync"; -export const IOS_DEVICE_PROJECT_ROOT_PATH = "Library/Application Support/LiveSync/app"; -export const IOS_DEVICE_SYNC_ZIP_PATH = "Library/Application Support/LiveSync/sync.zip"; +export class LiveSyncPaths { + static SYNC_DIR_NAME = "sync"; + static REMOVEDSYNC_DIR_NAME = "removedsync"; + static FULLSYNC_DIR_NAME = "fullsync"; + static IOS_DEVICE_PROJECT_ROOT_PATH = "Library/Application Support/LiveSync"; + static IOS_DEVICE_SYNC_ZIP_PATH = "Library/Application Support/LiveSync/sync.zip"; +}; export const ANGULAR_NAME = "angular"; export const TYPESCRIPT_NAME = "typescript"; export const BUILD_OUTPUT_EVENT_NAME = "buildOutput"; diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 74203f95e1..92f8d61636 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -1,3 +1,4 @@ +// This interface is a mashup of NodeJS' along with Chokidar's event watchers interface IFSWatcher extends NodeJS.EventEmitter { // from fs.FSWatcher close(): void; @@ -152,7 +153,6 @@ interface ILiveSyncWatchInfo { isRebuilt: boolean; syncAllFiles: boolean; useLiveEdit?: boolean; - } interface ILiveSyncResultInfo { @@ -190,12 +190,11 @@ interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase /** * Removes specified files from a connected device - * @param {string} appIdentifier Application identifier. + * @param {Mobile.IDeviceAppData} deviceAppData Data about device and app. * @param {Mobile.ILocalToDevicePathData[]} localToDevicePaths Object containing a mapping of file paths from the system to the device. - * @param {string} projectId Project identifier - for example org.nativescript.livesync. * @return {Promise} */ - removeFiles(appIdentifier: string, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectId: string): Promise; + removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise; } interface IAndroidNativeScriptDeviceLiveSyncService { @@ -209,12 +208,12 @@ interface IAndroidNativeScriptDeviceLiveSyncService { interface IDeviceProjectRootOptions { appIdentifier: string; + getDirname?: boolean; syncAllFiles?: boolean; watch?: boolean; } interface IDevicePathProvider { - getDeviceBuildInfoDirname(device: Mobile.IDevice, appIdentifier: string): Promise; getDeviceProjectRootPath(device: Mobile.IDevice, options: IDeviceProjectRootOptions): Promise; getDeviceSyncZipPath(device: Mobile.IDevice): string; } diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 944c74a5c8..d0d2b914f7 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -55,7 +55,8 @@ interface IPlatformService extends NodeJS.EventEmitter { * - the .nsbuildinfo file in product folder points to an old prepare. * @param {string} platform The platform to build. * @param {IProjectData} projectData DTO with information about the project. - * @param {IBuildConfig} buildConfig Indicates whether the build is for device or emulator. + * @param {IBuildConfig} @optional buildConfig Indicates whether the build is for device or emulator. + * @param {string} @optional outputPath Directory containing build information and artifacts. * @returns {boolean} true indicates that the platform should be build. */ shouldBuild(platform: string, projectData: IProjectData, buildConfig?: IBuildConfig, outputPath?: string): Promise; @@ -77,6 +78,7 @@ interface IPlatformService extends NodeJS.EventEmitter { * - the .nsbuildinfo file located in application root folder is different than the local .nsbuildinfo file * @param {Mobile.IDevice} device The device where the application should be installed. * @param {IProjectData} projectData DTO with information about the project. + * @param {string} @optional outputPath Directory containing build information and artifacts. * @returns {Promise} true indicates that the application should be installed. */ shouldInstall(device: Mobile.IDevice, projectData: IProjectData, outputPath?: string): Promise; @@ -87,6 +89,8 @@ interface IPlatformService extends NodeJS.EventEmitter { * * .nsbuildinfo is not persisted when building for release. * @param {Mobile.IDevice} device The device where the application should be installed. * @param {IRelease} options Whether the application was built in release configuration. + * @param {string} @optional pathToBuiltApp Path to build artifact. + * @param {string} @optional outputPath Directory containing build information and artifacts. * @param {IProjectData} projectData DTO with information about the project. * @returns {void} */ @@ -129,7 +133,10 @@ interface IPlatformService extends NodeJS.EventEmitter { validatePlatform(platform: string, projectData: IProjectData): void; /** - * Ensures that passed platform can be built on the current OS + * Checks whether passed platform can be built on the current OS + * @param {string} platform The mobile platform. + * @param {IProjectData} projectData DTO with information about the project. + * @returns {boolean} Whether the platform is supported for current OS or not. */ isPlatformSupportedForOS(platform: string, projectData: IProjectData): boolean; diff --git a/lib/definitions/simple-plist.d.ts b/lib/definitions/simple-plist.d.ts new file mode 100644 index 0000000000..adc559fc81 --- /dev/null +++ b/lib/definitions/simple-plist.d.ts @@ -0,0 +1,4 @@ +declare module "simple-plist" { + export function readFile(filePath: string, callback?:(err: Error, obj: any) => void): void; + export function readFileSync(filePath: string): any; +} diff --git a/lib/device-path-provider.ts b/lib/device-path-provider.ts index e4e41c8a58..ee360590ec 100644 --- a/lib/device-path-provider.ts +++ b/lib/device-path-provider.ts @@ -1,5 +1,5 @@ import { fromWindowsRelativePathToUnix } from "./common/helpers"; -import { IOS_DEVICE_SYNC_ZIP_PATH, IOS_DEVICE_PROJECT_ROOT_PATH, SYNC_DIR_NAME, FULLSYNC_DIR_NAME } from "./constants"; +import { APP_FOLDER_NAME, LiveSyncPaths } from "./constants"; import { AndroidDeviceLiveSyncService } from "./services/livesync/android-device-livesync-service"; import * as path from "path"; @@ -9,39 +9,35 @@ export class DevicePathProvider implements IDevicePathProvider { private $iOSSimResolver: Mobile.IiOSSimResolver) { } - public async getDeviceBuildInfoDirname(device: Mobile.IDevice, appIdentifier: string): Promise { - let result = ""; - if (this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform)) { - result = path.dirname(await this.getDeviceProjectRootPath(device, { appIdentifier })); - } else if (this.$mobileHelper.isAndroidPlatform(device.deviceInfo.platform)) { - result = `/data/local/tmp/${appIdentifier}`; - } - - return result; - } - public async getDeviceProjectRootPath(device: Mobile.IDevice, options: IDeviceProjectRootOptions): Promise { let projectRoot = ""; if (this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform)) { if (device.isEmulator) { let applicationPath = this.$iOSSimResolver.iOSSim.getApplicationPath(device.deviceInfo.identifier, options.appIdentifier); - projectRoot = path.join(applicationPath, "app"); + projectRoot = path.join(applicationPath); } else { - projectRoot = IOS_DEVICE_PROJECT_ROOT_PATH; + projectRoot = LiveSyncPaths.IOS_DEVICE_PROJECT_ROOT_PATH; + } + + if (!options.getDirname) { + projectRoot = path.join(projectRoot, APP_FOLDER_NAME); } } else if (this.$mobileHelper.isAndroidPlatform(device.deviceInfo.platform)) { - const deviceLiveSyncService = this.$injector.resolve(AndroidDeviceLiveSyncService, { _device: device }); - const hashService = deviceLiveSyncService.getDeviceHashService(options.appIdentifier); - const hashFile = options.syncAllFiles ? null : await hashService.doesShasumFileExistsOnDevice(); - const syncFolderName = options.watch || hashFile ? SYNC_DIR_NAME : FULLSYNC_DIR_NAME; - projectRoot = `/data/local/tmp/${options.appIdentifier}/${syncFolderName}`; + projectRoot = `/data/local/tmp/${options.appIdentifier}`; + if (!options.getDirname) { + const deviceLiveSyncService = this.$injector.resolve(AndroidDeviceLiveSyncService, { _device: device }); + const hashService = deviceLiveSyncService.getDeviceHashService(options.appIdentifier); + const hashFile = options.syncAllFiles ? null : await hashService.doesShasumFileExistsOnDevice(); + const syncFolderName = options.watch || hashFile ? LiveSyncPaths.SYNC_DIR_NAME : LiveSyncPaths.FULLSYNC_DIR_NAME; + projectRoot = path.join(projectRoot, syncFolderName); + } } return fromWindowsRelativePathToUnix(projectRoot); } public getDeviceSyncZipPath(device: Mobile.IDevice): string { - return this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform) && !device.isEmulator ? IOS_DEVICE_SYNC_ZIP_PATH : undefined; + return this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform) && !device.isEmulator ? LiveSyncPaths.IOS_DEVICE_SYNC_ZIP_PATH : undefined; } } diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 41da36848e..de20861a70 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -13,7 +13,7 @@ import * as plist from "plist"; import { IOSProvisionService } from "./ios-provision-service"; import { IOSEntitlementsService } from "./ios-entitlements-service"; import { XCConfigService } from "./xcconfig-service"; -const simplePlist = require("simple-plist"); +import * as simplePlist from "simple-plist"; export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService { private static XCODE_PROJECT_EXT_NAME = ".xcodeproj"; @@ -1005,9 +1005,9 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } private async prepareFrameworks(pluginPlatformsFolderPath: string, pluginData: IPluginData, projectData: IProjectData): Promise { - await _.each(this.getAllLibsForPluginWithFileExtension(pluginData, ".framework"), (fileName) => { - this.addFramework(path.join(pluginPlatformsFolderPath, fileName), projectData); - }); + for (let fileName of this.getAllLibsForPluginWithFileExtension(pluginData, ".framework")) { + await this.addFramework(path.join(pluginPlatformsFolderPath, fileName), projectData); + } } private async prepareStaticLibs(pluginPlatformsFolderPath: string, pluginData: IPluginData, projectData: IProjectData): Promise { diff --git a/lib/services/livesync/android-device-livesync-service.ts b/lib/services/livesync/android-device-livesync-service.ts index e6c4abdca4..bf1c0edbd9 100644 --- a/lib/services/livesync/android-device-livesync-service.ts +++ b/lib/services/livesync/android-device-livesync-service.ts @@ -2,17 +2,18 @@ import { DeviceAndroidDebugBridge } from "../../common/mobile/android/device-and import { AndroidDeviceHashService } from "../../common/mobile/android/android-device-hash-service"; import { DeviceLiveSyncServiceBase } from "./device-livesync-service-base"; import * as helpers from "../../common/helpers"; -import { SYNC_DIR_NAME, FULLSYNC_DIR_NAME, REMOVEDSYNC_DIR_NAME } from "../../constants"; +import { LiveSyncPaths } from "../../constants"; import { cache } from "../../common/decorators"; import * as path from "path"; import * as net from "net"; -export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase implements IAndroidNativeScriptDeviceLiveSyncService { +export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase implements IAndroidNativeScriptDeviceLiveSyncService, INativeScriptDeviceLiveSyncService { private static BACKEND_PORT = 18182; private device: Mobile.IAndroidDevice; constructor(_device: Mobile.IDevice, private $mobileHelper: Mobile.IMobileHelper, + private $devicePathProvider: IDevicePathProvider, private $injector: IInjector, protected $platformsData: IPlatformsData) { super($platformsData); @@ -22,13 +23,17 @@ export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase impl public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { const deviceAppData = liveSyncInfo.deviceAppData; const localToDevicePaths = liveSyncInfo.modifiedFilesData; + const deviceProjectRootDirname = await this.$devicePathProvider.getDeviceProjectRootPath(liveSyncInfo.deviceAppData.device, { + appIdentifier: liveSyncInfo.deviceAppData.appIdentifier, + getDirname: true + }); await this.device.adb.executeShellCommand( ["chmod", "777", - "/data/local/tmp/", - `/data/local/tmp/${deviceAppData.appIdentifier}`, - `/data/local/tmp/${deviceAppData.appIdentifier}/sync`] + path.dirname(deviceProjectRootDirname), + deviceProjectRootDirname, + `${deviceProjectRootDirname}/sync`] ); const canExecuteFastSync = !liveSyncInfo.isFullSync && !_.some(localToDevicePaths, @@ -50,21 +55,24 @@ export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase impl } public async beforeLiveSyncAction(deviceAppData: Mobile.IDeviceAppData): Promise { - let deviceRootPath = this.getDeviceRootPath(deviceAppData.appIdentifier), - deviceRootDir = path.dirname(deviceRootPath), - deviceRootBasename = path.basename(deviceRootPath), - listResult = await this.device.adb.executeShellCommand(["ls", "-l", deviceRootDir]), - regex = new RegExp(`^-.*${deviceRootBasename}$`, "m"), - matchingFile = (listResult || "").match(regex); + const deviceRootPath = await this.$devicePathProvider.getDeviceProjectRootPath(deviceAppData.device, { + appIdentifier: deviceAppData.appIdentifier, + getDirname: true + }); + const deviceRootDir = path.dirname(deviceRootPath); + const deviceRootBasename = path.basename(deviceRootPath); + const listResult = await this.device.adb.executeShellCommand(["ls", "-l", deviceRootDir]); + const regex = new RegExp(`^-.*${deviceRootBasename}$`, "m"); + const matchingFile = (listResult || "").match(regex); // Check if there is already a file with deviceRootBasename. If so, delete it as it breaks LiveSyncing. if (matchingFile && matchingFile[0] && _.startsWith(matchingFile[0], '-')) { await this.device.adb.executeShellCommand(["rm", "-f", deviceRootPath]); } - this.device.adb.executeShellCommand(["rm", "-rf", this.$mobileHelper.buildDevicePath(deviceRootPath, FULLSYNC_DIR_NAME), - this.$mobileHelper.buildDevicePath(deviceRootPath, SYNC_DIR_NAME), - await this.$mobileHelper.buildDevicePath(deviceRootPath, REMOVEDSYNC_DIR_NAME)]); + this.device.adb.executeShellCommand(["rm", "-rf", this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.FULLSYNC_DIR_NAME), + this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.SYNC_DIR_NAME), + await this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.REMOVEDSYNC_DIR_NAME)]); } private async reloadPage(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { @@ -74,16 +82,19 @@ export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase impl } } - public async removeFiles(appIdentifier: string, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectId: string): Promise { - let deviceRootPath = this.getDeviceRootPath(appIdentifier); + public async removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { + const deviceRootPath = await this.$devicePathProvider.getDeviceProjectRootPath(deviceAppData.device, { + appIdentifier: deviceAppData.appIdentifier, + getDirname: true + }); - for (let localToDevicePathData of localToDevicePaths) { - let relativeUnixPath = _.trimStart(helpers.fromWindowsRelativePathToUnix(localToDevicePathData.getRelativeToProjectBasePath()), "/"); - let deviceFilePath = this.$mobileHelper.buildDevicePath(deviceRootPath, REMOVEDSYNC_DIR_NAME, relativeUnixPath); + for (const localToDevicePathData of localToDevicePaths) { + const relativeUnixPath = _.trimStart(helpers.fromWindowsRelativePathToUnix(localToDevicePathData.getRelativeToProjectBasePath()), "/"); + const deviceFilePath = this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.REMOVEDSYNC_DIR_NAME, relativeUnixPath); await this.device.adb.executeShellCommand(["mkdir", "-p", path.dirname(deviceFilePath), " && ", "touch", deviceFilePath]); } - await this.getDeviceHashService(projectId).removeHashes(localToDevicePaths); + await this.getDeviceHashService(deviceAppData.appIdentifier).removeHashes(localToDevicePaths); } @cache() @@ -92,10 +103,6 @@ export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase impl return this.$injector.resolve(AndroidDeviceHashService, { adb, appIdentifier }); } - private getDeviceRootPath(appIdentifier: string): string { - return `/data/local/tmp/${appIdentifier}`; - } - private async sendPageReloadMessage(): Promise { return new Promise((resolve, reject) => { let isResolved = false; diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index 23c4d73ffe..23ea563a4e 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -47,8 +47,8 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen return true; } - public async removeFiles(appIdentifier: string, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { - await Promise.all(_.map(localToDevicePaths, localToDevicePathData => this.device.fileSystem.deleteFile(localToDevicePathData.getDevicePath(), appIdentifier))); + public async removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { + await Promise.all(_.map(localToDevicePaths, localToDevicePathData => this.device.fileSystem.deleteFile(localToDevicePathData.getDevicePath(), deviceAppData.appIdentifier))); } public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { @@ -168,4 +168,3 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen } } } -$injector.register("iosLiveSyncServiceLocator", { factory: IOSDeviceLiveSyncService }); diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 3d886687be..4de90f66bb 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -4,6 +4,7 @@ import { EventEmitter } from "events"; import { hook } from "../../common/helpers"; import { APP_FOLDER_NAME, PACKAGE_JSON_FILE_NAME, LiveSyncTrackActionNames } from "../../constants"; import { FileExtensions, DeviceTypes } from "../../common/constants"; +const deviceDescriptorPrimaryKey = "identifier"; const LiveSyncEvents = { liveSyncStopped: "liveSyncStopped", @@ -14,7 +15,6 @@ const LiveSyncEvents = { liveSyncNotification: "notify" }; -// TODO: emit events for "successfull livesync", "stoppedLivesync", export class LiveSyncService extends EventEmitter implements ILiveSyncService { // key is projectDir private liveSyncProcessesInfo: IDictionary = {}; @@ -35,14 +35,12 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { @hook("liveSync") public async liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise { - // TODO: Initialize devicesService before that. const projectData = this.$projectDataService.getProjectData(liveSyncData.projectDir); // In case liveSync is called for a second time for the same projectDir. const isAlreadyLiveSyncing = this.liveSyncProcessesInfo[projectData.projectDir] && !this.liveSyncProcessesInfo[projectData.projectDir].isStopped; this.setLiveSyncProcessInfo(liveSyncData.projectDir, deviceDescriptors); - // TODO: Check if the _.difference actually works. - const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.difference(deviceDescriptors, this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors) : deviceDescriptors; + const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors; await this.initialSync(projectData, deviceDescriptorsForInitialSync, liveSyncData); @@ -54,7 +52,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { } } - public async stopLiveSync(projectDir: string, deviceIdentifiers?: string[], ): Promise { + public async stopLiveSync(projectDir: string, deviceIdentifiers?: string[]): Promise { const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectDir]; if (liveSyncProcessInfo) { @@ -89,7 +87,6 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { liveSyncProcessInfo.deviceDescriptors = []; // Kill typescript watcher - // TODO: Pass the projectDir in hooks args. const projectData = this.$projectDataService.getProjectData(projectDir); await this.$hooksService.executeAfterHooks('watch', { hookArgs: { @@ -135,10 +132,9 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const currentDeviceDescriptors = this.liveSyncProcessesInfo[projectDir].deviceDescriptors || []; // Prevent cases where liveSync is called consecutive times with the same device, for example [ A, B, C ] and then [ A, B, D ] - we want to execute initialSync only for D. - this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), "identifier"); + this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), deviceDescriptorPrimaryKey); } - // TODO: Register both livesync services in injector private getLiveSyncService(platform: string): IPlatformLiveSyncService { if (this.$mobileHelper.isiOSPlatform(platform)) { return this.$injector.resolve("iOSLiveSyncService"); @@ -159,7 +155,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const platform = device.deviceInfo.platform; if (preparedPlatforms.indexOf(platform) === -1) { preparedPlatforms.push(platform); - // TODO: fix args cast to any + // TODO: Pass provision and sdk as a fifth argument here await this.$platformService.preparePlatform(platform, { bundle: false, release: false, @@ -175,7 +171,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { return; } - // TODO: fix args cast to any + // TODO: Pass provision and sdk as a fifth argument here const shouldBuild = await this.$platformService.shouldBuild(platform, projectData, { buildForDevice: !device.isEmulator }, deviceBuildInfoDescriptor.outputPath); let pathToBuildItem = null; let action = LiveSyncTrackActionNames.LIVESYNC_OPERATION; @@ -265,9 +261,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { }; } - private async startWatcher(projectData: IProjectData, - liveSyncData: ILiveSyncInfo): Promise { - + private async startWatcher(projectData: IProjectData, liveSyncData: ILiveSyncInfo): Promise { let pattern = [APP_FOLDER_NAME]; if (liveSyncData.watchAllFiles) { diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index 6f678aa970..99f4fe9316 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -61,9 +61,9 @@ export abstract class PlatformLiveSyncServiceBase { } if (existingFiles.length) { - let platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); - let localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, + const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, mappedFiles, []); modifiedLocalToDevicePaths.push(...localToDevicePaths); await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, false); @@ -72,15 +72,15 @@ export abstract class PlatformLiveSyncServiceBase { if (liveSyncInfo.filesToRemove.length) { const filePaths = liveSyncInfo.filesToRemove; - let platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); const mappedFiles = _.map(filePaths, filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)); const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); - let localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, mappedFiles, []); + const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, mappedFiles, []); modifiedLocalToDevicePaths.push(...localToDevicePaths); const deviceLiveSyncService = this.getDeviceLiveSyncService(device); - deviceLiveSyncService.removeFiles(projectData.projectId, localToDevicePaths, projectData.projectId); + deviceLiveSyncService.removeFiles(deviceAppData, localToDevicePaths); } return { diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 715e0ebe2c..6851c120fd 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -373,28 +373,34 @@ export class PlatformService extends EventEmitter implements IPlatformService { if (this.$projectChangesService.currentChanges.changesRequireBuild) { return true; } + let platformData = this.$platformsData.getPlatformData(platform, projectData); let forDevice = !buildConfig || buildConfig.buildForDevice; outputPath = outputPath || (forDevice ? platformData.deviceBuildOutputPath : platformData.emulatorBuildOutputPath || platformData.deviceBuildOutputPath); if (!this.$fs.exists(outputPath)) { return true; } + let packageNames = platformData.getValidPackageNames({ isForDevice: forDevice }); let packages = this.getApplicationPackages(outputPath, packageNames); if (packages.length === 0) { return true; } + let prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); let buildInfo = this.getBuildInfo(platform, platformData, buildConfig, outputPath); if (!prepareInfo || !buildInfo) { return true; } + if (buildConfig.clean) { return true; } + if (prepareInfo.time === buildInfo.prepareTime) { return false; } + return prepareInfo.changesRequireBuildTime !== buildInfo.prepareTime; } @@ -554,9 +560,10 @@ export class PlatformService extends EventEmitter implements IPlatformService { } private async getDeviceBuildInfoFilePath(device: Mobile.IDevice, projectData: IProjectData): Promise { - // let deviceAppData = this.$deviceAppDataFactory.create(projectData.projectId, device.deviceInfo.platform, device); - // let deviceRootPath = path.dirname(await deviceAppData.getDeviceProjectRootPath()); - const deviceRootPath = await this.$devicePathProvider.getDeviceBuildInfoDirname(device, projectData.projectId); + const deviceRootPath = await this.$devicePathProvider.getDeviceProjectRootPath(device, { + appIdentifier: projectData.projectId, + getDirname: true + }); return helpers.fromWindowsRelativePathToUnix(path.join(deviceRootPath, buildInfoFileName)); } From 82ba18faaf48904cd05b55d2589d846d095a3728 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Mon, 12 Jun 2017 15:30:04 +0300 Subject: [PATCH 096/212] Cache sockets per app per device --- .../livesync/android-livesync-service.ts | 2 +- lib/services/livesync/ios-livesync-service.ts | 2 +- .../platform-livesync-service-base.ts | 19 +++++++++++++++---- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/services/livesync/android-livesync-service.ts b/lib/services/livesync/android-livesync-service.ts index bce1431688..d9416643c5 100644 --- a/lib/services/livesync/android-livesync-service.ts +++ b/lib/services/livesync/android-livesync-service.ts @@ -13,7 +13,7 @@ export class AndroidLiveSyncService extends PlatformLiveSyncServiceBase implemen super($fs, $logger, $platformsData, $projectFilesManager, $devicePathProvider, $projectFilesProvider); } - public getDeviceLiveSyncService(device: Mobile.IDevice): INativeScriptDeviceLiveSyncService { + protected _getDeviceLiveSyncService(device: Mobile.IDevice): INativeScriptDeviceLiveSyncService { const service = this.$injector.resolve(AndroidDeviceLiveSyncService, { _device: device }); return service; } diff --git a/lib/services/livesync/ios-livesync-service.ts b/lib/services/livesync/ios-livesync-service.ts index 6932efee1b..3efc0bb3e1 100644 --- a/lib/services/livesync/ios-livesync-service.ts +++ b/lib/services/livesync/ios-livesync-service.ts @@ -67,7 +67,7 @@ export class IOSLiveSyncService extends PlatformLiveSyncServiceBase implements I } } - public getDeviceLiveSyncService(device: Mobile.IDevice): INativeScriptDeviceLiveSyncService { + protected _getDeviceLiveSyncService(device: Mobile.IDevice): INativeScriptDeviceLiveSyncService { const service = this.$injector.resolve(IOSDeviceLiveSyncService, { _device: device }); return service; } diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index 99f4fe9316..04a3883f20 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -3,6 +3,8 @@ import * as util from "util"; import { APP_FOLDER_NAME } from "../../constants"; export abstract class PlatformLiveSyncServiceBase { + private _deviceLiveSyncServicesCache: IDictionary = {}; + constructor(protected $fs: IFileSystem, protected $logger: ILogger, protected $platformsData: IPlatformsData, @@ -10,11 +12,20 @@ export abstract class PlatformLiveSyncServiceBase { private $devicePathProvider: IDevicePathProvider, private $projectFilesProvider: IProjectFilesProvider) { } - public abstract getDeviceLiveSyncService(device: Mobile.IDevice): INativeScriptDeviceLiveSyncService; + public getDeviceLiveSyncService(device: Mobile.IDevice, applicationIdentifier: string): INativeScriptDeviceLiveSyncService { + const key = device.deviceInfo.identifier + applicationIdentifier; + if (!this._deviceLiveSyncServicesCache[key]) { + this._deviceLiveSyncServicesCache[key] = this._getDeviceLiveSyncService(device); + } + + return this._deviceLiveSyncServicesCache[key]; + } + + protected abstract _getDeviceLiveSyncService(device: Mobile.IDevice): INativeScriptDeviceLiveSyncService; public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { if (liveSyncInfo.isFullSync || liveSyncInfo.modifiedFilesData.length) { - const deviceLiveSyncService = this.getDeviceLiveSyncService(liveSyncInfo.deviceAppData.device); + const deviceLiveSyncService = this.getDeviceLiveSyncService(liveSyncInfo.deviceAppData.device, projectData.projectId); this.$logger.info("Refreshing application..."); await deviceLiveSyncService.refreshApplication(projectData, liveSyncInfo); } @@ -23,7 +34,7 @@ export abstract class PlatformLiveSyncServiceBase { public async fullSync(syncInfo: IFullSyncInfo): Promise { const projectData = syncInfo.projectData; const device = syncInfo.device; - const deviceLiveSyncService = this.getDeviceLiveSyncService(device); + const deviceLiveSyncService = this.getDeviceLiveSyncService(device, syncInfo.projectData.projectId); const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); const deviceAppData = await this.getAppData(syncInfo); @@ -79,7 +90,7 @@ export abstract class PlatformLiveSyncServiceBase { const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, mappedFiles, []); modifiedLocalToDevicePaths.push(...localToDevicePaths); - const deviceLiveSyncService = this.getDeviceLiveSyncService(device); + const deviceLiveSyncService = this.getDeviceLiveSyncService(device, projectData.projectId); deviceLiveSyncService.removeFiles(deviceAppData, localToDevicePaths); } From 013c5fad722f90519ef993328d8face057a69ed1 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Mon, 12 Jun 2017 15:40:32 +0300 Subject: [PATCH 097/212] Fix run --- lib/commands/run.ts | 4 ++-- lib/common | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 0bca94dc18..348086101d 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -126,7 +126,7 @@ export class RunIosCommand extends RunCommandBase implements ICommand { } public async canExecute(args: string[]): Promise { - return super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); + return await super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); } } @@ -158,7 +158,7 @@ export class RunAndroidCommand extends RunCommandBase implements ICommand { } public async canExecute(args: string[]): Promise { - super.canExecute(args); + await super.canExecute(args); if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.Android, this.$projectData)) { this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.Android, process.platform); } diff --git a/lib/common b/lib/common index 333ee88c7b..a4a944853c 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 333ee88c7b2963d76ed44e016bbf0ad54e9f9b88 +Subproject commit a4a944853c945a5f329fa733d6eadd60b7bf5974 From 714275cfdb7f93e1693e262aed26aee8b9edf1ea Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Tue, 13 Jun 2017 10:39:16 +0300 Subject: [PATCH 098/212] Fix run not starting emulator --- lib/commands/run.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 348086101d..e7a2ddb436 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -39,7 +39,12 @@ export class RunCommandBase implements ICommand { this.$options.watch = false; } - await this.$devicesService.initialize({ deviceId: this.$options.device, platform: this.platform, skipDeviceDetectionInterval: true, skipInferPlatform: true }); + await this.$devicesService.initialize({ + deviceId: this.$options.device, + platform: this.platform, + skipDeviceDetectionInterval: true, + skipInferPlatform: !this.platform + }); await this.$devicesService.detectCurrentlyAttachedDevices(); const devices = this.$devicesService.getDeviceInstances(); From 64080f7d75b42a280f9890cadd808a076abfdc25 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Tue, 13 Jun 2017 14:51:56 +0300 Subject: [PATCH 099/212] Pass emulator flag and fix process.platform messages --- lib/commands/appstore-list.ts | 2 +- lib/commands/appstore-upload.ts | 2 +- lib/commands/build.ts | 2 +- lib/commands/clean-app.ts | 4 ++-- lib/commands/debug.ts | 16 +++++++++++---- lib/commands/run.ts | 5 +++-- lib/services/test-execution-service.ts | 28 ++++++++++++++++---------- 7 files changed, 37 insertions(+), 22 deletions(-) diff --git a/lib/commands/appstore-list.ts b/lib/commands/appstore-list.ts index 7ddc8bf90f..379508f538 100644 --- a/lib/commands/appstore-list.ts +++ b/lib/commands/appstore-list.ts @@ -17,7 +17,7 @@ export class ListiOSApps implements ICommand { public async execute(args: string[]): Promise { if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { - this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.iOS, process.platform); + this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } let username = args[0], diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index fbeef8e039..dfca00ff9c 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -100,7 +100,7 @@ export class PublishIOS implements ICommand { public async canExecute(args: string[]): Promise { if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { - this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.iOS, process.platform); + this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } return true; diff --git a/lib/commands/build.ts b/lib/commands/build.ts index 83825a5859..810baa4334 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -34,7 +34,7 @@ export class BuildCommandBase { protected validatePlatform(platform: string): void { if (!this.$platformService.isPlatformSupportedForOS(platform, this.$projectData)) { - this.$errors.fail(`Applications for platform ${platform} can not be built on this OS - ${process.platform}`); + this.$errors.fail(`Applications for platform ${platform} can not be built on this OS`); } } } diff --git a/lib/commands/clean-app.ts b/lib/commands/clean-app.ts index e014eaf62c..2d760fa04d 100644 --- a/lib/commands/clean-app.ts +++ b/lib/commands/clean-app.ts @@ -26,7 +26,7 @@ export class CleanAppIosCommand extends CleanAppCommandBase implements ICommand public async execute(args: string[]): Promise { if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { - this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.iOS, process.platform); + this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } return super.execute([this.$platformsData.availablePlatforms.iOS]); } @@ -48,7 +48,7 @@ export class CleanAppAndroidCommand extends CleanAppCommandBase implements IComm public async execute(args: string[]): Promise { if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { - this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.iOS, process.platform); + this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } return super.execute([this.$platformsData.availablePlatforms.Android]); } diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index d1fe45c1f2..9e499277ad 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -30,7 +30,9 @@ export abstract class DebugPlatformCommand implements ICommand { this.$config.debugLivesync = true; - await this.$devicesService.initialize({ deviceId: this.$options.device, platform: this.platform, skipDeviceDetectionInterval: true, skipInferPlatform: true }); + await this.$devicesService.initialize({ + + }); await this.$devicesService.detectCurrentlyAttachedDevices(); const devices = this.$devicesService.getDeviceInstances(); @@ -73,7 +75,13 @@ export abstract class DebugPlatformCommand implements ICommand { } public async canExecute(args: string[]): Promise { - await this.$devicesService.initialize({ platform: this.debugService.platform, deviceId: this.$options.device }); + await this.$devicesService.initialize({ + platform: this.platform, + deviceId: this.$options.device, + emulator: this.$options.emulator, + skipDeviceDetectionInterval: true, + skipInferPlatform: true + }); // Start emulator if --emulator is selected or no devices found. if (this.$options.emulator || this.$devicesService.deviceCount === 0) { return true; @@ -120,7 +128,7 @@ export class DebugIOSCommand extends DebugPlatformCommand { public async canExecute(args: string[]): Promise { if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { - this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.iOS, process.platform); + this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } return await super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); @@ -155,7 +163,7 @@ export class DebugAndroidCommand extends DebugPlatformCommand { public async canExecute(args: string[]): Promise { if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.Android, this.$projectData)) { - this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.Android, process.platform); + this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.Android} can not be built on this OS`); } return await super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.Android); diff --git a/lib/commands/run.ts b/lib/commands/run.ts index e7a2ddb436..c14988c7e2 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -42,6 +42,7 @@ export class RunCommandBase implements ICommand { await this.$devicesService.initialize({ deviceId: this.$options.device, platform: this.platform, + emulator: this.$options.emulator, skipDeviceDetectionInterval: true, skipInferPlatform: !this.platform }); @@ -124,7 +125,7 @@ export class RunIosCommand extends RunCommandBase implements ICommand { public async execute(args: string[]): Promise { if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { - this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.iOS, process.platform); + this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } return this.executeCore([this.$platformsData.availablePlatforms.iOS]); @@ -165,7 +166,7 @@ export class RunAndroidCommand extends RunCommandBase implements ICommand { public async canExecute(args: string[]): Promise { await super.canExecute(args); if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.Android, this.$projectData)) { - this.$errors.fail("Applications for platform %s can not be built on this OS - %s", this.$devicePlatformsConstants.Android, process.platform); + this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.Android} can not be built on this OS`); } if (this.$options.release && (!this.$options.keyStorePath || !this.$options.keyStorePassword || !this.$options.keyStoreAlias || !this.$options.keyStoreAliasPassword)) { diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index 27271535ac..f6bcc41a45 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -41,7 +41,11 @@ class TestExecutionService implements ITestExecutionService { try { let platformData = this.$platformsData.getPlatformData(platform.toLowerCase(), projectData); let projectDir = projectData.projectDir; - await this.$devicesService.initialize({ platform: platform, deviceId: this.$options.device }); + await this.$devicesService.initialize({ + platform: platform, + deviceId: this.$options.device, + emulator: this.$options.emulator + }); await this.$devicesService.detectCurrentlyAttachedDevices(); let projectFilesPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); @@ -59,6 +63,7 @@ class TestExecutionService implements ITestExecutionService { if (!await this.$platformService.preparePlatform(platform, appFilesUpdaterOptions, this.$options.platformTemplate, projectData, this.$options)) { this.$errors.failWithoutHelp("Verify that listed files are well-formed and try again the operation."); } + this.detourEntryPoint(projectFilesPath); const deployOptions: IDeployPlatformOptions = { @@ -78,7 +83,8 @@ class TestExecutionService implements ITestExecutionService { const devices = this.$devicesService.getDeviceInstances(); // Now let's take data for each device: - const deviceDescriptors: ILiveSyncDeviceInfo[] = devices.filter(d => !this.platform || d.deviceInfo.platform === this.platform) + const platformLowerCase = this.platform && this.platform.toLowerCase(); + const deviceDescriptors: ILiveSyncDeviceInfo[] = devices.filter(d => !platformLowerCase || d.deviceInfo.platform.toLowerCase() === platformLowerCase) .map(d => { const info: ILiveSyncDeviceInfo = { identifier: d.deviceInfo.identifier, @@ -99,7 +105,6 @@ class TestExecutionService implements ITestExecutionService { await this.$platformService.buildPlatform(d.deviceInfo.platform, buildConfig, projectData); const pathToBuildResult = await this.$platformService.lastOutputPath(d.deviceInfo.platform, buildConfig, projectData); - console.log("3##### return path to buildResult = ", pathToBuildResult); return pathToBuildResult; } }; @@ -107,11 +112,9 @@ class TestExecutionService implements ITestExecutionService { return info; }); - // TODO: Fix this call const liveSyncInfo: ILiveSyncInfo = { projectDir: projectData.projectDir, skipWatcher: !this.$options.watch || this.$options.justlaunch, watchAllFiles: this.$options.syncAllFiles }; + await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); - // TODO: Fix - // await this.$liveSyncService.liveSync(platform, projectData, null, this.$options); if (this.$options.debugBrk) { this.$logger.info('Starting debugger...'); @@ -142,7 +145,11 @@ class TestExecutionService implements ITestExecutionService { await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); let projectDir = projectData.projectDir; - await this.$devicesService.initialize({ platform: platform, deviceId: this.$options.device }); + await this.$devicesService.initialize({ + platform: platform, + deviceId: this.$options.device, + emulator: this.$options.emulator + }); let karmaConfig = this.getKarmaConfiguration(platform, projectData), karmaRunner = this.$childProcess.fork(path.join(__dirname, "karma-execution.js")), @@ -187,13 +194,14 @@ class TestExecutionService implements ITestExecutionService { } else { const devices = this.$devicesService.getDeviceInstances(); // Now let's take data for each device: - const deviceDescriptors: ILiveSyncDeviceInfo[] = devices.filter(d => !this.platform || d.deviceInfo.platform === this.platform) + const platformLowerCase = this.platform && this.platform.toLowerCase(); + const deviceDescriptors: ILiveSyncDeviceInfo[] = devices.filter(d => !platformLowerCase || d.deviceInfo.platform.toLowerCase() === platformLowerCase) .map(d => { const info: ILiveSyncDeviceInfo = { identifier: d.deviceInfo.identifier, buildAction: async (): Promise => { const buildConfig: IBuildConfig = { - buildForDevice: !d.isEmulator, // this.$options.forDevice, + buildForDevice: !d.isEmulator, projectDir: this.$options.path, clean: this.$options.clean, teamId: this.$options.teamId, @@ -208,7 +216,6 @@ class TestExecutionService implements ITestExecutionService { await this.$platformService.buildPlatform(d.deviceInfo.platform, buildConfig, projectData); const pathToBuildResult = await this.$platformService.lastOutputPath(d.deviceInfo.platform, buildConfig, projectData); - console.log("3##### return path to buildResult = ", pathToBuildResult); return pathToBuildResult; } }; @@ -216,7 +223,6 @@ class TestExecutionService implements ITestExecutionService { return info; }); - // TODO: Fix this call const liveSyncInfo: ILiveSyncInfo = { projectDir: projectData.projectDir, skipWatcher: !this.$options.watch || this.$options.justlaunch, watchAllFiles: this.$options.syncAllFiles }; await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } From c6127eaadaac478381b4cf520016500acefa4de8 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Tue, 13 Jun 2017 15:28:04 +0300 Subject: [PATCH 100/212] Fix chrome debug message --- lib/commands/debug.ts | 24 ++++--------------- lib/definitions/livesync.d.ts | 12 ++++++++++ .../livesync/debug-livesync-service.ts | 5 ++-- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 9e499277ad..d8ba7a9447 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -1,6 +1,4 @@ -import { EOL } from "os"; - -export abstract class DebugPlatformCommand implements ICommand { +export abstract class DebugPlatformCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; public platform: string; @@ -12,7 +10,7 @@ export abstract class DebugPlatformCommand implements ICommand { protected $options: IOptions, protected $platformsData: IPlatformsData, protected $logger: ILogger, - private $debugLiveSyncService: ILiveSyncService, + private $debugLiveSyncService: IDebugLiveSyncService, private $config: IConfiguration) { this.$projectData.initializeProjectData(); } @@ -25,7 +23,7 @@ export abstract class DebugPlatformCommand implements ICommand { await this.$platformService.trackProjectType(this.$projectData); if (this.$options.start) { - return this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); + return this.$debugLiveSyncService.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); } this.$config.debugLivesync = true; @@ -96,12 +94,6 @@ export abstract class DebugPlatformCommand implements ICommand { return true; } - - protected printDebugInformation(information: string[]): void { - _.each(information, i => { - this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${i}${EOL}`.cyan); - }); - } } export class DebugIOSCommand extends DebugPlatformCommand { @@ -117,7 +109,7 @@ export class DebugIOSCommand extends DebugPlatformCommand { $projectData: IProjectData, $platformsData: IPlatformsData, $iosDeviceOperations: IIOSDeviceOperations, - $debugLiveSyncService: ILiveSyncService) { + $debugLiveSyncService: IDebugLiveSyncService) { super($iOSDebugService, $devicesService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger, $debugLiveSyncService, $config); // Do not dispose ios-device-lib, so the process will remain alive and the debug application (NativeScript Inspector or Chrome DevTools) will be able to connect to the socket. // In case we dispose ios-device-lib, the socket will be closed and the code will fail when the debug application tries to read/send data to device socket. @@ -134,12 +126,6 @@ export class DebugIOSCommand extends DebugPlatformCommand { return await super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); } - protected printDebugInformation(information: string[]): void { - if (this.$options.chrome) { - super.printDebugInformation(information); - } - } - public platform = this.$devicePlatformsConstants.iOS; } @@ -157,7 +143,7 @@ export class DebugAndroidCommand extends DebugPlatformCommand { $options: IOptions, $projectData: IProjectData, $platformsData: IPlatformsData, - $debugLiveSyncService: ILiveSyncService) { + $debugLiveSyncService: IDebugLiveSyncService) { super($androidDebugService, $devicesService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger, $debugLiveSyncService, $config); } diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 92f8d61636..a9ae655db0 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -146,6 +146,18 @@ interface ILiveSyncService { stopLiveSync(projectDir: string, deviceIdentifiers?: string[]): Promise; } +/** + * Describes LiveSync operations while debuggging. + */ +interface IDebugLiveSyncService extends ILiveSyncService { + /** + * Prints debug information. + * @param {string[]} information Array of information to be printed. Note that false-like values will be stripped from the array. + * @returns {void} + */ + printDebugInformation(information: string[]): void; +} + interface ILiveSyncWatchInfo { projectData: IProjectData; filesToRemove: string[]; diff --git a/lib/services/livesync/debug-livesync-service.ts b/lib/services/livesync/debug-livesync-service.ts index a6d63b2378..be75692cf1 100644 --- a/lib/services/livesync/debug-livesync-service.ts +++ b/lib/services/livesync/debug-livesync-service.ts @@ -1,7 +1,7 @@ import { EOL } from "os"; import { LiveSyncService } from "./livesync-service"; -export class DebugLiveSyncService extends LiveSyncService { +export class DebugLiveSyncService extends LiveSyncService implements IDebugLiveSyncService { constructor(protected $platformService: IPlatformService, $projectDataService: IProjectDataService, @@ -67,7 +67,8 @@ export class DebugLiveSyncService extends LiveSyncService { this.printDebugInformation(await debugService.debug(debugData, debugOptions)); } - protected printDebugInformation(information: string[]): void { + public printDebugInformation(information: string[]): void { + information = information.filter(i => !!i); _.each(information, i => { this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${i}${EOL}`.cyan); }); From f32f243dd6451547bc3b34962b6134361f0c79b3 Mon Sep 17 00:00:00 2001 From: Nadya Atanasova Date: Wed, 14 Jun 2017 16:17:28 +0300 Subject: [PATCH 101/212] Do not determine dynamic dependencies on Windows We need to explicitly skip this step on Windows due to lack of tooling that is properly licensed. --- lib/services/ios-project-service.ts | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index de20861a70..7a2b72c520 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -498,24 +498,23 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } private async addFramework(frameworkPath: string, projectData: IProjectData): Promise { - this.validateFramework(frameworkPath); + if (!this.$hostInfo.isWindows) { + this.validateFramework(frameworkPath); - let project = this.createPbxProj(projectData); - let frameworkName = path.basename(frameworkPath, path.extname(frameworkPath)); - let frameworkBinaryPath = path.join(frameworkPath, frameworkName); - const pathToFileCommand = this.$hostInfo.isWindows ? path.join(__dirname, "..", "..", "vendor", "file", "file.exe") : "file"; - let isDynamic = _.includes((await this.$childProcess.spawnFromEvent(pathToFileCommand, [frameworkBinaryPath], "close")).stdout, "dynamically linked"); + let project = this.createPbxProj(projectData); + let frameworkName = path.basename(frameworkPath, path.extname(frameworkPath)); + let frameworkBinaryPath = path.join(frameworkPath, frameworkName); + let isDynamic = _.includes((await this.$childProcess.spawnFromEvent("file", [frameworkBinaryPath], "close")).stdout, "dynamically linked"); + let frameworkAddOptions: IXcode.Options = { customFramework: true }; - let frameworkAddOptions: IXcode.Options = { customFramework: true }; + if (isDynamic) { + frameworkAddOptions["embed"] = true; + } - if (isDynamic) { - frameworkAddOptions["embed"] = true; + let frameworkRelativePath = '$(SRCROOT)/' + this.getLibSubpathRelativeToProjectPath(frameworkPath, projectData); + project.addFramework(frameworkRelativePath, frameworkAddOptions); + this.savePbxProj(project, projectData); } - - let frameworkRelativePath = '$(SRCROOT)/' + this.getLibSubpathRelativeToProjectPath(frameworkPath, projectData); - project.addFramework(frameworkRelativePath, frameworkAddOptions); - this.savePbxProj(project, projectData); - } private async addStaticLibrary(staticLibPath: string, projectData: IProjectData): Promise { From 5b1141c1bcc8362acce97d2b69422de19959942e Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Thu, 15 Jun 2017 08:56:26 +0300 Subject: [PATCH 102/212] Restart app upon debugging --- lib/services/debug-service.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index dda56ac784..379e0f79bd 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -8,6 +8,7 @@ export class DebugService extends EventEmitter implements IDebugService { private $androidDebugService: IPlatformDebugService, private $iOSDebugService: IPlatformDebugService, private $errors: IErrors, + private $logger: ILogger, private $hostInfo: IHostInfo, private $mobileHelper: Mobile.IMobileHelper) { super(); @@ -29,6 +30,14 @@ export class DebugService extends EventEmitter implements IDebugService { this.$errors.failWithoutHelp(`The application ${debugData.applicationIdentifier} is not installed on device with identifier ${debugData.deviceIdentifier}.`); } + if (this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform)) { + try { + await device.applicationManager.restartApplication(debugData.applicationIdentifier, debugData.projectName); + } catch (err) { + this.$logger.trace("Failed to restart app", err); + } + } + const debugOptions: IDebugOptions = _.merge({}, options); debugOptions.start = true; From 0acb22d707063261f0a5225e710eae99b1d6fb62 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Thu, 15 Jun 2017 09:01:03 +0300 Subject: [PATCH 103/212] Update common to latest master --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index a4a944853c..cf3276e6bd 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit a4a944853c945a5f329fa733d6eadd60b7bf5974 +Subproject commit cf3276e6bd5fdd66070a88cd70bc40d29fc65a5a From baab31b5978f54b9e8ada05325c319d899bc693b Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Thu, 15 Jun 2017 09:01:26 +0300 Subject: [PATCH 104/212] Remove obsolete vendor directory --- vendor/file/COPYING | 29 ----------------------------- vendor/file/file.exe | Bin 21670 -> 0 bytes 2 files changed, 29 deletions(-) delete mode 100644 vendor/file/COPYING delete mode 100644 vendor/file/file.exe diff --git a/vendor/file/COPYING b/vendor/file/COPYING deleted file mode 100644 index b3db8b23fb..0000000000 --- a/vendor/file/COPYING +++ /dev/null @@ -1,29 +0,0 @@ -$File: COPYING,v 1.1 2008/02/05 19:08:11 christos Exp $ -Copyright (c) Ian F. Darwin 1986, 1987, 1989, 1990, 1991, 1992, 1994, 1995. -Software written by Ian F. Darwin and others; -maintained 1994- Christos Zoulas. - -This software is not subject to any export provision of the United States -Department of Commerce, and may be exported to any country or planet. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice immediately at the beginning of the file, without modification, - this list of conditions, and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -SUCH DAMAGE. diff --git a/vendor/file/file.exe b/vendor/file/file.exe deleted file mode 100644 index a56647b8e21137163fd30fc7365f1ebc21e17f12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21670 zcmeHveRx~NmG_lx1!FLBAzo4%;1cX&vymoe!fZ_b>VId|sF+^Z~EvsEgTB&i5@CL>APLFsB^e?R)qD6-Eu zf5#cpL#IA{!FG%5=?i)X12H)g4G%;;L$cQs3WeixzbZ$ELvkP_cP?8Y4~2bdO=)RK zg;3qoB}u+pilx&Xr}arLNot4uh;&iGX$1wJl;(m_aFcOm;)Z1wBpF}8vxv1yVCdS0 z8~6uY7HZe1B*_rqqVnt#i7cO&MfqDLY4<6h_eqjY_4Yvns}Sk`#x>_oNqYPv^wh-F zkvQ~Mor6DQ8?~M4&%64hp1vBNC+>kvAzcpK)NC1d-qj{WCTgM_D4>Hr#V?&%>Q} z0oPU&5jfJFpkKiWUB4TnE!}Z5vB#|k9lv00ujgoxeP~B4>V=q%#eGQ3!hJdJyo+#o?8nJJqX(44;VLCnYCQv8JORJ&a!xo8 zIG@m;%S#{3OaFT=tt56=rHS8~cdb;k*VgUw*VxV`+iiB_10^b5&I7KZG;Ef^KcMZEQIn z03{X4D9!PTs#wYBNJh4eT}Z;@=87tpcBs4hoi1m_m1^vUo4Qi=m&n%YV~Vy*(K;)d z^qqKGm84RJ8ulWqT|3yVz4!m=vC_|miS4P*ij4CBpbb;!ZOF-y^Y*KO;1L%%jxQ9m$CwEyVVbp1z=E*a0n zw^QjIPd~*k<@c{BkNu`VNfq7M-8?ybhdvju&LoTwrdC2jxAvAZqen?CSu-BRaOiIr zQghZ;G%3lRG6f~zCayUJ=yT<|&$!pMuWet~{+V`n`YKlJO^b2~`pq`>vkxJXG5i|CFK~ zg=gw4X2@I=fo5$9L1mm z_@ds%fQ>h>a+CU|T)F#Mx%axYBl@!>1^7ZxluwBAChu;8*~(;R1+5E`{w?YV^6tI1 zv0GVHlg>9>sbGb}mGV{8OqKEW@3APBUneaS}8der>x@G1=M?EtrpWiT7u zLB(vH&+4@(l`MA>=D@J3B+K9Y6mn9f5dicvDbqTaGAH6|Fqh1wVyV(qAlgy=ZTJP$ z?Vzqxv^3NAO-ciO361m=;EuXhDm)|lipSM4k zKb`+`^_@)r#nbAaH;q4M8h_?AzGNC-FpWR{+58nZQ+d?H(|Ybaps$2?Wy&A9F(1mh zAnRzNL;hTTC6-aKqHE7$x_Ol1HvM&)`Nm(ijkV$-Q?ytc)~`FUioof=_6bOm)4bAqC@wKPcAX&|mO_<4kKud>~pkN;>?7?!(d`~D^$7(QVF;qt% zk`pSSItGz^o(4qgh#>joB1$G`(**9voU7v8?XS^jCmi7PJJGtlS#TYnPcdo%8*-OFTiRGJl)tc%pxZ{7xsBB{>TTSQ+L#|rj^-Srg!xUX z^g`09Z4>@wr81*APuapfrSx-uU?}Ef8bZ6q!#-x5m4-%B;6A12uw(@O)_6Wk_QQM!2rnAnfr`^#gxrV+l8c)Z&H8n8d3r8`FGgYOIPo8n z$;ri4;D?Am#CaL~O5%5N9uJANmG}oZUk1LC_**z%48D~3LBdakwdQ+!7y%HYSc2#Lr3`FC3n`1MWhLSyTF7kl2A<;2f== z#t=90@x|PvL5f9n3fdp`V(sm$n6K}lB9qqp!Q(;BAl#fk^(4h7SaDP30}w62Q+D@+ zqLuz5FfPrS0GYJjgum!%Nwa#9(tki|5G$fd*MYbF!-VbO2{H_ok0B3jxdgh5#Q>C< zmp2LKB^fGH`NiSQ-T59r)WP%L*ML2BffeZ!<;IA1!=?* zEJjFU1HrdhnaUDVdGH%3D^q!#$w?m`=5s4ZQ|&Qw7#4x{&LeGWDHMK@!q72f@ld!0 zsa%Jc_DeS%eh~E{9BbE}(U1O~ne{ppvFJ>gaxDU zbq|uM(t-<#{J7#aK=p323Lzz?oQLC(BVbh^mp=J&Hg<)mheW@B7O753f%X!mz1C{_ z)0g-)Jh!s~v(H!sJ*_~r83=4ztZPU&01+2rygJ&lStk%qv;6>K+)nH)euXB%TWn)j z7Sk5v#*GU4{Y-2cvRpLJjUX z6?zkUuzlNIrX)Tn8UAGgW?QDl$tH5o^a#8zVZFZ(-kJ^~ebc@o9HFE=NW#<~;>cu2 zD{!u)OtJSP)YY`_=&TrtHz0sV{dbkI`m0~>#=5seJ3_c;fTQ}yVLq>a5B5+W%o=_f z9xxA;>GpT20_FiJ#JOy*cu>C;Qa<>tNF}VZm|f3dx=HLSNI%7<9rUo0L~mLD4=PS? zhivqjg7h{}YE%J?P5%=y)_+5E%i4H1!xW`&15NBJOOJuxMB%X|y@_QNr*8n&zxumO z#>F1lT1c;G6DRhQ-4m z>EGcn2f_aETfjb@gDqvSA%cx^*wqAkj$oO0vfA!rFa#}*ufm^Ck(cR zV09d}k6^bD>|b-R*BA_sq@< z2E*bj=|ABxlIWix*rFWlGCXwc+Yz5g`jF<5|L3Wwc6uzLwM378mBoxw^0<0IJwO*V=WcHb`mz6 z!&XArzCDM1hb&8#l31TZ*p3{wj$zBQdLQRlCt(l20qof%;9hen!(vk?={IoLVuI}< z*pZ`I*fk6mB-oW4wt`^aAlMT*n2*6+1iO&Kf&|-0u={hc%?vh3u=h!tD!rLtJp}t~ z4whuF5W$}3u-ge%O|ae^>}w3Rlwd#Lux}EqfME6@n9PE1x zMhD0G6iHL14-xD#f?c13Wf-i3V7ocYdJ)od2-cB<&4sXiyN_UB=dkk#_SO_&b0H9} z+RR|ETDaf0kcHUyVvxkXqVyiNysfQJ*v4jiA*R@5ey zq^Ut!{ZWw(`6Wz#Q2#qpJ)zvR`$5!}J0NLy?@H_|PG5(bMxU{yR}kGdn!XA&VI4wM zmh@%J3R?4MGR8`ownIWwFbtB?GC}vr={cY`?JFySsFXy%{vd57y1AlFvW;&qp!H!h z&V_7a>>MfSs&MExpxbD5!wOpJqJxsjv5KuwN*ASRQKd^sBI5jwUWYb}PYi!Se-ZPH zD>+!~NDH=){WP)=2!;m1#dd5_JdGoxN1yAJYlw%enyTuI($*kvKRhM(S>6HZMq zhFhLq##U<%*S%2Uf)S^IcAo01=)scTYe|2JLW<3ZS?Jity9dr&U6zhufu~;ke`rejaVnvm zyXhKu?F+OlKK5Es`)O z$9@Q5;#lEupFRetw$nM0*lDqi*HeWke=C(wp1XY&_5EIbg0XD$(%2=7L+5kQ3+WXw zwmo%4=8{6zxwdc6#zeE;iIVB>Qc%>(^iO?5UylUNlAl8_8S9pj*ydc=3nOvIi@tzRW0PSs^wqxLXMTqbhQe()g+aq zL%k=qXBzBdR`kG7Y*WlW;Jx1NTu|c+1||2E8{Es)fj}&-MmvI@SWIQF^Jna@l`+qN z+9FrRGH-mbyGdRK61C=!irxN(hR9fWpIBob6v3ST&=p;#asV%$(*NVTgWZ`cGZ63+8(Sw6 zk!xyd41F-u-hnbER9+^`9aMvnbtTe#S&}NlQf1A>F{v_E8I$IhRI+4?+~Emb6o(CQ zYIS9-1o_kfB~qR!>yl?}&K&Us?8NA`w;;gDU7pz4!@a5ZWaD{Ksf*@_$&8XoFbqik5w z-&ymA;YCA{sESWda#%SYMXSSdFdSYF%VPnbipn_*^)e4Rp$>Z}991LXD9(?lKfpL6k5Jbi4XFM<>Q7nrg=P9E3dTlMj00Ig?i(XZMQ;Z&*7>dT z4yxYuct-5Z2!Oa*=0blo3@*D~K6_I~JoMaRT&N}V)aGf-&S65|ir(ejORr!WX}Dr>cpSPtmYeKYXHs1l z;l7rO@^sNu%GJPMr(MFSmUJ)a;u8qh9Fwc;b~XjGSp{>#$@)XUjTyU|>ZeQ0@H5me zs;BO0r-eZ7Sl-p%yKH%F@3lRwJGhGM(je4GBsMOg!z0JAAPt^i?#PyAj|~;eYn$j_ zLm6nEP1$bAwZ^nANk*~hWsGjf8-?|%8nF+AF>I*PCoMLw)$bV& z#@WD$c|R1k=W7s!xxv%Q=NXDp{lk7#Yi_48xA8pD%jRMc51q=;m9vwjjA@a)o2@x{ z<=C|6;Kinw<4dC_p8xQ%xvPCeM|ZcpbfwFrNR6q{4XPcV9!`H=X!W2x+2i$M9g*XB zq%g;a*h8H^8&l*lL`yO^D8C&P63`qVW0YvU&O$MR4F%${lRaU9kS`E56gnH_sOlYN z>oPA-(>vw>U{p3qpG+OCD~2|?-oaJ@W(ZS6xwSOY{7DP18uYudZlOzLxvp^m3XRBJ zuFGjHgA8B%Qm3gWokK`XWDu)3)Hv&!vTEp)VN7(aS%0Rw7*khYw{SsD-3c^F*K()aOH0q%a5#Tih(%Oyz!PLK508@#t$0-N zu#0@Tsy`U^uCFCESWhx=` z8Q~9L9qQkNH9hZvU`zXn${Vo-1HhLDWIPQN>ElV@SIKe~BFTsiSjP;;RVzvc24Y&L?fp_b6nNKXsja3E$!EKXq=TJPhoosC|vtMskI90h+R zbJ*-Fv$-OMVF5ZA=#NrI^IDCVmr9}0wFfepE1%6|7UN!syB7CFL?QoMxaa&8Z9Prs zDg^!Q4DJ6B#&3jT*9Z*LkY;Y-tk~}eQARu{t ze#zJu5L7ZE1t}Kx$ECO@DzQ)f=KSs2TX3#4pBCuL()p33%SUbMvmr~Ei(T3C~dSCFC*L_`$En$P2b>}oC1 z3Spkv#f&25O_;npCAsIs=S{}BSLF=(8$@}#$T#ppEDec7(+uTr6!{%9~#*jdR3r z&5&=LCw^~+d@HYBdUS?-dSgu2?iupwRWV&p%#cs-i0OKMhJ1QCOxLS3HT;q`U>uQkUj$X7Vi6yrr#!7a6f=_8R#c)KZvvpdI9e5BkcgS&Bf0<2?JV< zdxGSkI_}*_9|8Rx?x&EJ@T;I%xc3tt^b@$BLwXeSd$T_ir%Giv(=T;PBB%Bg zPt4k1bg1A6aHkYWrz|gY6!cjlEL|tkXB10kL`xi}_7z7?>6tZAw7=kxrDT>=Qa8(C z?JJ5D_7qH5&MJ}4vXrym|K@AW+Jyn2wI_R*Sgj~u$Z8-PDUaHKfF_F}8QEM1n?<`I z`uI9Q5AqKHjOEGh{fZ=wKqk>Gu7@Cd{4Rqt(?ddj#K^y!=a&g83+fQG zP0$`e`vi>$Iwq)5%iV&1K+tW1P6+yxpa%uj1wA6@`+}DJ1J@`ES|wu%Uah$kX@M<&~ zjw1hB!PADFdgMC652*2oCq5{x6+GL6BY&O2hxt*zTkv%HN98?&$5}bsaFeWG$c$r# zQpxT1j(FTSg~e-XNtz8F_JqQ~%1EQ`sET*6RJIEIkOyzvajlTt_=Lgjjm90)YRMfK ziVR0pcO*{j*d^h60cLh0R<7EbddrSV>yAtFK`O+>|eVrkv?)d9m77E4b9yRN;u-M9R z0y*In+^g@Cq%Rc_$FD_~jSK~iqxaT%&Kk!&ycXjh`?Stm*?YOYX&zp3V~9QQc(rxj zCN(zivP(-A(I-bk{rKbpIO9^2kCF?JWs4Uv;oJx(RsB-p+&VbYFt#CU;CokY$K@b zB07o02RQwKAj~^?vrKU6EpEUfI%N%b*_S+7lP<8^d-0mSyAvN+(3@jA1_uQ1OY+ci zY&b&S#;|vY*8mdU7?W4wP{f0G?paj=r26nb{&f3V=Y3YiX^OLEexJQbt@GJEbqx)U zy1GVxbG_%UV@K0R+R@;t^Vc=0_4da4{$_haLxa=a?`ihheG8g>4W0(i0=2&BuVhD! z^CK;B`WGx*;9a=D-sDi7_J+FpW_xpEf4{w{-skWy@Hu>*h0GFJyrx|Amk*88@sXA^ zsSf`_kF(C+@9(e2*r-i*j8vn&p~>%SbaS+>%ALd0xpLjarklV=AdN=8{W| zx0c52^fArv0h+d(3UL+!%Fos5cUHTt`4SwJNDl#!!!$#358ydTHzeCkl2wrGH|fDP zT^cdfMIk_+sno5I6q_)#X`78ZSJ!vIZ82dUf`oqPlEXX+iDJV13X%s+k~bhZW-4_I zl2Man77iBXm?ZSG?t|#ITwT?WtTtgT{S-1_7ttQM*31gBx1<7iYU#%m5_!nJ~lPMop5N!6~LvcR_N{Bq1GTrc(5OLd-EqCcyQWBu|4oWYY5@B(KiO zxA_Pp_o4~8Rutk%>dBUx4bDxQH@Dc;xZRs<4LyVw975V#Lju$(@yiVYDIn5nmD5Rki8(vfZHZESmm()>dYQJ8+q;AUqpTwokBI3 z^DJ<*wa-_?&UHjVe(1p&^d>vG$a1KIpIvZ?IAstenR~Jk&hs4c*ycY~rYD(0gLIJe EU)#=5&Hw-a From 7b2f1ab3d8aa486b860ed6abfb3effb3409b08cb Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Wed, 14 Jun 2017 12:49:35 +0300 Subject: [PATCH 105/212] Fix run with --release and --clean --- lib/commands/run.ts | 15 ++++- lib/definitions/livesync.d.ts | 19 +++++++ lib/services/livesync/livesync-service.ts | 68 +++++++++++++---------- 3 files changed, 70 insertions(+), 32 deletions(-) diff --git a/lib/commands/run.ts b/lib/commands/run.ts index c14988c7e2..3540b92e53 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -85,17 +85,26 @@ export class RunCommandBase implements ICommand { } if (this.$options.release) { - const deployOpts: IRunPlatformOptions = { + const runPlatformOptions: IRunPlatformOptions = { device: this.$options.device, emulator: this.$options.emulator, justlaunch: this.$options.justlaunch, }; - await this.$platformService.startApplication(args[0], deployOpts, this.$projectData.projectId); + const deployOptions = _.merge({ projectDir: this.$projectData.projectDir, clean: true }, this.$options); + + await this.$platformService.deployPlatform(args[0], this.$options, deployOptions, this.$projectData, this.$options); + await this.$platformService.startApplication(args[0], runPlatformOptions, this.$projectData.projectId); return this.$platformService.trackProjectType(this.$projectData); } - const liveSyncInfo: ILiveSyncInfo = { projectDir: this.$projectData.projectDir, skipWatcher: !this.$options.watch, watchAllFiles: this.$options.syncAllFiles }; + const liveSyncInfo: ILiveSyncInfo = { + projectDir: this.$projectData.projectDir, + skipWatcher: !this.$options.watch, + watchAllFiles: this.$options.syncAllFiles, + clean: this.$options.clean + }; + await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } } diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index a9ae655db0..e936c86d56 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -115,6 +115,11 @@ interface ILiveSyncInfo { * NOTE: Currently this is available only for iOS. */ useLiveEdit?: boolean; + + /** + * Forces a build before the initial livesync. + */ + clean?: boolean; } interface ILatestAppPackageInstalledSettings extends IDictionary> { /* empty */ } @@ -125,6 +130,20 @@ interface ILiveSyncBuildInfo { pathToBuildItem: string; } +/** + * Desribes object that can be passed to ensureLatestAppPackageIsInstalledOnDevice method. + */ +interface IEnsureLatestAppPackageIsInstalledOnDeviceOptions { + device: Mobile.IDevice; + preparedPlatforms: string[]; + rebuiltInformation: ILiveSyncBuildInfo[]; + projectData: IProjectData; + deviceBuildInfoDescriptor: ILiveSyncDeviceInfo; + settings: ILatestAppPackageInstalledSettings; + liveSyncData?: ILiveSyncInfo; + modifiedFiles?: string[]; +} + /** * Describes LiveSync operations. */ diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 4de90f66bb..f5f84f0692 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -145,60 +145,54 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { throw new Error(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); } - private async ensureLatestAppPackageIsInstalledOnDevice(device: Mobile.IDevice, - preparedPlatforms: string[], - rebuiltInformation: ILiveSyncBuildInfo[], - projectData: IProjectData, - deviceBuildInfoDescriptor: ILiveSyncDeviceInfo, - settings: ILatestAppPackageInstalledSettings, - modifiedFiles?: string[]): Promise { - const platform = device.deviceInfo.platform; - if (preparedPlatforms.indexOf(platform) === -1) { - preparedPlatforms.push(platform); + private async ensureLatestAppPackageIsInstalledOnDevice(options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions): Promise { + const platform = options.device.deviceInfo.platform; + if (options.preparedPlatforms.indexOf(platform) === -1) { + options.preparedPlatforms.push(platform); // TODO: Pass provision and sdk as a fifth argument here await this.$platformService.preparePlatform(platform, { bundle: false, release: false, - }, null, projectData, {}, modifiedFiles); + }, null, options.projectData, {}, options.modifiedFiles); } - const rebuildInfo = _.find(rebuiltInformation, info => info.isEmulator === device.isEmulator && info.platform === platform); + const rebuildInfo = _.find(options.rebuiltInformation, info => info.isEmulator === options.device.isEmulator && info.platform === platform); if (rebuildInfo) { // Case where we have three devices attached, a change that requires build is found, // we'll rebuild the app only for the first device, but we should install new package on all three devices. - await this.$platformService.installApplication(device, { release: false }, projectData, rebuildInfo.pathToBuildItem, deviceBuildInfoDescriptor.outputPath); + await this.$platformService.installApplication(options.device, { release: false }, options.projectData, rebuildInfo.pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); return; } // TODO: Pass provision and sdk as a fifth argument here - const shouldBuild = await this.$platformService.shouldBuild(platform, projectData, { buildForDevice: !device.isEmulator }, deviceBuildInfoDescriptor.outputPath); + const shouldBuild = await this.$platformService.shouldBuild(platform, options.projectData, { buildForDevice: !options.device.isEmulator, clean: options.liveSyncData && options.liveSyncData.clean }, options.deviceBuildInfoDescriptor.outputPath); let pathToBuildItem = null; let action = LiveSyncTrackActionNames.LIVESYNC_OPERATION; if (shouldBuild) { - pathToBuildItem = await deviceBuildInfoDescriptor.buildAction(); + pathToBuildItem = await options.deviceBuildInfoDescriptor.buildAction(); // Is it possible to return shouldBuild for two devices? What about android device and android emulator? - rebuiltInformation.push({ isEmulator: device.isEmulator, platform, pathToBuildItem }); + options.rebuiltInformation.push({ isEmulator: options.device.isEmulator, platform, pathToBuildItem }); action = LiveSyncTrackActionNames.LIVESYNC_OPERATION_BUILD; } - if (!settings[platform][device.deviceInfo.type]) { - let isForDevice = !device.isEmulator; - settings[platform][device.deviceInfo.type] = true; + if (!options.settings[platform][options.device.deviceInfo.type]) { + let isForDevice = !options.device.isEmulator; + options.settings[platform][options.device.deviceInfo.type] = true; if (this.$mobileHelper.isAndroidPlatform(platform)) { - settings[platform][DeviceTypes.Emulator] = true; - settings[platform][DeviceTypes.Device] = true; + options.settings[platform][DeviceTypes.Emulator] = true; + options.settings[platform][DeviceTypes.Device] = true; isForDevice = null; } await this.$platformService.trackActionForPlatform({ action, platform, isForDevice }); } - await this.$platformService.trackActionForPlatform({ action: LiveSyncTrackActionNames.DEVICE_INFO, platform, isForDevice: !device.isEmulator, deviceOsVersion: device.deviceInfo.version }); + await this.$platformService.trackActionForPlatform({ action: LiveSyncTrackActionNames.DEVICE_INFO, platform, isForDevice: !options.device.isEmulator, deviceOsVersion: options.device.deviceInfo.version }); - const shouldInstall = await this.$platformService.shouldInstall(device, projectData, deviceBuildInfoDescriptor.outputPath); + const shouldInstall = await this.$platformService.shouldInstall(options.device, options.projectData, options.deviceBuildInfoDescriptor.outputPath); if (shouldInstall) { - await this.$platformService.installApplication(device, { release: false }, projectData, pathToBuildItem, deviceBuildInfoDescriptor.outputPath); + await this.$platformService.installApplication(options.device, { release: false }, options.projectData, pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); } } @@ -217,9 +211,17 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { }); const platform = device.deviceInfo.platform; - const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - - await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, projectData, deviceDescriptor, settings); + const deviceBuildInfoDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + + await this.ensureLatestAppPackageIsInstalledOnDevice({ + device, + preparedPlatforms, + rebuiltInformation, + projectData, + deviceBuildInfoDescriptor, + liveSyncData, + settings + }); const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ projectData, device, @@ -305,9 +307,17 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { await this.$devicesService.execute(async (device: Mobile.IDevice) => { const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; - const deviceDescriptor = _.find(liveSyncProcessInfo.deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + const deviceBuildInfoDescriptor = _.find(liveSyncProcessInfo.deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - await this.ensureLatestAppPackageIsInstalledOnDevice(device, preparedPlatforms, rebuiltInformation, projectData, deviceDescriptor, latestAppPackageInstalledSettings, allModifiedFiles); + await this.ensureLatestAppPackageIsInstalledOnDevice({ + device, + preparedPlatforms, + rebuiltInformation, + projectData, + deviceBuildInfoDescriptor, + settings: latestAppPackageInstalledSettings, + modifiedFiles: allModifiedFiles + }); const service = this.getLiveSyncService(device.deviceInfo.platform); const settings: ILiveSyncWatchInfo = { From f3dc66e4ce8496c76e2b4620a4c2108e61a93361 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 14 Jun 2017 13:42:16 +0300 Subject: [PATCH 106/212] Fix unit tests --- test/services/ios-log-filter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/services/ios-log-filter.ts b/test/services/ios-log-filter.ts index 8b4f317f9f..c8e2588275 100644 --- a/test/services/ios-log-filter.ts +++ b/test/services/ios-log-filter.ts @@ -43,7 +43,7 @@ describe("iOSLogFilter", () => { null, null, "CONSOLE ERROR file:///app/tns_modules/@angular/core/bundles/core.umd.js:3472:32: EXCEPTION: Uncaught (in promise): Error: CUSTOM EXCEPTION", - null + "" ] }, { @@ -84,7 +84,7 @@ describe("iOSLogFilter", () => { null, null, null, - null + "" ] } ]; From b4b4232deef0100d4ab6843cd9cf94899da49794 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Thu, 15 Jun 2017 10:15:12 +0300 Subject: [PATCH 107/212] Fix debug-service unit tests --- test/services/debug-service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/services/debug-service.ts b/test/services/debug-service.ts index eb670443f8..ea5224621c 100644 --- a/test/services/debug-service.ts +++ b/test/services/debug-service.ts @@ -86,6 +86,8 @@ describe("debugService", () => { testInjector.register("hostInfo", testData.hostInfo); + testInjector.register("logger", stubs.LoggerStub); + return testInjector; }; From b74d44d73fffcd0a5a18c4092376a20575447c8d Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Thu, 15 Jun 2017 11:03:40 +0300 Subject: [PATCH 108/212] Update ios-device-lib to 0.4.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cfe922ebd3..4b1438302a 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "glob": "^7.0.3", "iconv-lite": "0.4.11", "inquirer": "0.9.0", - "ios-device-lib": "0.4.3", + "ios-device-lib": "0.4.4", "ios-mobileprovision-finder": "1.0.9", "ios-sim-portable": "~3.0.0", "lockfile": "1.0.1", From 32569e451b4612a9bb33d0643b799ebffd2afbfa Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Thu, 15 Jun 2017 20:45:05 +0300 Subject: [PATCH 109/212] Update ios-device-lib (#2893) Update ios-device-lib where the following changes are applied: * uninstall application is changed to use `AMDeviceSecureUninstallApplication` instead of `AMDeviceUninstallApplication`. The `secure` function is guarded against some racing issues that might happen when usign the insecure variant. * Guard against errors when polling for installed applications - fixes issue when checking for currently installed apps is called during uninstalling of application. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4b1438302a..0eb8cc7d44 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "glob": "^7.0.3", "iconv-lite": "0.4.11", "inquirer": "0.9.0", - "ios-device-lib": "0.4.4", + "ios-device-lib": "0.4.5", "ios-mobileprovision-finder": "1.0.9", "ios-sim-portable": "~3.0.0", "lockfile": "1.0.1", From 2adfc16e258580f19289af9498d7d04b679b7228 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Fri, 16 Jun 2017 13:04:30 +0300 Subject: [PATCH 110/212] Pass mobileProvisionData to prepare (#2895) On Windows and Linux we are unable to execute macOS specific commands. During project preparation for iOS we need to know the data from specified provison. In this scenario, pass the data from it as a non mandatory parameter. This way the default behavior will be kept, but we'll have a way to skip the `security` command which fails on Windows. --- lib/definitions/platform.d.ts | 5 +++++ lib/services/ios-project-service.ts | 7 ++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index d0d2b914f7..57450d94f7 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -228,6 +228,11 @@ interface IPlatformSpecificData { * Target SDK for Android. */ sdk: string; + + /** + * Data from mobileProvision. + */ + mobileProvisionData?: any; } /** diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 7a2b72c520..2a0b1629be 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -14,6 +14,7 @@ import { IOSProvisionService } from "./ios-provision-service"; import { IOSEntitlementsService } from "./ios-entitlements-service"; import { XCConfigService } from "./xcconfig-service"; import * as simplePlist from "simple-plist"; +import * as mobileprovision from "ios-mobileprovision-finder"; export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService { private static XCODE_PROJECT_EXT_NAME = ".xcodeproj"; @@ -392,7 +393,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ await this.createIpa(projectRoot, projectData, buildConfig); } - private async setupSigningFromProvision(projectRoot: string, projectData: IProjectData, provision?: string): Promise { + private async setupSigningFromProvision(projectRoot: string, projectData: IProjectData, provision?: string, mobileProvisionData?: mobileprovision.provision.MobileProvision): Promise { if (provision) { const xcode = this.$pbxprojDomXcode.Xcode.open(this.getPbxProjPath(projectData)); const signing = xcode.getSigning(projectData.projectName); @@ -412,7 +413,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ if (shouldUpdateXcode) { const pickStart = Date.now(); - const mobileprovision = await this.$iOSProvisionService.pick(provision, projectData.projectId); + const mobileprovision = mobileProvisionData || await this.$iOSProvisionService.pick(provision, projectData.projectId); const pickEnd = Date.now(); this.$logger.trace("Searched and " + (mobileprovision ? "found" : "failed to find ") + " matching provisioning profile. (" + (pickEnd - pickStart) + "ms.)"); if (!mobileprovision) { @@ -638,7 +639,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f if (provision) { let projectRoot = path.join(projectData.platformsDir, "ios"); - await this.setupSigningFromProvision(projectRoot, projectData, provision); + await this.setupSigningFromProvision(projectRoot, projectData, provision, platformSpecificData.mobileProvisionData); } let project = this.createPbxProj(projectData); From 0aa938af0b6e6cdbc82f3a51d89dc19f0ee44d28 Mon Sep 17 00:00:00 2001 From: Nadezhda Atanasova Date: Mon, 19 Jun 2017 11:27:38 +0300 Subject: [PATCH 111/212] Return information about the error if loadExtension fails (#2899) * Add actual error when loading extension * PR comments --- lib/services/extensibility-service.ts | 2 +- test/services/extensibility-service.ts | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/services/extensibility-service.ts b/lib/services/extensibility-service.ts index 9cbed92579..0104e117fe 100644 --- a/lib/services/extensibility-service.ts +++ b/lib/services/extensibility-service.ts @@ -84,7 +84,7 @@ export class ExtensibilityService implements IExtensibilityService { return { extensionName }; } catch (error) { this.$logger.warn(`Error while loading ${extensionName} is: ${error.message}`); - const err = new Error(`Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds.`); + const err = new Error(`Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds. Error: ${error.message}`); err.extensionName = extensionName; throw err; } diff --git a/test/services/extensibility-service.ts b/test/services/extensibility-service.ts index ba32641507..dc2e7d42c9 100644 --- a/test/services/extensibility-service.ts +++ b/test/services/extensibility-service.ts @@ -238,7 +238,7 @@ describe("extensibilityService", () => { }; const expectedResults: any[] = _.map(extensionNames, extensionName => ({ extensionName })); - expectedResults[0] = new Error("Unable to load extension extension1. You will not be able to use the functionality that it adds."); + expectedResults[0] = new Error("Unable to load extension extension1. You will not be able to use the functionality that it adds. Error: Unable to load module."); const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); const promises = extensibilityService.loadExtensions(); assert.deepEqual(promises.length, extensionNames.length); @@ -258,6 +258,7 @@ describe("extensibilityService", () => { const testInjector = getTestInjector(); const extensionNames = ["extension1", "extension2", "extension3"]; const fs: IFileSystem = testInjector.resolve("fs"); + const expectedErrorMessage = `Unable to read ${constants.NODE_MODULES_FOLDER_NAME} dir.`; fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) === "extensions" || path.basename(pathToCheck) === constants.PACKAGE_JSON_FILE_NAME; fs.readJson = (filename: string, encoding?: string): any => { const dependencies: any = {}; @@ -272,7 +273,7 @@ describe("extensibilityService", () => { fs.readDirectory = (dir: string): string[] => { isReadDirCalled = true; assert.deepEqual(path.basename(dir), constants.NODE_MODULES_FOLDER_NAME); - throw new Error(`Unable to read ${constants.NODE_MODULES_FOLDER_NAME} dir.`); + throw new Error(expectedErrorMessage); }; const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); @@ -283,7 +284,7 @@ describe("extensibilityService", () => { await loadExtensionPromise.then(res => { throw new Error("Shouldn't get here!"); }, err => { const extensionName = extensionNames[index]; - assert.deepEqual(err.message, `Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds.`); + assert.deepEqual(err.message, `Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds. Error: ${expectedErrorMessage}`); assert.deepEqual(err.extensionName, extensionName); }); }; @@ -295,6 +296,9 @@ describe("extensibilityService", () => { it("rejects all promises when unable to install extensions to extension dir (simulate EPERM error)", async () => { const testInjector = getTestInjector(); const extensionNames = ["extension1", "extension2", "extension3"]; + const expectedErrorMessages = ["Unable to install to node_modules dir.", + "expected 'extension2' to deeply equal 'extension1'", + "expected 'extension3' to deeply equal 'extension1'"]; const fs: IFileSystem = testInjector.resolve("fs"); fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) === "extensions" || path.basename(pathToCheck) === constants.PACKAGE_JSON_FILE_NAME; fs.readJson = (filename: string, encoding?: string): any => { @@ -315,10 +319,11 @@ describe("extensibilityService", () => { let isNpmInstallCalled = false; const npm: INodePackageManager = testInjector.resolve("npm"); + const expectedErrorMessage = `Unable to install to ${constants.NODE_MODULES_FOLDER_NAME} dir.`; npm.install = async (packageName: string, pathToSave: string, config?: any): Promise => { assert.deepEqual(packageName, extensionNames[0]); isNpmInstallCalled = true; - throw new Error(`Unable to install to ${constants.NODE_MODULES_FOLDER_NAME} dir.`); + throw new Error(expectedErrorMessage); }; const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); @@ -327,11 +332,12 @@ describe("extensibilityService", () => { for (let index = 0; index < promises.length; index++) { const loadExtensionPromise = promises[index]; await loadExtensionPromise.then(res => { - console.log("######### res = ", res); throw new Error("Shouldn't get here!"); + throw new Error("Shouldn't get here!"); }, + err => { const extensionName = extensionNames[index]; - assert.deepEqual(err.message, `Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds.`); + assert.deepEqual(err.message, `Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds. Error: ${expectedErrorMessages[index]}`); assert.deepEqual(err.extensionName, extensionName); }); }; @@ -604,7 +610,7 @@ describe("extensibilityService", () => { await extensibilityService.loadExtension(extensionName); } catch (err) { isErrorRaised = true; - assert.deepEqual(err.message, `Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds.`); + assert.deepEqual(err.message, `Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds. Error: ${expectedErrorMessage}`); assert.deepEqual(err.extensionName, extensionName); } From 8cef8da5389bf7839539e8aa51fd424e5be9d2e2 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Mon, 19 Jun 2017 13:02:52 +0300 Subject: [PATCH 112/212] Emit liveSyncStopped for every device (#2900) --- lib/services/livesync/livesync-service.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index f5f84f0692..b4968e9682 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -83,6 +83,10 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { await liveSyncProcessInfo.actionsChain; } + _.each(liveSyncProcessInfo.deviceDescriptors, descriptor => { + this.emit(LiveSyncEvents.liveSyncStopped, { projectDir, deviceIdentifier: descriptor.identifier }); + }); + liveSyncProcessInfo.isStopped = true; liveSyncProcessInfo.deviceDescriptors = []; @@ -93,8 +97,6 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { projectData } }); - - this.emit(LiveSyncEvents.liveSyncStopped, { projectDir }); } } } From e3ce4e6521baa2d79b2bf92ade6e189663a9ee18 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Mon, 19 Jun 2017 13:33:01 +0300 Subject: [PATCH 113/212] Revert "Restart app upon debugging" (#2898) This reverts commit 5b1141c1bcc8362acce97d2b69422de19959942e. In case a second debug attempt fails just throw the error. --- lib/services/debug-service.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index 379e0f79bd..dda56ac784 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -8,7 +8,6 @@ export class DebugService extends EventEmitter implements IDebugService { private $androidDebugService: IPlatformDebugService, private $iOSDebugService: IPlatformDebugService, private $errors: IErrors, - private $logger: ILogger, private $hostInfo: IHostInfo, private $mobileHelper: Mobile.IMobileHelper) { super(); @@ -30,14 +29,6 @@ export class DebugService extends EventEmitter implements IDebugService { this.$errors.failWithoutHelp(`The application ${debugData.applicationIdentifier} is not installed on device with identifier ${debugData.deviceIdentifier}.`); } - if (this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform)) { - try { - await device.applicationManager.restartApplication(debugData.applicationIdentifier, debugData.projectName); - } catch (err) { - this.$logger.trace("Failed to restart app", err); - } - } - const debugOptions: IDebugOptions = _.merge({}, options); debugOptions.start = true; From 80c9aac82ebf0069b2147343b6dc68414c503914 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Tue, 20 Jun 2017 14:41:50 +0300 Subject: [PATCH 114/212] Allow prepare for Android without ANDROID_HOME (#2904) * Allow prepare for Android without ANDROID_HOME Allow preparing Android project without the requirement to have ANDROID_HOME set. The change is in the required services, which should work without ANDROID_HOME in some cases. However the commands should not allow this, so move the validation in the commands. * Fix unable to uninstall application on macOS Update ios-device-lib where a fix for uninstalling application on macOS was not working. Update common lib where the following fixes are applied: * Fix `tns devices` not showing attached iOS devices. * Do not call uninstall of application when it is not installed - this produces some confusing warnings. * Fix execution of livesync hooks when there are no node_modules In case `node_modules` are deleted from the project and `tns run ` or `tns debug ` is executed, the livesync hook fails as the package that's required in it is not installed. In order to fix this, extract the logic of the liveSync method in a separate method and decorate it with the hook. In the public method make sure all dependencies are installed and call the private method. This way the hook will be executed correctly. --- lib/android-tools-info.ts | 9 +-- lib/commands/add-platform.ts | 8 ++- lib/commands/build.ts | 5 ++ lib/commands/clean-app.ts | 60 +++++++++++-------- lib/commands/debug.ts | 23 ++++--- lib/commands/deploy.ts | 7 ++- lib/commands/install.ts | 3 + lib/commands/platform-clean.ts | 11 +++- lib/commands/prepare.ts | 13 +++- lib/commands/remove-platform.ts | 14 +++-- lib/commands/run.ts | 21 +++++-- lib/commands/update-platform.ts | 11 +++- lib/commands/update.ts | 8 +++ lib/common | 2 +- lib/services/android-project-service.ts | 37 +++++++----- .../livesync/debug-livesync-service.ts | 2 + lib/services/livesync/livesync-service.ts | 37 +++++++----- lib/services/platform-service.ts | 4 -- package.json | 2 +- test/platform-commands.ts | 6 +- test/stubs.ts | 2 +- 21 files changed, 192 insertions(+), 93 deletions(-) diff --git a/lib/android-tools-info.ts b/lib/android-tools-info.ts index e519dab0ec..b864c91dc4 100644 --- a/lib/android-tools-info.ts +++ b/lib/android-tools-info.ts @@ -317,11 +317,12 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { @cache() private getInstalledTargets(): string[] { let installedTargets: string[] = []; - const pathToInstalledTargets = path.join(this.androidHome, "platforms"); - if (this.$fs.exists(pathToInstalledTargets)) { - installedTargets = this.$fs.readDirectory(pathToInstalledTargets); + if (this.androidHome) { + const pathToInstalledTargets = path.join(this.androidHome, "platforms"); + if (this.$fs.exists(pathToInstalledTargets)) { + installedTargets = this.$fs.readDirectory(pathToInstalledTargets); + } } - this.$logger.trace("Installed Android Targets are: ", installedTargets); return installedTargets; diff --git a/lib/commands/add-platform.ts b/lib/commands/add-platform.ts index 48ff06a732..ddb6156c9f 100644 --- a/lib/commands/add-platform.ts +++ b/lib/commands/add-platform.ts @@ -4,6 +4,7 @@ export class AddPlatformCommand implements ICommand { constructor(private $options: IOptions, private $platformService: IPlatformService, private $projectData: IProjectData, + private $platformsData: IPlatformsData, private $errors: IErrors) { this.$projectData.initializeProjectData(); } @@ -17,7 +18,12 @@ export class AddPlatformCommand implements ICommand { this.$errors.fail("No platform specified. Please specify a platform to add"); } - _.each(args, arg => this.$platformService.validatePlatform(arg, this.$projectData)); + for (let arg of args) { + this.$platformService.validatePlatform(arg, this.$projectData); + const platformData = this.$platformsData.getPlatformData(arg, this.$projectData); + const platformProjectService = platformData.platformProjectService; + await platformProjectService.validate(this.$projectData); + } return true; } diff --git a/lib/commands/build.ts b/lib/commands/build.ts index 810baa4334..d0185286d2 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -84,6 +84,11 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { if (this.$options.release && (!this.$options.keyStorePath || !this.$options.keyStorePassword || !this.$options.keyStoreAlias || !this.$options.keyStoreAliasPassword)) { this.$errors.fail("When producing a release build, you need to specify all --key-store-* options."); } + + const platformData = this.$platformsData.getPlatformData(this.$devicePlatformsConstants.Android, this.$projectData); + const platformProjectService = platformData.platformProjectService; + await platformProjectService.validate(this.$projectData); + return args.length === 0 && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.Android); } } diff --git a/lib/commands/clean-app.ts b/lib/commands/clean-app.ts index 2d760fa04d..f948b4bfe8 100644 --- a/lib/commands/clean-app.ts +++ b/lib/commands/clean-app.ts @@ -1,56 +1,64 @@ -export class CleanAppCommandBase { +export class CleanAppCommandBase implements ICommand { + public allowedParameters: ICommandParameter[] = []; + + protected platform: string; + constructor(protected $options: IOptions, protected $projectData: IProjectData, - protected $platformService: IPlatformService) { + protected $platformService: IPlatformService, + protected $errors: IErrors, + protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + protected $platformsData: IPlatformsData) { + this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { - let platform = args[0].toLowerCase(); const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; - return this.$platformService.cleanDestinationApp(platform, appFilesUpdaterOptions, this.$options.platformTemplate, this.$projectData, this.$options); + return this.$platformService.cleanDestinationApp(this.platform.toLowerCase(), appFilesUpdaterOptions, this.$options.platformTemplate, this.$projectData, this.$options); + } + + public async canExecute(args: string[]): Promise { + if (!this.$platformService.isPlatformSupportedForOS(this.platform, this.$projectData)) { + this.$errors.fail(`Applications for platform ${this.platform} can not be built on this OS`); + } + + let platformData = this.$platformsData.getPlatformData(this.platform, this.$projectData); + let platformProjectService = platformData.platformProjectService; + await platformProjectService.validate(this.$projectData); + return true; } } export class CleanAppIosCommand extends CleanAppCommandBase implements ICommand { constructor(protected $options: IOptions, - private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - private $platformsData: IPlatformsData, - private $errors: IErrors, + protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + protected $platformsData: IPlatformsData, + protected $errors: IErrors, $platformService: IPlatformService, $projectData: IProjectData) { - super($options, $projectData, $platformService); + super($options, $projectData, $platformService, $errors, $devicePlatformsConstants, $platformsData); } - public allowedParameters: ICommandParameter[] = []; - - public async execute(args: string[]): Promise { - if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { - this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); - } - return super.execute([this.$platformsData.availablePlatforms.iOS]); + protected get platform(): string { + return this.$devicePlatformsConstants.iOS; } } $injector.registerCommand("clean-app|ios", CleanAppIosCommand); export class CleanAppAndroidCommand extends CleanAppCommandBase implements ICommand { - public allowedParameters: ICommandParameter[] = []; - constructor(protected $options: IOptions, - private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - private $platformsData: IPlatformsData, - private $errors: IErrors, + protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + protected $platformsData: IPlatformsData, + protected $errors: IErrors, $platformService: IPlatformService, $projectData: IProjectData) { - super($options, $projectData, $platformService); + super($options, $projectData, $platformService, $errors, $devicePlatformsConstants, $platformsData); } - public async execute(args: string[]): Promise { - if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { - this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); - } - return super.execute([this.$platformsData.availablePlatforms.Android]); + protected get platform(): string { + return this.$devicePlatformsConstants.Android; } } diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index d8ba7a9447..1a9a79e12b 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -10,6 +10,7 @@ protected $options: IOptions, protected $platformsData: IPlatformsData, protected $logger: ILogger, + protected $errors: IErrors, private $debugLiveSyncService: IDebugLiveSyncService, private $config: IConfiguration) { this.$projectData.initializeProjectData(); @@ -73,6 +74,14 @@ } public async canExecute(args: string[]): Promise { + if (!this.$platformService.isPlatformSupportedForOS(this.platform, this.$projectData)) { + this.$errors.fail(`Applications for platform ${this.platform} can not be built on this OS`); + } + + const platformData = this.$platformsData.getPlatformData(this.platform, this.$projectData); + const platformProjectService = platformData.platformProjectService; + await platformProjectService.validate(this.$projectData); + await this.$devicesService.initialize({ platform: this.platform, deviceId: this.$options.device, @@ -97,7 +106,7 @@ } export class DebugIOSCommand extends DebugPlatformCommand { - constructor(private $errors: IErrors, + constructor(protected $errors: IErrors, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $logger: ILogger, $iOSDebugService: IPlatformDebugService, @@ -110,7 +119,8 @@ export class DebugIOSCommand extends DebugPlatformCommand { $platformsData: IPlatformsData, $iosDeviceOperations: IIOSDeviceOperations, $debugLiveSyncService: IDebugLiveSyncService) { - super($iOSDebugService, $devicesService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger, $debugLiveSyncService, $config); + super($iOSDebugService, $devicesService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger, + $errors, $debugLiveSyncService, $config); // Do not dispose ios-device-lib, so the process will remain alive and the debug application (NativeScript Inspector or Chrome DevTools) will be able to connect to the socket. // In case we dispose ios-device-lib, the socket will be closed and the code will fail when the debug application tries to read/send data to device socket. // That's why the `$ tns debug ios --justlaunch` command will not release the terminal. @@ -132,7 +142,7 @@ export class DebugIOSCommand extends DebugPlatformCommand { $injector.registerCommand("debug|ios", DebugIOSCommand); export class DebugAndroidCommand extends DebugPlatformCommand { - constructor(private $errors: IErrors, + constructor(protected $errors: IErrors, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $logger: ILogger, $androidDebugService: IPlatformDebugService, @@ -144,14 +154,11 @@ export class DebugAndroidCommand extends DebugPlatformCommand { $projectData: IProjectData, $platformsData: IPlatformsData, $debugLiveSyncService: IDebugLiveSyncService) { - super($androidDebugService, $devicesService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger, $debugLiveSyncService, $config); + super($androidDebugService, $devicesService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger, + $errors, $debugLiveSyncService, $config); } public async canExecute(args: string[]): Promise { - if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.Android, this.$projectData)) { - this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.Android} can not be built on this OS`); - } - return await super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.Android); } diff --git a/lib/commands/deploy.ts b/lib/commands/deploy.ts index ef6f904f4e..3625fb8ae2 100644 --- a/lib/commands/deploy.ts +++ b/lib/commands/deploy.ts @@ -6,7 +6,8 @@ export class DeployOnDeviceCommand implements ICommand { private $options: IOptions, private $projectData: IProjectData, private $errors: IErrors, - private $mobileHelper: Mobile.IMobileHelper) { + private $mobileHelper: Mobile.IMobileHelper, + private $platformsData: IPlatformsData) { this.$projectData.initializeProjectData(); } @@ -43,6 +44,10 @@ export class DeployOnDeviceCommand implements ICommand { this.$errors.fail("When producing a release build, you need to specify all --key-store-* options."); } + const platformData = this.$platformsData.getPlatformData(args[0], this.$projectData); + const platformProjectService = platformData.platformProjectService; + await platformProjectService.validate(this.$projectData); + return this.$platformService.validateOptions(this.$options.provision, this.$projectData, args[0]); } } diff --git a/lib/commands/install.ts b/lib/commands/install.ts index 56b1cd329b..798eed654b 100644 --- a/lib/commands/install.ts +++ b/lib/commands/install.ts @@ -31,6 +31,9 @@ export class InstallCommand implements ICommand { const frameworkPackageData = this.$projectDataService.getNSValue(this.$projectData.projectDir, platformData.frameworkPackageName); if (frameworkPackageData && frameworkPackageData.version) { try { + const platformProjectService = platformData.platformProjectService; + await platformProjectService.validate(this.$projectData); + await this.$platformService.addPlatforms([`${platform}@${frameworkPackageData.version}`], this.$options.platformTemplate, this.$projectData, this.$options, this.$options.frameworkPath); } catch (err) { error = `${error}${EOL}${err}`; diff --git a/lib/commands/platform-clean.ts b/lib/commands/platform-clean.ts index e5c334b9d7..046ece328b 100644 --- a/lib/commands/platform-clean.ts +++ b/lib/commands/platform-clean.ts @@ -4,7 +4,8 @@ export class CleanCommand implements ICommand { constructor(private $options: IOptions, private $projectData: IProjectData, private $platformService: IPlatformService, - private $errors: IErrors) { + private $errors: IErrors, + private $platformsData: IPlatformsData) { this.$projectData.initializeProjectData(); } @@ -17,7 +18,13 @@ export class CleanCommand implements ICommand { this.$errors.fail("No platform specified. Please specify a platform to clean"); } - _.each(args, arg => this.$platformService.validatePlatformInstalled(arg, this.$projectData)); + for (let platform of args) { + this.$platformService.validatePlatformInstalled(platform, this.$projectData); + + const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + const platformProjectService = platformData.platformProjectService; + await platformProjectService.validate(this.$projectData); + } return true; } diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts index 20b85624d3..bf1d775c9a 100644 --- a/lib/commands/prepare.ts +++ b/lib/commands/prepare.ts @@ -4,7 +4,8 @@ export class PrepareCommand implements ICommand { constructor(private $options: IOptions, private $platformService: IPlatformService, private $projectData: IProjectData, - private $platformCommandParameter: ICommandParameter) { + private $platformCommandParameter: ICommandParameter, + private $platformsData: IPlatformsData) { this.$projectData.initializeProjectData(); } @@ -14,7 +15,15 @@ export class PrepareCommand implements ICommand { } public async canExecute(args: string[]): Promise { - return await this.$platformCommandParameter.validate(args[0]) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, args[0]); + const platform = args[0]; + const result = await this.$platformCommandParameter.validate(platform) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, platform); + if (result) { + const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + const platformProjectService = platformData.platformProjectService; + await platformProjectService.validate(this.$projectData); + } + + return result; } } diff --git a/lib/commands/remove-platform.ts b/lib/commands/remove-platform.ts index 2432525753..38b7ca016d 100644 --- a/lib/commands/remove-platform.ts +++ b/lib/commands/remove-platform.ts @@ -3,9 +3,10 @@ export class RemovePlatformCommand implements ICommand { constructor(private $platformService: IPlatformService, private $projectData: IProjectData, - private $errors: IErrors) { - this.$projectData.initializeProjectData(); - } + private $errors: IErrors, + private $platformsData: IPlatformsData) { + this.$projectData.initializeProjectData(); + } public execute(args: string[]): Promise { return this.$platformService.removePlatforms(args, this.$projectData); @@ -16,7 +17,12 @@ export class RemovePlatformCommand implements ICommand { this.$errors.fail("No platform specified. Please specify a platform to remove"); } - _.each(args, arg => this.$platformService.validatePlatformInstalled(arg, this.$projectData)); + for (let platform of args) { + this.$platformService.validatePlatformInstalled(platform, this.$projectData); + const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + const platformProjectService = platformData.platformProjectService; + await platformProjectService.validate(this.$projectData); + } return true; } diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 3540b92e53..d9ddb79c1a 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -13,7 +13,8 @@ export class RunCommandBase implements ICommand { private $devicesService: Mobile.IDevicesService, private $hostInfo: IHostInfo, private $iosDeviceOperations: IIOSDeviceOperations, - private $mobileHelper: Mobile.IMobileHelper) { + private $mobileHelper: Mobile.IMobileHelper, + protected $platformsData: IPlatformsData) { } public allowedParameters: ICommandParameter[] = []; @@ -31,6 +32,13 @@ export class RunCommandBase implements ICommand { this.platform = this.$devicePlatformsConstants.Android; } + const availablePlatforms = this.platform ? [this.platform] : this.$platformsData.availablePlatforms; + for (let platform of availablePlatforms) { + const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + const platformProjectService = platformData.platformProjectService; + await platformProjectService.validate(this.$projectData); + } + return true; } @@ -46,6 +54,7 @@ export class RunCommandBase implements ICommand { skipDeviceDetectionInterval: true, skipInferPlatform: !this.platform }); + await this.$devicesService.detectCurrentlyAttachedDevices(); const devices = this.$devicesService.getDeviceInstances(); @@ -118,7 +127,7 @@ export class RunIosCommand extends RunCommandBase implements ICommand { } constructor($platformService: IPlatformService, - private $platformsData: IPlatformsData, + protected $platformsData: IPlatformsData, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, protected $errors: IErrors, $liveSyncService: ILiveSyncService, @@ -129,7 +138,8 @@ export class RunIosCommand extends RunCommandBase implements ICommand { $hostInfo: IHostInfo, $iosDeviceOperations: IIOSDeviceOperations, $mobileHelper: Mobile.IMobileHelper) { - super($platformService, $liveSyncService, $projectData, $options, $emulatorPlatformService, $devicePlatformsConstants, $errors, $devicesService, $hostInfo, $iosDeviceOperations, $mobileHelper); + super($platformService, $liveSyncService, $projectData, $options, $emulatorPlatformService, $devicePlatformsConstants, $errors, + $devicesService, $hostInfo, $iosDeviceOperations, $mobileHelper, $platformsData); } public async execute(args: string[]): Promise { @@ -154,7 +164,7 @@ export class RunAndroidCommand extends RunCommandBase implements ICommand { } constructor($platformService: IPlatformService, - private $platformsData: IPlatformsData, + protected $platformsData: IPlatformsData, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, protected $errors: IErrors, $liveSyncService: ILiveSyncService, @@ -165,7 +175,8 @@ export class RunAndroidCommand extends RunCommandBase implements ICommand { $hostInfo: IHostInfo, $iosDeviceOperations: IIOSDeviceOperations, $mobileHelper: Mobile.IMobileHelper) { - super($platformService, $liveSyncService, $projectData, $options, $emulatorPlatformService, $devicePlatformsConstants, $errors, $devicesService, $hostInfo, $iosDeviceOperations, $mobileHelper); + super($platformService, $liveSyncService, $projectData, $options, $emulatorPlatformService, $devicePlatformsConstants, $errors, + $devicesService, $hostInfo, $iosDeviceOperations, $mobileHelper, $platformsData); } public async execute(args: string[]): Promise { diff --git a/lib/commands/update-platform.ts b/lib/commands/update-platform.ts index d66257aa2e..e740d2bad9 100644 --- a/lib/commands/update-platform.ts +++ b/lib/commands/update-platform.ts @@ -4,7 +4,8 @@ export class UpdatePlatformCommand implements ICommand { constructor(private $options: IOptions, private $projectData: IProjectData, private $platformService: IPlatformService, - private $errors: IErrors) { + private $errors: IErrors, + private $platformsData: IPlatformsData) { this.$projectData.initializeProjectData(); } @@ -17,7 +18,13 @@ export class UpdatePlatformCommand implements ICommand { this.$errors.fail("No platform specified. Please specify platforms to update."); } - _.each(args, arg => this.$platformService.validatePlatform(arg.split("@")[0], this.$projectData)); + for (let arg of args) { + const platform = arg.split("@")[0]; + this.$platformService.validatePlatformInstalled(platform, this.$projectData); + const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + const platformProjectService = platformData.platformProjectService; + await platformProjectService.validate(this.$projectData); + } return true; } diff --git a/lib/commands/update.ts b/lib/commands/update.ts index d7fff8024b..ca5ba7e888 100644 --- a/lib/commands/update.ts +++ b/lib/commands/update.ts @@ -55,6 +55,14 @@ export class UpdateCommand implements ICommand { } public async canExecute(args: string[]): Promise { + for (let arg of args) { + const platform = arg.split("@")[0]; + this.$platformService.validatePlatformInstalled(platform, this.$projectData); + const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + const platformProjectService = platformData.platformProjectService; + await platformProjectService.validate(this.$projectData); + } + return args.length < 2 && this.$projectData.projectDir !== ""; } diff --git a/lib/common b/lib/common index cf3276e6bd..c01fd47f28 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit cf3276e6bd5fdd66070a88cd70bc40d29fc65a5a +Subproject commit c01fd47f2815ed528747427bcf72a4e6dc66da18 diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index e04613141e..60c8708cda 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -101,6 +101,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject let javaCompilerVersion = await this.$sysInfo.getJavaCompilerVersion(); await this.$androidToolsInfo.validateJavacVersion(javaCompilerVersion, { showWarningsAsErrors: true }); + + await this.$androidToolsInfo.validateInfo({ showWarningsAsErrors: true, validateTargetSdk: true }); } public async validatePlugins(): Promise { @@ -113,9 +115,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } this.$fs.ensureDirectoryExists(this.getPlatformData(projectData).projectRoot); - this.$androidToolsInfo.validateInfo({ showWarningsAsErrors: true, validateTargetSdk: true }); let androidToolsInfo = this.$androidToolsInfo.getToolsInfo(); - let targetSdkVersion = androidToolsInfo.targetSdkVersion; + let targetSdkVersion = androidToolsInfo && androidToolsInfo.targetSdkVersion; this.$logger.trace(`Using Android SDK '${targetSdkVersion}'.`); this.copy(this.getPlatformData(projectData).projectRoot, frameworkDir, "libs", "-R"); @@ -219,8 +220,10 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject public interpolateConfigurationFile(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): void { let manifestPath = this.getPlatformData(projectData).configurationFilePath; shell.sed('-i', /__PACKAGE__/, projectData.projectId, manifestPath); - const sdk = (platformSpecificData && platformSpecificData.sdk) || this.$androidToolsInfo.getToolsInfo().compileSdkVersion.toString(); - shell.sed('-i', /__APILEVEL__/, sdk, manifestPath); + if (this.$androidToolsInfo.getToolsInfo().androidHomeEnvVar) { + const sdk = (platformSpecificData && platformSpecificData.sdk) || (this.$androidToolsInfo.getToolsInfo().compileSdkVersion).toString(); + shell.sed('-i', /__APILEVEL__/, sdk, manifestPath); + } } private getProjectNameFromId(projectData: IProjectData): string { @@ -431,9 +434,11 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } public async cleanProject(projectRoot: string, projectData: IProjectData): Promise { - const buildOptions = this.getBuildOptions({ release: false }, projectData); - buildOptions.unshift("clean"); - await this.executeGradleCommand(projectRoot, buildOptions); + if (this.$androidToolsInfo.getToolsInfo().androidHomeEnvVar) { + const buildOptions = this.getBuildOptions({ release: false }, projectData); + buildOptions.unshift("clean"); + await this.executeGradleCommand(projectRoot, buildOptions); + } } public async cleanDeviceTempFolder(deviceIdentifier: string, projectData: IProjectData): Promise { @@ -509,16 +514,18 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } private async executeGradleCommand(projectRoot: string, gradleArgs: string[], childProcessOpts?: SpawnOptions, spawnFromEventOptions?: ISpawnFromEventOptions): Promise { - const gradlew = this.$hostInfo.isWindows ? "gradlew.bat" : "./gradlew"; + if (this.$androidToolsInfo.getToolsInfo().androidHomeEnvVar) { + const gradlew = this.$hostInfo.isWindows ? "gradlew.bat" : "./gradlew"; - childProcessOpts = childProcessOpts || {}; - childProcessOpts.cwd = childProcessOpts.cwd || projectRoot; - childProcessOpts.stdio = childProcessOpts.stdio || "inherit"; + childProcessOpts = childProcessOpts || {}; + childProcessOpts.cwd = childProcessOpts.cwd || projectRoot; + childProcessOpts.stdio = childProcessOpts.stdio || "inherit"; - return await this.spawn(gradlew, - gradleArgs, - childProcessOpts, - spawnFromEventOptions); + return await this.spawn(gradlew, + gradleArgs, + childProcessOpts, + spawnFromEventOptions); + } } } diff --git a/lib/services/livesync/debug-livesync-service.ts b/lib/services/livesync/debug-livesync-service.ts index be75692cf1..cf3dac408e 100644 --- a/lib/services/livesync/debug-livesync-service.ts +++ b/lib/services/livesync/debug-livesync-service.ts @@ -12,6 +12,7 @@ export class DebugLiveSyncService extends LiveSyncService implements IDebugLiveS protected $logger: ILogger, $processService: IProcessService, $hooksService: IHooksService, + $pluginsService: IPluginsService, protected $injector: IInjector, private $options: IOptions, private $debugDataService: IDebugDataService, @@ -28,6 +29,7 @@ export class DebugLiveSyncService extends LiveSyncService implements IDebugLiveS $logger, $processService, $hooksService, + $pluginsService, $injector); } diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index b4968e9682..9235d3bf69 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -28,28 +28,16 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { protected $logger: ILogger, private $processService: IProcessService, private $hooksService: IHooksService, + private $pluginsService: IPluginsService, protected $injector: IInjector) { super(); } - @hook("liveSync") public async liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise { const projectData = this.$projectDataService.getProjectData(liveSyncData.projectDir); - // In case liveSync is called for a second time for the same projectDir. - const isAlreadyLiveSyncing = this.liveSyncProcessesInfo[projectData.projectDir] && !this.liveSyncProcessesInfo[projectData.projectDir].isStopped; - this.setLiveSyncProcessInfo(liveSyncData.projectDir, deviceDescriptors); - - const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors; - - await this.initialSync(projectData, deviceDescriptorsForInitialSync, liveSyncData); - - if (!liveSyncData.skipWatcher && deviceDescriptors && deviceDescriptors.length) { - // Should be set after prepare - this.$injector.resolve("usbLiveSyncService").isInitialized = true; - - await this.startWatcher(projectData, liveSyncData); - } + await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); + await this.liveSyncOperation(deviceDescriptors, liveSyncData, projectData); } public async stopLiveSync(projectDir: string, deviceIdentifiers?: string[]): Promise { @@ -127,6 +115,25 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); } + @hook("liveSync") + private async liveSyncOperation(deviceDescriptors: ILiveSyncDeviceInfo[], + liveSyncData: ILiveSyncInfo, projectData: IProjectData): Promise { + // In case liveSync is called for a second time for the same projectDir. + const isAlreadyLiveSyncing = this.liveSyncProcessesInfo[projectData.projectDir] && !this.liveSyncProcessesInfo[projectData.projectDir].isStopped; + this.setLiveSyncProcessInfo(liveSyncData.projectDir, deviceDescriptors); + + const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors; + + await this.initialSync(projectData, deviceDescriptorsForInitialSync, liveSyncData); + + if (!liveSyncData.skipWatcher && deviceDescriptors && deviceDescriptors.length) { + // Should be set after prepare + this.$injector.resolve("usbLiveSyncService").isInitialized = true; + + await this.startWatcher(projectData, liveSyncData); + } + } + private setLiveSyncProcessInfo(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[]): void { this.liveSyncProcessesInfo[projectDir] = this.liveSyncProcessesInfo[projectDir] || Object.create(null); this.liveSyncProcessesInfo[projectDir].actionsChain = this.liveSyncProcessesInfo[projectDir].actionsChain || Promise.resolve(); diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 6851c120fd..8b92bc5a19 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -96,10 +96,6 @@ export class PlatformService extends EventEmitter implements IPlatformService { version = this.getCurrentPlatformVersion(platform, projectData); } - // Copy platform specific files in platforms dir - let platformProjectService = platformData.platformProjectService; - await platformProjectService.validate(projectData); - // Log the values for project this.$logger.trace("Creating NativeScript project for the %s platform", platform); this.$logger.trace("Path: %s", platformData.projectRoot); diff --git a/package.json b/package.json index 0eb8cc7d44..ed70bb2d90 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "glob": "^7.0.3", "iconv-lite": "0.4.11", "inquirer": "0.9.0", - "ios-device-lib": "0.4.5", + "ios-device-lib": "0.4.6", "ios-mobileprovision-finder": "1.0.9", "ios-sim-portable": "~3.0.0", "lockfile": "1.0.1", diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 31a2350f60..9c5b8ee2d9 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -27,7 +27,11 @@ let isCommandExecuted = true; class PlatformData implements IPlatformData { frameworkPackageName = "tns-android"; normalizedPlatformName = "Android"; - platformProjectService: IPlatformProjectService = null; + platformProjectService: IPlatformProjectService = { + validate: async (projectData: IProjectData): Promise => { + // intentionally left blank + } + }; emulatorServices: Mobile.IEmulatorPlatformServices = null; projectRoot = ""; deviceBuildOutputPath = ""; diff --git a/test/stubs.ts b/test/stubs.ts index d96520608e..f16b5b0016 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -509,7 +509,7 @@ export class LiveSyncServiceStub implements ILiveSyncService { export class AndroidToolsInfoStub implements IAndroidToolsInfo { public getToolsInfo(): IAndroidToolsInfoData { let infoData: IAndroidToolsInfoData = Object.create(null); - infoData.androidHomeEnvVar = ""; + infoData.androidHomeEnvVar = "ANDROID_HOME"; infoData.compileSdkVersion = 23; infoData.buildToolsVersion = "23"; infoData.targetSdkVersion = 23; From fc128fddc3ca8baf1524cc52dafb82d07e248f4d Mon Sep 17 00:00:00 2001 From: Nadezhda Atanasova Date: Tue, 20 Jun 2017 16:18:46 +0300 Subject: [PATCH 115/212] Pass correct release arguments to run command (#2906) * Pass correct release arguments to run command https://github.com/NativeScript/nativescript-cli/issues/2903 * Fix merge of options during `tns run` The `$options` object has the required information for all passed `--` args, but all of them are in `get` methods. Calling `merge` does not get functions (like `get`ers), so we do not receive expected options (like `--release, --keyStorePath`, etc.) in the merged object. In order to fix this just use the `this.$options.argv` which contains the information we need. --- lib/commands/run.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/commands/run.ts b/lib/commands/run.ts index d9ddb79c1a..17ece86524 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -97,10 +97,13 @@ export class RunCommandBase implements ICommand { const runPlatformOptions: IRunPlatformOptions = { device: this.$options.device, emulator: this.$options.emulator, - justlaunch: this.$options.justlaunch, + justlaunch: this.$options.justlaunch }; - const deployOptions = _.merge({ projectDir: this.$projectData.projectDir, clean: true }, this.$options); + const deployOptions = _.merge({ + projectDir: this.$projectData.projectDir, + clean: true, + }, this.$options.argv); await this.$platformService.deployPlatform(args[0], this.$options, deployOptions, this.$projectData, this.$options); await this.$platformService.startApplication(args[0], runPlatformOptions, this.$projectData.projectId); From b7e9bf70dfae5e67426e377a529fa7ec6084d7ed Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Tue, 20 Jun 2017 17:58:03 +0300 Subject: [PATCH 116/212] Fix adding specific platform (#2907) When trying to execute `tns platform add @` we fail with message: `Cannot read property 'platformProjectService' of undefined` The reason is incorrect check when getting platforms-data where we do not expect the version. Split the incoming string by `@` and use the real platform when trying to get the platforms data. --- lib/platforms-data.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/platforms-data.ts b/lib/platforms-data.ts index 2e12d32eec..7af6a383f1 100644 --- a/lib/platforms-data.ts +++ b/lib/platforms-data.ts @@ -15,7 +15,13 @@ export class PlatformsData implements IPlatformsData { } public getPlatformData(platform: string, projectData: IProjectData): IPlatformData { - return this.platformsData[platform.toLowerCase()] && this.platformsData[platform.toLowerCase()].getPlatformData(projectData); + const platformKey = platform && _.first(platform.toLowerCase().split("@")); + let platformData: IPlatformData; + if (platformKey) { + platformData = this.platformsData[platformKey] && this.platformsData[platformKey].getPlatformData(projectData); + } + + return platformData; } public get availablePlatforms(): any { From 69d14c4dcae69b6d70bad2fb72af3a121dd366cc Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Wed, 21 Jun 2017 13:58:32 +0300 Subject: [PATCH 117/212] Fix platform update command (#2910) `tns platform update` command should install the latest platform in case it is not installed. Currently this fails (regression from previous commit). Fix this by calling correct validate method. --- lib/commands/update-platform.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/commands/update-platform.ts b/lib/commands/update-platform.ts index e740d2bad9..78975d546e 100644 --- a/lib/commands/update-platform.ts +++ b/lib/commands/update-platform.ts @@ -20,7 +20,7 @@ export class UpdatePlatformCommand implements ICommand { for (let arg of args) { const platform = arg.split("@")[0]; - this.$platformService.validatePlatformInstalled(platform, this.$projectData); + this.$platformService.validatePlatform(platform, this.$projectData); const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); const platformProjectService = platformData.platformProjectService; await platformProjectService.validate(this.$projectData); From 761fc4e2318591b25287c41f8033b7f40eb944b1 Mon Sep 17 00:00:00 2001 From: Vasil Chimev Date: Thu, 22 Jun 2017 09:17:24 +0300 Subject: [PATCH 118/212] Add changelog for 3.1.0 (#2912) * Add changelog for 3.1.0 * Update CHANGELOG.md --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5804e77b03..f72a4fa158 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,28 @@ NativeScript CLI Changelog ================ +3.1.0 (2017, June 22) +== + +### Fixed + +* [Fixed #2905](https://github.com/NativeScript/nativescript-cli/issues/2905): Doctor command fails when ANDROID_HOME is not set +* [Fixed #2874](https://github.com/NativeScript/nativescript-cli/issues/2874): Unable to build and deploy app to iTunes: Unable to connect to iTunes Connect +* [Fixed #2856](https://github.com/NativeScript/nativescript-cli/issues/2856): DevDependencies' dependencies are added to native project +* [Fixed #2860](https://github.com/NativeScript/nativescript-cli/issues/2860): `tns run ios` fails on iOS devices after rebuilding application in the process +* [Fixed #2850](https://github.com/NativeScript/nativescript-cli/issues/2850): Document properly the "Emulate Options" +* [Fixed #2757](https://github.com/NativeScript/nativescript-cli/issues/2757): `tns build ios --forDevice --path TestApp` start simulator +* [Fixed #2720](https://github.com/NativeScript/nativescript-cli/issues/2720): Livesync error with webstorm temporary files +* [Fixed #2716](https://github.com/NativeScript/nativescript-cli/issues/2716): Web pack issues when build in release mode +* [Fixed #2501](https://github.com/NativeScript/nativescript-cli/issues/2501): Manual signing with distribution provisioning profile fails with NS 2.5 +* [Fixed #2446](https://github.com/NativeScript/nativescript-cli/issues/2446): tns run--device NonexistentName retunrs different messages for ios and android +* [Fixed #1358](https://github.com/NativeScript/nativescript-cli/issues/1358): Webstorm Ubuntu .bash_profile +* [Fixed #521](https://github.com/NativeScript/nativescript-cli/issues/521): EPERM error with .local/share directory after installing CLI + +### Deprecated + +* [Implemented #2329](https://github.com/NativeScript/nativescript-cli/issues/2329): Remove the emulate command + 3.0.3 (2017, June 1) == From 6df2bc14efca0935b666b214093b5318f31b59a0 Mon Sep 17 00:00:00 2001 From: Panayot Cankov Date: Fri, 23 Jun 2017 17:56:09 +0300 Subject: [PATCH 119/212] Fix snapshot (#2918) --- lib/commands/run.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 17ece86524..548eacffb9 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -93,7 +93,7 @@ export class RunCommandBase implements ICommand { this.$iosDeviceOperations.setShouldDispose(false); } - if (this.$options.release) { + if (this.$options.release || this.$options.bundle) { const runPlatformOptions: IRunPlatformOptions = { device: this.$options.device, emulator: this.$options.emulator, From a8763db2fcc27eb8adbb9a2d9d4143c8af8964d7 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Fri, 23 Jun 2017 18:42:27 +0300 Subject: [PATCH 120/212] Fix merging of CFBundleURLSchemes in debug builds (#2913) In debug builds we add CFBundleURLSchemes that's required to restart the app on Windows. However, in case the Info.plist of the application already has the same key, the module we are using is not merging the values correctly. Fix this by manually merging the values. --- lib/common | 2 +- lib/services/ios-project-service.ts | 64 ++++++++++++++++++----------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/lib/common b/lib/common index c01fd47f28..f906af7e73 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit c01fd47f2815ed528747427bcf72a4e6dc66da18 +Subproject commit f906af7e73171ca839a8313f4d33f8e6a0ae88ee diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 2a0b1629be..deec5de691 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -743,40 +743,28 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f makePatch(pluginInfoPlistPath); } - makePatch(infoPlistPath); + if (!buildOptions.release && projectData.projectId) { + const modifiedPlistContent = this.updateCFBundleURLSchemes(infoPlistPath, projectData); - if (projectData.projectId) { session.patch({ - name: "CFBundleIdentifier from package.json nativescript.id", - read: () => - ` - - - - CFBundleIdentifier - ${projectData.projectId} - - ` + name: "CFBundleURLTypes from Info.plist and required one for restarting application", + read: () => modifiedPlistContent }); + + } else { + makePatch(infoPlistPath); } - if (!buildOptions.release && projectData.projectId) { + if (projectData.projectId) { session.patch({ - name: "CFBundleURLTypes from package.json nativescript.id", + name: "CFBundleIdentifier from package.json nativescript.id", read: () => ` - CFBundleURLTypes - - - CFBundleURLSchemes - - ${projectData.projectId.replace(/[^A-Za-z0-9]/g, "")} - - - + CFBundleIdentifier + ${projectData.projectId} ` }); @@ -788,6 +776,36 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.$fs.writeFile(this.getPlatformData(projectData).configurationFilePath, plistContent); } + private updateCFBundleURLSchemes(infoPlistPath: string, projectData: IProjectData): string { + // This code is required due to bug in session.patch logic which cannot merge values which are both arrays - it uses the second one directly. + // In our case we want to merge the values of CFBundleURLSchemes (which are arrays), which are in CFBundleURLTypes arrays. + let parsedPlist: any = plist.parse(this.$fs.readFile(infoPlistPath).toString()); + parsedPlist.CFBundleURLTypes = parsedPlist.CFBundleURLTypes || []; + + const appIdCfBundleUrlScheme = projectData.projectId.replace(/[^A-Za-z0-9]/g, ""); + + let hasAddedCFBundleURLSchemes = false; + + _.each(parsedPlist.CFBundleURLTypes, type => { + if (type.CFBundleURLSchemes) { + hasAddedCFBundleURLSchemes = true; + type.CFBundleURLSchemes.push(appIdCfBundleUrlScheme); + return false; + } + }); + + if (!hasAddedCFBundleURLSchemes) { + parsedPlist.CFBundleURLTypes.push( + { + CFBundleURLSchemes: [appIdCfBundleUrlScheme] + } + ); + } + + const newPlistContent = plist.build(parsedPlist); + return newPlistContent; + } + private getAllInstalledPlugins(projectData: IProjectData): Promise { return (this.$injector.resolve("pluginsService")).getAllInstalledPlugins(projectData); } From e0bc1cb2f65262765fb2fdf6092da2c80f0ae1ee Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 23 Jun 2017 17:29:29 +0300 Subject: [PATCH 121/212] Fix starting of emulator on `tns debug ` when device is not attached `tns debug ` command should start emulator in case device is not attached. The current command does not start emulator as we have passed `skipInferPlatform` - when this option is used, devicesService does not know for which platform to start the emulator. Fix this by passing correct options. --- lib/commands/debug.ts | 6 +----- lib/common | 2 +- lib/services/ios-project-service.ts | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 1a9a79e12b..4d2217c508 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -29,9 +29,6 @@ this.$config.debugLivesync = true; - await this.$devicesService.initialize({ - - }); await this.$devicesService.detectCurrentlyAttachedDevices(); const devices = this.$devicesService.getDeviceInstances(); @@ -86,8 +83,7 @@ platform: this.platform, deviceId: this.$options.device, emulator: this.$options.emulator, - skipDeviceDetectionInterval: true, - skipInferPlatform: true + skipDeviceDetectionInterval: true }); // Start emulator if --emulator is selected or no devices found. if (this.$options.emulator || this.$devicesService.deviceCount === 0) { diff --git a/lib/common b/lib/common index f906af7e73..2a6d178e57 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit f906af7e73171ca839a8313f4d33f8e6a0ae88ee +Subproject commit 2a6d178e5722486e03c70ea721f1be512952f102 diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index deec5de691..2d6d966e00 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -338,7 +338,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ if (!buildConfig.release && !buildConfig.architectures) { await this.$devicesService.initialize({ platform: this.$devicePlatformsConstants.iOS.toLowerCase(), deviceId: buildConfig.device, - isBuildForDevice: true + skipEmulatorStart: true }); let instances = this.$devicesService.getDeviceInstances(); let devicesArchitectures = _(instances) From a0c637983abb9bb7003607623f22daf64fad786b Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 23 Jun 2017 18:14:35 +0300 Subject: [PATCH 122/212] Fix asking for user email on postinstall On postinstall CLI asks the users if they want to subscribe for NativeScript's newsletter. This check has been broken in 3.0.0 because of incorrect await. In order to add tests for this functionality, introduce a new service that handles the check and sending of information about the user's mail. Add the following tests: - ensure postinstall.js calls correct CLI command - ensure the post-install-cli command calls the new service - ensure the new service works correctly --- lib/bootstrap.ts | 1 + lib/commands/post-install.ts | 62 +---- lib/constants.ts | 1 + lib/definitions/subscription-service.d.ts | 11 + lib/services/subscription-service.ts | 73 ++++++ postinstall.js | 5 +- test/commands/post-install.ts | 59 +++++ test/post-install.ts | 35 +++ test/services/subscription-service.ts | 286 ++++++++++++++++++++++ 9 files changed, 471 insertions(+), 62 deletions(-) create mode 100644 lib/definitions/subscription-service.d.ts create mode 100644 lib/services/subscription-service.ts create mode 100644 test/commands/post-install.ts create mode 100644 test/post-install.ts create mode 100644 test/services/subscription-service.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 0ebcc14815..ef4717e4eb 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -139,3 +139,4 @@ $injector.requireCommand("extension|uninstall", "./commands/extensibility/uninst $injector.requirePublic("extensibilityService", "./services/extensibility-service"); $injector.require("nodeModulesDependenciesBuilder", "./tools/node-modules/node-modules-dependencies-builder"); +$injector.require("subscriptionService", "./services/subscription-service"); diff --git a/lib/commands/post-install.ts b/lib/commands/post-install.ts index 87263def0c..779703e5c3 100644 --- a/lib/commands/post-install.ts +++ b/lib/commands/post-install.ts @@ -1,15 +1,8 @@ import { PostInstallCommand } from "../common/commands/post-install"; -import * as emailValidator from "email-validator"; -import * as queryString from "querystring"; -import * as helpers from "../common/helpers"; export class PostInstallCliCommand extends PostInstallCommand { - private logger: ILogger; - constructor($fs: IFileSystem, - private $httpClient: Server.IHttpClient, - private $prompter: IPrompter, - private $userSettingsService: IUserSettingsService, + private $subscriptionService: ISubscriptionService, $staticConfig: Config.IStaticConfig, $commandsService: ICommandsService, $htmlHelpService: IHtmlHelpService, @@ -18,63 +11,12 @@ export class PostInstallCliCommand extends PostInstallCommand { $analyticsService: IAnalyticsService, $logger: ILogger) { super($fs, $staticConfig, $commandsService, $htmlHelpService, $options, $doctorService, $analyticsService, $logger); - this.logger = $logger; } public async execute(args: string[]): Promise { await super.execute(args); - if (await this.shouldAskForEmail()) { - this.logger.out("Leave your e-mail address here to subscribe for NativeScript newsletter and product updates, tips and tricks:"); - let email = await this.getEmail("(press Enter for blank)"); - await this.$userSettingsService.saveSetting("EMAIL_REGISTERED", true); - await this.sendEmail(email); - } - } - - private async shouldAskForEmail(): Promise { - return helpers.isInteractive() && await process.env.CLI_NOPROMPT !== "1" && !this.$userSettingsService.getSettingValue("EMAIL_REGISTERED"); - } - - private async getEmail(prompt: string, options?: IPrompterOptions): Promise { - let schema: IPromptSchema = { - message: prompt, - type: "input", - name: "inputEmail", - validate: (value: any) => { - if (value === "" || emailValidator.validate(value)) { - return true; - } - return "Please provide a valid e-mail or simply leave it blank."; - }, - default: options && options.defaultAction - }; - - let result = await this.$prompter.get([schema]); - return result.inputEmail; - } - - private async sendEmail(email: string): Promise { - if (email) { - let postData = queryString.stringify({ - 'elqFormName': "dev_uins_cli", - 'elqSiteID': '1325', - 'emailAddress': email, - 'elqCookieWrite': '0' - }); - - let options = { - url: 'https://s1325.t.eloqua.com/e/f2', - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': postData.length - }, - body: postData - }; - - await this.$httpClient.httpRequest(options); - } + await this.$subscriptionService.subscribeForNewsletter(); } } diff --git a/lib/constants.ts b/lib/constants.ts index 984accc309..7da59a05a0 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -86,3 +86,4 @@ export const BUILD_OUTPUT_EVENT_NAME = "buildOutput"; export const CONNECTION_ERROR_EVENT_NAME = "connectionError"; export const VERSION_STRING = "version"; export const INSPECTOR_CACHE_DIRNAME = "ios-inspector"; +export const POST_INSTALL_COMMAND_NAME = "post-install-cli"; diff --git a/lib/definitions/subscription-service.d.ts b/lib/definitions/subscription-service.d.ts new file mode 100644 index 0000000000..4e4d0e79d1 --- /dev/null +++ b/lib/definitions/subscription-service.d.ts @@ -0,0 +1,11 @@ +/** + * Describes methods for subscribing to different NativeScript campaigns. + */ +interface ISubscriptionService { + /** + * Subscribes users for NativeScript's newsletter by asking them for their email. + * In case we've already asked the user for his email, this method will do nothing. + * @returns {Promise} + */ + subscribeForNewsletter(): Promise; +} diff --git a/lib/services/subscription-service.ts b/lib/services/subscription-service.ts new file mode 100644 index 0000000000..8a9e18a6cf --- /dev/null +++ b/lib/services/subscription-service.ts @@ -0,0 +1,73 @@ +import * as emailValidator from "email-validator"; +import * as queryString from "querystring"; +import * as helpers from "../common/helpers"; + +export class SubscriptionService implements ISubscriptionService { + constructor(private $httpClient: Server.IHttpClient, + private $prompter: IPrompter, + private $userSettingsService: IUserSettingsService, + private $logger: ILogger) { + } + + public async subscribeForNewsletter(): Promise { + if (await this.shouldAskForEmail()) { + this.$logger.out("Leave your e-mail address here to subscribe for NativeScript newsletter and product updates, tips and tricks:"); + let email = await this.getEmail("(press Enter for blank)"); + await this.$userSettingsService.saveSetting("EMAIL_REGISTERED", true); + await this.sendEmail(email); + } + } + + /** + * Checks whether we should ask the current user if they want to subscribe to NativeScript newsletter. + * NOTE: This method is protected, not private, only because of our unit tests. + * @returns {Promise} + */ + protected async shouldAskForEmail(): Promise { + return helpers.isInteractive() && process.env.CLI_NOPROMPT !== "1" && !(await this.$userSettingsService.getSettingValue("EMAIL_REGISTERED")); + } + + private async getEmail(prompt: string, options?: IPrompterOptions): Promise { + let schema: IPromptSchema = { + message: prompt, + type: "input", + name: "inputEmail", + validate: (value: any) => { + if (value === "" || emailValidator.validate(value)) { + return true; + } + + return "Please provide a valid e-mail or simply leave it blank."; + } + }; + + let result = await this.$prompter.get([schema]); + return result.inputEmail; + } + + private async sendEmail(email: string): Promise { + if (email) { + let postData = queryString.stringify({ + 'elqFormName': "dev_uins_cli", + 'elqSiteID': '1325', + 'emailAddress': email, + 'elqCookieWrite': '0' + }); + + let options = { + url: 'https://s1325.t.eloqua.com/e/f2', + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': postData.length + }, + body: postData + }; + + await this.$httpClient.httpRequest(options); + } + } + +} + +$injector.register("subscriptionService", SubscriptionService); diff --git a/postinstall.js b/postinstall.js index a5bb15e6a1..8c3a76fab2 100644 --- a/postinstall.js +++ b/postinstall.js @@ -1,8 +1,9 @@ "use strict"; var child_process = require("child_process"); -var commandArgs = ["bin/tns", "post-install-cli"]; var path = require("path"); +var constants = require(path.join(__dirname, "lib", "constants")); +var commandArgs = [path.join(__dirname, "bin", "tns"), constants.POST_INSTALL_COMMAND_NAME]; var nodeArgs = require(path.join(__dirname, "lib", "common", "scripts", "node-args")).getNodeArgs(); -child_process.spawn(process.argv[0], nodeArgs.concat(commandArgs), {stdio: "inherit"}); +child_process.spawn(process.argv[0], nodeArgs.concat(commandArgs), { stdio: "inherit" }); diff --git a/test/commands/post-install.ts b/test/commands/post-install.ts new file mode 100644 index 0000000000..5cadda5c0b --- /dev/null +++ b/test/commands/post-install.ts @@ -0,0 +1,59 @@ +import { Yok } from "../../lib/common/yok"; +import { assert } from "chai"; +import { PostInstallCliCommand } from "../../lib/commands/post-install"; + +const createTestInjector = (): IInjector => { + const testInjector = new Yok(); + testInjector.register("fs", { + setCurrentUserAsOwner: async (path: string, owner: string): Promise => undefined + }); + + testInjector.register("subscriptionService", { + subscribeForNewsletter: async (): Promise => undefined + }); + + testInjector.register("staticConfig", {}); + + testInjector.register("commandsService", { + tryExecuteCommand: async (commandName: string, commandArguments: string[]): Promise => undefined + }); + + testInjector.register("htmlHelpService", { + generateHtmlPages: async (): Promise => undefined + }); + + testInjector.register("options", {}); + + testInjector.register("doctorService", { + printWarnings: async (configOptions?: { trackResult: boolean }): Promise => undefined + }); + + testInjector.register("analyticsService", { + checkConsent: async (): Promise => undefined, + track: async (featureName: string, featureValue: string): Promise => undefined + }); + + testInjector.register("logger", { + out: (formatStr?: any, ...args: any[]): void => undefined, + printMarkdown: (...args: any[]): void => undefined + }); + + testInjector.registerCommand("post-install-cli", PostInstallCliCommand); + + return testInjector; +}; + +describe("post-install command", () => { + it("calls subscriptionService.subscribeForNewsletter method", async () => { + const testInjector = createTestInjector(); + const subscriptionService = testInjector.resolve("subscriptionService"); + let isSubscribeForNewsletterCalled = false; + subscriptionService.subscribeForNewsletter = async (): Promise => { + isSubscribeForNewsletterCalled = true; + }; + const postInstallCommand = testInjector.resolveCommand("post-install-cli"); + + await postInstallCommand.execute([]); + assert.isTrue(isSubscribeForNewsletterCalled, "post-install-cli command must call subscriptionService.subscribeForNewsletter"); + }); +}); diff --git a/test/post-install.ts b/test/post-install.ts new file mode 100644 index 0000000000..2df08117b8 --- /dev/null +++ b/test/post-install.ts @@ -0,0 +1,35 @@ +import { assert } from "chai"; + +// Use require instead of import in order to replace the `spawn` method of child_process +let childProcess = require("child_process"); + +import { SpawnOptions, ChildProcess } from "child_process"; +import * as path from "path"; +import { POST_INSTALL_COMMAND_NAME } from "../lib/constants"; + +describe("postinstall.js", () => { + it("calls post-install-cli command of CLI", () => { + const originalSpawn = childProcess.spawn; + let isSpawnCalled = false; + let argsPassedToSpawn: string[] = []; + childProcess.spawn = (command: string, args?: string[], options?: SpawnOptions): ChildProcess => { + isSpawnCalled = true; + argsPassedToSpawn = args; + + return null; + }; + + require(path.join(__dirname, "..", "postinstall")); + + childProcess.spawn = originalSpawn; + + assert.isTrue(isSpawnCalled, "child_process.spawn must be called from postinstall.js"); + + const expectedPathToCliExecutable = path.join(__dirname, "..", "bin", "tns"); + + assert.isTrue(argsPassedToSpawn.indexOf(expectedPathToCliExecutable) !== -1, `The spawned args must contain path to TNS. + Expected path is: ${expectedPathToCliExecutable}, current args are: ${argsPassedToSpawn}.`); + assert.isTrue(argsPassedToSpawn.indexOf(POST_INSTALL_COMMAND_NAME) !== -1, `The spawned args must contain the name of the post-install command. + Expected path is: ${expectedPathToCliExecutable}, current args are: ${argsPassedToSpawn}.`); + }); +}); diff --git a/test/services/subscription-service.ts b/test/services/subscription-service.ts new file mode 100644 index 0000000000..ba4e9df083 --- /dev/null +++ b/test/services/subscription-service.ts @@ -0,0 +1,286 @@ +import { Yok } from "../../lib/common/yok"; +import { assert } from "chai"; +import { SubscriptionService } from "../../lib/services/subscription-service"; +import { LoggerStub } from "../stubs"; +import { stringify } from "querystring"; +let helpers = require("../../lib/common/helpers"); + +interface IValidateTestData { + name: string; + valuePassedToValidate: string; + expectedResult: boolean | string; +}; + +const createTestInjector = (): IInjector => { + const testInjector = new Yok(); + testInjector.register("logger", LoggerStub); + + testInjector.register("userSettingsService", { + getSettingValue: async (value: string) => true, + saveSetting: async (key: string, value: any): Promise => undefined + }); + + testInjector.register("prompter", { + get: async (schemas: IPromptSchema[]): Promise => ({ + inputEmail: "SomeEmail" + }) + }); + + testInjector.register("httpClient", { + httpRequest: async (options: any, proxySettings?: IProxySettings): Promise => undefined + }); + + return testInjector; +}; + +class SubscriptionServiceTester extends SubscriptionService { + public shouldAskForEmailResult: boolean = null; + + constructor($httpClient: Server.IHttpClient, + $prompter: IPrompter, + $userSettingsService: IUserSettingsService, + $logger: ILogger) { + super($httpClient, $prompter, $userSettingsService, $logger); + } + + public async shouldAskForEmail(): Promise { + if (this.shouldAskForEmailResult !== null) { + return this.shouldAskForEmailResult; + } + + return super.shouldAskForEmail(); + } +} + +describe("subscriptionService", () => { + describe("shouldAskForEmail", () => { + describe("returns false", () => { + it("when terminal is not interactive", async () => { + const originalIsInteractive = helpers.isInteractive; + helpers.isInteractive = () => false; + + const testInjector = createTestInjector(); + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + const shouldAskForEmailResult = await subscriptionService.shouldAskForEmail(); + + helpers.isInteractive = originalIsInteractive; + + assert.isFalse(shouldAskForEmailResult, "When console is not interactive, we should not ask for email."); + }); + + it("when environment variable CLI_NOPROMPT is set to 1", async () => { + const originalIsInteractive = helpers.isInteractive; + helpers.isInteractive = () => true; + + const originalCliNoPrompt = process.env.CLI_NOPROMPT; + process.env.CLI_NOPROMPT = "1"; + + const testInjector = createTestInjector(); + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + const shouldAskForEmailResult = await subscriptionService.shouldAskForEmail(); + + helpers.isInteractive = originalIsInteractive; + process.env.CLI_NOPROMPT = originalCliNoPrompt; + + assert.isFalse(shouldAskForEmailResult, "When the environment variable CLI_NOPROMPT is set to 1, we should not ask for email."); + }); + + it("when user had already been asked for mail", async () => { + const originalIsInteractive = helpers.isInteractive; + helpers.isInteractive = () => true; + + const originalCliNoPrompt = process.env.CLI_NOPROMPT; + process.env.CLI_NOPROMPT = "random_value"; + + const testInjector = createTestInjector(); + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + const shouldAskForEmailResult = await subscriptionService.shouldAskForEmail(); + + helpers.isInteractive = originalIsInteractive; + process.env.CLI_NOPROMPT = originalCliNoPrompt; + + assert.isFalse(shouldAskForEmailResult, "When the user had already been asked for mail, we should not ask for email."); + }); + }); + + describe("returns true", () => { + it("when console is interactive, CLI_NOPROMPT is not 1 and we have not asked user before that", async () => { + const originalIsInteractive = helpers.isInteractive; + helpers.isInteractive = () => true; + + const originalCliNoPrompt = process.env.CLI_NOPROMPT; + process.env.CLI_NOPROMPT = "random_value"; + + const testInjector = createTestInjector(); + const userSettingsService = testInjector.resolve("userSettingsService"); + userSettingsService.getSettingValue = async (settingName: string): Promise => false; + + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + const shouldAskForEmailResult = await subscriptionService.shouldAskForEmail(); + + helpers.isInteractive = originalIsInteractive; + process.env.CLI_NOPROMPT = originalCliNoPrompt; + + assert.isTrue(shouldAskForEmailResult, "When the user had already been asked for mail, we should not ask for email."); + }); + }); + }); + + describe("subscribeForNewsletter", () => { + it("does nothing when shouldAskForEmail returns false", async () => { + const testInjector = createTestInjector(); + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + subscriptionService.shouldAskForEmailResult = false; + const logger = testInjector.resolve("logger"); + let loggerOutput = ""; + logger.out = (...args: string[]): void => { + loggerOutput += args.join(" "); + }; + + await subscriptionService.subscribeForNewsletter(); + assert.deepEqual(loggerOutput, ""); + }); + + it("shows message that asks for e-mail address", async () => { + const testInjector = createTestInjector(); + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + subscriptionService.shouldAskForEmailResult = true; + + const logger = testInjector.resolve("logger"); + let loggerOutput = ""; + + logger.out = (...args: string[]): void => { + loggerOutput += args.join(" "); + }; + + await subscriptionService.subscribeForNewsletter(); + + assert.equal(loggerOutput, "Leave your e-mail address here to subscribe for NativeScript newsletter and product updates, tips and tricks:"); + }); + + const expectedMessageInPrompter = "(press Enter for blank)"; + it(`calls prompter with specific message - ${expectedMessageInPrompter}`, async () => { + const testInjector = createTestInjector(); + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + subscriptionService.shouldAskForEmailResult = true; + const prompter = testInjector.resolve("prompter"); + let schemasPassedToPromter: IPromptSchema[] = null; + prompter.get = async (schemas: IPromptSchema[]): Promise => { + schemasPassedToPromter = schemas; + + return { inputEmail: "SomeEmail" }; + }; + + await subscriptionService.subscribeForNewsletter(); + + assert.isNotNull(schemasPassedToPromter, "Prompter should have been called."); + assert.equal(schemasPassedToPromter.length, 1, "A single schema should have been passed to schemas."); + + assert.equal(schemasPassedToPromter[0].message, expectedMessageInPrompter); + }); + + describe("calls prompter with validate method", () => { + const testData: IValidateTestData[] = [ + { + name: "returning true when empty string is passed", + valuePassedToValidate: "", + expectedResult: true + }, + { + name: "returning true when passing valid email", + valuePassedToValidate: "abc@def.gh", + expectedResult: true + }, + { + name: "returning specific message when invalid email is passed", + valuePassedToValidate: "abcdef.gh", + expectedResult: "Please provide a valid e-mail or simply leave it blank." + } + ]; + + _.each(testData, testCase => { + it(testCase.name, async () => { + const testInjector = createTestInjector(); + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + subscriptionService.shouldAskForEmailResult = true; + const prompter = testInjector.resolve("prompter"); + let schemasPassedToPromter: IPromptSchema[] = null; + prompter.get = async (schemas: IPromptSchema[]): Promise => { + schemasPassedToPromter = schemas; + return { inputEmail: "SomeEmail" }; + }; + + await subscriptionService.subscribeForNewsletter(); + + const schemaPassedToPromter = schemasPassedToPromter[0]; + const resultOfValidateMethod = schemaPassedToPromter.validate(testCase.valuePassedToValidate); + assert.equal(resultOfValidateMethod, testCase.expectedResult); + }); + }); + + }); + + const emailRegisteredKey = "EMAIL_REGISTERED"; + it(`persists ${emailRegisteredKey} setting with value true in user settings`, async () => { + const testInjector = createTestInjector(); + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + subscriptionService.shouldAskForEmailResult = true; + const userSettingsService = testInjector.resolve("userSettingsService"); + let keyPassedToSaveSetting: string = null; + let valuePassedToSaveSetting: boolean = null; + userSettingsService.saveSetting = async (key: string, value: any): Promise => { + keyPassedToSaveSetting = key; + valuePassedToSaveSetting = value; + }; + + await subscriptionService.subscribeForNewsletter(); + + assert.deepEqual(keyPassedToSaveSetting, emailRegisteredKey); + assert.deepEqual(valuePassedToSaveSetting, true); + }); + + it("calls httpRequest with concrete data", async () => { + const email = "abc@def.gh"; + + const postData = stringify({ + 'elqFormName': "dev_uins_cli", + 'elqSiteID': '1325', + 'emailAddress': email, + 'elqCookieWrite': '0' + }); + + const expectedOptions = { + url: 'https://s1325.t.eloqua.com/e/f2', + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': postData.length + }, + body: postData + }; + + const testInjector = createTestInjector(); + + const prompter = testInjector.resolve("prompter"); + let schemasPassedToPromter: IPromptSchema[] = null; + prompter.get = async (schemas: IPromptSchema[]): Promise => { + schemasPassedToPromter = schemas; + return { inputEmail: email }; + }; + + const httpClient = testInjector.resolve("httpClient"); + let optionsPassedToHttpRequest: any = null; + httpClient.httpRequest = async (options: any, proxySettings?: IProxySettings): Promise => { + optionsPassedToHttpRequest = options; + return null; + }; + + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + subscriptionService.shouldAskForEmailResult = true; + + await subscriptionService.subscribeForNewsletter(); + + assert.deepEqual(optionsPassedToHttpRequest, expectedOptions); + }); + }); +}); From 6c089b25fdd78dae875da010995288db816f6dd4 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 23 Jun 2017 18:32:29 +0300 Subject: [PATCH 123/212] Update .travis.yml --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 26b0021abb..30a1f019ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,8 @@ node_js: - '6' git: submodules: true +install: +- npm install --ignore-scripts before_script: - gem install xcodeproj - gem install cocoapods From e5ba743082d2bb4f24882030eddc2bd090e8f3eb Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Mon, 26 Jun 2017 08:54:26 +0300 Subject: [PATCH 124/212] Update ios-device-lib to 0.4.7 Update ios-device-lib to 0.4.7, which will fix livesync of `.css` files on iOS Device after application has been restarted. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ed70bb2d90..4166b8cdee 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "glob": "^7.0.3", "iconv-lite": "0.4.11", "inquirer": "0.9.0", - "ios-device-lib": "0.4.6", + "ios-device-lib": "0.4.7", "ios-mobileprovision-finder": "1.0.9", "ios-sim-portable": "~3.0.0", "lockfile": "1.0.1", From b525faaa005a54c8fd4d57d684ce59f5ae6b65a6 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Mon, 26 Jun 2017 12:03:22 +0300 Subject: [PATCH 125/212] Update to latest common lib --- lib/common | 2 +- test/services/subscription-service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/common b/lib/common index 2a6d178e57..d5e8675ed3 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 2a6d178e5722486e03c70ea721f1be512952f102 +Subproject commit d5e8675ed30da6f096643ff1aac4c6d2ec18b46f diff --git a/test/services/subscription-service.ts b/test/services/subscription-service.ts index ba4e9df083..da01584f13 100644 --- a/test/services/subscription-service.ts +++ b/test/services/subscription-service.ts @@ -121,7 +121,7 @@ describe("subscriptionService", () => { helpers.isInteractive = originalIsInteractive; process.env.CLI_NOPROMPT = originalCliNoPrompt; - assert.isTrue(shouldAskForEmailResult, "When the user had already been asked for mail, we should not ask for email."); + assert.isTrue(shouldAskForEmailResult, "We should ask the user for email when console is interactiv, CLI_NOPROMPT is not 1 and we have never asked the user before."); }); }); }); From ca2e5dec3394bfefc90d0b8b38fb95168690ed8b Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Mon, 26 Jun 2017 12:07:41 +0300 Subject: [PATCH 126/212] Set version to 3.1.1 and prepare CHANGELOG --- CHANGELOG.md | 41 +++++++++++++++++++++++++---------------- package.json | 2 +- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f72a4fa158..2a994e9ea7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ NativeScript CLI Changelog ================ +3.1.1 (2017, June 28) +== + +### Fixed +* [Fixed #2879](https://github.com/NativeScript/nativescript-cli/issues/2879): Livesync does not apply changes in CSS files on physical iOS devices +* [Fixed #2892](https://github.com/NativeScript/nativescript-cli/issues/2892): Not copying the CFBundleURLTypes from the plist file to the project +* [Fixed #2916](https://github.com/NativeScript/nativescript-cli/issues/2916): If no device or emulator is attached `tns debug android` kills the commandline process and doesn't start an emulator +* [Fixed #2923](https://github.com/NativeScript/nativescript-cli/issues/2923): Fix asking for user email on postinstall + 3.1.0 (2017, June 22) == @@ -66,7 +75,7 @@ NativeScript CLI Changelog * [Fixed #2661](https://github.com/NativeScript/nativescript-cli/issues/2661): Adding new files during livesync doesn't succeed on iOS Devices * [Fixed #2650](https://github.com/NativeScript/nativescript-cli/issues/2650): Release Build Android error: gradlew.bat failed with exit code 1 When Path contains Space * [Fixed #2125](https://github.com/NativeScript/nativescript-cli/issues/2125): NativeScript setup script fails on Mac -* [Fixed #2697](https://github.com/NativeScript/nativescript-cli/issues/2697): App_Resources being copied into app RAW +* [Fixed #2697](https://github.com/NativeScript/nativescript-cli/issues/2697): App_Resources being copied into app RAW 3.0.0-RC.1 (2017, March 29) == @@ -76,19 +85,19 @@ NativeScript CLI Changelog * [Implemented #2170](https://github.com/NativeScript/nativescript-cli/issues/2170): CLI should report adb install errors * [Implemented #2204](https://github.com/NativeScript/nativescript-cli/issues/2204): Remove fibers from CLI and use async/await * [Implemented #2349](https://github.com/NativeScript/nativescript-cli/issues/2349): Command "emulate" and options "--emulator/--device/--avd" are a bit confusing to use -* [Implemented #2513](https://github.com/NativeScript/nativescript-cli/issues/2513): Allow templates to omit App_Resources +* [Implemented #2513](https://github.com/NativeScript/nativescript-cli/issues/2513): Allow templates to omit App_Resources * [Implemented #2633](https://github.com/NativeScript/nativescript-cli/issues/2633): Deprecate `tns livesync` command ### Fixed -* [Fixed #2225](https://github.com/NativeScript/nativescript-cli/issues/2225): The tns debug ios command hangs on launch screen +* [Fixed #2225](https://github.com/NativeScript/nativescript-cli/issues/2225): The tns debug ios command hangs on launch screen * [Fixed #2357](https://github.com/NativeScript/nativescript-cli/issues/2357): --copy-to option is broken * [Fixed #2446](https://github.com/NativeScript/nativescript-cli/issues/2446): tns emulate --device NonexistentName retunrs different messages for ios and android * [Fixed #2486](https://github.com/NativeScript/nativescript-cli/issues/2486): tns run android (without started emulator/connected device) fails to start app * [Fixed #2487](https://github.com/NativeScript/nativescript-cli/issues/2487): Exception: The plugin tns-android@2.5.0 is already installed * [Fixed #2496](https://github.com/NativeScript/nativescript-cli/issues/2496): `tns platform clean android` does not maintain the same version of the runtimes * [Fixed #2511](https://github.com/NativeScript/nativescript-cli/issues/2511): Second run of `tns run android --release` does not respect changes in application code -* [Fixed #2557](https://github.com/NativeScript/nativescript-cli/issues/2557): 2.5.1 iOS incremental build generates inconsistent binary +* [Fixed #2557](https://github.com/NativeScript/nativescript-cli/issues/2557): 2.5.1 iOS incremental build generates inconsistent binary * [Fixed #2559](https://github.com/NativeScript/nativescript-cli/issues/2559): `tns init` fails, `ins init --force` produce invalid app * [Fixed #2560](https://github.com/NativeScript/nativescript-cli/issues/2560): `tns run` should do full prepare/rebuild if something in App_Resources is changes * [Fixed #2561](https://github.com/NativeScript/nativescript-cli/issues/2561): Fix prepare process terminates if passing too many arguments to a new node process @@ -116,8 +125,8 @@ NativeScript CLI Changelog ### Fixed * [Fixed #2476](https://github.com/NativeScript/nativescript-cli/issues/2476): `tns run ios` unable to sync files on iOS Simulator -* [Fixed #2491](https://github.com/NativeScript/nativescript-cli/issues/2491): "ERROR Error: Could not find module" after 2.5 upgrade -* [Fixed #2472](https://github.com/NativeScript/nativescript-cli/issues/2472): Livesync watches .DS_Store files +* [Fixed #2491](https://github.com/NativeScript/nativescript-cli/issues/2491): "ERROR Error: Could not find module" after 2.5 upgrade +* [Fixed #2472](https://github.com/NativeScript/nativescript-cli/issues/2472): Livesync watches .DS_Store files * [Fixed #2469](https://github.com/NativeScript/nativescript-cli/issues/2469): `tns run` can get stuck in a loop since 2.5 * [Fixed #2512](https://github.com/NativeScript/nativescript-cli/issues/2512): Run should not watch hidden files * [Fixed #2521](https://github.com/NativeScript/nativescript-cli/issues/2521): Enable requesting user input during plugin installation @@ -130,7 +139,7 @@ NativeScript CLI Changelog * [Implemented #2276](https://github.com/NativeScript/nativescript-cli/issues/2276): Support plugin development when using the watch option * [Implemented #2260]( https://github.com/NativeScript/nativescript-cli/issues/2260): Deprecate support for Node.js 4.x feature * [Implemented #2112]( https://github.com/NativeScript/nativescript-cli/issues/2112): Update webpack plugin to run as a npm script feature -* [Implemented #1906]( https://github.com/NativeScript/nativescript-cli/issues/1906): TNS Doctor Android tools detection feature +* [Implemented #1906]( https://github.com/NativeScript/nativescript-cli/issues/1906): TNS Doctor Android tools detection feature * [Implemented #1845](https://github.com/NativeScript/nativescript-cli/issues/1845): Remove NPM as a dependency, use the NPM installed on users' machine feature ### Fixed @@ -138,7 +147,7 @@ NativeScript CLI Changelog **Install and setup issues** * [Fixed #2302](https://github.com/NativeScript/nativescript-cli/issues/2302): App runs in Xcode and CLI but not on test flight -* [Fixed #1922](https://github.com/NativeScript/nativescript-cli/issues/1922): TNS doctor does not detect if JDK is not installed and throws an error +* [Fixed #1922](https://github.com/NativeScript/nativescript-cli/issues/1922): TNS doctor does not detect if JDK is not installed and throws an error * [Fixed #2312](https://github.com/NativeScript/nativescript-cli/issues/2312): Creating app w/ nativescript@next is not deploying App_Resources folder * [Fixed #2308](https://github.com/NativeScript/nativescript-cli/issues/2308): Using nativescript@next brings multiple libraries into node_modules * [Fixed #2301](https://github.com/NativeScript/nativescript-cli/issues/2301): Rework logic of handling 2 package.json files after tns create @@ -149,8 +158,8 @@ NativeScript CLI Changelog * [Fixed #2213](https://github.com/NativeScript/nativescript-cli/issues/2213): When switching build configurations plugin platform files are copied into assets * [Fixed #2177](https://github.com/NativeScript/nativescript-cli/issues/2177): Prepare does not flatten scoped dependencies -* [Fixed #2150](https://github.com/NativeScript/nativescript-cli/issues/2150): TNS run command broken with using --watch -* [Fixed #1740](https://github.com/NativeScript/nativescript-cli/issues/1740): Dev Dependencies (like Gulp, etc) getting built and build is failing because of which Gulp * [Fixed #](): integration is not working currently. +* [Fixed #2150](https://github.com/NativeScript/nativescript-cli/issues/2150): TNS run command broken with using --watch +* [Fixed #1740](https://github.com/NativeScript/nativescript-cli/issues/1740): Dev Dependencies (like Gulp, etc) getting built and build is failing because of which Gulp * [Fixed #](): integration is not working currently. * [Fixed #2270](https://github.com/NativeScript/nativescript-cli/issues/2270): App is not rebuilt after removing plugin with native dependencies bug * [Fixed #2253](https://github.com/NativeScript/nativescript-cli/issues/2253): Prepare command with bundle option doesn't copy files @@ -163,7 +172,7 @@ NativeScript CLI Changelog **Emulate/ Device issues** -* [Fixed #1522](https://github.com/NativeScript/nativescript-cli/issues/1522): Cannot specify emulator device while debugging +* [Fixed #1522](https://github.com/NativeScript/nativescript-cli/issues/1522): Cannot specify emulator device while debugging * [Fixed #2345](https://github.com/NativeScript/nativescript-cli/issues/2345): Option --device {DeviceName} not working for emulate/run/debug/deploy bug * [Fixed #2344](https://github.com/NativeScript/nativescript-cli/issues/2344): Emulate command not working for iOS @@ -186,18 +195,18 @@ NativeScript CLI Changelog * [Fixed #2228](https://github.com/NativeScript/nativescript-cli/issues/2228): Homebrew as root is no longer supported * [Fixed #2227]( https://github.com/NativeScript/nativescript-cli/issues/2227): Remove use of lib/iOS folder for native plugins * [Fixed #282]( https://github.com/NativeScript/nativescript-cli/issues/282): "tns platform add android --path TNSApp --symlink" does not work on Windows -* [Fixed #1802](https://github.com/NativeScript/nativescript-cli/issues/1802): semver copied to platforms/android even though it's a dev dependency +* [Fixed #1802](https://github.com/NativeScript/nativescript-cli/issues/1802): semver copied to platforms/android even though it's a dev dependency 2.4.2 (2016, December 8) == ### Fixed -* [Fixed #2332](https://github.com/NativeScript/nativescript-cli/pull/2332): Fixed email registration process +* [Fixed #2332](https://github.com/NativeScript/nativescript-cli/pull/2332): Fixed email registration process 2.4.1 (2016, December 4) == ### Fixed -* [Fixed #2200](https://github.com/NativeScript/nativescript-cli/pull/2200): TypeScript files don't show in {N} debugger. -* [Fixed #2273](https://github.com/NativeScript/nativescript-cli/pull/2273): Livesync should continue to work after app crash +* [Fixed #2200](https://github.com/NativeScript/nativescript-cli/pull/2200): TypeScript files don't show in {N} debugger. +* [Fixed #2273](https://github.com/NativeScript/nativescript-cli/pull/2273): Livesync should continue to work after app crash 2.4.0 (2016, November 16) == @@ -260,7 +269,7 @@ NativeScript CLI Changelog ### New * [Implemented #1959](https://github.com/NativeScript/nativescript-cli/issues/1959): XCode8/iOS10 support -* [Implemented #1948](https://github.com/NativeScript/nativescript-cli/issues/1948): npm WARN deprecated minimatch@0.2.14: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue +* [Implemented #1948](https://github.com/NativeScript/nativescript-cli/issues/1948): npm WARN deprecated minimatch@0.2.14: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue * [Implemented #1944](https://github.com/NativeScript/nativescript-cli/issues/1944): Enhancement: tns plugin INSTALL alias * [Implemented #1565](https://github.com/NativeScript/nativescript-cli/issues/1565): Livesync debugging support * [Implemented #526](https://github.com/NativeScript/nativescript-cli/issues/526): Update plugin command diff --git a/package.json b/package.json index 4166b8cdee..44623ea456 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "3.1.0", + "version": "3.1.1", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { From 79c631088193cf6a004487be1256f13b98a0327f Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Mon, 26 Jun 2017 12:42:21 +0300 Subject: [PATCH 127/212] Set version to 3.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 44623ea456..a99f1db36e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "3.1.1", + "version": "3.2.0", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { From 22daa6169efcaf68999c6a4d92422fed659d5c79 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Mon, 26 Jun 2017 12:43:25 +0300 Subject: [PATCH 128/212] Add Android 26 as verified version --- lib/android-tools-info.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/android-tools-info.ts b/lib/android-tools-info.ts index b864c91dc4..32667f66b4 100644 --- a/lib/android-tools-info.ts +++ b/lib/android-tools-info.ts @@ -5,7 +5,7 @@ import { cache } from "./common/decorators"; export class AndroidToolsInfo implements IAndroidToolsInfo { private static ANDROID_TARGET_PREFIX = "android"; - private static SUPPORTED_TARGETS = ["android-17", "android-18", "android-19", "android-21", "android-22", "android-23", "android-24", "android-25"]; + private static SUPPORTED_TARGETS = ["android-17", "android-18", "android-19", "android-21", "android-22", "android-23", "android-24", "android-25", "android-26"]; private static MIN_REQUIRED_COMPILE_TARGET = 22; private static REQUIRED_BUILD_TOOLS_RANGE_PREFIX = ">=23"; private static VERSION_REGEX = /((\d+\.){2}\d+)/; From 7f9c2011a094375d0c28ca74692198753c33078d Mon Sep 17 00:00:00 2001 From: Panayot Cankov Date: Tue, 27 Jun 2017 15:20:52 +0300 Subject: [PATCH 129/212] fix android with webpack (#2925) --- lib/definitions/plugins.d.ts | 2 ++ lib/services/platform-service.ts | 19 +++++++++++++++++-- lib/services/plugins-service.ts | 4 ++-- test/platform-commands.ts | 1 + test/platform-service.ts | 1 + 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/definitions/plugins.d.ts b/lib/definitions/plugins.d.ts index 99c50b7893..24926cd12a 100644 --- a/lib/definitions/plugins.d.ts +++ b/lib/definitions/plugins.d.ts @@ -12,6 +12,8 @@ interface IPluginsService { */ getDependenciesFromPackageJson(projectDir: string): IPackageJsonDepedenciesResult; validate(platformData: IPlatformData, projectData: IProjectData): Promise; + preparePluginNativeCode(pluginData: IPluginData, platform: string, projectData: IProjectData): Promise; + convertToPluginData(cacheData: any, projectDir: string): IPluginData; } interface IPackageJsonDepedenciesResult { diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 8b92bc5a19..f1a3bd5b94 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -39,7 +39,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { private $npm: INodePackageManager, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $projectChangesService: IProjectChangesService, - private $analyticsService: IAnalyticsService) { + private $analyticsService: IAnalyticsService, + private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder) { super(); } @@ -223,13 +224,17 @@ export class PlatformService extends EventEmitter implements IPlatformService { let platformData = this.$platformsData.getPlatformData(platform, projectData); await this.$pluginsService.validate(platformData, projectData); + const bundle = appFilesUpdaterOptions.bundle; + await this.ensurePlatformInstalled(platform, platformTemplate, projectData, config); - let changesInfo = this.$projectChangesService.checkForChanges(platform, projectData, { bundle: appFilesUpdaterOptions.bundle, release: appFilesUpdaterOptions.release, provision: config.provision }); + let changesInfo = this.$projectChangesService.checkForChanges(platform, projectData, { bundle, release: appFilesUpdaterOptions.release, provision: config.provision }); this.$logger.trace("Changes info in prepare platform:", changesInfo); if (changesInfo.hasChanges) { await this.cleanProject(platform, appFilesUpdaterOptions, platformData, projectData); + } + if (changesInfo.hasChanges || bundle) { await this.preparePlatformCore(platform, appFilesUpdaterOptions, projectData, config, changesInfo, filesToSync); this.$projectChangesService.savePrepareInfo(platform, projectData); } else { @@ -302,6 +307,16 @@ export class PlatformService extends EventEmitter implements IPlatformService { if (!changesInfo || changesInfo.modulesChanged) { await this.copyTnsModules(platform, projectData); + } else if (appFilesUpdaterOptions.bundle) { + let dependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); + for (let dependencyKey in dependencies) { + const dependency = dependencies[dependencyKey]; + let isPlugin = !!dependency.nativescript; + if (isPlugin) { + let pluginData = this.$pluginsService.convertToPluginData(dependency, projectData.projectDir); + await this.$pluginsService.preparePluginNativeCode(pluginData, platform, projectData); + } + } } let directoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index c1fae529f9..df436d5e9d 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -139,7 +139,7 @@ export class PluginsService implements IPluginsService { this.$projectFilesManager.processPlatformSpecificFiles(pluginScriptsDestinationPath, platform); } - private async preparePluginNativeCode(pluginData: IPluginData, platform: string, projectData: IProjectData): Promise { + public async preparePluginNativeCode(pluginData: IPluginData, platform: string, projectData: IProjectData): Promise { let platformData = this.$platformsData.getPlatformData(platform, projectData); pluginData.pluginPlatformsFolderPath = (_platform: string) => path.join(pluginData.fullPath, "platforms", _platform); @@ -228,7 +228,7 @@ export class PluginsService implements IPluginsService { }; } - private convertToPluginData(cacheData: any, projectDir: string): IPluginData { + public convertToPluginData(cacheData: any, projectDir: string): IPluginData { let pluginData: any = {}; pluginData.name = cacheData.name; pluginData.version = cacheData.version; diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 9c5b8ee2d9..63288c0261 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -96,6 +96,7 @@ function createTestInjector() { testInjector.register("injector", testInjector); testInjector.register("hooksService", stubs.HooksServiceStub); testInjector.register("staticConfig", StaticConfigLib.StaticConfig); + testInjector.register("nodeModulesDependenciesBuilder", {}); testInjector.register('platformService', PlatformServiceLib.PlatformService); testInjector.register('errors', ErrorsNoFailStub); testInjector.register('logger', stubs.LoggerStub); diff --git a/test/platform-service.ts b/test/platform-service.ts index d8898f0b26..e4ce0e5352 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -30,6 +30,7 @@ function createTestInjector() { testInjector.register('platformService', PlatformServiceLib.PlatformService); testInjector.register('errors', stubs.ErrorsStub); testInjector.register('logger', stubs.LoggerStub); + testInjector.register("nodeModulesDependenciesBuilder", {}); testInjector.register('npmInstallationManager', stubs.NpmInstallationManagerStub); testInjector.register('projectData', stubs.ProjectDataStub); testInjector.register('platformsData', stubs.PlatformsDataStub); From e50a7fc28b0a31b2a26752b3d0d6c5f0cd11b685 Mon Sep 17 00:00:00 2001 From: Toma Nikolov Date: Tue, 27 Jun 2017 18:44:47 +0300 Subject: [PATCH 130/212] Different npm install result from npm 5. (#2926) --- lib/declarations.d.ts | 74 ++++++++++++++++++++++++++++++++++++- lib/node-package-manager.ts | 20 ++++++++-- 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 2d9637d780..8a6bfea5b6 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -149,6 +149,37 @@ interface INpmPeerDependencyInfo { peerMissing: boolean; } +/** + * Describes information about dependency update packages. + */ +interface INpm5DependencyInfo { + /** + * Npm action type. + * @type {string} + */ + action: string; + /** + * Dependency name. + * @type {string} + */ + name: string; + /** + * Dependency version. + * @type {string} + */ + version: string; + /** + * Destination of the installation. + * @type {string} + */ + path: string; + /** + * Dependency previous version. + * @type {string} + */ + previousVersion: string; +} + /** * Describes information returned by the npm CLI upon calling install with --json flag. */ @@ -175,6 +206,47 @@ interface INpmInstallCLIResult { problems?: string[]; } +/** + * Describes information returned by the npm 5 CLI upon calling install with --json flag. + */ +interface INpm5InstallCliResult { + /** + * Added dependencies. Note that whenever add a particular dependency with npm 5 it is listed inside of array with key "Added". + * @type {INpmDependencyUpdateInfo[]} + */ + added: INpm5DependencyInfo[]; + /** + * Removed dependencies. Note that whenever remove a particular dependency with npm 5 it is listed inside of array with key "removed". + * @type {INpmDependencyUpdateInfo[]} + */ + removed: INpm5DependencyInfo[]; + /** + * Updated dependencies. Note that whenever update a particular dependency with npm 5 it is listed inside of array with key "updated". + * @type {INpmDependencyUpdateInfo[]} + */ + updated: INpm5DependencyInfo[]; + /** + * Moved dependencies. Note that whenever move a particular dependency with npm 5 it is listed inside of array with key "moved". + * @type {INpmDependencyUpdateInfo[]} + */ + moved: INpm5DependencyInfo[]; + /** + * Failed dependencies. Note that whenever use npm 5 and the operation over particular dependency fail it is listed inside of array with key "failed". + * @type {INpmDependencyUpdateInfo[]} + */ + failed: INpm5DependencyInfo[]; + /** + * Warnings. Note that whenever use npm 5 and the operation over particular dependency have warnings they are listed inside of array with key "warnings". + * @type {INpmDependencyUpdateInfo[]} + */ + warnings: INpm5DependencyInfo[]; + /** + *Time elapsed. + * @type {Number} + */ + elapsed: Number +} + /** * Describes information about installed package. */ @@ -193,7 +265,7 @@ interface INpmInstallResultInfo { * The original output that npm CLI produced upon installation. * @type {INpmInstallCLIResult} */ - originalOutput?: INpmInstallCLIResult; + originalOutput?: INpmInstallCLIResult | INpm5InstallCliResult; } interface INpmInstallOptions { diff --git a/lib/node-package-manager.ts b/lib/node-package-manager.ts index c64f2c8236..0538c9cef9 100644 --- a/lib/node-package-manager.ts +++ b/lib/node-package-manager.ts @@ -143,9 +143,23 @@ export class NodePackageManager implements INodePackageManager { private parseNpmInstallResult(npmDryRunInstallOutput: string, npmInstallOutput: string, userSpecifiedPackageName: string): INpmInstallResultInfo { // TODO: Add tests for this functionality try { - const originalOutput: INpmInstallCLIResult = JSON.parse(npmDryRunInstallOutput); - const name = _.head(_.keys(originalOutput.dependencies)); - const dependency = _.pick(originalOutput.dependencies, name); + const originalOutput: INpmInstallCLIResult | INpm5InstallCliResult = JSON.parse(npmDryRunInstallOutput); + const npm5Output = originalOutput; + const npmOutput = originalOutput; + const name = _.head(_.keys(npmOutput.dependencies)); + + // Npm 5 return different object after performing `npm install --dry-run`. + // Considering that the dependency is already installed we should + // find it in the `updated` key as a first element of the array. + if (!name && npm5Output.updated) { + const updatedDependency = npm5Output.updated[0]; + return { + name: updatedDependency.name, + originalOutput, + version: updatedDependency.version + }; + } + const dependency = _.pick(npmOutput.dependencies, name); return { name, originalOutput, From f0dfab282457dc705ee596061766489fd7fdeb30 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Wed, 28 Jun 2017 06:28:42 +0300 Subject: [PATCH 131/212] Cherry-pick "fix android with webpack" from master (#2930) * fix android with webpack (#2925) * Update CHANGELOG --- CHANGELOG.md | 1 + lib/definitions/plugins.d.ts | 2 ++ lib/services/platform-service.ts | 19 +++++++++++++++++-- lib/services/plugins-service.ts | 4 ++-- test/platform-commands.ts | 1 + test/platform-service.ts | 1 + 6 files changed, 24 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a994e9ea7..2d88b73194 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ NativeScript CLI Changelog * [Fixed #2892](https://github.com/NativeScript/nativescript-cli/issues/2892): Not copying the CFBundleURLTypes from the plist file to the project * [Fixed #2916](https://github.com/NativeScript/nativescript-cli/issues/2916): If no device or emulator is attached `tns debug android` kills the commandline process and doesn't start an emulator * [Fixed #2923](https://github.com/NativeScript/nativescript-cli/issues/2923): Fix asking for user email on postinstall +* [Fixed #2929](https://github.com/NativeScript/nativescript-cli/issues/2929): Android release builds with webpack disregards plugin's gradle dependencies. 3.1.0 (2017, June 22) diff --git a/lib/definitions/plugins.d.ts b/lib/definitions/plugins.d.ts index 99c50b7893..24926cd12a 100644 --- a/lib/definitions/plugins.d.ts +++ b/lib/definitions/plugins.d.ts @@ -12,6 +12,8 @@ interface IPluginsService { */ getDependenciesFromPackageJson(projectDir: string): IPackageJsonDepedenciesResult; validate(platformData: IPlatformData, projectData: IProjectData): Promise; + preparePluginNativeCode(pluginData: IPluginData, platform: string, projectData: IProjectData): Promise; + convertToPluginData(cacheData: any, projectDir: string): IPluginData; } interface IPackageJsonDepedenciesResult { diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 8b92bc5a19..f1a3bd5b94 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -39,7 +39,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { private $npm: INodePackageManager, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $projectChangesService: IProjectChangesService, - private $analyticsService: IAnalyticsService) { + private $analyticsService: IAnalyticsService, + private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder) { super(); } @@ -223,13 +224,17 @@ export class PlatformService extends EventEmitter implements IPlatformService { let platformData = this.$platformsData.getPlatformData(platform, projectData); await this.$pluginsService.validate(platformData, projectData); + const bundle = appFilesUpdaterOptions.bundle; + await this.ensurePlatformInstalled(platform, platformTemplate, projectData, config); - let changesInfo = this.$projectChangesService.checkForChanges(platform, projectData, { bundle: appFilesUpdaterOptions.bundle, release: appFilesUpdaterOptions.release, provision: config.provision }); + let changesInfo = this.$projectChangesService.checkForChanges(platform, projectData, { bundle, release: appFilesUpdaterOptions.release, provision: config.provision }); this.$logger.trace("Changes info in prepare platform:", changesInfo); if (changesInfo.hasChanges) { await this.cleanProject(platform, appFilesUpdaterOptions, platformData, projectData); + } + if (changesInfo.hasChanges || bundle) { await this.preparePlatformCore(platform, appFilesUpdaterOptions, projectData, config, changesInfo, filesToSync); this.$projectChangesService.savePrepareInfo(platform, projectData); } else { @@ -302,6 +307,16 @@ export class PlatformService extends EventEmitter implements IPlatformService { if (!changesInfo || changesInfo.modulesChanged) { await this.copyTnsModules(platform, projectData); + } else if (appFilesUpdaterOptions.bundle) { + let dependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); + for (let dependencyKey in dependencies) { + const dependency = dependencies[dependencyKey]; + let isPlugin = !!dependency.nativescript; + if (isPlugin) { + let pluginData = this.$pluginsService.convertToPluginData(dependency, projectData.projectDir); + await this.$pluginsService.preparePluginNativeCode(pluginData, platform, projectData); + } + } } let directoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index c1fae529f9..df436d5e9d 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -139,7 +139,7 @@ export class PluginsService implements IPluginsService { this.$projectFilesManager.processPlatformSpecificFiles(pluginScriptsDestinationPath, platform); } - private async preparePluginNativeCode(pluginData: IPluginData, platform: string, projectData: IProjectData): Promise { + public async preparePluginNativeCode(pluginData: IPluginData, platform: string, projectData: IProjectData): Promise { let platformData = this.$platformsData.getPlatformData(platform, projectData); pluginData.pluginPlatformsFolderPath = (_platform: string) => path.join(pluginData.fullPath, "platforms", _platform); @@ -228,7 +228,7 @@ export class PluginsService implements IPluginsService { }; } - private convertToPluginData(cacheData: any, projectDir: string): IPluginData { + public convertToPluginData(cacheData: any, projectDir: string): IPluginData { let pluginData: any = {}; pluginData.name = cacheData.name; pluginData.version = cacheData.version; diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 9c5b8ee2d9..63288c0261 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -96,6 +96,7 @@ function createTestInjector() { testInjector.register("injector", testInjector); testInjector.register("hooksService", stubs.HooksServiceStub); testInjector.register("staticConfig", StaticConfigLib.StaticConfig); + testInjector.register("nodeModulesDependenciesBuilder", {}); testInjector.register('platformService', PlatformServiceLib.PlatformService); testInjector.register('errors', ErrorsNoFailStub); testInjector.register('logger', stubs.LoggerStub); diff --git a/test/platform-service.ts b/test/platform-service.ts index d8898f0b26..e4ce0e5352 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -30,6 +30,7 @@ function createTestInjector() { testInjector.register('platformService', PlatformServiceLib.PlatformService); testInjector.register('errors', stubs.ErrorsStub); testInjector.register('logger', stubs.LoggerStub); + testInjector.register("nodeModulesDependenciesBuilder", {}); testInjector.register('npmInstallationManager', stubs.NpmInstallationManagerStub); testInjector.register('projectData', stubs.ProjectDataStub); testInjector.register('platformsData', stubs.PlatformsDataStub); From 895e0bfcce07120f6bac93cbfd4fcdf71e21baff Mon Sep 17 00:00:00 2001 From: Vasil Chimev Date: Fri, 30 Jun 2017 13:04:40 +0300 Subject: [PATCH 132/212] Update Travis CI Scripts (#2915) Fix publish to S3 --- .travis.yml | 52 ++++++++++++++++++------------------ .travis/add-publishConfig.js | 18 +++++-------- 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/.travis.yml b/.travis.yml index 30a1f019ab..dd0e46bfed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,8 +16,8 @@ before_script: - npm install grunt - node_modules/.bin/grunt enableScripts:false - grunt rebuild -- ./bin/nativescript error-reporting disable # force ~/.local dir creation -- some tests rely on it -- ./bin/nativescript usage-reporting disable +- "./bin/nativescript error-reporting disable" +- "./bin/nativescript usage-reporting disable" - npm test - node_modules/.bin/grunt enableScripts:true script: @@ -30,27 +30,27 @@ after_success: before_deploy: - node .travis/add-publishConfig.js $TRAVIS_BRANCH deploy: - - provider: s3 - bucket: nativescript-ci - access_key_id: AKIAIYSWYOZRFLVKPCTQ - secret_access_key: - secure: THGlblH9XdRcTQMc3jm4kpwCB3myl8MGB3v9XjB5ObK4gqxUxuPi6e158LEG9Dgb730MGEYtaAjc9OneH59WAjQOrdcf3GXiGKOiCYzGYZLqVE4pjNDuxHaVGOj7mso4TzMinMCaDSQajTvadCfVmXqgT6p9eSXkiV3V2d2DN6c= - skip_cleanup: true - local-dir: s3-publish - upload-dir: build_result - on: - branch: master - - provider: npm - skip_cleanup: true - email: nativescript@telerik.com - on: - branch: master - api_key: - secure: KzzsvF3eA3j4gRQa8tO//+XWNSR3XiX8Sa18o3PyKyG9/cBZ6PQ3Te74cNS1C3ZiLUOgs5dWA6/TmRVPci4XjvFaWo/B6e2fuVSl5H94Od99bkeBHJsbLSEkLN4ClV/YbGuyKgA5Q2yIFt6p2EJjL90RjbbIk7I4YuyG2Mo3j0Q= - - provider: npm - skip_cleanup: true - email: nativescript@telerik.com - on: - branch: release - api_key: - secure: KzzsvF3eA3j4gRQa8tO//+XWNSR3XiX8Sa18o3PyKyG9/cBZ6PQ3Te74cNS1C3ZiLUOgs5dWA6/TmRVPci4XjvFaWo/B6e2fuVSl5H94Od99bkeBHJsbLSEkLN4ClV/YbGuyKgA5Q2yIFt6p2EJjL90RjbbIk7I4YuyG2Mo3j0Q= +- provider: s3 + access_key_id: AKIAJL6X6724CSX64X3Q + secret_access_key: + secure: a0T/2S+/rkRJqEotWPAr1VELA3k5TGyRw6VmXgBQnkirc6H0Pfu0P2DY8iriO7pnTPDCPAskdBCuk6t+RYw/OCrGDzFPApnAQ7t3tksKPr2bGYqh2HVqbFKZyEbNjzwsgxn7cmLPo936ZTHP2muQItEI3o9Zh9EZ5XHtv0Maw0k= + bucket: nativescript-ci + skip_cleanup: true + local-dir: s3-publish + upload-dir: build_result + on: + branch: master +- provider: npm + skip_cleanup: true + email: nativescript@telerik.com + on: + branch: master + api_key: + secure: KzzsvF3eA3j4gRQa8tO//+XWNSR3XiX8Sa18o3PyKyG9/cBZ6PQ3Te74cNS1C3ZiLUOgs5dWA6/TmRVPci4XjvFaWo/B6e2fuVSl5H94Od99bkeBHJsbLSEkLN4ClV/YbGuyKgA5Q2yIFt6p2EJjL90RjbbIk7I4YuyG2Mo3j0Q= +- provider: npm + skip_cleanup: true + email: nativescript@telerik.com + on: + branch: release + api_key: + secure: KzzsvF3eA3j4gRQa8tO//+XWNSR3XiX8Sa18o3PyKyG9/cBZ6PQ3Te74cNS1C3ZiLUOgs5dWA6/TmRVPci4XjvFaWo/B6e2fuVSl5H94Od99bkeBHJsbLSEkLN4ClV/YbGuyKgA5Q2yIFt6p2EJjL90RjbbIk7I4YuyG2Mo3j0Q= diff --git a/.travis/add-publishConfig.js b/.travis/add-publishConfig.js index e11c0dd434..306595c06a 100644 --- a/.travis/add-publishConfig.js +++ b/.travis/add-publishConfig.js @@ -1,25 +1,21 @@ #!/usr/bin/env node -var fsModule = require("fs"); +const fsModule = require("fs"); +const path = "./package.json"; +const fileOptions = {encoding: "utf-8"}; +const content = fsModule.readFileSync(path, fileOptions); -// Adds a publishConfig section to the package.json file -// and sets a tag to it - -var path = "./package.json"; -var fileOptions = {encoding: "utf-8"}; -var content = fsModule.readFileSync(path, fileOptions); - -var packageDef = JSON.parse(content); +const packageDef = JSON.parse(content); if (!packageDef.publishConfig) { packageDef.publishConfig = {}; } -var branch = process.argv[2]; +const branch = process.argv[2]; if (!branch) { console.log("Please pass the branch name as an argument!"); process.exit(1); } packageDef.publishConfig.tag = branch === "release" ? "rc" : "next"; -var newContent = JSON.stringify(packageDef, null, " "); +const newContent = JSON.stringify(packageDef, null, " "); fsModule.writeFileSync(path, newContent, fileOptions); From 60cf564b19c439f0b21b83222f19aec21704878d Mon Sep 17 00:00:00 2001 From: Nadezhda Atanasova Date: Tue, 4 Jul 2017 15:23:04 +0300 Subject: [PATCH 133/212] Update TS to latest release (#2928) * Update TS to latest release Fix correcponding tests * Update TS to 2.4 *Update dependencies *Fix errors due to new TS rules *Change debug on device logic - there wil be no multiple devicxes debug supported. *Update lib reference --- lib/commands/debug.ts | 9 ++--- lib/common | 2 +- lib/constants.ts | 2 +- lib/definitions/debug.d.ts | 2 +- lib/definitions/livesync.d.ts | 2 +- lib/services/android-debug-service.ts | 15 ++++---- lib/services/android-project-service.ts | 2 +- lib/services/debug-service-base.ts | 7 ++-- lib/services/debug-service.ts | 6 ++-- lib/services/init-service.ts | 2 +- lib/services/ios-debug-service.ts | 24 ++++++------- lib/services/ios-project-service.ts | 2 +- .../livesync/debug-livesync-service.ts | 13 ++++--- lib/services/platform-service.ts | 4 +-- lib/services/plugins-service.ts | 2 +- lib/services/xcconfig-service.ts | 2 +- package.json | 19 +++++----- test/ios-entitlements-service.ts | 10 +++--- test/platform-commands.ts | 6 ++-- test/platform-service.ts | 2 +- test/plugins-service.ts | 36 +++++-------------- test/project-templates-service.ts | 2 +- test/services/debug-service.ts | 6 ++-- test/services/extensibility-service.ts | 6 ++-- test/services/subscription-service.ts | 2 +- test/stubs.ts | 8 ++--- .../node-modules-dependencies-builder.ts | 2 +- test/xcconfig-service.ts | 4 +-- 28 files changed, 87 insertions(+), 112 deletions(-) diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 4d2217c508..cddbfb8ada 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -24,15 +24,15 @@ await this.$platformService.trackProjectType(this.$projectData); if (this.$options.start) { - return this.$debugLiveSyncService.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); + return this.$debugLiveSyncService.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); } this.$config.debugLivesync = true; await this.$devicesService.detectCurrentlyAttachedDevices(); - const devices = this.$devicesService.getDeviceInstances(); // Now let's take data for each device: + const devices = this.$devicesService.getDeviceInstances(); const deviceDescriptors: ILiveSyncDeviceInfo[] = devices.filter(d => !this.platform || d.deviceInfo.platform === this.platform) .map(d => { const info: ILiveSyncDeviceInfo = { @@ -91,10 +91,7 @@ } if (this.$devicesService.deviceCount > 1) { - // Starting debugger on emulator. - this.$options.emulator = true; - - this.$logger.warn("Multiple devices found! Starting debugger on emulator. If you want to debug on specific device please select device with --device option.".yellow.bold); + this.$errors.failWithoutHelp("Multiple devices found! To debug on specific device please select device with --device option."); } return true; diff --git a/lib/common b/lib/common index d5e8675ed3..8be9f921aa 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit d5e8675ed30da6f096643ff1aac4c6d2ec18b46f +Subproject commit 8be9f921aa50000405af9ded63c770da7910d741 diff --git a/lib/constants.ts b/lib/constants.ts index 7da59a05a0..31c19aead4 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -79,7 +79,7 @@ export class LiveSyncPaths { static FULLSYNC_DIR_NAME = "fullsync"; static IOS_DEVICE_PROJECT_ROOT_PATH = "Library/Application Support/LiveSync"; static IOS_DEVICE_SYNC_ZIP_PATH = "Library/Application Support/LiveSync/sync.zip"; -}; +} export const ANGULAR_NAME = "angular"; export const TYPESCRIPT_NAME = "typescript"; export const BUILD_OUTPUT_EVENT_NAME = "buildOutput"; diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 916fd9e391..04008db7ae 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -96,7 +96,7 @@ interface IDebugServiceBase extends NodeJS.EventEmitter { * @param {IDebugOptions} debugOptions Describe possible options to modify the behaivor of the debug operation, for example stop on the first line. * @returns {Promise} Array of URLs that can be used for debugging or a string representing a single url that can be used for debugging. */ - debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; + debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; } interface IDebugService { diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index e936c86d56..6fa8ae0691 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -174,7 +174,7 @@ interface IDebugLiveSyncService extends ILiveSyncService { * @param {string[]} information Array of information to be printed. Note that false-like values will be stripped from the array. * @returns {void} */ - printDebugInformation(information: string[]): void; + printDebugInformation(information: string): void; } interface ILiveSyncWatchInfo { diff --git a/lib/services/android-debug-service.ts b/lib/services/android-debug-service.ts index 54c59b2f1b..04713f1752 100644 --- a/lib/services/android-debug-service.ts +++ b/lib/services/android-debug-service.ts @@ -18,17 +18,17 @@ class AndroidDebugService extends DebugServiceBase implements IPlatformDebugServ this._device = newDevice; } - constructor(private $devicesService: Mobile.IDevicesService, + constructor(protected $devicesService: Mobile.IDevicesService, private $errors: IErrors, private $logger: ILogger, private $config: IConfiguration, private $androidDeviceDiscovery: Mobile.IDeviceDiscovery, private $androidProcessService: Mobile.IAndroidProcessService, private $net: INet) { - super(); + super($devicesService); } - public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { return debugOptions.emulator ? this.debugOnEmulator(debugData, debugOptions) : this.debugOnDevice(debugData, debugOptions); @@ -49,7 +49,7 @@ class AndroidDebugService extends DebugServiceBase implements IPlatformDebugServ return; } - private async debugOnEmulator(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + private async debugOnEmulator(debugData: IDebugData, debugOptions: IDebugOptions): Promise { // Assure we've detected the emulator as device // For example in case deployOnEmulator had stated new emulator instance // we need some time to detect it. Let's force detection. @@ -82,7 +82,7 @@ class AndroidDebugService extends DebugServiceBase implements IPlatformDebugServ return this.device.adb.executeCommand(["forward", `tcp:${local}`, `localabstract:${remote}`]); } - private async debugOnDevice(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + private async debugOnDevice(debugData: IDebugData, debugOptions: IDebugOptions): Promise { let packageFile = ""; if (!debugOptions.start && !debugOptions.emulator) { @@ -94,9 +94,8 @@ class AndroidDebugService extends DebugServiceBase implements IPlatformDebugServ let action = (device: Mobile.IAndroidDevice): Promise => this.debugCore(device, packageFile, debugData.applicationIdentifier, debugOptions); - const result = await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); - - return _.map(result, r => r.result); + const deviceActionResult = await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); + return deviceActionResult[0].result; } private async debugCore(device: Mobile.IAndroidDevice, packageFile: string, packageName: string, debugOptions: IDebugOptions): Promise { diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 60c8708cda..170bae39d9 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -171,7 +171,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject this.$errors.failWithoutHelp(`Your project have installed ${dependency.name} version ${cleanedVerson} but Android platform requires version ${dependency.version}.`); } } - }; + } } private cleanResValues(targetSdkVersion: number, projectData: IProjectData, frameworkVersion: string): void { diff --git a/lib/services/debug-service-base.ts b/lib/services/debug-service-base.ts index 417c080454..8dec2fa5fe 100644 --- a/lib/services/debug-service-base.ts +++ b/lib/services/debug-service-base.ts @@ -1,9 +1,11 @@ import { EventEmitter } from "events"; export abstract class DebugServiceBase extends EventEmitter implements IPlatformDebugService { + constructor(protected $devicesService: Mobile.IDevicesService) { super(); } + public abstract get platform(): string; - public abstract async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; + public abstract async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; public abstract async debugStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise; @@ -12,7 +14,8 @@ export abstract class DebugServiceBase extends EventEmitter implements IPlatform protected getCanExecuteAction(deviceIdentifier: string): (device: Mobile.IDevice) => boolean { return (device: Mobile.IDevice): boolean => { if (deviceIdentifier) { - return device.deviceInfo.identifier === deviceIdentifier; + return device.deviceInfo.identifier === deviceIdentifier + || device.deviceInfo.identifier === this.$devicesService.getDeviceByDeviceOption().deviceInfo.identifier; } else { return true; } diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index dda56ac784..b1ec3b850f 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -36,7 +36,7 @@ export class DebugService extends EventEmitter implements IDebugService { // For now we can only check if app is running on Android. // After we find a way to check on iOS we should use it here. const isAppRunning = true; - let result: string[]; + let result: string; debugOptions.chrome = true; const debugService = this.getDebugService(device); @@ -59,9 +59,9 @@ export class DebugService extends EventEmitter implements IDebugService { this.$errors.failWithoutHelp(`Debugging on iOS devices is not supported for ${platform()} yet.`); } - result = await debugService.debug(debugData, debugOptions); + result = await debugService.debug(debugData, debugOptions); } else if (this.$mobileHelper.isAndroidPlatform(device.deviceInfo.platform)) { - result = await debugService.debug(debugData, debugOptions); + result = await debugService.debug(debugData, debugOptions); } return _.first(result); diff --git a/lib/services/init-service.ts b/lib/services/init-service.ts index f60751fa83..84358a67e9 100644 --- a/lib/services/init-service.ts +++ b/lib/services/init-service.ts @@ -56,7 +56,7 @@ export class InitService implements IInitService { projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][platformData.frameworkPackageName] = _.extend(currentPlatformData, await this.getVersionData(platformData.frameworkPackageName)); } - }; + } } let dependencies = projectData.dependencies; diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index 60600caaf6..b120065040 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -21,9 +21,9 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService private _childProcess: ChildProcess; private _socketProxy: any; - constructor(private $platformService: IPlatformService, + constructor(protected $devicesService: Mobile.IDevicesService, + private $platformService: IPlatformService, private $iOSEmulatorServices: Mobile.IEmulatorPlatformServices, - private $devicesService: Mobile.IDevicesService, private $childProcess: IChildProcess, private $logger: ILogger, private $errors: IErrors, @@ -32,7 +32,7 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, private $processService: IProcessService, private $socketProxyFactory: ISocketProxyFactory) { - super(); + super($devicesService); this.$processService.attachToProcessExitSignals(this, this.debugStop); this.$socketProxyFactory.on(CONNECTION_ERROR_EVENT_NAME, (e: Error) => this.emit(CONNECTION_ERROR_EVENT_NAME, e)); } @@ -41,7 +41,7 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService return "ios"; } - public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { if (debugOptions.debugBrk && debugOptions.start) { this.$errors.failWithoutHelp("Expected exactly one of the --debug-brk or --start options."); } @@ -52,9 +52,9 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService if (debugOptions.emulator) { if (debugOptions.start) { - return [await this.emulatorStart(debugData, debugOptions)]; + return await this.emulatorStart(debugData, debugOptions); } else { - return [await this.emulatorDebugBrk(debugData, debugOptions)]; + return await this.emulatorDebugBrk(debugData, debugOptions); } } else { if (debugOptions.start) { @@ -149,7 +149,7 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService return result; } - private async deviceDebugBrk(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + private async deviceDebugBrk(debugData: IDebugData, debugOptions: IDebugOptions): Promise { await this.$devicesService.initialize({ platform: this.platform, deviceId: debugData.deviceIdentifier }); const action = async (device: iOSDevice.IOSDevice) => { if (device.isEmulator) { @@ -171,8 +171,8 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService return result; }; - const results = await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); - return _.map(results, r => r.result); + const deviceActionResult = await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); + return deviceActionResult[0].result; } private async debugBrkCore(device: Mobile.IiOSDevice, debugData: IDebugData, debugOptions: IDebugOptions): Promise { @@ -180,11 +180,11 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService return this.wireDebuggerClient(debugData, debugOptions, device); } - private async deviceStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + private async deviceStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise { await this.$devicesService.initialize({ platform: this.platform, deviceId: debugData.deviceIdentifier }); const action = async (device: Mobile.IiOSDevice) => device.isEmulator ? await this.emulatorStart(debugData, debugOptions) : await this.deviceStartCore(device, debugData, debugOptions); - const results = await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); - return _.map(results, r => r.result); + const deviceActionResult = await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); + return deviceActionResult[0].result; } private async deviceStartCore(device: Mobile.IiOSDevice, debugData: IDebugData, debugOptions: IDebugOptions): Promise { diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 2d6d966e00..b46b6e89b7 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -933,7 +933,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f private getAllLibsForPluginWithFileExtension(pluginData: IPluginData, fileExtension: string): string[] { let filterCallback = (fileName: string, pluginPlatformsFolderPath: string) => path.extname(fileName) === fileExtension; return this.getAllNativeLibrariesForPlugin(pluginData, IOSProjectService.IOS_PLATFORM_NAME, filterCallback); - }; + } private buildPathToCurrentXcodeProjectFile(projectData: IProjectData): string { return path.join(projectData.platformsDir, "ios", `${projectData.projectName}.xcodeproj`, "project.pbxproj"); diff --git a/lib/services/livesync/debug-livesync-service.ts b/lib/services/livesync/debug-livesync-service.ts index cf3dac408e..3d8354b2c8 100644 --- a/lib/services/livesync/debug-livesync-service.ts +++ b/lib/services/livesync/debug-livesync-service.ts @@ -52,7 +52,7 @@ export class DebugLiveSyncService extends LiveSyncService implements IDebugLiveS await this.$platformService.trackProjectType(this.$projectData); if (this.$options.start) { - return this.printDebugInformation(await debugService.debug(debugData, debugOptions)); + return this.printDebugInformation(await debugService.debug(debugData, debugOptions)); } const deviceAppData = liveSyncResultInfo.deviceAppData; @@ -66,14 +66,13 @@ export class DebugLiveSyncService extends LiveSyncService implements IDebugLiveS const buildConfig: IBuildConfig = _.merge({ buildForDevice: !deviceAppData.device.isEmulator }, deployOptions); debugData.pathToAppPackage = this.$platformService.lastOutputPath(debugService.platform, buildConfig, projectData); - this.printDebugInformation(await debugService.debug(debugData, debugOptions)); + this.printDebugInformation(await debugService.debug(debugData, debugOptions)); } - public printDebugInformation(information: string[]): void { - information = information.filter(i => !!i); - _.each(information, i => { - this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${i}${EOL}`.cyan); - }); + public printDebugInformation(information: string): void { + if (!!information) { + this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${information}${EOL}`.cyan); + } } } diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index f1a3bd5b94..fb68a435b6 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -73,7 +73,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { let version: string; if (currentPlatformData && currentPlatformData[constants.VERSION_STRING]) { version = currentPlatformData[constants.VERSION_STRING]; - }; + } return version; } @@ -669,7 +669,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } else { await this.addPlatform(platformParam, platformTemplate, projectData, config); } - }; + } } private getCanExecuteAction(platform: string, options: IDeviceEmulator): any { diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index df436d5e9d..38faf585e5 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -280,7 +280,7 @@ export class PluginsService implements IPluginsService { let pluginDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, "tns_modules"); await action(pluginDestinationPath, platform.toLowerCase(), platformData); } - }; + } } private getInstalledFrameworkVersion(platform: string, projectData: IProjectData): string { diff --git a/lib/services/xcconfig-service.ts b/lib/services/xcconfig-service.ts index 63fb6b2fca..d6fcb658ea 100644 --- a/lib/services/xcconfig-service.ts +++ b/lib/services/xcconfig-service.ts @@ -4,7 +4,7 @@ export class XCConfigService { /** * Returns the Value of a Property from a XC Config file. - * @param xcconfigFilePath + * @param xcconfigFilePath * @param propertyName */ public readPropertyValue(xcconfigFilePath: string, propertyName: string): string { diff --git a/package.json b/package.json index a99f1db36e..fbd777cb85 100644 --- a/package.json +++ b/package.json @@ -81,30 +81,29 @@ }, "analyze": true, "devDependencies": { - "@types/chai": "3.4.34", - "@types/chai-as-promised": "0.0.29", + "@types/chai": "4.0.1", + "@types/chai-as-promised": "0.0.31", "@types/chokidar": "1.6.0", - "@types/lodash": "4.14.50", "@types/node": "6.0.61", "@types/qr-image": "3.2.0", - "@types/request": "0.0.42", + "@types/request": "0.0.45", "@types/semver": "^5.3.31", "@types/source-map": "0.5.0", - "chai": "3.5.0", - "chai-as-promised": "6.0.0", + "chai": "4.0.2", + "chai-as-promised": "7.0.0", "grunt": "1.0.1", "grunt-contrib-clean": "1.0.0", "grunt-contrib-copy": "1.0.0", "grunt-contrib-watch": "1.0.0", "grunt-shell": "1.3.0", - "grunt-ts": "6.0.0-beta.6", - "grunt-tslint": "4.0.0", + "grunt-ts": "6.0.0-beta.16", + "grunt-tslint": "5.0.1", "istanbul": "0.4.5", "mocha": "3.1.2", "should": "7.0.2", "source-map-support": "^0.4.14", - "tslint": "4.3.1", - "typescript": "2.1.4" + "tslint": "5.4.3", + "typescript": "2.4.1" }, "license": "Apache-2.0", "engines": { diff --git a/test/ios-entitlements-service.ts b/test/ios-entitlements-service.ts index b3e99093be..5c9f950531 100644 --- a/test/ios-entitlements-service.ts +++ b/test/ios-entitlements-service.ts @@ -29,7 +29,7 @@ describe("IOSEntitlements Service Tests", () => { testInjector.register("errors", ErrorsLib.Errors); testInjector.register("pluginsService", { - getAllInstalledPlugins: async () => [] + getAllInstalledPlugins: async (): Promise => [] }); return testInjector; @@ -137,8 +137,8 @@ describe("IOSEntitlements Service Tests", () => { it("Merge uses the entitlements file from a Plugin", async () => { let pluginsService = injector.resolve("pluginsService"); - let testPluginFolderPath = temp.mkdirSync("testPlugin"); - pluginsService.getAllInstalledPlugins = async() => [{ + let testPluginFolderPath = temp.mkdirSync("testPlugin"); + pluginsService.getAllInstalledPlugins = async () => [{ pluginPlatformsFolderPath: (platform: string) => { return testPluginFolderPath; } @@ -161,8 +161,8 @@ describe("IOSEntitlements Service Tests", () => { // setup plugin entitlements let pluginsService = injector.resolve("pluginsService"); - let testPluginFolderPath = temp.mkdirSync("testPlugin"); - pluginsService.getAllInstalledPlugins = async() => [{ + let testPluginFolderPath = temp.mkdirSync("testPlugin"); + pluginsService.getAllInstalledPlugins = async () => [{ pluginPlatformsFolderPath: (platform: string) => { return testPluginFolderPath; } diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 63288c0261..cf7ff0e1e8 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -35,7 +35,7 @@ class PlatformData implements IPlatformData { emulatorServices: Mobile.IEmulatorPlatformServices = null; projectRoot = ""; deviceBuildOutputPath = ""; - getValidPackageNames = (buildOptions: {isForDevice?: boolean, isReleaseBuild?: boolean}) => [""]; + getValidPackageNames = (buildOptions: { isForDevice?: boolean, isReleaseBuild?: boolean }) => [""]; validPackageNamesForDevice: string[] = []; frameworkFilesExtensions = [".jar", ".dat"]; appDestinationDirectoryPath = ""; @@ -126,7 +126,7 @@ function createTestInjector() { prepareNodeModulesFolder: () => { /* intentionally left blank */ } }); testInjector.register("pluginsService", { - getAllInstalledPlugins: async () => [] + getAllInstalledPlugins: async (): Promise => [] }); testInjector.register("projectFilesManager", ProjectFilesManagerLib.ProjectFilesManager); testInjector.register("hooksService", stubs.HooksServiceStub); @@ -143,7 +143,7 @@ function createTestInjector() { testInjector.register("projectChangesService", ProjectChangesLib.ProjectChangesService); testInjector.register("emulatorPlatformService", stubs.EmulatorPlatformService); testInjector.register("analyticsService", { - track: async () => undefined + track: async () => async (): Promise => undefined }); testInjector.register("messages", Messages); testInjector.register("devicePathProvider", {}); diff --git a/test/platform-service.ts b/test/platform-service.ts index e4ce0e5352..94ae456ac5 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -81,7 +81,7 @@ function createTestInjector() { testInjector.register("projectChangesService", ProjectChangesLib.ProjectChangesService); testInjector.register("emulatorPlatformService", stubs.EmulatorPlatformService); testInjector.register("analyticsService", { - track: async () => undefined + track: async (): Promise => undefined }); testInjector.register("messages", Messages); testInjector.register("devicePathProvider", {}); diff --git a/test/plugins-service.ts b/test/plugins-service.ts index fa21aa9b98..4cad62b65b 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -137,9 +137,7 @@ async function addPluginWhenExpectingToFail(testInjector: IInjector, plugin: str let pluginsService: IPluginsService = testInjector.resolve("pluginsService"); pluginsService.getAllInstalledPlugins = async (projectData: IProjectData) => { - return [{ - name: "" - }]; + return [{ name: "" }]; }; pluginsService.ensureAllDependenciesAreInstalled = () => { return Promise.resolve(); @@ -210,9 +208,7 @@ describe("Plugins service", () => { let pluginsService: IPluginsService = testInjector.resolve("pluginsService"); pluginsService.getAllInstalledPlugins = async (projData: IProjectData) => { - return [{ - name: "plugin1" - }]; + return [{ name: "plugin1" }]; }; mockBeginCommand(testInjector, "Exception: " + 'Plugin "plugin1" is already installed.'); @@ -260,9 +256,7 @@ describe("Plugins service", () => { let projectData: IProjectData = testInjector.resolve("projectData"); projectData.initializeProjectData(); pluginsService.getAllInstalledPlugins = async (projData: IProjectData) => { - return [{ - name: "" - }]; + return [{ name: "" }]; }; // Mock platformsData @@ -285,9 +279,7 @@ describe("Plugins service", () => { let pluginsService: IPluginsService = testInjector.resolve("pluginsService"); pluginsService.getAllInstalledPlugins = async (projectData: IProjectData) => { - return [{ - name: "" - }]; + return [{ name: "" }]; }; let commandsService = testInjector.resolve(CommandsService); @@ -320,9 +312,7 @@ describe("Plugins service", () => { let pluginsService: IPluginsService = testInjector.resolve("pluginsService"); pluginsService.getAllInstalledPlugins = async (projectData: IProjectData) => { - return [{ - name: "" - }]; + return [{ name: "" }]; }; let commandsService = testInjector.resolve(CommandsService); @@ -368,9 +358,7 @@ describe("Plugins service", () => { let pluginsService: IPluginsService = testInjector.resolve("pluginsService"); pluginsService.getAllInstalledPlugins = async (projectData: IProjectData) => { - return [{ - name: "" - }]; + return [{ name: "" }]; }; let commandsService = testInjector.resolve(CommandsService); @@ -411,9 +399,7 @@ describe("Plugins service", () => { let pluginsService: IPluginsService = testInjector.resolve("pluginsService"); pluginsService.getAllInstalledPlugins = async (projectData: IProjectData) => { - return [{ - name: "" - }]; + return [{ name: "" }]; }; // Mock options @@ -451,9 +437,7 @@ describe("Plugins service", () => { let pluginsService: IPluginsService = testInjector.resolve("pluginsService"); pluginsService.getAllInstalledPlugins = async (projectData: IProjectData) => { - return [{ - name: "" - }]; + return [{ name: "" }]; }; // Mock options @@ -494,9 +478,7 @@ describe("Plugins service", () => { // Mock plugins service let pluginsService: IPluginsService = testInjector.resolve("pluginsService"); pluginsService.getAllInstalledPlugins = async (projectData: IProjectData) => { - return [{ - name: "" - }]; + return [{ name: "" }]; }; let appDestinationDirectoryPath = path.join(projectFolder, "platforms", "android"); diff --git a/test/project-templates-service.ts b/test/project-templates-service.ts index 6ec449d736..ac46ac405c 100644 --- a/test/project-templates-service.ts +++ b/test/project-templates-service.ts @@ -50,7 +50,7 @@ function createTestInjector(configuration?: { shouldNpmInstallThrow: boolean, np injector.register("projectTemplatesService", ProjectTemplatesService); - injector.register("analyticsService", { track: async () => undefined }); + injector.register("analyticsService", { track: async (): Promise => undefined }); return injector; } diff --git a/test/services/debug-service.ts b/test/services/debug-service.ts index ea5224621c..f23fa11be8 100644 --- a/test/services/debug-service.ts +++ b/test/services/debug-service.ts @@ -11,7 +11,7 @@ class PlatformDebugService extends EventEmitter /* implements IPlatformDebugServ public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { return [fakeChromeDebugUrl]; } -}; +} interface IDebugTestDeviceInfo { deviceInfo: { @@ -20,7 +20,7 @@ interface IDebugTestDeviceInfo { }; isEmulator: boolean; -}; +} interface IDebugTestData { isDeviceFound: boolean; @@ -30,7 +30,7 @@ interface IDebugTestData { isWindows: boolean; isDarwin: boolean; }; -}; +} const getDefaultDeviceInformation = (platform?: string): IDebugTestDeviceInfo => ({ deviceInfo: { diff --git a/test/services/extensibility-service.ts b/test/services/extensibility-service.ts index dc2e7d42c9..a03138676f 100644 --- a/test/services/extensibility-service.ts +++ b/test/services/extensibility-service.ts @@ -251,7 +251,7 @@ describe("extensibilityService", () => { assert.deepEqual(err.message, expectedResults[index].message); assert.deepEqual(err.extensionName, extensionNames[index]); }); - }; + } }); it("rejects all promises when unable to read node_modules dir (simulate EPERM error)", async () => { @@ -287,7 +287,7 @@ describe("extensibilityService", () => { assert.deepEqual(err.message, `Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds. Error: ${expectedErrorMessage}`); assert.deepEqual(err.extensionName, extensionName); }); - }; + } assert.deepEqual(promises.length, extensionNames.length); assert.isTrue(isReadDirCalled, "readDirectory should have been called for the extensions."); @@ -340,7 +340,7 @@ describe("extensibilityService", () => { assert.deepEqual(err.message, `Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds. Error: ${expectedErrorMessages[index]}`); assert.deepEqual(err.extensionName, extensionName); }); - }; + } assert.deepEqual(promises.length, extensionNames.length); assert.isTrue(isNpmInstallCalled, "Npm install should have been called for the extensions."); diff --git a/test/services/subscription-service.ts b/test/services/subscription-service.ts index da01584f13..ac2106ad0c 100644 --- a/test/services/subscription-service.ts +++ b/test/services/subscription-service.ts @@ -9,7 +9,7 @@ interface IValidateTestData { name: string; valuePassedToValidate: string; expectedResult: boolean | string; -}; +} const createTestInjector = (): IInjector => { const testInjector = new Yok(); diff --git a/test/stubs.ts b/test/stubs.ts index f16b5b0016..2948172aa6 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -178,10 +178,6 @@ export class FileSystemStub implements IFileSystem { } export class ErrorsStub implements IErrors { - constructor() { - new (require("../lib/common/errors").Errors)(); // we need the side effect of require'ing errors - } - fail(formatStr: string, ...args: any[]): void; fail(opts: { formatStr?: string; errorCode?: number; suppressCommandHelp?: boolean }, ...args: any[]): void; @@ -210,7 +206,7 @@ export class ErrorsStub implements IErrors { } export class NpmInstallationManagerStub implements INpmInstallationManager { - async install(packageName: string, pathToSave?: string, version?: string): Promise { + async install(packageName: string, pathToSave?: string, options?: INpmInstallOptions ): Promise { return Promise.resolve(""); } @@ -481,7 +477,7 @@ function unexpected(msg: string): Error { } export class DebugServiceStub extends EventEmitter implements IPlatformDebugService { - public async debug(): Promise { + public async debug(): Promise { return; } diff --git a/test/tools/node-modules/node-modules-dependencies-builder.ts b/test/tools/node-modules/node-modules-dependencies-builder.ts index fb25057a12..8b25691fde 100644 --- a/test/tools/node-modules/node-modules-dependencies-builder.ts +++ b/test/tools/node-modules/node-modules-dependencies-builder.ts @@ -10,7 +10,7 @@ interface IDependencyInfo { depth: number; dependencies?: IDependencyInfo[]; nativescript?: any; -}; +} // TODO: Add integration tests. // The tests assumes npm 3 or later is used, so all dependencies (and their dependencies) will be installed at the root node_modules diff --git a/test/xcconfig-service.ts b/test/xcconfig-service.ts index 099ee44b47..ba9e13a330 100644 --- a/test/xcconfig-service.ts +++ b/test/xcconfig-service.ts @@ -57,7 +57,7 @@ describe("XCConfig Service Tests", () => { fs.readText = (filename: string, options?: IReadFileOptions | string): string => { return `// You can add custom settings here // for example you can uncomment the following line to force distribution code signing - CODE_SIGN_IDENTITY = iPhone Distribution + CODE_SIGN_IDENTITY = iPhone Distribution // To build for device with XCode 8 you need to specify your development team. More info: https://developer.apple.com/library/prerelease/content/releasenotes/DeveloperTools/RN-Xcode/Introduction.html // DEVELOPMENT_TEAM = YOUR_TEAM_ID; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -79,7 +79,7 @@ describe("XCConfig Service Tests", () => { fs.readText = (filename: string, options?: IReadFileOptions | string): string => { return `// You can add custom settings here // for example you can uncomment the following line to force distribution code signing - CODE_SIGN_IDENTITY = iPhone Distribution + CODE_SIGN_IDENTITY = iPhone Distribution // To build for device with XCode 8 you need to specify your development team. More info: https://developer.apple.com/library/prerelease/content/releasenotes/DeveloperTools/RN-Xcode/Introduction.html // DEVELOPMENT_TEAM = YOUR_TEAM_ID ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon From fd4fcef51bc5f97635fa7d166dc221739f2fafee Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Wed, 5 Jul 2017 15:30:06 +0300 Subject: [PATCH 134/212] Track exceptions in separate Analytics project (#2948) * Track exceptions in separate Analytics project Track all exceptions from CLI in a separate analytics project. Also track the result of `tns error-reporting enable/disable` in the same project where the exceptions are tracked. Update common-lib where the following changes had been applied: Allow tracking exceptions in a different Analytics project. In case the specified CLI wants to track exceptions to a separeate project, it can do it by setting the new property in staticConfig - `ANALYTICS_EXCEPTIONS_API_KEY`. Allow tracking the result of `tns error-reporting enable/disable` to the same project where exceptions tracking is sent. Speed up check if current Analytics request is sent - we check on 1000ms, while in fact it takes between 150 and 400ms. So make the check on 100ms. * Bump CLI version to 3.1.2 --- lib/common | 2 +- lib/config.ts | 1 + package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/common b/lib/common index d5e8675ed3..f1706350f3 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit d5e8675ed30da6f096643ff1aac4c6d2ec18b46f +Subproject commit f1706350f378b77abf00276af31fee90bfb8120c diff --git a/lib/config.ts b/lib/config.ts index 68e9409bca..a3b9d77e49 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -24,6 +24,7 @@ export class StaticConfig extends StaticConfigBase implements IStaticConfig { public CLIENT_NAME = "tns"; public CLIENT_NAME_ALIAS = "NativeScript"; public ANALYTICS_API_KEY = "5752dabccfc54c4ab82aea9626b7338e"; + public ANALYTICS_EXCEPTIONS_API_KEY = "35478fe7de68431399e96212540a3d5d"; public TRACK_FEATURE_USAGE_SETTING_NAME = "TrackFeatureUsage"; public ERROR_REPORT_SETTING_NAME = "TrackExceptions"; public ANALYTICS_INSTALLATION_ID_SETTING_NAME = "AnalyticsInstallationID"; diff --git a/package.json b/package.json index 44623ea456..c66f8c9465 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "3.1.1", + "version": "3.1.2", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { From d51e767b6a0219bf06dfd7da2e4592a4e3d6bb01 Mon Sep 17 00:00:00 2001 From: Toma Nikolov Date: Wed, 5 Jul 2017 15:47:35 +0300 Subject: [PATCH 135/212] Error handling fixes. (#2938) --- lib/node-package-manager.ts | 3 +-- lib/npm-installation-manager.ts | 14 ++++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/node-package-manager.ts b/lib/node-package-manager.ts index 0538c9cef9..f506855a4c 100644 --- a/lib/node-package-manager.ts +++ b/lib/node-package-manager.ts @@ -104,7 +104,7 @@ export class NodePackageManager implements INodePackageManager { try { viewResult = await this.$childProcess.exec(`npm view ${packageName} ${flags}`); } catch (e) { - this.$errors.failWithoutHelp(e); + this.$errors.failWithoutHelp(e.message); } return JSON.parse(viewResult); } @@ -221,7 +221,6 @@ export class NodePackageManager implements INodePackageManager { if (childProcess.stderr) { childProcess.stderr.on("data", (data: string) => { - console.error(data.toString()); capturedErr += data; }); } diff --git a/lib/npm-installation-manager.ts b/lib/npm-installation-manager.ts index 34fbe7b5ac..89b698470e 100644 --- a/lib/npm-installation-manager.ts +++ b/lib/npm-installation-manager.ts @@ -42,7 +42,7 @@ export class NpmInstallationManager implements INpmInstallationManager { } catch (error) { this.$logger.debug(error); - throw new Error(error); + throw error; } } @@ -91,11 +91,9 @@ export class NpmInstallationManager implements INpmInstallationManager { if (this.$fs.exists(possiblePackageName)) { packageName = possiblePackageName; } - if (packageName.indexOf(".tgz") >= 0) { - version = null; - } + // check if the packageName is url or local file and if it is, let npm install deal with the version - if (this.isURL(packageName) || this.$fs.exists(packageName)) { + if (this.isURL(packageName) || this.$fs.exists(packageName) || this.isTgz(packageName)) { version = null; } else { version = version || await this.getLatestCompatibleVersion(packageName); @@ -108,7 +106,11 @@ export class NpmInstallationManager implements INpmInstallationManager { return pathToInstalledPackage; } - private isURL(str: string) { + private isTgz(packageName: string): boolean { + return packageName.indexOf(".tgz") >= 0; + } + + private isURL(str: string): boolean { let urlRegex = '^(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$'; let url = new RegExp(urlRegex, 'i'); return str.length < 2083 && url.test(str); From e5001669bb13ea4ef8fb5407b5453f8a65960984 Mon Sep 17 00:00:00 2001 From: TJ VanToll Date: Wed, 5 Jul 2017 05:48:05 -0700 Subject: [PATCH 136/212] Removing files that now live in the {N}/docs repo. (#2937) Previously, these files were automatically committed from this repo to the {N}/docs repo by a bot. That workflow confused people (mostly me), so the bot no longer does this. The canonical source of these files is now directly in the {N}/docs repo (specifically in https://github.com/NativeScript/docs/tree/master/plugins), so the files in this repo are no longer necessary. --- CocoaPods.md | 104 --------------- PLUGINS.md | 359 --------------------------------------------------- 2 files changed, 463 deletions(-) delete mode 100644 CocoaPods.md delete mode 100644 PLUGINS.md diff --git a/CocoaPods.md b/CocoaPods.md deleted file mode 100644 index 51bb940a7b..0000000000 --- a/CocoaPods.md +++ /dev/null @@ -1,104 +0,0 @@ -# Using CocoaPods - -When you develop for iOS, you can quickly add third-party libraries to your NativeScript projects via the [CocoaPods](https://cocoapods.org/) dependency manager. - -To work with such libraries, you need to wrap them as a custom NativeScript plugin and add them to your project. - - * [Install CocoaPods](#install-cocoapods) - * [Create CLI Project](#create-cli-project) - * [Wrap the Library as NativeScript Plugin](#wrap-the-library-as-nativescript-plugin) - * [Build the Project](#build-the-project) - -## Install CocoaPods -You need to install CocoaPods. If you haven't yet, you can do so by running: - -```bash -$ sudo gem install cocoapods -$ pod setup -``` -> **NOTE:** The minimum required version of CocoaPods is 1.0.0. - -To check your current version, run the following command. - -```bash -$ pod --version -``` - -To update CocoaPods, just run the installation command again. - -``` -sudo gem install cocoapods -pod setup -``` - -## Create CLI Project -To start, create a project and add the iOS platform. - -```bash -$ tns create MYCocoaPodsApp -$ cd MYCocoaPodsApp -$ tns platform add ios -``` - -## Wrap the Library as NativeScript Plugin - -For more information about working with NativeScript plugins, click [here](PLUGINS.md). - -```bash -cd .. -mkdir my-plugin -cd my-plugin -``` - -Create a `package.json` file with the following content: - -```json -{ - "name": "my-plugin", - "version": "0.0.1", - "nativescript": { - "platforms": { - "ios": "1.3.0" - } - } -} -``` - -Create a [Podfile](https://guides.cocoapods.org/syntax/podfile.html) which describes the dependency to the library that you want to use. Move it to the `platforms/ios` folder. - -``` -my-plugin/ -├── package.json -└── platforms/ - └── ios/ - └── Podfile -``` - -Podfile: -``` -pod 'GoogleMaps' -``` - -## Install the Plugin - -Next, install the plugin: - -```bash -tns plugin add ../my-plugin -``` - -> **NOTE:** Installing CocoaPods sets the deployment target of your app to iOS 8, if not already set to iOS 8 or later. This change is required because CocoaPods are installed as shared frameworks to ensure that all symbols are available at runtime. - -## Build the Project - -```bash -tns build ios -``` - -This modifies the `MYCocoaPodsApp.xcodeproj` and creates a workspace with the same name. - -> **IMPORTANT:** You will no longer be able to run the `xcodeproj` alone. NativeScript CLI will build the newly created workspace and produce the correct package. - -## Troubleshooting - -In case of post-build linker errors, you might need to resolve missing dependencies to native frameworks required by the installed CocoaPod. For more information about how to create the required links, see the [build.xcconfig specification](PLUGINS.md#buildxcconfig-specification). diff --git a/PLUGINS.md b/PLUGINS.md deleted file mode 100644 index 80bbc82cfd..0000000000 --- a/PLUGINS.md +++ /dev/null @@ -1,359 +0,0 @@ -Plugins -========= - -Starting with NativeScript CLI 1.1.0, you can develop or use plugins in your NativeScript projects. - -* [What Are NativeScript Plugins](#what-are-nativescript-plugins) -* [Where Can You Find NativeScript Plugins](#where-can-you-find-nativescript-plugins) -* [Create a Plugin](#create-a-plugin) - * [Directory Structure](#directory-structure) - * [`package.json` Specification](#packagejson-specification) - * [`include.gradle` Specification](#includegradle-specification) - * [`build.xcconfig` Specification](#buildxcconfig-specification) -* [Install a Plugin](#install-a-plugin) - * [Valid Plugin Sources](#valid-plugin-sources) - * [Installation Specifics](#installation-specifics) - * [Manual Steps After Installation](#manual-steps-after-installation) -* [Use a Plugin](#use-a-plugin) -* [Remove a Plugin](#remove-a-plugin) - * [Removal Specifics](#removal-specifics) - * [Manual Steps After Removal](#manual-steps-after-removal) - -## What Are NativeScript Plugins - -A NativeScript plugin is any npm package, published or not, that exposes a native API via JavaScript and consists of the following elements. - -* A `package.json` file which contains the following metadata: name, version, supported runtime versions, dependencies and others. For more information, see the [`package.json` Specification](#packagejson-specification) section. -* One or more CommonJS modules that expose a native API via a unified JavaScript API. For more information about Common JS modules, see the [CommonJS Wiki](http://wiki.commonjs.org/wiki/CommonJS). -* (Optional) `AndroidManifest.xml` and `Info.plist` which describe the permissions, features or other configurations required or used by your app for Android and iOS, respectively. -* (Optional) Native Android libraries and the native Android `include.gradle` configuration file which describes the native dependencies. For more information, see the [`include.gradle` Specification](#includegradle-specification) section. -* (Optional) Native iOS libraries and the native `build.xcconfig` configuration file which describes the native dependencies. For more information, see the [`build.xcconfig` Specification](#buildxcconfig-specification) section. - -The plugin must have the directory structure, described in the [Directory Structure](#directory-structure) section. - -## Where Can You Find NativeScript Plugins - -You can find a list of Telerik-verified NativeScript plugins on the [Telerik Verified Plugin Marketplace](http://plugins.telerik.com/nativescript), and a list of community-written NativeScript plugins by [searching for “nativescript” on npm](https://www.npmjs.com/search?q=nativescript). - -## Create a Plugin - -If the NativeScript framework does not expose a native API that you need, you can develop a plugin which exposes the required functionality. When you develop a plugin, keep in mind the following requirements. - -* The plugin must be a valid npm package. -* The plugin must expose a built-in native API or a native API available via custom native libraries. -* The plugin must be written in JavaScript and must comply with the CommonJS specification. If you are using a transpiler, make sure to include the transpiled JavaScript files in your plugin. -* The plugin directory structure must comply with the specification described below. -* The plugin must contain a valid `package.json` which complies with the specification described below. -* If the plugin requires any permissions, features or other configuration specifics, it must contain `AndroidManifest.xml` or `Info.plist` file which describe them. -* If the plugin depends on native libraries, it must contain a valid `include.gradle` or `build.xcconfig` file, which describes the dependencies. - -### Directory Structure - -NativeScript plugins which consist of one CommonJS module might have the following directory structure. - -``` -my-plugin/ -├── index.js -├── package.json -└── platforms/ - ├── android/ - │ ├── res/ - │ └── AndroidManifest.xml - └── ios/ - └── Info.plist -``` - -NativeScript plugins which consist of multiple CommonJS modules might have the following directory structure. - -``` -my-plugin/ -├── index.js -├── package.json -├── MyModule1/ -│ ├── index1.js -│ └── package.json -├── MyModule2/ -│ ├── index2.js -│ └── package.json -└── platforms/ - ├── android/ - │ ├── AndroidManifest.xml - │ └── res/ - └── ios/ - └── Info.plist -``` - -* `index.js`: This file is the CommonJS module which exposes the native API. You can use platform-specific `*.platform.js` files. For example: `index.ios.js` and `index.android.js`. During the plugin installation, the NativeScript CLI will copy the platform resources to the `tns_modules` subdirectory in the correct platform destination in the `platforms` directory of your project.
Alternatively, you can give any name to this CommonJS module. In this case, however, you need to point to this file by setting the `main` key in the `package.json` for the plugin. For more information, see [Folders as Modules](https://nodejs.org/api/modules.html#modules_folders_as_modules). -* `package.json`: This file contains the metadata for your plugin. It sets the supported runtimes, the plugin name and version and any dependencies. The `package.json` specification is described in detail below. -* `platforms\android\AndroidManifest.xml`: This file describes any specific configuration changes required for your plugin to work. For example: required permissions. For more information about the format of `AndroidManifest.xml`, see [App Manifest](http://developer.android.com/guide/topics/manifest/manifest-intro.html).
During build, gradle will merge the plugin `AndroidManifest.xml` with the `AndroidManifest.xml` for your project. The NativeScript CLI will not resolve any contradicting or duplicate entries during the merge. After the plugin is installed, you need to manually resolve such issues. -* `platforms\android\res`: (Optional) This directory contains resources declared by the `AndroidManifest.xml` file. You can look at the folder structure [here](http://developer.android.com/guide/topics/resources/providing-resources.html#ResourceTypes). -* `platforms\ios\Info.plist`: This file describes any specific configuration changes required for your plugin to work. For example, required permissions. For more information about the format of `Info.plist`, see [About Information Property List Files](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html).
During the plugin installation, the NativeScript CLI will merge the plugin `Info.plist` with the `Info.plist` for your project. The NativeScript CLI will not resolve any contradicting or duplicate entries during the merge. After the plugin is installed, you need to manually resolve such issues. - -NativeScript plugins which contain both native Android and iOS libraries might have the following directory structure. - -``` -my-plugin/ -├── ... -└── platforms/ - ├── android/ - │ ├── res/ - │ ├── MyLibrary.jar - │ ├── MyLibrary.aar - │ ├── include.gradle - │ └── AndroidManifest.xml - └── ios/ - ├── MyiOSFramework.framework - ├── build.xcconfig - ├── Podfile - ├── Info.plist - ├── MyStaticiOSLibrary.a - └── include/ - └── MyStaticiOSLibrary/ - └── ... -``` - -* `platforms\android`: This directory contains any native Android libraries packaged as `*.jar` and `*.aar` packages. These native libraries can reside in the root of this directory or in a user-created sub-directory. During the plugin installation, the NativeScript CLI will configure the Android project in `platforms\android` to work with the plugin. -* `platforms\android\res`: (Optional) This directory contains resources declared by the `AndroidManifest.xml` file. You can look at the folder structure [here](http://developer.android.com/guide/topics/resources/providing-resources.html#ResourceTypes). -* `platforms\android\include.gradle`: This file modifies the native Android configuration of your NativeScript project such as native dependencies, build types and configurations. For more information about the format of `include.gradle`, see [`include.gradle` file](#includegradle-specification). -* `platforms\ios`: This directory contains native dynamic iOS Cocoa Touch Frameworks (`.framework`) and Cocoa Touch Static Libraries (`.a`). During the plugin installation, the NativeScript CLI will copy these files to `lib\iOS` in your project and will configure the iOS project in `platforms\ios` to work with the libraries. If the library is written in Swift, only APIs exposed to Objective-C are exposed to NativeScript. In case the plugin contains a Cocoa Touch Static Library (`.a`), you must place all public headers (`.h`) under `include\\`. Make sure that the static libraries are built at least for the following processor architectures - armv7, arm64, i386. -* `platforms\ios\build.xcconfig`: This file modifies the native iOS configuration of your NativeScript project such as native dependencies and configurations. For more information about the format of `build.xcconfig`, see [`build.xcconfig` file](#buildxcconfig-specification). -* `platforms\ios\Podfile`: This file describes the dependency to the library that you want to use. For more information, see [the CocoaPods article](CocoaPods.md). - -### Package.json Specification - -Every NativeScript plugin should contain a valid `package.json` file in its root. This `package.json` file must meet the following requirements. - -* It must comply with the [npm specification](https://docs.npmjs.com/files/package.json).
The `package.json` must contain at least `name` and `version` pairs. You will later use the plugin in your code by requiring it by its `name`. -* It must contain a `nativescript` section which describes the supported NativeScript runtimes and their versions. This section can be empty. If you want to define supported platforms and runtimes, you can nest a `platforms` section. In this `platforms` section, you can nest `ios` and `android` key-value pairs. The values in these pairs must be valid runtime versions or ranges of values specified by a valid semver(7) syntax. -* If the plugin depends on other npm modules, it must contain a `dependencies` section as described [here](https://docs.npmjs.com/files/package.json#dependencies).
The NativeScript CLI will resolve the dependencies during the plugin installation. - -#### Package.json Example - -The following is an example of a `package.json` file for a NativeScript plugin which supports the 1.0.0 version of the iOS runtime and the 1.1.0 version of the Android runtime. - -```JSON -{ - "name": "myplugin", - "version": "0.0.1", - "nativescript": { - "platforms": { - "ios": "1.0.0", - "android": "1.1.0" - } - } -} -``` - -### Include.gradle Specification - -Every NativeScript plugin, which contains native Android dependencies, should also contain a valid `include.gradle` file in the root of its `platforms\android` directory. This `include.gradle` file must meet the following requirements. - -* It must contain its own [configuration](http://developer.android.com/tools/building/configuring-gradle.html). -* It might contain native dependencies required to build the plugin properly. -* Any native dependencies should be available in [jcenter](https://bintray.com/bintray/jcenter) or from the Android SDK installed on your machine. - -> **IMPORTANT:** If you don't have an `include.gradle` file, at build time, gradle will create a default one containing all default elements. - -#### Include.gradle Example -```gradle -//default elements -android { - productFlavors { - "my-plugin" { - dimension "my-plugin" - } - } -} - -//optional elements -dependencies { - compile "groupName:pluginName:ver" -} -``` - -### Build.xcconfig Specification -Every NativeScript plugin, which contains native iOS dependencies, can also contain a [valid](https://pewpewthespells.com/blog/xcconfig_guide.html) `build.xcconfig` file in the root of its `platforms\ios` directory. This `build.xcconfig` file might contain native dependencies required to build the plugin properly. - -#### Build.xcconfig Example -``` -OTHER_LDFLAGS = $(inherited) -framework "QuartzCore" -l"sqlite3" -``` - -## Install a Plugin - -To install a plugin for your project, inside your project, run the following command. - -```Shell -tns plugin add -``` - -### Valid Plugin Sources - -You can specify a plugin by name in the npm registry, local path or URL. The following are valid values for the `` attribute. - -* A `` or `@` for plugins published in the npm registry. -* A `` to the directory which contains the plugin files and its `package.json` file. -* A `` to a `.tar.gz` archive containing a directory with the plugin and its `package.json` file. -* A `` which resolves to a `.tar.gz` archive containing a directory with the plugin and its `package.json` file. -* A `` which resolves to a `.tar.gz` archive containing a directory with the plugin and its `package.json` file. - -### Installation Specifics - -The installation of a NativeScript plugin mimics the installation of an npm module. - -The NativeScript CLI takes the plugin and installs it to the `node_modules` directory in the root of your project. During this process, the NativeScript CLI resolves any dependencies described in the plugin `package.json` file and adds the plugin to the project `package.json` file in the project root. - -If the NativeScript CLI detects any native iOS libraries in the plugin, it copies the library files to the `lib\ios` folder in your project and configures the iOS-specific projects in `platforms\ios` to work with the library. - -Next, the NativeScript CLI runs a partial `prepare` operation for the plugin for all platforms configured for the project. During this operation, the CLI copies only the plugin to the `tns_modules` subdirectories in the `platforms\android` and `platforms\ios` directories in your project. If your plugin contains platform-specific `JS` files, the CLI copies them to the respective platform subdirectory and renames them by removing the platform modifier. - -> **TIP:** If you have not configured any platforms, when you run `$ tns platform add`, the NativeScript CLI will automatically prepare all installed plugins for the newly added platform. - -Finally, the CLI merges the plugin `Info.plist` file with `platforms\ios\Info.plist` in your project. The plugin `AndroidManifest.xml` will be merged with `platforms\android\AndroidManifest.xml` later, at build time. - -> **IMPORTANT:** Currently, the merging of the platform configuration files does not resolve any contradicting or duplicate entries. - -#### AndroidManifest.xml Merge Example - -The following is an example of a plugin `AndroidManifest`, project `AndroidManifest.xml` and the resulting merged file after the plugin installation. - -**The Plugin Manifest** - -```XML - - - - - - - - - - - - - - - -``` - -**The Project Manifest Located in `platforms\android\`** - -```XML - - - - - - - - - - - - - - - - - - - - -``` - -**The Merged Manifest Located in `platforms\android\`** - -```XML - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -### Manual Steps After Installation - -After the installation is complete, you need to open `platforms\android\AndroidManifest.xml` and `platforms\ios\Info.plist` in your project and inspect them for duplicate or contradicting entries. Make sure to preserve the settings required by the plugin. Otherwise, your app might not build or it might not work as expected, when deployed on device. - -## Use a Plugin - -To use a plugin inside your project, you need to add a `require` in your app. - -```JavaScript -var myPlugin = require("myplugin"); -``` - -This will look for a `myplugin` module with a valid `package.json` file in the `tns_modules` directory. Note that you must require the plugin with the value for the `name` key in the plugin `package.json` file. - -## Remove a Plugin - -To remove a plugin from your project, inside your project, run the following command. - -```Shell -tns plugin remove -``` - -You must specify the plugin by the value for the `name` key in the plugin `package.json` file. - -### Removal Specifics - -The removal of a NativeScript plugin mimics the removal of an npm module. - -The NativeScript CLI removes any plugin files from the `node_modules` directory in the root of your project. During this process, the NativeScript CLI removes any dependencies described in the plugin `package.json` file and removes the plugin from the project `package.json` file in the project root. - -> **IMPORTANT:** For iOS, this operation does not remove files from the `platforms\ios` directories and native iOS libraries, and does not unmerge the `Info.plist` file. For Android, this operation unmerges the `AndroidManifest.xml` file and takes care of removing any plugin files located in `platforms\android`. - -### Manual Steps After Removal - -After the plugin removal is complete, make sure to remove any leftover native iOS library files from the `lib\ios` directory in the root of the project. Update the iOS-specific projects in `platforms\ios` to remove any dependencies on the removed native libraries. - -Next, you need to run the following command. - -```Shell -tns prepare -``` - -Make sure to run the command for all platforms configured for the project. During this operation, the NativeScript CLI will remove any leftover plugin files from your `platforms\ios` directory. - -> **TIP:** Instead of `$ tns prepare` you can run `$ tns build`, `$ tns run` or `$ tns deploy`. All these commands run `$ tns prepare`. - -Next, open your `platforms\ios\Info.plist` file and remove any leftover entries from the plugin `Info.plist` file. - -Finally, make sure to update your code not to use the uninstalled plugin. From 4e2c5108ccebbfccc29475038de5ec9cd7d702e5 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Wed, 5 Jul 2017 16:39:33 +0300 Subject: [PATCH 137/212] Fix plugins prompts on postinstall (#2949) * Fix plugins prompts on postinstall In case some plugins (like `nativescript-plugin-firebase`) try to prompt the user on postinstall, users are unable to answer the question when `tns plugin add ` is used. The reason is that the stdin of current process is not passed to the stdin of the `npm install` process and the user has no way to input the result. Fix this by using "inherit" of current stdio when the console is interactive. * Update CHANGELOG for 3.1.2 --- CHANGELOG.md | 7 +++++++ lib/node-package-manager.ts | 15 ++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d88b73194..3b014a88ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ NativeScript CLI Changelog ================ + +3.1.2 (2017, July 06) +== + +### Fixed +* [Fixed #2950](https://github.com/NativeScript/nativescript-cli/issues/2950): Unable to provide user input on postinstall of plugin + 3.1.1 (2017, June 28) == diff --git a/lib/node-package-manager.ts b/lib/node-package-manager.ts index c64f2c8236..8289e66baa 100644 --- a/lib/node-package-manager.ts +++ b/lib/node-package-manager.ts @@ -1,5 +1,6 @@ import * as path from "path"; import { exported } from "./common/decorators"; +import { isInteractive } from "./common/helpers"; export class NodePackageManager implements INodePackageManager { private static SCOPED_DEPENDENCY_REGEXP = /^(@.+?)(?:@(.+?))?$/; @@ -194,16 +195,20 @@ export class NodePackageManager implements INodePackageManager { private async getNpmInstallResult(params: string[], cwd: string): Promise { return new Promise((resolve, reject) => { const npmExecutable = this.getNpmExecutableName(); - let childProcess = this.$childProcess.spawn(npmExecutable, params, { cwd, stdio: "pipe" }); + const stdioValue = isInteractive() ? "inherit" : "pipe"; + + const childProcess = this.$childProcess.spawn(npmExecutable, params, { cwd, stdio: stdioValue }); let isFulfilled = false; let capturedOut = ""; let capturedErr = ""; - childProcess.stdout.on("data", (data: string) => { - this.$logger.write(data.toString()); - capturedOut += data; - }); + if (childProcess.stdout) { + childProcess.stdout.on("data", (data: string) => { + this.$logger.write(data.toString()); + capturedOut += data; + }); + } if (childProcess.stderr) { childProcess.stderr.on("data", (data: string) => { From 6ecffae57a623474e99e9108be4fc027ff6586a9 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 5 Jul 2017 17:50:06 +0300 Subject: [PATCH 138/212] Upate to latest common-lib --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index 8be9f921aa..cc5d470197 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 8be9f921aa50000405af9ded63c770da7910d741 +Subproject commit cc5d47019765a918e0937e45ffb31597076d3d6f From b6d4427215ec4ee48d283b2b1e5de1398888bd59 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Wed, 5 Jul 2017 19:01:59 +0300 Subject: [PATCH 139/212] Fix LICENSE years (#2953) --- LICENSE | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 2c63567572..dda4f06e7e 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ Apache License same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright (c) 2015-2016 Telerik AD + Copyright (c) 2015-2017 Telerik AD Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -198,4 +198,4 @@ Apache License distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file + limitations under the License. From 0166da3ec6e35861dbec13c541a12de2f4c3edda Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Thu, 6 Jul 2017 11:39:04 +0300 Subject: [PATCH 140/212] Replace grunt-tslint with direct calling to tslint (#2954) Some rules in our tslint.json require passing `--type-check` flag to `tslint` executable. However grunt-tslint does not support it. So remove the `grunt-tslint` and spawn the `tslint` via npm script. Add the new `tslint` script, so now the project can be linted by calling: * grunt lint * grunt tslint:build /for backwards compatibility/ * npm run tslint Fix the errors that are received after passing `--type-check` flag. Disable all linter errors for all automatically generated files (for messages). --- Gruntfile.js | 34 ++++++++++++++-------------------- lib/common | 2 +- package.json | 2 +- test/stubs.ts | 48 ++++++++++++++++++++++++------------------------ 4 files changed, 40 insertions(+), 46 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 0cf68a12d2..a3b13b2453 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,8 +1,9 @@ -var now = new Date().toISOString(); +const childProcess = require("child_process"); +const now = new Date().toISOString(); function shallowCopy(obj) { var result = {}; - Object.keys(obj).forEach(function(key) { + Object.keys(obj).forEach(function (key) { result[key] = obj[key]; }); return result; @@ -11,10 +12,10 @@ function shallowCopy(obj) { var travis = process.env["TRAVIS"]; var buildNumber = process.env["PACKAGE_VERSION"] || process.env["BUILD_NUMBER"] || "non-ci"; -module.exports = function(grunt) { +module.exports = function (grunt) { var path = require("path"); var commonLibNodeModules = path.join("lib", "common", "node_modules"); - if(require("fs").existsSync(commonLibNodeModules)) { + if (require("fs").existsSync(commonLibNodeModules)) { grunt.file.delete(commonLibNodeModules); } grunt.file.write(path.join("lib", "common", ".d.ts"), ""); @@ -50,17 +51,6 @@ module.exports = function(grunt) { }, }, - tslint: { - build: { - files: { - src: ["lib/**/*.ts", "test/**/*.ts", "!lib/common/node_modules/**/*.ts", "!lib/common/messages/**/*.ts", "lib/common/test/unit-tests/**/*.ts", "definitions/**/*.ts", "!lib/**/*.d.ts" , "!test/**/*.d.ts"] - }, - options: { - configuration: grunt.file.readJSON("./tslint.json") - } - } - }, - watch: { devall: { files: ["lib/**/*.ts", 'test/**/*.ts', "!lib/common/node_modules/**/*.ts", "!lib/common/messages/**/*.ts"], @@ -96,7 +86,7 @@ module.exports = function(grunt) { command: "npm pack", options: { execOptions: { - env: (function() { + env: (function () { var env = shallowCopy(process.env); env["NATIVESCRIPT_SKIP_POSTINSTALL_TASKS"] = "1"; return env; @@ -145,7 +135,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks("grunt-ts"); grunt.loadNpmTasks("grunt-tslint"); - grunt.registerTask("set_package_version", function(version) { + grunt.registerTask("set_package_version", function (version) { var buildVersion = version !== undefined ? version : buildNumber; if (process.env["BUILD_CAUSE_GHPRBCAUSE"]) { buildVersion = "PR" + buildVersion; @@ -154,8 +144,8 @@ module.exports = function(grunt) { var packageJson = grunt.file.readJSON("package.json"); var versionParts = packageJson.version.split("-"); if (process.env["RELEASE_BUILD"]) { -// HACK - excluded until 1.0.0 release or we refactor our project infrastructure (whichever comes first) -// packageJson.version = versionParts[0]; + // HACK - excluded until 1.0.0 release or we refactor our project infrastructure (whichever comes first) + // packageJson.version = versionParts[0]; } else { versionParts[1] = buildVersion; packageJson.version = versionParts.join("-"); @@ -163,7 +153,11 @@ module.exports = function(grunt) { grunt.file.write("package.json", JSON.stringify(packageJson, null, " ")); }); - grunt.registerTask("enableScripts", function(enable) { + grunt.registerTask("tslint:build", function (version) { + childProcess.execSync("npm run tslint", { stdio: "inherit" }); + }); + + grunt.registerTask("enableScripts", function (enable) { var enableTester = /false/i; var newScriptsAttr = !enableTester.test(enable) ? "scripts" : "skippedScripts"; var packageJson = grunt.file.readJSON("package.json"); diff --git a/lib/common b/lib/common index cc5d470197..0fb7b5f9c3 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit cc5d47019765a918e0937e45ffb31597076d3d6f +Subproject commit 0fb7b5f9c3df92a84345735b1416c9013e222efb diff --git a/package.json b/package.json index fbd777cb85..cd710a0686 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "preuninstall": "node preuninstall.js", "mocha": "node test-scripts/mocha.js", "tsc": "tsc", + "tslint": "tslint -p tsconfig.json --type-check", "test-watch": "node ./dev/tsc-to-mocha-watch.js" }, "repository": { @@ -97,7 +98,6 @@ "grunt-contrib-watch": "1.0.0", "grunt-shell": "1.3.0", "grunt-ts": "6.0.0-beta.16", - "grunt-tslint": "5.0.1", "istanbul": "0.4.5", "mocha": "3.1.2", "should": "7.0.2", diff --git a/test/stubs.ts b/test/stubs.ts index 2948172aa6..7c12e0e866 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -245,30 +245,6 @@ export class ProjectDataStub implements IProjectData { } } -export class PlatformsDataStub extends EventEmitter implements IPlatformsData { - public platformsNames: string[]; - - public getPlatformData(platform: string, projectData: IProjectData): IPlatformData { - return { - frameworkPackageName: "", - platformProjectService: new PlatformProjectServiceStub(), - emulatorServices: undefined, - projectRoot: "", - normalizedPlatformName: "", - appDestinationDirectoryPath: "", - deviceBuildOutputPath: "", - getValidPackageNames: (buildOptions: { isForDevice?: boolean, isReleaseBuild?: boolean }) => [], - frameworkFilesExtensions: [], - relativeToFrameworkConfigurationFilePath: "", - fastLivesyncFileExtensions: [] - }; - } - - public get availablePlatforms(): any { - return undefined; - } -} - export class PlatformProjectServiceStub extends EventEmitter implements IPlatformProjectService { getPlatformData(projectData: IProjectData): IPlatformData { return { @@ -362,6 +338,30 @@ export class PlatformProjectServiceStub extends EventEmitter implements IPlatfor } } +export class PlatformsDataStub extends EventEmitter implements IPlatformsData { + public platformsNames: string[]; + + public getPlatformData(platform: string, projectData: IProjectData): IPlatformData { + return { + frameworkPackageName: "", + platformProjectService: new PlatformProjectServiceStub(), + emulatorServices: undefined, + projectRoot: "", + normalizedPlatformName: "", + appDestinationDirectoryPath: "", + deviceBuildOutputPath: "", + getValidPackageNames: (buildOptions: { isForDevice?: boolean, isReleaseBuild?: boolean }) => [], + frameworkFilesExtensions: [], + relativeToFrameworkConfigurationFilePath: "", + fastLivesyncFileExtensions: [] + }; + } + + public get availablePlatforms(): any { + return undefined; + } +} + export class ProjectDataService implements IProjectDataService { getNSValue(propertyName: string): any { return {}; From e191e95aec010e6673d677e1770c4fb73c9457a0 Mon Sep 17 00:00:00 2001 From: Toma Nikolov Date: Fri, 7 Jul 2017 16:19:35 +0300 Subject: [PATCH 141/212] Fix remove of non-production dependencies. (#2959) --- lib/tools/node-modules/node-modules-dest-copy.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts index cf904d28e6..eefa5b10a0 100644 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ b/lib/tools/node-modules/node-modules-dest-copy.ts @@ -67,8 +67,16 @@ export class TnsModulesCopy { const dependenciesFolder = path.join(targetPackageDir, constants.NODE_MODULES_FOLDER_NAME); if (this.$fs.exists(dependenciesFolder)) { - const dependencies = this.$fs.readDirectory(dependenciesFolder); - dependencies.filter(dir => !!productionDependencies || !productionDependencies.hasOwnProperty(dir)) + const dependencies = _.flatten(this.$fs.readDirectory(dependenciesFolder) + .map(dir => { + if (_.startsWith(dir, "@")) { + return this.$fs.readDirectory(path.join(dependenciesFolder, dir)); + } + + return dir; + })); + + dependencies.filter(dir => !productionDependencies || !productionDependencies.hasOwnProperty(dir)) .forEach(dir => shelljs.rm("-rf", path.join(dependenciesFolder, dir))); } } From 19e682921023982eb0bac5a2b687be6b3c6de4bd Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Mon, 10 Jul 2017 23:39:03 +0300 Subject: [PATCH 142/212] Debug on emulator by default when multiple devices/emulators attached (#2957) When there are multiple devices/emulators, calling `tns debug android` will fail that multiple devices are attached. This break VS Code as previous code of CLI has been using emulators by default. Fix this by using the most recent version (highest API Level) of running emulators, i.e. in case you have three Android emulators with version 5.1, 6.0 and 7.0, the one with 7.0 will be used for debugging. In case the terminal is interactive, CLI will prompt the user for selecting a device on which to start debug operation. In case the terminal is not interactive and there's no emulator running, the device with highest API level will be used. --- Gruntfile.js | 1 - lib/commands/debug.ts | 89 ++++- lib/common | 2 +- lib/constants.ts | 5 + lib/definitions/debug.d.ts | 2 +- lib/services/debug-data-service.ts | 2 +- lib/services/debug-service-base.ts | 9 +- .../livesync/debug-livesync-service.ts | 2 +- test/debug.ts | 346 ++++++++++++++++-- test/stubs.ts | 2 +- 10 files changed, 398 insertions(+), 62 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index a3b13b2453..819193adb7 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -133,7 +133,6 @@ module.exports = function (grunt) { grunt.loadNpmTasks("grunt-contrib-watch"); grunt.loadNpmTasks("grunt-shell"); grunt.loadNpmTasks("grunt-ts"); - grunt.loadNpmTasks("grunt-tslint"); grunt.registerTask("set_package_version", function (version) { var buildVersion = version !== undefined ? version : buildNumber; diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index cddbfb8ada..7c090d2eb3 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -1,4 +1,8 @@ -export abstract class DebugPlatformCommand implements ICommand { +import { CONNECTED_STATUS } from "../common/constants"; +import { isInteractive } from "../common/helpers"; +import { DebugCommandErrors } from "../constants"; + +export abstract class DebugPlatformCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; public platform: string; @@ -12,7 +16,8 @@ protected $logger: ILogger, protected $errors: IErrors, private $debugLiveSyncService: IDebugLiveSyncService, - private $config: IConfiguration) { + private $config: IConfiguration, + private $prompter: IPrompter) { this.$projectData.initializeProjectData(); } @@ -29,11 +34,9 @@ this.$config.debugLivesync = true; - await this.$devicesService.detectCurrentlyAttachedDevices(); + const selectedDeviceForDebug = await this.getDeviceForDebug(); - // Now let's take data for each device: - const devices = this.$devicesService.getDeviceInstances(); - const deviceDescriptors: ILiveSyncDeviceInfo[] = devices.filter(d => !this.platform || d.deviceInfo.platform === this.platform) + const deviceDescriptors: ILiveSyncDeviceInfo[] = [selectedDeviceForDebug] .map(d => { const info: ILiveSyncDeviceInfo = { identifier: d.deviceInfo.identifier, @@ -70,6 +73,62 @@ await this.$debugLiveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } + public async getDeviceForDebug(): Promise { + if (this.$options.forDevice && this.$options.emulator) { + this.$errors.fail(DebugCommandErrors.UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR); + } + + await this.$devicesService.detectCurrentlyAttachedDevices(); + + if (this.$options.device) { + const device = await this.$devicesService.getDevice(this.$options.device); + return device; + } + + // Now let's take data for each device: + const availableDevicesAndEmulators = this.$devicesService.getDeviceInstances() + .filter(d => d.deviceInfo.status === CONNECTED_STATUS && (!this.platform || d.deviceInfo.platform.toLowerCase() === this.platform.toLowerCase())); + + const selectedDevices = availableDevicesAndEmulators.filter(d => this.$options.emulator ? d.isEmulator : (this.$options.forDevice ? !d.isEmulator : true)); + + if (selectedDevices.length > 1) { + if (isInteractive()) { + const choices = selectedDevices.map(e => `${e.deviceInfo.identifier} - ${e.deviceInfo.displayName}`); + + const selectedDeviceString = await this.$prompter.promptForChoice("Select device for debugging", choices); + + const selectedDevice = _.find(selectedDevices, d => `${d.deviceInfo.identifier} - ${d.deviceInfo.displayName}` === selectedDeviceString); + return selectedDevice; + } else { + const sortedInstances = _.sortBy(selectedDevices, e => e.deviceInfo.version); + const emulators = sortedInstances.filter(e => e.isEmulator); + const devices = sortedInstances.filter(d => !d.isEmulator); + let selectedInstance: Mobile.IDevice; + + if (this.$options.emulator || this.$options.forDevice) { + // When --emulator or --forDevice is passed, the instances are already filtered + // So we are sure we have exactly the type we need and we can safely return the last one (highest OS version). + selectedInstance = _.last(sortedInstances); + } else { + if (emulators.length) { + selectedInstance = _.last(emulators); + } else { + selectedInstance = _.last(devices); + } + } + + this.$logger.warn(`Multiple devices/emulators found. Starting debugger on ${selectedInstance.deviceInfo.identifier}. ` + + "If you want to debug on specific device/emulator, you can specify it with --device option."); + + return selectedInstance; + } + } else if (selectedDevices.length === 1) { + return _.head(selectedDevices); + } + + this.$errors.failWithoutHelp(DebugCommandErrors.NO_DEVICES_EMULATORS_FOUND_FOR_OPTIONS); + } + public async canExecute(args: string[]): Promise { if (!this.$platformService.isPlatformSupportedForOS(this.platform, this.$projectData)) { this.$errors.fail(`Applications for platform ${this.platform} can not be built on this OS`); @@ -85,14 +144,6 @@ emulator: this.$options.emulator, skipDeviceDetectionInterval: true }); - // Start emulator if --emulator is selected or no devices found. - if (this.$options.emulator || this.$devicesService.deviceCount === 0) { - return true; - } - - if (this.$devicesService.deviceCount > 1) { - this.$errors.failWithoutHelp("Multiple devices found! To debug on specific device please select device with --device option."); - } return true; } @@ -111,9 +162,10 @@ export class DebugIOSCommand extends DebugPlatformCommand { $projectData: IProjectData, $platformsData: IPlatformsData, $iosDeviceOperations: IIOSDeviceOperations, - $debugLiveSyncService: IDebugLiveSyncService) { + $debugLiveSyncService: IDebugLiveSyncService, + $prompter: IPrompter) { super($iOSDebugService, $devicesService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger, - $errors, $debugLiveSyncService, $config); + $errors, $debugLiveSyncService, $config, $prompter); // Do not dispose ios-device-lib, so the process will remain alive and the debug application (NativeScript Inspector or Chrome DevTools) will be able to connect to the socket. // In case we dispose ios-device-lib, the socket will be closed and the code will fail when the debug application tries to read/send data to device socket. // That's why the `$ tns debug ios --justlaunch` command will not release the terminal. @@ -146,9 +198,10 @@ export class DebugAndroidCommand extends DebugPlatformCommand { $options: IOptions, $projectData: IProjectData, $platformsData: IPlatformsData, - $debugLiveSyncService: IDebugLiveSyncService) { + $debugLiveSyncService: IDebugLiveSyncService, + $prompter: IPrompter) { super($androidDebugService, $devicesService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger, - $errors, $debugLiveSyncService, $config); + $errors, $debugLiveSyncService, $config, $prompter); } public async canExecute(args: string[]): Promise { diff --git a/lib/common b/lib/common index 0fb7b5f9c3..c6898cf6d5 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 0fb7b5f9c3df92a84345735b1416c9013e222efb +Subproject commit c6898cf6d5d309ae2169fad3a487e8672969a15d diff --git a/lib/constants.ts b/lib/constants.ts index 31c19aead4..d58e856ae4 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -87,3 +87,8 @@ export const CONNECTION_ERROR_EVENT_NAME = "connectionError"; export const VERSION_STRING = "version"; export const INSPECTOR_CACHE_DIRNAME = "ios-inspector"; export const POST_INSTALL_COMMAND_NAME = "post-install-cli"; + +export class DebugCommandErrors { + public static UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR = "The options --for-device and --emulator cannot be used simultaneously. Please use only one of them."; + public static NO_DEVICES_EMULATORS_FOUND_FOR_OPTIONS = "Unable to find device or emulator for specified options."; +} diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 04008db7ae..5e9cfde535 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -83,7 +83,7 @@ interface IDebugDataService { * @param {IOptions} options The options based on which debugData will be created * @returns {IDebugData} Data describing the required information for starting debug process. */ - createDebugData(projectData: IProjectData, options: IOptions): IDebugData; + createDebugData(projectData: IProjectData, options: IDeviceIdentifier): IDebugData; } /** diff --git a/lib/services/debug-data-service.ts b/lib/services/debug-data-service.ts index 038e2b6091..3584f2165f 100644 --- a/lib/services/debug-data-service.ts +++ b/lib/services/debug-data-service.ts @@ -1,5 +1,5 @@ export class DebugDataService implements IDebugDataService { - public createDebugData(projectData: IProjectData, options: IOptions): IDebugData { + public createDebugData(projectData: IProjectData, options: IDeviceIdentifier): IDebugData { return { applicationIdentifier: projectData.projectId, projectDir: projectData.projectDir, diff --git a/lib/services/debug-service-base.ts b/lib/services/debug-service-base.ts index 8dec2fa5fe..d50327c442 100644 --- a/lib/services/debug-service-base.ts +++ b/lib/services/debug-service-base.ts @@ -14,8 +14,13 @@ export abstract class DebugServiceBase extends EventEmitter implements IPlatform protected getCanExecuteAction(deviceIdentifier: string): (device: Mobile.IDevice) => boolean { return (device: Mobile.IDevice): boolean => { if (deviceIdentifier) { - return device.deviceInfo.identifier === deviceIdentifier - || device.deviceInfo.identifier === this.$devicesService.getDeviceByDeviceOption().deviceInfo.identifier; + let isSearchedDevice = device.deviceInfo.identifier === deviceIdentifier; + if (!isSearchedDevice) { + const deviceByDeviceOption = this.$devicesService.getDeviceByDeviceOption(); + isSearchedDevice = deviceByDeviceOption && device.deviceInfo.identifier === deviceByDeviceOption.deviceInfo.identifier; + } + + return isSearchedDevice; } else { return true; } diff --git a/lib/services/livesync/debug-livesync-service.ts b/lib/services/livesync/debug-livesync-service.ts index 3d8354b2c8..568ba3a3ea 100644 --- a/lib/services/livesync/debug-livesync-service.ts +++ b/lib/services/livesync/debug-livesync-service.ts @@ -46,7 +46,7 @@ export class DebugLiveSyncService extends LiveSyncService implements IDebugLiveS teamId: this.$options.teamId }; - let debugData = this.$debugDataService.createDebugData(this.$projectData, this.$options); + let debugData = this.$debugDataService.createDebugData(this.$projectData, { device: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier }); const debugService = this.$debugService.getDebugService(liveSyncResultInfo.deviceAppData.device); await this.$platformService.trackProjectType(this.$projectData); diff --git a/test/debug.ts b/test/debug.ts index 4b1aae6a89..d204cb3576 100644 --- a/test/debug.ts +++ b/test/debug.ts @@ -9,6 +9,10 @@ import { FileSystem } from "../lib/common/file-system"; import { AndroidProjectService } from "../lib/services/android-project-service"; import { AndroidDebugBridge } from "../lib/common/mobile/android/android-debug-bridge"; import { AndroidDebugBridgeResultHandler } from "../lib/common/mobile/android/android-debug-bridge-result-handler"; +import { DebugCommandErrors } from "../lib/constants"; +import { CONNECTED_STATUS, UNREACHABLE_STATUS } from "../lib/common/constants"; +const helpers = require("../lib/common/helpers"); +const originalIsInteracive = helpers.isInteractive; function createTestInjector(): IInjector { let testInjector: IInjector = new yok.Yok(); @@ -19,7 +23,6 @@ function createTestInjector(): IInjector { testInjector.register("logger", stubs.LoggerStub); testInjector.register("options", Options); testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); - testInjector.register('devicesService', {}); testInjector.register('childProcess', stubs.ChildProcessStub); testInjector.register('androidDebugService', stubs.DebugServiceStub); testInjector.register('fs', FileSystem); @@ -63,48 +66,319 @@ function createTestInjector(): IInjector { } }); + testInjector.register("prompter", {}); + testInjector.registerCommand("debug|android", DebugAndroidCommand); + return testInjector; } -describe("Debugger tests", () => { - let testInjector: IInjector; +describe("debug command tests", () => { + describe("getDeviceForDebug", () => { + it("throws error when both --for-device and --emulator are passed", async () => { + const testInjector = createTestInjector(); + const options = testInjector.resolve("options"); + options.forDevice = options.emulator = true; + const debugCommand = testInjector.resolveCommand("debug|android"); + await assert.isRejected(debugCommand.getDeviceForDebug(), DebugCommandErrors.UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR); + }); - beforeEach(() => { - testInjector = createTestInjector(); - }); + it("returns selected device, when --device is passed", async () => { + const testInjector = createTestInjector(); + const devicesService = testInjector.resolve("devicesService"); + const deviceInstance = {}; + const specifiedDeviceOption = "device1"; + devicesService.getDevice = async (deviceOption: string): Promise => { + if (deviceOption === specifiedDeviceOption) { + return deviceInstance; + } + }; - it("Ensures that debugLivesync flag is true when executing debug --watch command", async () => { - let debugCommand: ICommand = testInjector.resolve("debug|android"); - let options: IOptions = testInjector.resolve("options"); - options.watch = true; - await debugCommand.execute(["android", "--watch"]); - let config: IConfiguration = testInjector.resolve("config"); - assert.isTrue(config.debugLivesync); - }); + const options = testInjector.resolve("options"); + options.device = specifiedDeviceOption; + const debugCommand = testInjector.resolveCommand("debug|android"); + const selectedDeviceInstance = await debugCommand.getDeviceForDebug(); + assert.deepEqual(selectedDeviceInstance, deviceInstance); + }); + + const assertErrorIsThrown = async (getDeviceInstancesResult: Mobile.IDevice[], passedOptions?: { forDevice: boolean, emulator: boolean }) => { + const testInjector = createTestInjector(); + if (passedOptions) { + const options = testInjector.resolve("options"); + options.forDevice = passedOptions.forDevice; + options.emulator = passedOptions.emulator; + } + + const devicesService = testInjector.resolve("devicesService"); + devicesService.getDeviceInstances = (): Mobile.IDevice[] => getDeviceInstancesResult; + + const debugCommand = testInjector.resolveCommand("debug|android"); + await assert.isRejected(debugCommand.getDeviceForDebug(), DebugCommandErrors.NO_DEVICES_EMULATORS_FOUND_FOR_OPTIONS); + }; + + it("throws error when there are no devices/emulators available", () => { + return assertErrorIsThrown([]); + }); + + it("throws error when there are no devices/emulators available for selected platform", () => { + return assertErrorIsThrown([ + { + deviceInfo: { + platform: "ios", + status: CONNECTED_STATUS + } + } + ]); + }); + + it("throws error when there are only not-trusted devices/emulators available for selected platform", () => { + return assertErrorIsThrown([ + { + deviceInfo: { + platform: "android", + status: UNREACHABLE_STATUS + } + } + ]); + }); + + it("throws error when there are only devices and --emulator is passed", () => { + return assertErrorIsThrown([ + { + deviceInfo: { + platform: "android", + status: CONNECTED_STATUS + }, + isEmulator: false + } + ], { + forDevice: false, + emulator: true + }); + }); + + it("throws error when there are only emulators and --forDevice is passed", () => { + return assertErrorIsThrown([ + { + deviceInfo: { + platform: "android", + status: CONNECTED_STATUS + }, + isEmulator: true + } + ], { + forDevice: true, + emulator: false + }); + }); + + it("returns the only available device/emulator when it matches passed -- options", async () => { + const testInjector = createTestInjector(); + const deviceInstance = { + deviceInfo: { + platform: "android", + status: CONNECTED_STATUS + }, + isEmulator: true + }; + + const devicesService = testInjector.resolve("devicesService"); + devicesService.getDeviceInstances = (): Mobile.IDevice[] => [deviceInstance]; + + const debugCommand = testInjector.resolveCommand("debug|android"); + const actualDeviceInstance = await debugCommand.getDeviceForDebug(); + assert.deepEqual(actualDeviceInstance, deviceInstance); + }); + + describe("when multiple devices are detected", () => { + beforeEach(() => { + helpers.isInteractive = originalIsInteracive; + }); + + after(() => { + helpers.isInteractive = originalIsInteracive; + }); + + describe("when terminal is interactive", () => { + + it("prompts the user with information about available devices for specified platform only and returns the selected device instance", async () => { + helpers.isInteractive = () => true; + const testInjector = createTestInjector(); + const deviceInstance1 = { + deviceInfo: { + platform: "android", + status: CONNECTED_STATUS, + identifier: "deviceInstance1", + displayName: "displayName1" + }, + isEmulator: true + }; + + const deviceInstance2 = { + deviceInfo: { + platform: "android", + status: CONNECTED_STATUS, + identifier: "deviceInstance2", + displayName: "displayName2" + }, + isEmulator: true + }; + + const iOSDeviceInstance = { + deviceInfo: { + platform: "ios", + status: CONNECTED_STATUS, + identifier: "iosDevice", + displayName: "iPhone" + }, + isEmulator: true + }; + + const devicesService = testInjector.resolve("devicesService"); + devicesService.getDeviceInstances = (): Mobile.IDevice[] => [deviceInstance1, deviceInstance2, iOSDeviceInstance]; - it("Ensures that beforePrepareAllPlugins will not call gradle when livesyncing", async () => { - let config: IConfiguration = testInjector.resolve("config"); - config.debugLivesync = true; - let childProcess: stubs.ChildProcessStub = testInjector.resolve("childProcess"); - let androidProjectService: IPlatformProjectService = testInjector.resolve("androidProjectService"); - let projectData: IProjectData = testInjector.resolve("projectData"); - let spawnFromEventCount = childProcess.spawnFromEventCount; - await androidProjectService.beforePrepareAllPlugins(projectData); - assert.isTrue(spawnFromEventCount === 0); - assert.isTrue(spawnFromEventCount === childProcess.spawnFromEventCount); + let choicesPassedToPrompter: string[]; + const prompter = testInjector.resolve("prompter"); + prompter.promptForChoice = async (promptMessage: string, choices: any[]): Promise => { + choicesPassedToPrompter = choices; + return choices[1]; + }; + + const debugCommand = testInjector.resolveCommand("debug|android"); + const actualDeviceInstance = await debugCommand.getDeviceForDebug(); + const expectedChoicesPassedToPrompter = [deviceInstance1, deviceInstance2].map(d => `${d.deviceInfo.identifier} - ${d.deviceInfo.displayName}`); + assert.deepEqual(choicesPassedToPrompter, expectedChoicesPassedToPrompter); + + assert.deepEqual(actualDeviceInstance, deviceInstance2); + }); + }); + + describe("when terminal is not interactive", () => { + beforeEach(() => { + helpers.isInteractive = () => false; + }); + + const assertCorrectInstanceIsUsed = async (opts: { forDevice: boolean, emulator: boolean, isEmulatorTest: boolean, excludeLastDevice?: boolean }) => { + const testInjector = createTestInjector(); + const deviceInstance1 = { + deviceInfo: { + platform: "android", + status: CONNECTED_STATUS, + identifier: "deviceInstance1", + displayName: "displayName1", + version: "5.1" + }, + isEmulator: opts.isEmulatorTest + }; + + const deviceInstance2 = { + deviceInfo: { + platform: "android", + status: CONNECTED_STATUS, + identifier: "deviceInstance2", + displayName: "displayName2", + version: "6.0" + }, + isEmulator: opts.isEmulatorTest + }; + + const deviceInstance3 = { + deviceInfo: { + platform: "android", + status: CONNECTED_STATUS, + identifier: "deviceInstance3", + displayName: "displayName3", + version: "7.1" + }, + isEmulator: !opts.isEmulatorTest + }; + + const options = testInjector.resolve("options"); + options.forDevice = opts.forDevice; + options.emulator = opts.emulator; + + const devicesService = testInjector.resolve("devicesService"); + const deviceInstances = [deviceInstance1, deviceInstance2]; + if (!opts.excludeLastDevice) { + deviceInstances.push(deviceInstance3); + } + + devicesService.getDeviceInstances = (): Mobile.IDevice[] => deviceInstances; + + const debugCommand = testInjector.resolveCommand("debug|android"); + const actualDeviceInstance = await debugCommand.getDeviceForDebug(); + + assert.deepEqual(actualDeviceInstance, deviceInstance2); + }; + + it("returns the emulator with highest API level when --emulator is passed", () => { + return assertCorrectInstanceIsUsed({ forDevice: false, emulator: true, isEmulatorTest: true }); + }); + + it("returns the device with highest API level when --forDevice is passed", () => { + return assertCorrectInstanceIsUsed({ forDevice: true, emulator: false, isEmulatorTest: false }); + }); + + it("returns the emulator with highest API level when neither --emulator and --forDevice are passed", () => { + return assertCorrectInstanceIsUsed({ forDevice: false, emulator: false, isEmulatorTest: true }); + }); + + it("returns the device with highest API level when neither --emulator and --forDevice are passed and emulators are not available", async () => { + return assertCorrectInstanceIsUsed({ forDevice: false, emulator: false, isEmulatorTest: false, excludeLastDevice: true }); + }); + }); + }); }); - it("Ensures that beforePrepareAllPlugins will call gradle with clean option when *NOT* livesyncing", async () => { - let config: IConfiguration = testInjector.resolve("config"); - config.debugLivesync = false; - let childProcess: stubs.ChildProcessStub = testInjector.resolve("childProcess"); - let androidProjectService: IPlatformProjectService = testInjector.resolve("androidProjectService"); - let projectData: IProjectData = testInjector.resolve("projectData"); - let spawnFromEventCount = childProcess.spawnFromEventCount; - await androidProjectService.beforePrepareAllPlugins(projectData); - assert.isTrue(childProcess.lastCommand.indexOf("gradle") !== -1); - assert.isTrue(childProcess.lastCommandArgs[0] === "clean"); - assert.isTrue(spawnFromEventCount === 0); - assert.isTrue(spawnFromEventCount + 1 === childProcess.spawnFromEventCount); + describe("Debugger tests", () => { + let testInjector: IInjector; + + beforeEach(() => { + testInjector = createTestInjector(); + }); + + it("Ensures that debugLivesync flag is true when executing debug --watch command", async () => { + const debugCommand = testInjector.resolveCommand("debug|android"); + const options: IOptions = testInjector.resolve("options"); + options.watch = true; + const devicesService = testInjector.resolve("devicesService"); + devicesService.getDeviceInstances = (): Mobile.IDevice[] => { + return [{ deviceInfo: { status: "Connected", platform: "android" } }]; + }; + + const debugLiveSyncService = testInjector.resolve("debugLiveSyncService"); + debugLiveSyncService.liveSync = async (deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise => { + return null; + }; + + await debugCommand.execute(["android", "--watch"]); + const config: IConfiguration = testInjector.resolve("config"); + assert.isTrue(config.debugLivesync); + }); + + it("Ensures that beforePrepareAllPlugins will not call gradle when livesyncing", async () => { + let config: IConfiguration = testInjector.resolve("config"); + config.debugLivesync = true; + let childProcess: stubs.ChildProcessStub = testInjector.resolve("childProcess"); + let androidProjectService: IPlatformProjectService = testInjector.resolve("androidProjectService"); + let projectData: IProjectData = testInjector.resolve("projectData"); + let spawnFromEventCount = childProcess.spawnFromEventCount; + await androidProjectService.beforePrepareAllPlugins(projectData); + assert.isTrue(spawnFromEventCount === 0); + assert.isTrue(spawnFromEventCount === childProcess.spawnFromEventCount); + }); + + it("Ensures that beforePrepareAllPlugins will call gradle with clean option when *NOT* livesyncing", async () => { + let config: IConfiguration = testInjector.resolve("config"); + config.debugLivesync = false; + let childProcess: stubs.ChildProcessStub = testInjector.resolve("childProcess"); + let androidProjectService: IPlatformProjectService = testInjector.resolve("androidProjectService"); + let projectData: IProjectData = testInjector.resolve("projectData"); + let spawnFromEventCount = childProcess.spawnFromEventCount; + await androidProjectService.beforePrepareAllPlugins(projectData); + assert.isTrue(childProcess.lastCommand.indexOf("gradle") !== -1); + assert.isTrue(childProcess.lastCommandArgs[0] === "clean"); + assert.isTrue(spawnFromEventCount === 0); + assert.isTrue(spawnFromEventCount + 1 === childProcess.spawnFromEventCount); + }); }); }); diff --git a/test/stubs.ts b/test/stubs.ts index 7c12e0e866..8fa4e100a7 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -182,7 +182,7 @@ export class ErrorsStub implements IErrors { fail(opts: { formatStr?: string; errorCode?: number; suppressCommandHelp?: boolean }, ...args: any[]): void; fail(...args: any[]) { - throw args; + throw new Error(require("util").format.apply(null, args || [])); } failWithoutHelp(message: string, ...args: any[]): void { From 7e51ba8fb9938012aa2fee0b57e4d3a046dd8971 Mon Sep 17 00:00:00 2001 From: Toma Nikolov Date: Tue, 11 Jul 2017 15:05:36 +0300 Subject: [PATCH 143/212] Fix CFBundleURLTypes merging. (#2961) --- lib/services/ios-project-service.ts | 76 +++++++++++++---------------- package.json | 2 +- 2 files changed, 34 insertions(+), 44 deletions(-) diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index b46b6e89b7..b684faec92 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -6,7 +6,7 @@ import * as constants from "../constants"; import * as helpers from "../common/helpers"; import { attachAwaitDetach } from "../common/helpers"; import * as projectServiceBaseLib from "./platform-project-service-base"; -import { PlistSession } from "plist-merge-patch"; +import { PlistSession, Reporter } from "plist-merge-patch"; import { EOL } from "os"; import * as temp from "temp"; import * as plist from "plist"; @@ -723,7 +723,13 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f return; } - let session = new PlistSession({ log: (txt: string) => this.$logger.trace("Info.plist: " + txt) }); + const reporterTraceMessage = "Info.plist:"; + const reporter: Reporter = { + log: (txt: string) => this.$logger.trace(`${reporterTraceMessage} ${txt}`), + warn: (txt: string) => this.$logger.warn(`${reporterTraceMessage} ${txt}`) + }; + + let session = new PlistSession(reporter); let makePatch = (plistPath: string) => { if (!this.$fs.exists(plistPath)) { this.$logger.trace("No plist found at: " + plistPath); @@ -743,17 +749,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f makePatch(pluginInfoPlistPath); } - if (!buildOptions.release && projectData.projectId) { - const modifiedPlistContent = this.updateCFBundleURLSchemes(infoPlistPath, projectData); - - session.patch({ - name: "CFBundleURLTypes from Info.plist and required one for restarting application", - read: () => modifiedPlistContent - }); - - } else { - makePatch(infoPlistPath); - } + makePatch(infoPlistPath); if (projectData.projectId) { session.patch({ @@ -770,42 +766,36 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f }); } + if (!buildOptions.release && projectData.projectId) { + session.patch({ + name: "CFBundleURLTypes from package.json nativescript.id", + read: () => + ` + + + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + ${projectData.projectId.replace(/[^A-Za-z0-9]/g, "")} + + + + + ` + }); + } + let plistContent = session.build(); this.$logger.trace("Info.plist: Write to: " + this.getPlatformData(projectData).configurationFilePath); this.$fs.writeFile(this.getPlatformData(projectData).configurationFilePath, plistContent); } - private updateCFBundleURLSchemes(infoPlistPath: string, projectData: IProjectData): string { - // This code is required due to bug in session.patch logic which cannot merge values which are both arrays - it uses the second one directly. - // In our case we want to merge the values of CFBundleURLSchemes (which are arrays), which are in CFBundleURLTypes arrays. - let parsedPlist: any = plist.parse(this.$fs.readFile(infoPlistPath).toString()); - parsedPlist.CFBundleURLTypes = parsedPlist.CFBundleURLTypes || []; - - const appIdCfBundleUrlScheme = projectData.projectId.replace(/[^A-Za-z0-9]/g, ""); - - let hasAddedCFBundleURLSchemes = false; - - _.each(parsedPlist.CFBundleURLTypes, type => { - if (type.CFBundleURLSchemes) { - hasAddedCFBundleURLSchemes = true; - type.CFBundleURLSchemes.push(appIdCfBundleUrlScheme); - return false; - } - }); - - if (!hasAddedCFBundleURLSchemes) { - parsedPlist.CFBundleURLTypes.push( - { - CFBundleURLSchemes: [appIdCfBundleUrlScheme] - } - ); - } - - const newPlistContent = plist.build(parsedPlist); - return newPlistContent; - } - private getAllInstalledPlugins(projectData: IProjectData): Promise { return (this.$injector.resolve("pluginsService")).getAllInstalledPlugins(projectData); } diff --git a/package.json b/package.json index cd710a0686..49a25c7cea 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "osenv": "0.1.3", "pbxproj-dom": "1.0.11", "plist": "1.1.0", - "plist-merge-patch": "0.0.9", + "plist-merge-patch": "0.1.0", "plistlib": "0.2.1", "progress-stream": "1.1.1", "properties-parser": "0.2.3", From 00776b09e8bd45027c24c22ee5621cbd6e78f417 Mon Sep 17 00:00:00 2001 From: Toma Nikolov Date: Tue, 11 Jul 2017 16:48:43 +0300 Subject: [PATCH 144/212] Fix unmet peer dependency break adding of platform. (#2969) --- lib/node-package-manager.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/node-package-manager.ts b/lib/node-package-manager.ts index c8115d5342..f3b5a39ba4 100644 --- a/lib/node-package-manager.ts +++ b/lib/node-package-manager.ts @@ -147,7 +147,13 @@ export class NodePackageManager implements INodePackageManager { const originalOutput: INpmInstallCLIResult | INpm5InstallCliResult = JSON.parse(npmDryRunInstallOutput); const npm5Output = originalOutput; const npmOutput = originalOutput; - const name = _.head(_.keys(npmOutput.dependencies)); + let name: string; + _.forOwn(npmOutput.dependencies, (peerDependency: INpmPeerDependencyInfo, key: string) => { + if (!peerDependency.required && !peerDependency.peerMissing) { + name = key; + return false; + } + }); // Npm 5 return different object after performing `npm install --dry-run`. // Considering that the dependency is already installed we should From 70a6e1725b90d23dba290bcfce0d32b36325b51e Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Thu, 13 Jul 2017 12:28:25 +0300 Subject: [PATCH 145/212] Fix LiveSync calls cosecutive times (#2973) When CLI is used as a library, the LiveSync method can be called consecutive times for the same projects, but with different device identifiers. For example we may want to start LiveSync on devices with identifiers [ A, B, C ] and later we would like to add devices with identifiers [ D, E ]. In the second call, CLI should detect that LiveSync is already running and execute initial sync only for newly added devices. However the current check for unique device identifiers is not correct and it always returns empty array as we first add the new devices to the old ones and then compare the new array with the already modified old array of devices. Check the difference before adding new device identifiers to the old ones. --- lib/services/livesync/livesync-service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 9235d3bf69..f87aa65301 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -120,9 +120,10 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { liveSyncData: ILiveSyncInfo, projectData: IProjectData): Promise { // In case liveSync is called for a second time for the same projectDir. const isAlreadyLiveSyncing = this.liveSyncProcessesInfo[projectData.projectDir] && !this.liveSyncProcessesInfo[projectData.projectDir].isStopped; - this.setLiveSyncProcessInfo(liveSyncData.projectDir, deviceDescriptors); + // Prevent cases where liveSync is called consecutive times with the same device, for example [ A, B, C ] and then [ A, B, D ] - we want to execute initialSync only for D. const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors; + this.setLiveSyncProcessInfo(liveSyncData.projectDir, deviceDescriptors); await this.initialSync(projectData, deviceDescriptorsForInitialSync, liveSyncData); @@ -140,7 +141,6 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { this.liveSyncProcessesInfo[projectDir].isStopped = false; const currentDeviceDescriptors = this.liveSyncProcessesInfo[projectDir].deviceDescriptors || []; - // Prevent cases where liveSync is called consecutive times with the same device, for example [ A, B, C ] and then [ A, B, D ] - we want to execute initialSync only for D. this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), deviceDescriptorPrimaryKey); } From 98db3bdc77ed5babd78996ee84c0f5edbcea17eb Mon Sep 17 00:00:00 2001 From: Toma Nikolov Date: Thu, 13 Jul 2017 16:10:47 +0300 Subject: [PATCH 146/212] Remove emulator launch from ios-provision-service. (#2971) --- lib/services/ios-provision-service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/services/ios-provision-service.ts b/lib/services/ios-provision-service.ts index a8d2db581f..65622ac9e5 100644 --- a/lib/services/ios-provision-service.ts +++ b/lib/services/ios-provision-service.ts @@ -77,7 +77,8 @@ export class IOSProvisionService { devices = [this.$options.device]; } else { await this.$devicesService.initialize({ - platform: "ios" + platform: "ios", + skipEmulatorStart: true }); devices = _(this.$devicesService.getDeviceInstances()) .filter(d => this.$mobileHelper.isiOSPlatform(d.deviceInfo.platform)) From 17e6611b8740073b1289c147e9834a8fbce4062c Mon Sep 17 00:00:00 2001 From: Toma Nikolov Date: Thu, 13 Jul 2017 16:12:52 +0300 Subject: [PATCH 147/212] Fix recursive calling of actions. (#2976) --- lib/definitions/livesync.d.ts | 3 ++- lib/services/livesync/livesync-service.ts | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 6fa8ae0691..472c76cc50 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -160,9 +160,10 @@ interface ILiveSyncService { * Stops LiveSync operation for specified directory. * @param {string} projectDir The directory for which to stop the operation. * @param {string[]} @optional deviceIdentifiers Device ids for which to stop the application. In case nothing is passed, LiveSync operation will be stopped for all devices. + * @param { shouldAwaitAllActions: boolean } @optional stopOptions Specifies whether we should await all actions. * @returns {Promise} */ - stopLiveSync(projectDir: string, deviceIdentifiers?: string[]): Promise; + stopLiveSync(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise; } /** diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index f87aa65301..db10860bee 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -40,7 +40,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { await this.liveSyncOperation(deviceDescriptors, liveSyncData, projectData); } - public async stopLiveSync(projectDir: string, deviceIdentifiers?: string[]): Promise { + public async stopLiveSync(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise { const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectDir]; if (liveSyncProcessInfo) { @@ -67,7 +67,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { liveSyncProcessInfo.watcherInfo = null; - if (liveSyncProcessInfo.actionsChain) { + if (liveSyncProcessInfo.actionsChain && (!stopOptions || stopOptions.shouldAwaitAllActions)) { await liveSyncProcessInfo.actionsChain; } @@ -127,7 +127,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { await this.initialSync(projectData, deviceDescriptorsForInitialSync, liveSyncData); - if (!liveSyncData.skipWatcher && deviceDescriptors && deviceDescriptors.length) { + if (!liveSyncData.skipWatcher && this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors.length) { // Should be set after prepare this.$injector.resolve("usbLiveSyncService").isInitialized = true; @@ -250,7 +250,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { applicationIdentifier: projectData.projectId }); - await this.stopLiveSync(projectData.projectDir, [device.deviceInfo.identifier]); + await this.stopLiveSync(projectData.projectDir, [device.deviceInfo.identifier], { shouldAwaitAllActions: false }); } }; @@ -360,7 +360,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { applicationIdentifier: projectData.projectId }); - await this.stopLiveSync(projectData.projectDir, [deviceError.deviceIdentifier]); + await this.stopLiveSync(projectData.projectDir, [deviceError.deviceIdentifier], { shouldAwaitAllActions: false }); } } } From 703884ad3c41eaf41da1589b77a8baca82307598 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Thu, 13 Jul 2017 16:55:40 +0300 Subject: [PATCH 148/212] Emit `liveSyncStarted` when the application is really livesynced (#2977) `liveSyncStarted` event is currently raised before starting initial LiveSync operation. However this makes sense if the name was `liveSyncStarting`. So move the event at the end of the initialSync logic - this way it will be raised for each device when the LiveSync is really started. --- lib/services/livesync/livesync-service.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index db10860bee..4e2b289977 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -213,12 +213,6 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { // Now fullSync const deviceAction = async (device: Mobile.IDevice): Promise => { try { - this.emit(LiveSyncEvents.liveSyncStarted, { - projectDir: projectData.projectDir, - deviceIdentifier: device.deviceInfo.identifier, - applicationIdentifier: projectData.projectId - }); - const platform = device.deviceInfo.platform; const deviceBuildInfoDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); @@ -240,6 +234,12 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { }); await this.$platformService.trackActionForPlatform({ action: "LiveSync", platform: device.deviceInfo.platform, isForDevice: !device.isEmulator, deviceOsVersion: device.deviceInfo.version }); await this.refreshApplication(projectData, liveSyncResultInfo); + + this.emit(LiveSyncEvents.liveSyncStarted, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectId + }); } catch (err) { this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`); From 8590ede60b735e4228f19c4ce83aaff94072cb48 Mon Sep 17 00:00:00 2001 From: Toma Nikolov Date: Thu, 13 Jul 2017 17:54:30 +0300 Subject: [PATCH 149/212] Do not attach to the device log on every application start. (#2978) --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index c6898cf6d5..d9491f33cb 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit c6898cf6d5d309ae2169fad3a487e8672969a15d +Subproject commit d9491f33cbdb991caca18a94f5dc2a97eba4111b From 82d6abcfcf07d915ab0e798909aa0ebafb700055 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Fri, 14 Jul 2017 15:27:22 +0300 Subject: [PATCH 150/212] Add official support for Node.js 8 (#2979) * Add official support for Node.js 8 Add npm-shrinkwrap.json that is required for building CLI's package with npm 5. By default npm 5 creates package-lock.json, but it cannot be published. So in case we use `package-lock.json`, we'll always have exact dependency tree when using the repository, but end-users will have different dependency tree. So instead of using package-lock.json we'll use npm-shrinkwrap.json that can be published in npm. Update engine's tag in package.json, so in case Node.js 8 is used, we'll no longer show warning to the users. * Ensure symlinks from plugins are copied from realpath to platforms dir In case a plugin is symlinked or it has symlink files/directories, we do not want to copy the symlink to platforms dir, but the real files. This is required as the symlink may be relative to the current location and copying it makes it invalid. So pass `-L` option to `shelljs.cp`, so the symlinked files will be followed and the real ones will be copied. This also fixes unit tests with npm5. --- .../node-modules/node-modules-dest-copy.ts | 11 +- npm-shrinkwrap.json | 3804 +++++++++++++++++ package.json | 2 +- 3 files changed, 3808 insertions(+), 9 deletions(-) create mode 100644 npm-shrinkwrap.json diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts index eefa5b10a0..a66fb2eebe 100644 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ b/lib/tools/node-modules/node-modules-dest-copy.ts @@ -40,14 +40,9 @@ export class TnsModulesCopy { shelljs.mkdir("-p", targetPackageDir); - let isScoped = dependency.name.indexOf("@") === 0; - - if (isScoped) { - // copy module into tns_modules/@scope/module instead of tns_modules/module - shelljs.cp("-Rf", dependency.directory, path.join(this.outputRoot, dependency.name.substring(0, dependency.name.indexOf("/")))); - } else { - shelljs.cp("-Rf", dependency.directory, this.outputRoot); - } + const isScoped = dependency.name.indexOf("@") === 0; + const destinationPath = isScoped ? path.join(this.outputRoot, dependency.name.substring(0, dependency.name.indexOf("/"))) : this.outputRoot; + shelljs.cp("-RfL", dependency.directory, destinationPath); // remove platform-specific files (processed separately by plugin services) shelljs.rm("-rf", path.join(targetPackageDir, "platforms")); diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json new file mode 100644 index 0000000000..a6687e20b5 --- /dev/null +++ b/npm-shrinkwrap.json @@ -0,0 +1,3804 @@ +{ + "name": "nativescript", + "version": "3.2.0", + "lockfileVersion": 1, + "dependencies": { + "@types/chai": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.0.1.tgz", + "integrity": "sha512-DWrdkraJO+KvBB7+Jc6AuDd2+fwV6Z9iK8cqEEoYpcurYrH7GiUZmwjFuQIIWj5HhFz6NsSxdN72YMIHT7Fy2Q==", + "dev": true + }, + "@types/chai-as-promised": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-0.0.31.tgz", + "integrity": "sha512-DWT96QeM5AeASIkChnFtEOy6O45WvKmaw27MPhAKLkx06TaFNqrzJuWVurKjCEo3PqVV89YLR2iVON8PhTRaLg==", + "dev": true + }, + "@types/chokidar": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@types/chokidar/-/chokidar-1.6.0.tgz", + "integrity": "sha1-2xhDNg1UjyZ+84o1+Tj+IkM8Uoc=", + "dev": true + }, + "@types/form-data": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", + "integrity": "sha1-yayFsqX9GENbjIXZ7LUObWyJP/g=", + "dev": true + }, + "@types/node": { + "version": "6.0.61", + "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.61.tgz", + "integrity": "sha1-7qF0itmd7K8xm1cQFwGGMZdKxvA=", + "dev": true + }, + "@types/qr-image": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/qr-image/-/qr-image-3.2.0.tgz", + "integrity": "sha1-09XkVzWSOnIpoHbH/JC6ufDcYgs=", + "dev": true + }, + "@types/request": { + "version": "0.0.45", + "resolved": "https://registry.npmjs.org/@types/request/-/request-0.0.45.tgz", + "integrity": "sha512-OIIREjT58pnpfJjEY5PeBEuRtRR2ED4DF1Ez3Dj9474kCqEKfE+iNAYyM/P3RxxDjNxBhipo+peNBW0S/7Wrzg==", + "dev": true + }, + "@types/semver": { + "version": "5.3.32", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.3.32.tgz", + "integrity": "sha512-MdbWERx4JWmN4zP+skJy9Kio+Cddvmyn1k8x0S8UAqDoMgOJeobQo7yhlE4BfiimonHirgixWfva/hKUlXBsrw==", + "dev": true + }, + "@types/source-map": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@types/source-map/-/source-map-0.5.0.tgz", + "integrity": "sha1-3TS72OMv5OdPLj2KwH+KpbRaR6w=", + "dev": true + }, + "abbrev": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", + "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", + "dev": true + }, + "acorn": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.1.tgz", + "integrity": "sha512-vOk6uEMctu0vQrvuSqFdJyqj1Q0S5VTDL79qtjo+DhRr+1mmaD+tluFSCZqhvi/JUhXSzoZN2BhtstaPEeE8cw==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=" + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "ansicolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.2.1.tgz", + "integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8=" + }, + "anymatch": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz", + "integrity": "sha1-o+Uvo5FoyCX/V7AkgSbOWo/5VQc=" + }, + "argparse": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "dev": true + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=" + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + }, + "assertion-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", + "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", + "dev": true + }, + "async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=" + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "babel-code-frame": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", + "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base64-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.0.tgz", + "integrity": "sha1-o5mS1yNYSBGYK+XikLtqU9hnAPE=" + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true + }, + "big-integer": { + "version": "1.6.23", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.23.tgz", + "integrity": "sha1-6F1QgiDHTj9DpM5y7tUfPaTblNE=" + }, + "binary-extensions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.8.0.tgz", + "integrity": "sha1-SOyNFt9Dd+rl+liEaCSAr02Vx3Q=" + }, + "body-parser": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.2.tgz", + "integrity": "sha1-EBXLH+LEQ4WCWVgdtTMy+NDPUPk=", + "dev": true, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", + "dev": true + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "qs": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz", + "integrity": "sha1-qfMRQq9GjLcrJbMBNrokVoNJFr4=", + "dev": true + } + } + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=" + }, + "bplist-creator": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.7.tgz", + "integrity": "sha1-N98VNgkoJLh8QvlXsBNEEXNyrkU=" + }, + "bplist-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.1.0.tgz", + "integrity": "sha1-Ywgj8gVkN9Tb78IOhAF/i6xI4Ag=" + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=" + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=" + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "bufferpack": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/bufferpack/-/bufferpack-0.0.6.tgz", + "integrity": "sha1-+z2HOKDh5OA7z/mfmnX57Bip1z4=" + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" + }, + "byline": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/byline/-/byline-4.2.1.tgz", + "integrity": "sha1-90pm+m2P7/iLJyXgsrDPgwzfP4Y=" + }, + "bytes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz", + "integrity": "sha1-/TVGSkA/b5EXwt42Cez/nK4ABYg=", + "dev": true + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + } + } + }, + "cardinal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-1.0.0.tgz", + "integrity": "sha1-UOIcGwqjdyn5N33vGWtanOyTLuk=" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "optional": true + }, + "chai": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.0.2.tgz", + "integrity": "sha1-L3MnxN5vOF3XeHmZ4qsCaXoyuDs=", + "dev": true + }, + "chai-as-promised": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.0.0.tgz", + "integrity": "sha512-7YYdnXPq2pV9nvRBb36Wi/MXfT8j2iL/H76GtenlOMatXbMoQLb+PonuVHGFsw5wE2M6R/VFciq8AnSSAix0GA==", + "dev": true + }, + "chalk": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.0.tgz", + "integrity": "sha1-CbRTzsSXp1Ug5KYK5IIUqHAOCSE=" + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=" + }, + "circular-json": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz", + "integrity": "sha1-vos2rvzN6LPKeqLWr8B6NyQsDS0=", + "dev": true + }, + "cli-color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-0.3.2.tgz", + "integrity": "sha1-dfpfcowwjMSsWUsF4GzF2A2szYY=" + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true + }, + "cli-table": { + "version": "https://github.com/telerik/cli-table/tarball/v0.3.1.2", + "integrity": "sha1-B+E8MRgVTFOJPTvnp+CNmQ6G2Fk=", + "dependencies": { + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + }, + "lodash": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.6.0.tgz", + "integrity": "sha1-Umao9J3Zib5Pn2gbbyoMVShdDZo=" + } + } + }, + "cli-width": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.1.1.tgz", + "integrity": "sha1-pNKT72frt7iNSk1CwMzwDE0eNm0=" + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=" + }, + "clone": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz", + "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=" + }, + "clui": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/clui/-/clui-0.3.1.tgz", + "integrity": "sha1-AT0ILOht2/BguG05J/iauMM79CM=" + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "coffee-script": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.10.0.tgz", + "integrity": "sha1-EpOLz5vhlI+gBvkuDEyegXBRCMA=", + "dev": true + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=" + }, + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "dev": true + }, + "content-type": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", + "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=" + }, + "csproj2ts": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/csproj2ts/-/csproj2ts-0.0.8.tgz", + "integrity": "sha1-nRxxniDELM6MTeKQCO/DVUn9Em8=", + "dev": true, + "dependencies": { + "es6-promise": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.1.1.tgz", + "integrity": "sha512-OaU1hHjgJf+b0NzsxCg7NdIYERD6Hy/PEmFLTjw+b65scuisG3Kt4QoTvJ66BBkPZ581gr0kpoVzKnxniM8nng==", + "dev": true + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true + } + } + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true + }, + "d": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz", + "integrity": "sha1-2hhMU10Y2O57oqoim5FACfrhEwk=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "date-format": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-0.0.0.tgz", + "integrity": "sha1-CSBoY6sHDrRZrOpVQsvYVrEZZrM=" + }, + "dateformat": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "dev": true + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=" + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "deep-eql": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-2.0.2.tgz", + "integrity": "sha1-sbrAblbwp2d3aG1Qyf63XC7XZ5o=", + "dev": true, + "dependencies": { + "type-detect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-3.0.0.tgz", + "integrity": "sha1-RtDMhVOrt7E6NSsNbeov1Y8tm1U=", + "dev": true + } + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=" + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "depd": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", + "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=", + "dev": true + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true + }, + "doctrine": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", + "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "email-validator": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/email-validator/-/email-validator-1.0.4.tgz", + "integrity": "sha1-NHZdk3157uh3P7Q2FvxMAWSCEW8=" + }, + "error-ex": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=" + }, + "es5-ext": { + "version": "0.10.24", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.24.tgz", + "integrity": "sha1-pVh3yZJLwMjZvTwsvhdJWsFwmxQ=" + }, + "es6-iterator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz", + "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=", + "dependencies": { + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=" + } + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "dependencies": { + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true + } + } + }, + "es6-promise": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-0.1.2.tgz", + "integrity": "sha1-8RLCn+paCZhTn8tqL9IUQ9KPBfc=", + "dev": true + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "dependencies": { + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true + } + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dependencies": { + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=" + } + } + }, + "es6-weak-map": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-0.1.4.tgz", + "integrity": "sha1-cGzvnpmqI2undmwjnIueKG6n0ig=", + "dependencies": { + "es6-iterator": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-0.1.3.tgz", + "integrity": "sha1-1vWLjE/EE8JJtLqhl2j45NfIlE4=" + }, + "es6-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz", + "integrity": "sha1-dhtcZ8/U8dGK+yNPaR1nhoLLO/M=" + } + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + }, + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true + } + } + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "dependencies": { + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "dev": true + } + } + }, + "eslint": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", + "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", + "dev": true, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true + }, + "cli-width": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz", + "integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=", + "dev": true + }, + "inquirer": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "dev": true + }, + "readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "dev": true + }, + "rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", + "dev": true + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "espree": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz", + "integrity": "sha1-KRC1zNSc6JPC//+qtP2LOjG4I3Q=", + "dev": true + }, + "esprima": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.0.tgz", + "integrity": "sha1-dM+w5K5D8LgVQdzDAFD52ssfcH4=" + }, + "esquery": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", + "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "dev": true + }, + "esrecurse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", + "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "dev": true + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dependencies": { + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=" + } + } + }, + "eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", + "dev": true + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=" + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=" + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=" + }, + "extsprintf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", + "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=" + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true + }, + "file-sync-cmp": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", + "integrity": "sha1-peeo/7+kk7Q7kju9TKiaU7Y7YSs=", + "dev": true + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" + }, + "filesize": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.1.2.tgz", + "integrity": "sha1-jB0EdXYIY3CZmyPDLyF8TVGr8oo=" + }, + "fill-range": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=" + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=" + }, + "findup-sync": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", + "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", + "dev": true, + "dependencies": { + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true + } + } + }, + "flat-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz", + "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=", + "dev": true + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz", + "integrity": "sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw==", + "optional": true, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "bundled": true, + "optional": true + }, + "ajv": { + "version": "4.11.8", + "bundled": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "optional": true + }, + "asn1": { + "version": "0.2.3", + "bundled": true, + "optional": true + }, + "assert-plus": { + "version": "0.2.0", + "bundled": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "bundled": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true, + "optional": true + }, + "balanced-match": { + "version": "0.4.2", + "bundled": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "block-stream": { + "version": "0.0.9", + "bundled": true + }, + "boom": { + "version": "2.10.1", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.7", + "bundled": true + }, + "buffer-shims": { + "version": "1.0.0", + "bundled": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true, + "optional": true + }, + "co": { + "version": "4.6.0", + "bundled": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "optional": true + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "optional": true + } + } + }, + "debug": { + "version": "2.6.8", + "bundled": true, + "optional": true + }, + "deep-extend": { + "version": "0.4.2", + "bundled": true, + "optional": true + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true + }, + "extend": { + "version": "3.0.1", + "bundled": true, + "optional": true + }, + "extsprintf": { + "version": "1.0.2", + "bundled": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "optional": true + }, + "form-data": { + "version": "2.1.4", + "bundled": true, + "optional": true + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "fstream": { + "version": "1.0.11", + "bundled": true + }, + "fstream-ignore": { + "version": "1.0.5", + "bundled": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "optional": true + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "optional": true + } + } + }, + "glob": { + "version": "7.1.2", + "bundled": true + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true + }, + "har-schema": { + "version": "1.0.5", + "bundled": true, + "optional": true + }, + "har-validator": { + "version": "4.2.1", + "bundled": true, + "optional": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "hawk": { + "version": "3.1.3", + "bundled": true, + "optional": true + }, + "hoek": { + "version": "2.16.3", + "bundled": true + }, + "http-signature": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.4", + "bundled": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "optional": true + }, + "jodid25519": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "optional": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "optional": true + }, + "jsonify": { + "version": "0.0.0", + "bundled": true, + "optional": true + }, + "jsprim": { + "version": "1.4.0", + "bundled": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "optional": true + } + } + }, + "mime-db": { + "version": "1.27.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.15", + "bundled": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "node-pre-gyp": { + "version": "0.6.36", + "bundled": true, + "optional": true + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "optional": true + }, + "npmlog": { + "version": "4.1.0", + "bundled": true, + "optional": true + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "osenv": { + "version": "0.1.4", + "bundled": true, + "optional": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "performance-now": { + "version": "0.2.0", + "bundled": true, + "optional": true + }, + "process-nextick-args": { + "version": "1.0.7", + "bundled": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "optional": true + }, + "qs": { + "version": "6.4.0", + "bundled": true, + "optional": true + }, + "rc": { + "version": "1.2.1", + "bundled": true, + "optional": true, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.2.9", + "bundled": true + }, + "request": { + "version": "2.81.0", + "bundled": true, + "optional": true + }, + "rimraf": { + "version": "2.6.1", + "bundled": true + }, + "safe-buffer": { + "version": "5.0.1", + "bundled": true + }, + "semver": { + "version": "5.3.0", + "bundled": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "optional": true + }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "optional": true + }, + "sshpk": { + "version": "1.13.0", + "bundled": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "optional": true + } + } + }, + "string_decoder": { + "version": "1.0.1", + "bundled": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true + }, + "stringstream": { + "version": "0.0.5", + "bundled": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "tar": { + "version": "2.2.1", + "bundled": true + }, + "tar-pack": { + "version": "3.4.0", + "bundled": true, + "optional": true + }, + "tough-cookie": { + "version": "2.3.2", + "bundled": true, + "optional": true + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "optional": true + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "uuid": { + "version": "3.0.1", + "bundled": true, + "optional": true + }, + "verror": { + "version": "1.3.6", + "bundled": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "optional": true + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + } + } + }, + "gaze": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.0.tgz", + "integrity": "sha1-dNP/sBEO3nFcnxW7pWxum4UdPOA=" + }, + "generate-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", + "dev": true + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true + }, + "get-caller-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=" + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "getobject": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", + "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dependencies": { + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==" + } + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=" + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=" + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true + }, + "globule": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz", + "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=", + "dependencies": { + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + } + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "grunt": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.1.tgz", + "integrity": "sha1-6HeHZOlEsY8yuw8QuQeEdcnftWs=", + "dev": true, + "dependencies": { + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "dev": true + }, + "grunt-cli": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", + "integrity": "sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", + "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==", + "dev": true + }, + "js-yaml": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.5.5.tgz", + "integrity": "sha1-A3fDgBfKvHMisNH7zSWkkWQfL74=", + "dev": true + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "grunt-contrib-clean": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-1.0.0.tgz", + "integrity": "sha1-ay7ZQRfix//jLuBFeMlv5GJam20=", + "dev": true, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "rimraf": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", + "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "dev": true + } + } + }, + "grunt-contrib-copy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz", + "integrity": "sha1-cGDGWB6QS4qw0A8HbgqPbj58NXM=", + "dev": true, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true + } + } + }, + "grunt-contrib-watch": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.0.0.tgz", + "integrity": "sha1-hKGnodar0m7VaEE0lscxM+mQAY8=", + "dev": true, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + } + } + }, + "grunt-known-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.0.tgz", + "integrity": "sha1-pCdO6zL6dl2lp6OxcSYXzjsUQUk=", + "dev": true + }, + "grunt-legacy-log": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-1.0.0.tgz", + "integrity": "sha1-+4bxgJhHvAfcR4Q/ns1srLYt8tU=", + "dev": true, + "dependencies": { + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + } + } + }, + "grunt-legacy-log-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-1.0.0.tgz", + "integrity": "sha1-p7ji0Ps1taUPSvmG/BEnSevJbz0=", + "dev": true, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true + }, + "lodash": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", + "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=", + "dev": true + } + } + }, + "grunt-legacy-util": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.0.0.tgz", + "integrity": "sha1-OGqnjcbtUJhsKxiVcmWxtIq7m4Y=", + "dev": true, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "lodash": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", + "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=", + "dev": true + } + } + }, + "grunt-shell": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/grunt-shell/-/grunt-shell-1.3.0.tgz", + "integrity": "sha1-3lYGCpNN+OzuZAdLYcYwSQDXEVg=", + "dev": true + }, + "grunt-ts": { + "version": "6.0.0-beta.16", + "resolved": "https://registry.npmjs.org/grunt-ts/-/grunt-ts-6.0.0-beta.16.tgz", + "integrity": "sha1-wC9P+cgRAE7suTOJBaBds/hpIMM=", + "dev": true, + "dependencies": { + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true + }, + "rimraf": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.6.tgz", + "integrity": "sha1-xZWXVpsU2VatKcrMQr3d9fDqT0w=", + "dev": true + } + } + }, + "handlebars": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.10.tgz", + "integrity": "sha1-PTDHGLCaPZbyPqTMH0A8TTup/08=", + "dev": true, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true + } + } + }, + "har-schema": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" + }, + "har-validator": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=" + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=" + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=" + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + }, + "hooker": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", + "dev": true + }, + "hosted-git-info": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", + "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==" + }, + "http-errors": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=" + }, + "iconv-lite": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz", + "integrity": "sha1-LstC/SlHRJIiCaLnxATayHk9it4=" + }, + "ignore": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.3.tgz", + "integrity": "sha1-QyNS5XrM2HqzEQ6C0/6g5HgSFW0=", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "inquirer": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.9.0.tgz", + "integrity": "sha1-c2bjijMeYZBJWKzlstpKml9jZ5g=", + "dependencies": { + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + } + } + }, + "interpret": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz", + "integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=" + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "ios-device-lib": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/ios-device-lib/-/ios-device-lib-0.4.7.tgz", + "integrity": "sha1-E3G8cfGwnbICdtLcA1BR8OCTsIg=", + "dependencies": { + "node-uuid": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz", + "integrity": "sha1-baWhdmjEs91ZYjvaEc9/pMH2Cm8=" + } + } + }, + "ios-mobileprovision-finder": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/ios-mobileprovision-finder/-/ios-mobileprovision-finder-1.0.9.tgz", + "integrity": "sha1-Hc80ywKeP+oMhSkmu79K6GTswKc=", + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=" + }, + "plist": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-2.1.0.tgz", + "integrity": "sha1-V8zbeggh3yGDEhejytVOPhRqECU=" + }, + "yargs": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", + "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=" + } + } + }, + "ios-sim-portable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ios-sim-portable/-/ios-sim-portable-3.0.0.tgz", + "integrity": "sha1-VOCVccIb+TNH8sfAFSno9fHEtfI=", + "dependencies": { + "bplist-parser": { + "version": "https://github.com/telerik/node-bplist-parser/tarball/master", + "integrity": "sha1-X7BVlo1KqtkGi+pkMyFRiYKHtFY=" + }, + "colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=" + }, + "lodash": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.2.0.tgz", + "integrity": "sha1-S/UKMkP5rrC6xBpV09WZBnWkYvs=" + }, + "set-blocking": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-1.0.0.tgz", + "integrity": "sha1-zV5dk4BI3xrJLf6S4fFq3WVvXsU=" + }, + "shelljs": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.0.tgz", + "integrity": "sha1-P28uSWXOxWX2X/OGHWRPh5KBpXY=" + }, + "yargs": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.7.1.tgz", + "integrity": "sha1-5gQyZYozh/8mnAKOrN5KUS5Djf8=" + }, + "yargs-parser": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz", + "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=" + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=" + }, + "is-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", + "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=" + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=" + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=" + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=" + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=" + }, + "is-my-json-valid": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz", + "integrity": "sha1-8Hndm/2uZe4gOKrorLyGqxCeNpM=", + "dev": true, + "dependencies": { + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + } + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=" + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "dev": true + }, + "is-path-inside": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", + "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", + "dev": true + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=" + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-resolvable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", + "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "dev": true, + "dependencies": { + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true + } + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.9.0.tgz", + "integrity": "sha512-0LoUNELX4S+iofCT8f4uEHIiRBR+c2AINyC8qRWfC6QNruLtxVZRJaPcu/xwMgFIgDxF25tGHaDjvxzJCNE9yw==", + "dev": true, + "dependencies": { + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + } + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "jsmin2": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/jsmin2/-/jsmin2-1.2.1.tgz", + "integrity": "sha1-iPvi+/dfCpH2YCD9mBzWk/S/5X4=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "jsprim": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", + "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=" + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true, + "optional": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=" + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true + }, + "livereload-js": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.2.2.tgz", + "integrity": "sha1-bIclfmSKtHW8JOoldFftzB+NC8I=", + "dev": true + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=" + }, + "lockfile": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.1.tgz", + "integrity": "sha1-nTU+z+P1TRULtX+J1RdGk1o5xPU=" + }, + "lodash": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.13.1.tgz", + "integrity": "sha1-g+SxCRP0hJbU0W/sSlYK8u50S2g=" + }, + "lodash-node": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash-node/-/lodash-node-2.4.1.tgz", + "integrity": "sha1-6oL3sQDHM9GkKvdoAeUGEF4qgOw=" + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basecreate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" + }, + "lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true + }, + "lodash.toarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", + "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=" + }, + "log4js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-1.0.1.tgz", + "integrity": "sha1-+vZMEFa2NSpfu/CpO2Gpl5qEIsw=" + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true + }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=" + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "marked": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.6.tgz", + "integrity": "sha1-ssbGGPzOzk74bE/Gy4p8v1rtqNc=" + }, + "marked-terminal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-2.0.0.tgz", + "integrity": "sha1-Xq9Wi+ZvaGVBr6UqVYKAMQox3i0=", + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=" + } + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "memoizee": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.3.10.tgz", + "integrity": "sha1-TsoNiu057J0Bf0xcLy9kMvQuXI8=" + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=" + }, + "mime-db": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", + "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" + }, + "mime-types": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", + "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=" + }, + "minimatch": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.2.tgz", + "integrity": "sha1-DzmKcwDqRB6cNIyD2Yq4ydv5xAo=" + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=" + }, + "mocha": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.1.2.tgz", + "integrity": "sha1-Ufk7Qyv34bF1/8Iog8zQvjLbprU=", + "dev": true, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true + }, + "glob": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.5.tgz", + "integrity": "sha1-tCAqaQmbu00pKnwblbZoK2fr3JU=", + "dev": true + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "supports-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true + } + } + }, + "moment": { + "version": "2.10.6", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.10.6.tgz", + "integrity": "sha1-bLIZZ8ecunsMpeZmRPFzZis++nc=" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=" + }, + "nan": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz", + "integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U=", + "optional": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "ncp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.5.1.tgz", + "integrity": "sha1-dDmFMW49tFkoG1hxaehFc1oFQ58=", + "dev": true + }, + "next-tick": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-0.2.2.tgz", + "integrity": "sha1-ddpKkn7liH45BliABltzNkE7MQ0=" + }, + "node-emoji": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.7.0.tgz", + "integrity": "sha512-dYx345sjhPJUpWaVQKjP0/43y+nTcfBRTZfSciM3ZEbRGaU/9AKaHBPf7AJ9vOKcK0W3v67AgI4m4oo02NLHhQ==" + }, + "node-uuid": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.3.3.tgz", + "integrity": "sha1-09tNe1aBDZ5AMjQnZigq8HORcps=" + }, + "node-xml": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/node-xml/-/node-xml-1.0.2.tgz", + "integrity": "sha1-zqWQgrEAxwM3ERylPh84+CnjvMs=" + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==" + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=" + }, + "npm-run-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-1.0.0.tgz", + "integrity": "sha1-9cMr9ZX+ga6Sfa7FLoL4sACsPI8=", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=" + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=" + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "open": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/open/-/open-0.0.5.tgz", + "integrity": "sha1-QsPhjslUZra/DcQvOilFw/DK2Pw=" + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "dependencies": { + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.3.tgz", + "integrity": "sha1-g88FxtZFj8TVrGNi6jJdkvJ1Qhc=" + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=" + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=" + }, + "parseurl": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", + "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-1.0.0.tgz", + "integrity": "sha1-XVPVeAGWRsDWiADbThRua9wqx68=", + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=" + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "pbxproj-dom": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pbxproj-dom/-/pbxproj-dom-1.0.11.tgz", + "integrity": "sha1-MTbG1tphwkOW8Byvr5xBJGaPTAU=" + }, + "pegjs": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.6.2.tgz", + "integrity": "sha1-dGUfioAORE22iOTuro7bZWN6F6U=" + }, + "performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=" + }, + "pkg-conf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-1.1.3.tgz", + "integrity": "sha1-N45W1v0T6Iv7b0ol33qD+qvduls=" + }, + "plist": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-1.1.0.tgz", + "integrity": "sha1-/2cIWQyXzEOOe8Rd5SUb1yXz+J0=", + "dependencies": { + "base64-js": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.6.tgz", + "integrity": "sha1-e4WfefC7vVWGe6Z6f6s5fiSiCUc=" + }, + "util-deprecate": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.0.tgz", + "integrity": "sha1-MAevASwUDq4m3gVXbsInhcrDq/I=" + }, + "xmlbuilder": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.2.1.tgz", + "integrity": "sha1-kyZDDxMNh0NdTECGZDqikm4QWjI=" + } + } + }, + "plist-merge-patch": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/plist-merge-patch/-/plist-merge-patch-0.1.0.tgz", + "integrity": "sha1-eOTAlL6dtLpHWdpktvGkz2rTIJE=", + "dependencies": { + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "plist": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-2.1.0.tgz", + "integrity": "sha1-V8zbeggh3yGDEhejytVOPhRqECU=" + } + } + }, + "plistlib": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/plistlib/-/plistlib-0.2.1.tgz", + "integrity": "sha1-RukssEmKCjrtO+5bXE2IKQVNqGw=", + "dependencies": { + "moment": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.4.0.tgz", + "integrity": "sha1-Bt2N+7/bU6A1EAgKx4gWPJSQ510=" + } + } + }, + "pluralize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "progress-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.1.1.tgz", + "integrity": "sha1-nsvxh5MsSUHVUCGRkNdN7ArEX1Q=" + }, + "properties-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/properties-parser/-/properties-parser-0.2.3.tgz", + "integrity": "sha1-91kSVfcHq7/yJ8e1a2N9uwNzoQ8=" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qr-image": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/qr-image/-/qr-image-3.2.0.tgz", + "integrity": "sha1-n6gpW+rlDEoUnPn5CaHbRkqGcug=" + }, + "qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" + }, + "randomatic": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", + "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=" + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=" + } + } + }, + "raw-body": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", + "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", + "dev": true, + "dependencies": { + "bytes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", + "dev": true + } + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=" + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=" + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==" + }, + "readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=" + }, + "readline2": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-0.1.1.tgz", + "integrity": "sha1-mUQ7pug7gw7zBRv9fcJBqCco1Wg=", + "dependencies": { + "ansi-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz", + "integrity": "sha1-QchHGUZGN15qGl0Qw8oFTvn8mA0=" + }, + "mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha1-qSGZYKbV1dBGWXruUSUsZlX3F34=" + }, + "strip-ansi": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz", + "integrity": "sha1-32LBqpTtLxFOHQ8h/R1QSCt5pg4=" + } + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=" + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true + }, + "redeyed": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-1.0.1.tgz", + "integrity": "sha1-6WwZO0DAgWsArshCaY5hGF5VSYo=", + "dependencies": { + "esprima": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.0.0.tgz", + "integrity": "sha1-U88kes2ncxPlUcOqLnM0LT+099k=" + } + } + }, + "regex-cache": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz", + "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU=" + }, + "remove-trailing-separator": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz", + "integrity": "sha1-abBi2XhyetFNxrVrpKt3L9jXBRE=" + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true + }, + "request": { + "version": "2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=" + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true + }, + "resolve": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz", + "integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=" + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "optional": true + }, + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=" + }, + "run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=" + }, + "rx-lite": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-2.5.2.tgz", + "integrity": "sha1-X+9C1Nbna6tRmdIXEyfbcJ5Y5jQ=" + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, + "shelljs": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.6.tgz", + "integrity": "sha1-N5zM+1a5HIYB5HkzVutTgpJN6a0=" + }, + "should": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/should/-/should-7.0.2.tgz", + "integrity": "sha1-HfJOAqlxzx1ZWu0mfi2D8kw3CYM=", + "dev": true + }, + "should-equal": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-0.5.0.tgz", + "integrity": "sha1-x5fxNfMGf+tp6+zbMGscP+IbPm8=", + "dev": true + }, + "should-format": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-0.3.0.tgz", + "integrity": "sha1-QgB+wKochupEkUzJER8bnyfTzqw=", + "dev": true + }, + "should-type": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-0.2.0.tgz", + "integrity": "sha1-ZwfvlVKdmJ3MCY/gdTqx+RNrt/Y=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "simple-plist": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-0.2.1.tgz", + "integrity": "sha1-cXZts1IyaSjPOoByQrp2IyJjZyM=", + "dependencies": { + "base64-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.1.2.tgz", + "integrity": "sha1-1kAMrBxMZgl22Q0HoENR2JOV9eg=" + }, + "bplist-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.1.1.tgz", + "integrity": "sha1-1g1dzCDLptx+HymbNdPh+V2vuuY=" + }, + "plist": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/plist/-/plist-2.0.1.tgz", + "integrity": "sha1-CjLKlIGxw2TpLhjcVch23p0B2os=" + } + } + }, + "single-line-log": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-0.3.1.tgz", + "integrity": "sha1-p61lB/IYzl3+FsS/LWWSRkGeegY=" + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=" + }, + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" + }, + "source-map-support": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz", + "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E=", + "dev": true + }, + "spdx-correct": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=" + }, + "spdx-expression-parse": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=" + }, + "spdx-license-ids": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=" + }, + "speedometer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-0.1.4.tgz", + "integrity": "sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0=" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true + }, + "stream-buffers": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", + "integrity": "sha1-kdX1Ew0c75bc+n9yaUUYh0HQnuQ=" + }, + "streamroller": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.2.2.tgz", + "integrity": "sha1-oTQg4EFp5XPbBo9ZIO4j2IGr/jM=", + "dependencies": { + "debug": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", + "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=" + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==" + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=" + }, + "string.prototype.codepointat": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.0.tgz", + "integrity": "sha1-aybpvTr8qnvjtCabUm3huCAArHg=" + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=" + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=" + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "symbol": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/symbol/-/symbol-0.2.3.tgz", + "integrity": "sha1-O5hzuKkB5Hxu/iFSajrDcu8ou8c=" + }, + "table": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "dev": true, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.0.tgz", + "integrity": "sha1-AwZkVh/BRslCPsfZeP4kV0N/5tA=", + "dev": true, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true + } + } + } + } + }, + "tabtab": { + "version": "https://github.com/Icenium/node-tabtab/tarball/master", + "integrity": "sha1-xhMOobFKxMBo+ayFSilgv9cESkk=" + }, + "temp": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", + "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=" + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "through2": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", + "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=" + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "timers-ext": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.2.tgz", + "integrity": "sha1-YcxHp2wavTGV8UUn+XjViulMUgQ=", + "dependencies": { + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + } + } + }, + "tiny-lr": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-0.2.1.tgz", + "integrity": "sha1-s/26gC5dVqM8L28QeUsy5Hescp0=", + "dev": true, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "qs": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-5.1.0.tgz", + "integrity": "sha1-TZMuXH6kEcynajEtOaYGIA/VDNk=", + "dev": true + } + } + }, + "tough-cookie": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=" + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "tryit": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", + "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", + "dev": true + }, + "tslib": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.7.1.tgz", + "integrity": "sha1-vIAEFkaRkjp5/oN4u+s9ogF1OOw=", + "dev": true + }, + "tslint": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.4.3.tgz", + "integrity": "sha1-dhyEArgONHt3M6BDkKdXslNYBGc=", + "dev": true, + "dependencies": { + "diff": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.0.tgz", + "integrity": "sha512-w0XZubFWn0Adlsapj9EAWX0FqWdO4tz8kc3RiYdWLh4k/V8PTb6i0SMgXt0vRM3zyKnT8tKO7mUlieRQHIjMNg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true + } + } + }, + "tsutils": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.6.1.tgz", + "integrity": "sha1-mOzwCVlPTkr4hAV75M9BpFC8djc=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=" + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true + }, + "type-detect": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.3.tgz", + "integrity": "sha1-Dj8mcLRAmbC0bChNE2p+9Jx0wuo=", + "dev": true + }, + "type-is": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "dev": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typescript": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.4.1.tgz", + "integrity": "sha1-w8yxbdqgsjFN4DHn5v7onlujRrw=", + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "optional": true, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true, + "optional": true + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "optional": true + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true, + "optional": true + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true, + "optional": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "optional": true + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "ultron": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.0.tgz", + "integrity": "sha1-sHoualQagV/Go0zNRTO67DB8qGQ=" + }, + "underscore": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.5.2.tgz", + "integrity": "sha1-EzXF5PXm0zu7SwBrqMhqAPVW3gg=" + }, + "underscore.string": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.2.3.tgz", + "integrity": "sha1-gGmSYzZl1eX8tNsfs6hi62jp5to=", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", + "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=" + }, + "validate-npm-package-license": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=" + }, + "verror": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", + "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=" + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=" + }, + "websocket-driver": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", + "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", + "dev": true + }, + "websocket-extensions": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.1.tgz", + "integrity": "sha1-domUmcGEtu91Q3fC27DNbLVdKec=", + "dev": true + }, + "which": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "dev": true + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + }, + "window-size": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", + "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=" + }, + "winreg": { + "version": "0.0.17", + "resolved": "https://registry.npmjs.org/winreg/-/winreg-0.0.17.tgz", + "integrity": "sha1-ysqg4a2hdVXMGgDs/JQA1/ODrQE=" + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true + }, + "ws": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-2.2.0.tgz", + "integrity": "sha1-MhinsevRWgnFa7EqPpQ6lg63veU=" + }, + "xcode": { + "version": "https://github.com/NativeScript/node-xcode/archive/1.4.0.tar.gz", + "integrity": "sha1-pws+vYIXCzhk70x9hYy3eXmOHjg=" + }, + "xml": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/xml/-/xml-0.0.12.tgz", + "integrity": "sha1-8Is0cQmRK+AChXhfRvFa2OUKX2c=" + }, + "xml2js": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", + "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", + "dev": true, + "dependencies": { + "xmlbuilder": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", + "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", + "dev": true + } + } + }, + "xmlbuilder": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", + "integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=" + }, + "xmldom": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.21.tgz", + "integrity": "sha1-op4SENqx8QwlZltegBKbqo1pqXs=" + }, + "xmlhttprequest": { + "version": "https://github.com/telerik/node-XMLHttpRequest/tarball/master", + "integrity": "sha1-gyu8L8J4DhCCCmdOlAQRCpQ0M6c=" + }, + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=" + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "yargs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.0.0.tgz", + "integrity": "sha1-kAR5306L9qsOhyFvXtKydguWg0U=" + }, + "yargs-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", + "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=" + }, + "zipstream": { + "version": "https://github.com/Icenium/node-zipstream/tarball/master", + "integrity": "sha1-nYck2cc4rn9wPjWMHrJniS1yh3o=" + } + } +} diff --git a/package.json b/package.json index 49a25c7cea..a1753d7b62 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,6 @@ }, "license": "Apache-2.0", "engines": { - "node": ">=6.0.0 <8.0.0" + "node": ">=6.0.0 <9.0.0" } } From dea71ce1f08730170bd004780e848e7c7eada458 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Wed, 19 Jul 2017 14:22:58 +0300 Subject: [PATCH 151/212] Fix debug when using CLI as library (#2984) When CLI is used as a library, the method for debug returns incorrect result - previously the return type of the method we are calling was `string[]`, we've changed it to `string` but forgot to remove the `_.first(result)`. So now the debug returns incorrect URL. Remove the `_.first` and return the result directly. --- lib/services/debug-service.ts | 2 +- test/services/debug-service.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index b1ec3b850f..303177379b 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -64,7 +64,7 @@ export class DebugService extends EventEmitter implements IDebugService { result = await debugService.debug(debugData, debugOptions); } - return _.first(result); + return result; } public getDebugService(device: Mobile.IDevice): IPlatformDebugService { diff --git a/test/services/debug-service.ts b/test/services/debug-service.ts index f23fa11be8..259550d481 100644 --- a/test/services/debug-service.ts +++ b/test/services/debug-service.ts @@ -8,8 +8,8 @@ import { CONNECTION_ERROR_EVENT_NAME } from "../../lib/constants"; const fakeChromeDebugUrl = "fakeChromeDebugUrl"; class PlatformDebugService extends EventEmitter /* implements IPlatformDebugService */ { - public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { - return [fakeChromeDebugUrl]; + public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + return fakeChromeDebugUrl; } } From 1955028ac3ded6a012813476f08d0f4f0d89dddd Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Thu, 20 Jul 2017 10:47:35 +0300 Subject: [PATCH 152/212] Separate preparation of JS from native code (#2983) * Separate preparation of JS from native code * Introduce new parameter prepareNative to indicate that native code preparation is explicitly required. * Cache additional data in .nsprepareinfo file: * nativePlatformStatus - indicates whether native platform should be added, prepared or there's no need to do so; * projectFileHash - saves package.json file's contents hash for the current platform. We use this for better heuristics for checking whether package.json is changed. * Re-factoring i.e. extract common code to methods * Fix debug command and unit tests * Fix npm support tests Fix the tests by modifying current logic: - stop using ensurePrepareInfo for setting `nativePlatformStatus` property - introduce new method for setting it. The problem with using `ensurePrepareInfo` is that it saves the prepareInfo and calling checkForChanges after that is incorrect in case we modify the `bundle` option. Check test: `Ensures that tns_modules absent when bundling` - the last case was failing because when we call ensurePrepareInfo last time, bundle is false, we overwrite the prepareInfo in the file and checkForChanges says that bundle is currently false and has been false in the previous case. - in case prepareInfo is missing, we should not call addPlatform again - the else in `ensurePlatformInstalled` is not correct in case we execute `tns platform add ` and then try `tns run/build/prepare ` - in this case we do not have prepareInfo, so we try adding platform again. However, due to current implementation, we are sure that when we do not have prepareInfo, we've called platform add from CLI, so the platform is already added correctly. - fix a strange if inside `preparePlatformCoreNative` - we must prepare the node_modules in case `--bundle` is passed or the modulesChanged property of `changesInfo` is true. * Fix tests requiring package.json at the root of the project As projectChangesService now checks the package.json at the root of the project, several tests are failing as they are mocking the project creation and there's no package.json at the specified place. Fix this by creating the package.json in the tests. * Remove incorrect passing of skipNativePrepare * Cache instances of "base" commands in debug and run * Fix run android and run ios - set the platform correctly * Fix run command Fix run command by setting correct platform. * Update common lib * Fix PR comments --- lib/bootstrap.ts | 1 + lib/commands/debug.ts | 122 +++++------ lib/commands/run.ts | 143 ++++-------- lib/common | 2 +- lib/constants.ts | 6 + lib/definitions/livesync.d.ts | 9 + lib/definitions/platform.d.ts | 3 +- lib/definitions/plugins.d.ts | 1 + lib/definitions/project-changes.d.ts | 20 +- lib/definitions/project.d.ts | 4 + lib/services/android-project-service.ts | 2 +- lib/services/ios-project-service.ts | 13 +- .../livesync/livesync-command-helper.ts | 87 ++++++++ lib/services/livesync/livesync-service.ts | 39 ++-- lib/services/platform-service.ts | 203 ++++++++++-------- lib/services/plugins-service.ts | 2 +- lib/services/project-changes-service.ts | 52 ++++- .../node-modules/node-modules-builder.ts | 27 ++- .../node-modules/node-modules-dest-copy.ts | 28 ++- test/debug.ts | 19 +- test/platform-service.ts | 9 + test/plugin-prepare.ts | 2 +- test/project-changes-service.ts | 13 +- test/stubs.ts | 6 +- 24 files changed, 500 insertions(+), 313 deletions(-) create mode 100644 lib/services/livesync/livesync-command-helper.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index ef4717e4eb..4beee8e2c0 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -105,6 +105,7 @@ $injector.require("devicePathProvider", "./device-path-provider"); $injector.requireCommand("platform|clean", "./commands/platform-clean"); $injector.requirePublicClass("liveSyncService", "./services/livesync/livesync-service"); +$injector.require("liveSyncCommandHelper", "./services/livesync/livesync-command-helper"); $injector.require("debugLiveSyncService", "./services/livesync/debug-livesync-service"); $injector.require("androidLiveSyncService", "./services/livesync/android-livesync-service"); $injector.require("iOSLiveSyncService", "./services/livesync/ios-livesync-service"); diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 7c090d2eb3..5c1ff1b5a7 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -1,24 +1,25 @@ import { CONNECTED_STATUS } from "../common/constants"; import { isInteractive } from "../common/helpers"; +import { cache } from "../common/decorators"; import { DebugCommandErrors } from "../constants"; -export abstract class DebugPlatformCommand implements ICommand { +export class DebugPlatformCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; - public platform: string; constructor(private debugService: IPlatformDebugService, - private $devicesService: Mobile.IDevicesService, - private $debugDataService: IDebugDataService, + private platform: string, + protected $devicesService: Mobile.IDevicesService, protected $platformService: IPlatformService, protected $projectData: IProjectData, protected $options: IOptions, protected $platformsData: IPlatformsData, protected $logger: ILogger, protected $errors: IErrors, + private $debugDataService: IDebugDataService, private $debugLiveSyncService: IDebugLiveSyncService, private $config: IConfiguration, - private $prompter: IPrompter) { - this.$projectData.initializeProjectData(); + private $prompter: IPrompter, + private $liveSyncCommandHelper: ILiveSyncCommandHelper) { } public async execute(args: string[]): Promise { @@ -36,41 +37,7 @@ export abstract class DebugPlatformCommand implements ICommand { const selectedDeviceForDebug = await this.getDeviceForDebug(); - const deviceDescriptors: ILiveSyncDeviceInfo[] = [selectedDeviceForDebug] - .map(d => { - const info: ILiveSyncDeviceInfo = { - identifier: d.deviceInfo.identifier, - buildAction: async (): Promise => { - const buildConfig: IBuildConfig = { - buildForDevice: !d.isEmulator, - projectDir: this.$options.path, - clean: this.$options.clean, - teamId: this.$options.teamId, - device: this.$options.device, - provision: this.$options.provision, - release: this.$options.release, - keyStoreAlias: this.$options.keyStoreAlias, - keyStorePath: this.$options.keyStorePath, - keyStoreAliasPassword: this.$options.keyStoreAliasPassword, - keyStorePassword: this.$options.keyStorePassword - }; - - await this.$platformService.buildPlatform(d.deviceInfo.platform, buildConfig, this.$projectData); - const pathToBuildResult = await this.$platformService.lastOutputPath(d.deviceInfo.platform, buildConfig, this.$projectData); - return pathToBuildResult; - } - }; - - return info; - }); - - const liveSyncInfo: ILiveSyncInfo = { - projectDir: this.$projectData.projectDir, - skipWatcher: !this.$options.watch || this.$options.justlaunch, - watchAllFiles: this.$options.syncAllFiles - }; - - await this.$debugLiveSyncService.liveSync(deviceDescriptors, liveSyncInfo); + await this.$liveSyncCommandHelper.getDevicesLiveSyncInfo([selectedDeviceForDebug], this.$debugLiveSyncService, this.platform); } public async getDeviceForDebug(): Promise { @@ -149,23 +116,25 @@ export abstract class DebugPlatformCommand implements ICommand { } } -export class DebugIOSCommand extends DebugPlatformCommand { +export class DebugIOSCommand implements ICommand { + + @cache() + private get debugPlatformCommand(): DebugPlatformCommand { + return this.$injector.resolve(DebugPlatformCommand, { debugService: this.$iOSDebugService, platform: this.platform }); + } + + public allowedParameters: ICommandParameter[] = []; + constructor(protected $errors: IErrors, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $logger: ILogger, - $iOSDebugService: IPlatformDebugService, - $devicesService: Mobile.IDevicesService, - $config: IConfiguration, - $debugDataService: IDebugDataService, - $platformService: IPlatformService, - $options: IOptions, - $projectData: IProjectData, - $platformsData: IPlatformsData, - $iosDeviceOperations: IIOSDeviceOperations, - $debugLiveSyncService: IDebugLiveSyncService, - $prompter: IPrompter) { - super($iOSDebugService, $devicesService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger, - $errors, $debugLiveSyncService, $config, $prompter); + private $platformService: IPlatformService, + private $options: IOptions, + private $injector: IInjector, + private $projectData: IProjectData, + private $platformsData: IPlatformsData, + private $iOSDebugService: IDebugService, + $iosDeviceOperations: IIOSDeviceOperations) { + this.$projectData.initializeProjectData(); // Do not dispose ios-device-lib, so the process will remain alive and the debug application (NativeScript Inspector or Chrome DevTools) will be able to connect to the socket. // In case we dispose ios-device-lib, the socket will be closed and the code will fail when the debug application tries to read/send data to device socket. // That's why the `$ tns debug ios --justlaunch` command will not release the terminal. @@ -173,12 +142,16 @@ export class DebugIOSCommand extends DebugPlatformCommand { $iosDeviceOperations.setShouldDispose(false); } + public execute(args: string[]): Promise { + return this.debugPlatformCommand.execute(args); + } + public async canExecute(args: string[]): Promise { if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } - return await super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); + return await this.debugPlatformCommand.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); } public platform = this.$devicePlatformsConstants.iOS; @@ -186,26 +159,31 @@ export class DebugIOSCommand extends DebugPlatformCommand { $injector.registerCommand("debug|ios", DebugIOSCommand); -export class DebugAndroidCommand extends DebugPlatformCommand { +export class DebugAndroidCommand implements ICommand { + + @cache() + private get debugPlatformCommand(): DebugPlatformCommand { + return this.$injector.resolve(DebugPlatformCommand, { debugService: this.$androidDebugService, platform: this.platform }); + } + + public allowedParameters: ICommandParameter[] = []; + constructor(protected $errors: IErrors, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $logger: ILogger, - $androidDebugService: IPlatformDebugService, - $devicesService: Mobile.IDevicesService, - $config: IConfiguration, - $debugDataService: IDebugDataService, - $platformService: IPlatformService, - $options: IOptions, - $projectData: IProjectData, - $platformsData: IPlatformsData, - $debugLiveSyncService: IDebugLiveSyncService, - $prompter: IPrompter) { - super($androidDebugService, $devicesService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger, - $errors, $debugLiveSyncService, $config, $prompter); + private $platformService: IPlatformService, + private $options: IOptions, + private $injector: IInjector, + private $projectData: IProjectData, + private $platformsData: IPlatformsData, + private $androidDebugService: IDebugService) { + this.$projectData.initializeProjectData(); } + public execute(args: string[]): Promise { + return this.debugPlatformCommand.execute(args); + } public async canExecute(args: string[]): Promise { - return await super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.Android); + return await this.debugPlatformCommand.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.Android); } public platform = this.$devicePlatformsConstants.Android; diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 548eacffb9..b966d73a3c 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -1,21 +1,20 @@ import { ERROR_NO_VALID_SUBCOMMAND_FORMAT } from "../common/constants"; +import { cache } from "../common/decorators"; export class RunCommandBase implements ICommand { - protected platform: string; + public platform: string; constructor(protected $platformService: IPlatformService, protected $liveSyncService: ILiveSyncService, protected $projectData: IProjectData, protected $options: IOptions, - protected $emulatorPlatformService: IEmulatorPlatformService, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, protected $errors: IErrors, - private $devicesService: Mobile.IDevicesService, + protected $devicesService: Mobile.IDevicesService, + protected $platformsData: IPlatformsData, private $hostInfo: IHostInfo, - private $iosDeviceOperations: IIOSDeviceOperations, - private $mobileHelper: Mobile.IMobileHelper, - protected $platformsData: IPlatformsData) { - } + private $liveSyncCommandHelper: ILiveSyncCommandHelper + ) { } public allowedParameters: ICommandParameter[] = []; public async execute(args: string[]): Promise { @@ -28,6 +27,8 @@ export class RunCommandBase implements ICommand { } this.$projectData.initializeProjectData(); + this.platform = args[0] || this.platform; + if (!this.platform && !this.$hostInfo.isDarwin) { this.platform = this.$devicePlatformsConstants.Android; } @@ -56,93 +57,35 @@ export class RunCommandBase implements ICommand { }); await this.$devicesService.detectCurrentlyAttachedDevices(); - - const devices = this.$devicesService.getDeviceInstances(); - // Now let's take data for each device: - const deviceDescriptors: ILiveSyncDeviceInfo[] = devices.filter(d => !this.platform || d.deviceInfo.platform === this.platform) - .map(d => { - const info: ILiveSyncDeviceInfo = { - identifier: d.deviceInfo.identifier, - buildAction: async (): Promise => { - const buildConfig: IBuildConfig = { - buildForDevice: !d.isEmulator, - projectDir: this.$options.path, - clean: this.$options.clean, - teamId: this.$options.teamId, - device: this.$options.device, - provision: this.$options.provision, - release: this.$options.release, - keyStoreAlias: this.$options.keyStoreAlias, - keyStorePath: this.$options.keyStorePath, - keyStoreAliasPassword: this.$options.keyStoreAliasPassword, - keyStorePassword: this.$options.keyStorePassword - }; - - await this.$platformService.buildPlatform(d.deviceInfo.platform, buildConfig, this.$projectData); - const pathToBuildResult = await this.$platformService.lastOutputPath(d.deviceInfo.platform, buildConfig, this.$projectData); - return pathToBuildResult; - } - }; - - return info; - }); - - const workingWithiOSDevices = !this.platform || this.$mobileHelper.isiOSPlatform(this.platform); - const shouldKeepProcessAlive = this.$options.watch || !this.$options.justlaunch; - if (workingWithiOSDevices && shouldKeepProcessAlive) { - this.$iosDeviceOperations.setShouldDispose(false); - } - - if (this.$options.release || this.$options.bundle) { - const runPlatformOptions: IRunPlatformOptions = { - device: this.$options.device, - emulator: this.$options.emulator, - justlaunch: this.$options.justlaunch - }; - - const deployOptions = _.merge({ - projectDir: this.$projectData.projectDir, - clean: true, - }, this.$options.argv); - - await this.$platformService.deployPlatform(args[0], this.$options, deployOptions, this.$projectData, this.$options); - await this.$platformService.startApplication(args[0], runPlatformOptions, this.$projectData.projectId); - return this.$platformService.trackProjectType(this.$projectData); - } - - const liveSyncInfo: ILiveSyncInfo = { - projectDir: this.$projectData.projectDir, - skipWatcher: !this.$options.watch, - watchAllFiles: this.$options.syncAllFiles, - clean: this.$options.clean - }; - - await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); + let devices = this.$devicesService.getDeviceInstances(); + devices = devices.filter(d => !this.platform || d.deviceInfo.platform.toLowerCase() === this.platform.toLowerCase()); + await this.$liveSyncCommandHelper.getDevicesLiveSyncInfo(devices, this.$liveSyncService, this.platform); } } $injector.registerCommand("run|*all", RunCommandBase); -export class RunIosCommand extends RunCommandBase implements ICommand { +export class RunIosCommand implements ICommand { + + @cache() + private get runCommand(): RunCommandBase { + const runCommand = this.$injector.resolve(RunCommandBase); + runCommand.platform = this.platform; + return runCommand; + } + public allowedParameters: ICommandParameter[] = []; public get platform(): string { return this.$devicePlatformsConstants.iOS; } - constructor($platformService: IPlatformService, - protected $platformsData: IPlatformsData, + constructor(protected $platformsData: IPlatformsData, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, protected $errors: IErrors, - $liveSyncService: ILiveSyncService, - $projectData: IProjectData, - $options: IOptions, - $emulatorPlatformService: IEmulatorPlatformService, - $devicesService: Mobile.IDevicesService, - $hostInfo: IHostInfo, - $iosDeviceOperations: IIOSDeviceOperations, - $mobileHelper: Mobile.IMobileHelper) { - super($platformService, $liveSyncService, $projectData, $options, $emulatorPlatformService, $devicePlatformsConstants, $errors, - $devicesService, $hostInfo, $iosDeviceOperations, $mobileHelper, $platformsData); + private $injector: IInjector, + private $platformService: IPlatformService, + private $projectData: IProjectData, + private $options: IOptions) { } public async execute(args: string[]): Promise { @@ -150,44 +93,46 @@ export class RunIosCommand extends RunCommandBase implements ICommand { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } - return this.executeCore([this.$platformsData.availablePlatforms.iOS]); + return this.runCommand.executeCore(args); } public async canExecute(args: string[]): Promise { - return await super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); + return await this.runCommand.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); } } $injector.registerCommand("run|ios", RunIosCommand); -export class RunAndroidCommand extends RunCommandBase implements ICommand { +export class RunAndroidCommand implements ICommand { + + @cache() + private get runCommand(): RunCommandBase { + const runCommand = this.$injector.resolve(RunCommandBase); + runCommand.platform = this.platform; + return runCommand; + } + public allowedParameters: ICommandParameter[] = []; public get platform(): string { return this.$devicePlatformsConstants.Android; } - constructor($platformService: IPlatformService, - protected $platformsData: IPlatformsData, + constructor(protected $platformsData: IPlatformsData, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, protected $errors: IErrors, - $liveSyncService: ILiveSyncService, - $projectData: IProjectData, - $options: IOptions, - $emulatorPlatformService: IEmulatorPlatformService, - $devicesService: Mobile.IDevicesService, - $hostInfo: IHostInfo, - $iosDeviceOperations: IIOSDeviceOperations, - $mobileHelper: Mobile.IMobileHelper) { - super($platformService, $liveSyncService, $projectData, $options, $emulatorPlatformService, $devicePlatformsConstants, $errors, - $devicesService, $hostInfo, $iosDeviceOperations, $mobileHelper, $platformsData); + private $injector: IInjector, + private $platformService: IPlatformService, + private $projectData: IProjectData, + private $options: IOptions) { } public async execute(args: string[]): Promise { - return this.executeCore([this.$platformsData.availablePlatforms.Android]); + return this.runCommand.executeCore(args); } public async canExecute(args: string[]): Promise { - await super.canExecute(args); + await this.runCommand.canExecute(args); + if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.Android, this.$projectData)) { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.Android} can not be built on this OS`); } diff --git a/lib/common b/lib/common index d9491f33cb..c33a233679 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit d9491f33cbdb991caca18a94f5dc2a97eba4111b +Subproject commit c33a23367970966d1993749886fd689f3d048213 diff --git a/lib/constants.ts b/lib/constants.ts index d58e856ae4..d7512a2f78 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -92,3 +92,9 @@ export class DebugCommandErrors { public static UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR = "The options --for-device and --emulator cannot be used simultaneously. Please use only one of them."; public static NO_DEVICES_EMULATORS_FOUND_FOR_OPTIONS = "Unable to find device or emulator for specified options."; } + +export const enum NativePlatformStatus { + requiresPlatformAdd = "1", + requiresPrepare = "2", + alreadyPrepared = "3" +} diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 472c76cc50..d420e15a64 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -120,6 +120,11 @@ interface ILiveSyncInfo { * Forces a build before the initial livesync. */ clean?: boolean; + + /** + * Whether to skip preparing the native platform. + */ + skipNativePrepare?: boolean; } interface ILatestAppPackageInstalledSettings extends IDictionary> { /* empty */ } @@ -249,3 +254,7 @@ interface IDevicePathProvider { getDeviceProjectRootPath(device: Mobile.IDevice, options: IDeviceProjectRootOptions): Promise; getDeviceSyncZipPath(device: Mobile.IDevice): string; } + +interface ILiveSyncCommandHelper { + getDevicesLiveSyncInfo(devices: Mobile.IDevice[], liveSyncService: ILiveSyncService, platform: string): Promise; +} diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 57450d94f7..5645e49f21 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -47,7 +47,7 @@ interface IPlatformService extends NodeJS.EventEmitter { * @param {Array} filesToSync Files about to be synced to device. * @returns {boolean} true indicates that the platform was prepared. */ - preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, filesToSync?: Array): Promise; + preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, filesToSync?: Array, nativePrepare?: INativePrepare): Promise; /** * Determines whether a build is necessary. A build is necessary when one of the following is true: @@ -288,6 +288,7 @@ interface IPlatformsData { interface INodeModulesBuilder { prepareNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData): Promise; + prepareJSNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData): Promise; cleanNodeModules(absoluteOutputPath: string, platform: string): void; } diff --git a/lib/definitions/plugins.d.ts b/lib/definitions/plugins.d.ts index 24926cd12a..7feb3d5bc6 100644 --- a/lib/definitions/plugins.d.ts +++ b/lib/definitions/plugins.d.ts @@ -4,6 +4,7 @@ interface IPluginsService { prepare(pluginData: IDependencyData, platform: string, projectData: IProjectData): Promise; getAllInstalledPlugins(projectData: IProjectData): Promise; ensureAllDependenciesAreInstalled(projectData: IProjectData): Promise; + preparePluginScripts(pluginData: IPluginData, platform: string, projectData: IProjectData): void /** * Returns all dependencies and devDependencies from pacakge.json file. diff --git a/lib/definitions/project-changes.d.ts b/lib/definitions/project-changes.d.ts index ceefe2b371..04ae5edab4 100644 --- a/lib/definitions/project-changes.d.ts +++ b/lib/definitions/project-changes.d.ts @@ -1,14 +1,14 @@ -interface IPrepareInfo { +interface IPrepareInfo extends IAddedNativePlatform { time: string; bundle: boolean; release: boolean; + projectFileHash: string; changesRequireBuild: boolean; changesRequireBuildTime: string; - iOSProvisioningProfileUUID?: string; } -interface IProjectChangesInfo { +interface IProjectChangesInfo extends IAddedNativePlatform { appFilesChanged: boolean; appResourcesChanged: boolean; modulesChanged: boolean; @@ -22,12 +22,22 @@ interface IProjectChangesInfo { readonly changesRequirePrepare: boolean; } -interface IProjectChangesOptions extends IAppFilesUpdaterOptions, IProvision {} +interface IProjectChangesOptions extends IAppFilesUpdaterOptions, IProvision { + nativePlatformStatus?: "1" | "2" | "3"; +} interface IProjectChangesService { checkForChanges(platform: string, projectData: IProjectData, buildOptions: IProjectChangesOptions): IProjectChangesInfo; getPrepareInfo(platform: string, projectData: IProjectData): IPrepareInfo; savePrepareInfo(platform: string, projectData: IProjectData): void; getPrepareInfoFilePath(platform: string, projectData: IProjectData): string; + setNativePlatformStatus(platform: string, projectData: IProjectData, nativePlatformStatus: IAddedNativePlatform): void; currentChanges: IProjectChangesInfo; -} \ No newline at end of file +} + +/** + * NativePlatformStatus.requiresPlatformAdd | NativePlatformStatus.requiresPrepare | NativePlatformStatus.alreadyPrepared + */ +interface IAddedNativePlatform { + nativePlatformStatus: "1" | "2" | "3"; +} diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index a0aa63b25a..2656ddf251 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -132,6 +132,10 @@ interface IBuildForDevice { buildForDevice: boolean; } +interface INativePrepare { + skipNativePrepare: boolean; +} + interface IBuildConfig extends IAndroidBuildOptionsSettings, IiOSBuildConfig { projectDir: string; clean?: boolean; diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 170bae39d9..4dabcefe6d 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -221,7 +221,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject let manifestPath = this.getPlatformData(projectData).configurationFilePath; shell.sed('-i', /__PACKAGE__/, projectData.projectId, manifestPath); if (this.$androidToolsInfo.getToolsInfo().androidHomeEnvVar) { - const sdk = (platformSpecificData && platformSpecificData.sdk) || (this.$androidToolsInfo.getToolsInfo().compileSdkVersion).toString(); + const sdk = (platformSpecificData && platformSpecificData.sdk) || (this.$androidToolsInfo.getToolsInfo().compileSdkVersion || "").toString(); shell.sed('-i', /__APILEVEL__/, sdk, manifestPath); } } diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index b684faec92..db60e0f23f 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -876,7 +876,6 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f if (!this.$fs.exists(xcuserDataPath) && !this.$fs.exists(sharedDataPath)) { this.$logger.info("Creating project scheme..."); - await this.checkIfXcodeprojIsRequired(); let createSchemeRubyScript = `ruby -e "require 'xcodeproj'; xcproj = Xcodeproj::Project.open('${projectData.projectName}.xcodeproj'); xcproj.recreate_user_schemes; xcproj.save"`; @@ -1123,13 +1122,11 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.$fs.writeFile(projectFile, ""); } - if (this.$hostInfo.isDarwin) { - await this.checkIfXcodeprojIsRequired(); - let escapedProjectFile = projectFile.replace(/'/g, "\\'"), - escapedPluginFile = pluginFile.replace(/'/g, "\\'"), - mergeScript = `require 'xcodeproj'; Xcodeproj::Config.new('${escapedProjectFile}').merge(Xcodeproj::Config.new('${escapedPluginFile}')).save_as(Pathname.new('${escapedProjectFile}'))`; - await this.$childProcess.exec(`ruby -e "${mergeScript}"`); - } + await this.checkIfXcodeprojIsRequired(); + let escapedProjectFile = projectFile.replace(/'/g, "\\'"), + escapedPluginFile = pluginFile.replace(/'/g, "\\'"), + mergeScript = `require 'xcodeproj'; Xcodeproj::Config.new('${escapedProjectFile}').merge(Xcodeproj::Config.new('${escapedPluginFile}')).save_as(Pathname.new('${escapedProjectFile}'))`; + await this.$childProcess.exec(`ruby -e "${mergeScript}"`); } private async mergeProjectXcconfigFiles(release: boolean, projectData: IProjectData): Promise { diff --git a/lib/services/livesync/livesync-command-helper.ts b/lib/services/livesync/livesync-command-helper.ts new file mode 100644 index 0000000000..e37dd20b5f --- /dev/null +++ b/lib/services/livesync/livesync-command-helper.ts @@ -0,0 +1,87 @@ +export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { + + constructor(protected $platformService: IPlatformService, + protected $projectData: IProjectData, + protected $options: IOptions, + protected $devicesService: Mobile.IDevicesService, + private $iosDeviceOperations: IIOSDeviceOperations, + private $mobileHelper: Mobile.IMobileHelper, + private $platformsData: IPlatformsData) { + } + + public async getDevicesLiveSyncInfo(devices: Mobile.IDevice[], liveSyncService: ILiveSyncService, platform: string): Promise { + await this.$devicesService.detectCurrentlyAttachedDevices(); + + const workingWithiOSDevices = !platform || this.$mobileHelper.isiOSPlatform(platform); + const shouldKeepProcessAlive = this.$options.watch || !this.$options.justlaunch; + if (workingWithiOSDevices && shouldKeepProcessAlive) { + this.$iosDeviceOperations.setShouldDispose(false); + } + + if (this.$options.release || this.$options.bundle) { + await this.runInReleaseMode(platform); + return; + } + + // Now let's take data for each device: + const deviceDescriptors: ILiveSyncDeviceInfo[] = devices + .map(d => { + const info: ILiveSyncDeviceInfo = { + identifier: d.deviceInfo.identifier, + buildAction: async (): Promise => { + const buildConfig: IBuildConfig = { + buildForDevice: !d.isEmulator, + projectDir: this.$options.path, + clean: this.$options.clean, + teamId: this.$options.teamId, + device: this.$options.device, + provision: this.$options.provision, + release: this.$options.release, + keyStoreAlias: this.$options.keyStoreAlias, + keyStorePath: this.$options.keyStorePath, + keyStoreAliasPassword: this.$options.keyStoreAliasPassword, + keyStorePassword: this.$options.keyStorePassword + }; + + await this.$platformService.buildPlatform(d.deviceInfo.platform, buildConfig, this.$projectData); + const result = await this.$platformService.lastOutputPath(d.deviceInfo.platform, buildConfig, this.$projectData); + return result; + } + }; + + return info; + }); + + const liveSyncInfo: ILiveSyncInfo = { + projectDir: this.$projectData.projectDir, + skipWatcher: !this.$options.watch, + watchAllFiles: this.$options.syncAllFiles, + clean: this.$options.clean + }; + + await liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); + + } + + private async runInReleaseMode(platform: string): Promise { + const runPlatformOptions: IRunPlatformOptions = { + device: this.$options.device, + emulator: this.$options.emulator, + justlaunch: this.$options.justlaunch + }; + + const deployOptions = _.merge({ + projectDir: this.$projectData.projectDir, + clean: true, + }, this.$options.argv); + + const availablePlatforms = platform ? [platform] : _.values(this.$platformsData.availablePlatforms); + for (const currentPlatform of availablePlatforms) { + await this.$platformService.deployPlatform(currentPlatform, this.$options, deployOptions, this.$projectData, this.$options); + await this.$platformService.startApplication(currentPlatform, runPlatformOptions, this.$projectData.projectId); + this.$platformService.trackProjectType(this.$projectData); + } + } +} + +$injector.register("liveSyncCommandHelper", LiveSyncCommandHelper); diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 4e2b289977..dae7ecf36a 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -154,7 +154,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { throw new Error(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); } - private async ensureLatestAppPackageIsInstalledOnDevice(options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions): Promise { + private async ensureLatestAppPackageIsInstalledOnDevice(options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions, nativePrepare?: INativePrepare): Promise { const platform = options.device.deviceInfo.platform; if (options.preparedPlatforms.indexOf(platform) === -1) { options.preparedPlatforms.push(platform); @@ -162,15 +162,11 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { await this.$platformService.preparePlatform(platform, { bundle: false, release: false, - }, null, options.projectData, {}, options.modifiedFiles); + }, null, options.projectData, {}, options.modifiedFiles, nativePrepare); } - const rebuildInfo = _.find(options.rebuiltInformation, info => info.isEmulator === options.device.isEmulator && info.platform === platform); - - if (rebuildInfo) { - // Case where we have three devices attached, a change that requires build is found, - // we'll rebuild the app only for the first device, but we should install new package on all three devices. - await this.$platformService.installApplication(options.device, { release: false }, options.projectData, rebuildInfo.pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); + const buildResult = await this.installedCachedAppPackage(platform, options); + if (buildResult) { return; } @@ -185,6 +181,15 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { action = LiveSyncTrackActionNames.LIVESYNC_OPERATION_BUILD; } + await this.trackAction(action, platform, options); + + const shouldInstall = await this.$platformService.shouldInstall(options.device, options.projectData, options.deviceBuildInfoDescriptor.outputPath); + if (shouldInstall) { + await this.$platformService.installApplication(options.device, { release: false }, options.projectData, pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); + } + } + + private async trackAction(action: string, platform: string, options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions): Promise { if (!options.settings[platform][options.device.deviceInfo.type]) { let isForDevice = !options.device.isEmulator; options.settings[platform][options.device.deviceInfo.type] = true; @@ -198,11 +203,19 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { } await this.$platformService.trackActionForPlatform({ action: LiveSyncTrackActionNames.DEVICE_INFO, platform, isForDevice: !options.device.isEmulator, deviceOsVersion: options.device.deviceInfo.version }); + } - const shouldInstall = await this.$platformService.shouldInstall(options.device, options.projectData, options.deviceBuildInfoDescriptor.outputPath); - if (shouldInstall) { - await this.$platformService.installApplication(options.device, { release: false }, options.projectData, pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); + private async installedCachedAppPackage(platform: string, options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions): Promise { + const rebuildInfo = _.find(options.rebuiltInformation, info => info.isEmulator === options.device.isEmulator && info.platform === platform); + + if (rebuildInfo) { + // Case where we have three devices attached, a change that requires build is found, + // we'll rebuild the app only for the first device, but we should install new package on all three devices. + await this.$platformService.installApplication(options.device, { release: false }, options.projectData, rebuildInfo.pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); + return rebuildInfo.pathToBuildItem; } + + return null; } private async initialSync(projectData: IProjectData, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise { @@ -224,7 +237,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { deviceBuildInfoDescriptor, liveSyncData, settings - }); + }, { skipNativePrepare: liveSyncData.skipNativePrepare }); const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ projectData, device, @@ -326,7 +339,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { deviceBuildInfoDescriptor, settings: latestAppPackageInstalledSettings, modifiedFiles: allModifiedFiles - }); + }, { skipNativePrepare: liveSyncData.skipNativePrepare }); const service = this.getLiveSyncService(device.deviceInfo.platform); const settings: ILiveSyncWatchInfo = { diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index fb68a435b6..76e3bee56f 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -39,8 +39,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { private $npm: INodePackageManager, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $projectChangesService: IProjectChangesService, - private $analyticsService: IAnalyticsService, - private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder) { + private $analyticsService: IAnalyticsService) { super(); } @@ -59,10 +58,17 @@ export class PlatformService extends EventEmitter implements IPlatformService { } public async addPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, frameworkPath?: string): Promise { - let platformsDir = projectData.platformsDir; + const platformsDir = projectData.platformsDir; this.$fs.ensureDirectoryExists(platformsDir); for (let platform of platforms) { + this.validatePlatform(platform, projectData); + const platformPath = path.join(projectData.platformsDir, platform); + + if (this.$fs.exists(platformPath)) { + this.$errors.failWithoutHelp(`Platform ${platform} already added`); + } + await this.addPlatform(platform.toLowerCase(), platformTemplate, projectData, config, frameworkPath); } } @@ -78,19 +84,11 @@ export class PlatformService extends EventEmitter implements IPlatformService { return version; } - private async addPlatform(platformParam: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, frameworkPath?: string): Promise { + private async addPlatform(platformParam: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, frameworkPath?: string, nativePrepare?: INativePrepare): Promise { let data = platformParam.split("@"), platform = data[0].toLowerCase(), version = data[1]; - this.validatePlatform(platform, projectData); - - let platformPath = path.join(projectData.platformsDir, platform); - - if (this.$fs.exists(platformPath)) { - this.$errors.failWithoutHelp("Platform %s already added", platform); - } - let platformData = this.$platformsData.getPlatformData(platform, projectData); if (version === undefined) { @@ -116,15 +114,17 @@ export class PlatformService extends EventEmitter implements IPlatformService { npmOptions["version"] = version; } - let spinner = new clui.Spinner("Installing " + packageToInstall); - let projectDir = projectData.projectDir; + const spinner = new clui.Spinner("Installing " + packageToInstall); + const projectDir = projectData.projectDir; + const platformPath = path.join(projectData.platformsDir, platform); + try { spinner.start(); let downloadedPackagePath = await this.$npmInstallationManager.install(packageToInstall, projectDir, npmOptions); let frameworkDir = path.join(downloadedPackagePath, constants.PROJECT_FRAMEWORK_FOLDER_NAME); frameworkDir = path.resolve(frameworkDir); - let coreModuleName = await this.addPlatformCore(platformData, frameworkDir, platformTemplate, projectData, config); + const coreModuleName = await this.addPlatformCore(platformData, frameworkDir, platformTemplate, projectData, config, nativePrepare); await this.$npm.uninstall(coreModuleName, { save: true }, projectData.projectDir); } catch (err) { this.$fs.deleteDirectory(platformPath); @@ -137,17 +137,15 @@ export class PlatformService extends EventEmitter implements IPlatformService { } - private async addPlatformCore(platformData: IPlatformData, frameworkDir: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { - let coreModuleData = this.$fs.readJson(path.join(frameworkDir, "..", "package.json")); - let installedVersion = coreModuleData.version; - let coreModuleName = coreModuleData.name; - - let customTemplateOptions = await this.getPathToPlatformTemplate(platformTemplate, platformData.frameworkPackageName, projectData.projectDir); + private async addPlatformCore(platformData: IPlatformData, frameworkDir: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, nativePrepare?: INativePrepare): Promise { + const coreModuleData = this.$fs.readJson(path.join(frameworkDir, "..", "package.json")); + const installedVersion = coreModuleData.version; + const customTemplateOptions = await this.getPathToPlatformTemplate(platformTemplate, platformData.frameworkPackageName, projectData.projectDir); config.pathToTemplate = customTemplateOptions && customTemplateOptions.pathToTemplate; - await platformData.platformProjectService.createProject(path.resolve(frameworkDir), installedVersion, projectData, config); - platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData); - await platformData.platformProjectService.interpolateData(projectData, config); - platformData.platformProjectService.afterCreateProject(platformData.projectRoot, projectData); + + if (!nativePrepare || !nativePrepare.skipNativePrepare) { + await this.addPlatformCoreNative(platformData, frameworkDir, installedVersion, projectData, config); + } let frameworkPackageNameData: any = { version: installedVersion }; if (customTemplateOptions) { @@ -156,10 +154,18 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$projectDataService.setNSValue(projectData.projectDir, platformData.frameworkPackageName, frameworkPackageNameData); + const coreModuleName = coreModuleData.name; return coreModuleName; } + private async addPlatformCoreNative(platformData: IPlatformData, frameworkDir: string, installedVersion: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { + await platformData.platformProjectService.createProject(path.resolve(frameworkDir), installedVersion, projectData, config); + platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData); + await platformData.platformProjectService.interpolateData(projectData, config); + platformData.platformProjectService.afterCreateProject(platformData.projectRoot, projectData); + } + private async getPathToPlatformTemplate(selectedTemplate: string, frameworkPackageName: string, projectDir: string): Promise<{ selectedTemplate: string, pathToTemplate: string }> { if (!selectedTemplate) { // read data from package.json's nativescript key @@ -207,35 +213,13 @@ export class PlatformService extends EventEmitter implements IPlatformService { public getPreparedPlatforms(projectData: IProjectData): string[] { return _.filter(this.$platformsData.platformsNames, p => { return this.isPlatformPrepared(p, projectData); }); } + public async preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, filesToSync?: Array, nativePrepare?: INativePrepare): Promise { + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const changesInfo = await this.initialPrepare(platform, platformData, appFilesUpdaterOptions, platformTemplate, projectData, config, nativePrepare); + const requiresNativePrepare = (!nativePrepare || !nativePrepare.skipNativePrepare) && changesInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPrepare; - public async preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, filesToSync?: Array): Promise { - this.validatePlatform(platform, projectData); - - await this.trackProjectType(projectData); - - //We need dev-dependencies here, so before-prepare hooks will be executed correctly. - try { - await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); - } catch (err) { - this.$logger.trace(err); - this.$errors.failWithoutHelp(`Unable to install dependencies. Make sure your package.json is valid and all dependencies are correct. Error is: ${err.message}`); - } - - let platformData = this.$platformsData.getPlatformData(platform, projectData); - await this.$pluginsService.validate(platformData, projectData); - - const bundle = appFilesUpdaterOptions.bundle; - - await this.ensurePlatformInstalled(platform, platformTemplate, projectData, config); - let changesInfo = this.$projectChangesService.checkForChanges(platform, projectData, { bundle, release: appFilesUpdaterOptions.release, provision: config.provision }); - - this.$logger.trace("Changes info in prepare platform:", changesInfo); - - if (changesInfo.hasChanges) { - await this.cleanProject(platform, appFilesUpdaterOptions, platformData, projectData); - } - if (changesInfo.hasChanges || bundle) { - await this.preparePlatformCore(platform, appFilesUpdaterOptions, projectData, config, changesInfo, filesToSync); + if (changesInfo.hasChanges || appFilesUpdaterOptions.bundle || requiresNativePrepare) { + await this.preparePlatformCore(platform, appFilesUpdaterOptions, projectData, config, changesInfo, filesToSync, nativePrepare); this.$projectChangesService.savePrepareInfo(platform, projectData); } else { this.$logger.out("Skipping prepare."); @@ -281,15 +265,56 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } + private async initialPrepare(platform: string, platformData: IPlatformData, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, nativePrepare?: INativePrepare): Promise { + this.validatePlatform(platform, projectData); + + await this.trackProjectType(projectData); + + //We need dev-dependencies here, so before-prepare hooks will be executed correctly. + try { + await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); + } catch (err) { + this.$logger.trace(err); + this.$errors.failWithoutHelp(`Unable to install dependencies. Make sure your package.json is valid and all dependencies are correct. Error is: ${err.message}`); + } + + await this.$pluginsService.validate(platformData, projectData); + await this.ensurePlatformInstalled(platform, platformTemplate, projectData, config, nativePrepare); + + const bundle = appFilesUpdaterOptions.bundle; + const nativePlatformStatus = (nativePrepare && nativePrepare.skipNativePrepare) ? constants.NativePlatformStatus.requiresPlatformAdd : constants.NativePlatformStatus.requiresPrepare; + const changesInfo = this.$projectChangesService.checkForChanges(platform, projectData, { bundle, release: appFilesUpdaterOptions.release, provision: config.provision, nativePlatformStatus }); + + this.$logger.trace("Changes info in prepare platform:", changesInfo); + return changesInfo; + } + /* Hooks are expected to use "filesToSync" parameter, as to give plugin authors additional information about the sync process.*/ @helpers.hook('prepare') - private async preparePlatformCore(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, changesInfo?: IProjectChangesInfo, filesToSync?: Array): Promise { + private async preparePlatformCore(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, changesInfo?: IProjectChangesInfo, filesToSync?: Array, nativePrepare?: INativePrepare): Promise { this.$logger.out("Preparing project..."); let platformData = this.$platformsData.getPlatformData(platform, projectData); + await this.preparePlatformCoreJS(platform, platformData, appFilesUpdaterOptions, projectData, platformSpecificData, changesInfo); + + if (!nativePrepare || !nativePrepare.skipNativePrepare) { + await this.preparePlatformCoreNative(platform, platformData, appFilesUpdaterOptions, projectData, platformSpecificData, changesInfo); + } + let directoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); + let excludedDirs = [constants.APP_RESOURCES_FOLDER_NAME]; + if (!changesInfo || !changesInfo.modulesChanged) { + excludedDirs.push(constants.TNS_MODULES_FOLDER_NAME); + } + + this.$projectFilesManager.processPlatformSpecificFiles(directoryPath, platform, excludedDirs); + + this.$logger.out(`Project successfully prepared (${platform})`); + } + + private async preparePlatformCoreJS(platform: string, platformData: IPlatformData, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, changesInfo?: IProjectChangesInfo, filesToSync?: Array, ): Promise { if (!changesInfo || changesInfo.appFilesChanged) { - await this.copyAppFiles(platform, appFilesUpdaterOptions, projectData); + await this.copyAppFiles(platformData, appFilesUpdaterOptions, projectData); // remove the App_Resources folder from the app/assets as here we're applying other files changes. const appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); @@ -299,45 +324,41 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - if (!changesInfo || changesInfo.changesRequirePrepare) { - await this.copyAppFiles(platform, appFilesUpdaterOptions, projectData); - this.copyAppResources(platform, projectData); - await platformData.platformProjectService.prepareProject(projectData, platformSpecificData); + if (!changesInfo || changesInfo.modulesChanged) { + await this.copyTnsModules(platform, platformData, projectData); } + } - if (!changesInfo || changesInfo.modulesChanged) { - await this.copyTnsModules(platform, projectData); - } else if (appFilesUpdaterOptions.bundle) { - let dependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); - for (let dependencyKey in dependencies) { - const dependency = dependencies[dependencyKey]; - let isPlugin = !!dependency.nativescript; - if (isPlugin) { - let pluginData = this.$pluginsService.convertToPluginData(dependency, projectData.projectDir); - await this.$pluginsService.preparePluginNativeCode(pluginData, platform, projectData); - } - } + public async preparePlatformCoreNative(platform: string, platformData: IPlatformData, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, changesInfo?: IProjectChangesInfo): Promise { + if (changesInfo.hasChanges) { + await this.cleanProject(platform, appFilesUpdaterOptions, platformData, projectData); } - let directoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); - let excludedDirs = [constants.APP_RESOURCES_FOLDER_NAME]; - if (!changesInfo || !changesInfo.modulesChanged) { - excludedDirs.push(constants.TNS_MODULES_FOLDER_NAME); + if (!changesInfo || changesInfo.changesRequirePrepare) { + await this.copyAppFiles(platformData, appFilesUpdaterOptions, projectData); + this.copyAppResources(platformData, projectData); + await platformData.platformProjectService.prepareProject(projectData, platformSpecificData); } - this.$projectFilesManager.processPlatformSpecificFiles(directoryPath, platform, excludedDirs); + if (!changesInfo || changesInfo.modulesChanged || appFilesUpdaterOptions.bundle) { + let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); + let lastModifiedTime = this.$fs.exists(appDestinationDirectoryPath) ? this.$fs.getFsStats(appDestinationDirectoryPath).mtime : null; + + let tnsModulesDestinationPath = path.join(appDestinationDirectoryPath, constants.TNS_MODULES_FOLDER_NAME); + // Process node_modules folder + await this.$nodeModulesBuilder.prepareNodeModules(tnsModulesDestinationPath, platform, lastModifiedTime, projectData); + } if (!changesInfo || changesInfo.configChanged || changesInfo.modulesChanged) { await platformData.platformProjectService.processConfigurationFilesFromAppResources(appFilesUpdaterOptions.release, projectData); } platformData.platformProjectService.interpolateConfigurationFile(projectData, platformSpecificData); - - this.$logger.out("Project successfully prepared (" + platform + ")"); + this.$projectChangesService.setNativePlatformStatus(platform, projectData, + { nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); } - private async copyAppFiles(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData): Promise { - let platformData = this.$platformsData.getPlatformData(platform, projectData); + private async copyAppFiles(platformData: IPlatformData, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData): Promise { platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData); let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); @@ -351,8 +372,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { }); } - private copyAppResources(platform: string, projectData: IProjectData): void { - let platformData = this.$platformsData.getPlatformData(platform, projectData); + private copyAppResources(platformData: IPlatformData, projectData: IProjectData): void { let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); let appResourcesDirectoryPath = path.join(appDestinationDirectoryPath, constants.APP_RESOURCES_FOLDER_NAME); if (this.$fs.exists(appResourcesDirectoryPath)) { @@ -364,15 +384,14 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - private async copyTnsModules(platform: string, projectData: IProjectData): Promise { - let platformData = this.$platformsData.getPlatformData(platform, projectData); + private async copyTnsModules(platform: string, platformData: IPlatformData, projectData: IProjectData): Promise { let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); let lastModifiedTime = this.$fs.exists(appDestinationDirectoryPath) ? this.$fs.getFsStats(appDestinationDirectoryPath).mtime : null; try { let tnsModulesDestinationPath = path.join(appDestinationDirectoryPath, constants.TNS_MODULES_FOLDER_NAME); // Process node_modules folder - await this.$nodeModulesBuilder.prepareNodeModules(tnsModulesDestinationPath, platform, lastModifiedTime, projectData); + await this.$nodeModulesBuilder.prepareJSNodeModules(tnsModulesDestinationPath, platform, lastModifiedTime, projectData); } catch (error) { this.$logger.debug(error); shell.rm("-rf", appDestinationDirectoryPath); @@ -715,9 +734,19 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - public async ensurePlatformInstalled(platform: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { + public async ensurePlatformInstalled(platform: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, nativePrepare?: INativePrepare): Promise { + let requiresNativePlatformAdd = false; + if (!this.isPlatformInstalled(platform, projectData)) { - await this.addPlatform(platform, platformTemplate, projectData, config); + await this.addPlatform(platform, platformTemplate, projectData, config, "", nativePrepare); + } else { + const shouldAddNativePlatform = !nativePrepare || !nativePrepare.skipNativePrepare; + const prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); + // In case there's no prepare info, it means only platform add had been executed. So we've come from CLI and we do not need to prepare natively. + requiresNativePlatformAdd = prepareInfo && prepareInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPlatformAdd; + if (requiresNativePlatformAdd && shouldAddNativePlatform) { + await this.addPlatform(platform, platformTemplate, projectData, config, "", nativePrepare); + } } } diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index 38faf585e5..57d07d132b 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -122,7 +122,7 @@ export class PluginsService implements IPluginsService { } } - private preparePluginScripts(pluginData: IPluginData, platform: string, projectData: IProjectData): void { + public preparePluginScripts(pluginData: IPluginData, platform: string, projectData: IProjectData): void { let platformData = this.$platformsData.getPlatformData(platform, projectData); let pluginScriptsDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, "tns_modules"); let scriptsDestinationExists = this.$fs.exists(pluginScriptsDestinationPath); diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index c28b4272b8..77c6d536ee 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -1,5 +1,6 @@ import * as path from "path"; -import { NODE_MODULES_FOLDER_NAME } from "../constants"; +import { NODE_MODULES_FOLDER_NAME, NativePlatformStatus, PACKAGE_JSON_FILE_NAME } from "../constants"; +import { getHash } from "../common/helpers"; const prepareInfoFileName = ".nsprepareinfo"; @@ -12,6 +13,7 @@ class ProjectChangesInfo implements IProjectChangesInfo { public packageChanged: boolean; public nativeChanged: boolean; public signingChanged: boolean; + public nativePlatformStatus: NativePlatformStatus; public get hasChanges(): boolean { return this.packageChanged || @@ -58,7 +60,7 @@ export class ProjectChangesService implements IProjectChangesService { if (!this.ensurePrepareInfo(platform, projectData, projectChangesOptions)) { this._newFiles = 0; this._changesInfo.appFilesChanged = this.containsNewerFiles(projectData.appDirectoryPath, projectData.appResourcesDirectoryPath, projectData); - this._changesInfo.packageChanged = this.filesChanged([path.join(projectData.projectDir, "package.json")]); + this._changesInfo.packageChanged = this.isProjectFileChanged(projectData, platform); this._changesInfo.appResourcesChanged = this.containsNewerFiles(projectData.appResourcesDirectoryPath, null, projectData); /*done because currently all node_modules are traversed, a possible improvement could be traversing only the production dependencies*/ this._changesInfo.nativeChanged = this.containsNewerFiles( @@ -72,8 +74,8 @@ export class ProjectChangesService implements IProjectChangesService { let platformResourcesDir = path.join(projectData.appResourcesDirectoryPath, platformData.normalizedPlatformName); if (platform === this.$devicePlatformsConstants.iOS.toLowerCase()) { this._changesInfo.configChanged = this.filesChanged([path.join(platformResourcesDir, platformData.configurationFileName), - path.join(platformResourcesDir, "LaunchScreen.storyboard"), - path.join(platformResourcesDir, "build.xcconfig") + path.join(platformResourcesDir, "LaunchScreen.storyboard"), + path.join(platformResourcesDir, "build.xcconfig") ]); } else { this._changesInfo.configChanged = this.filesChanged([ @@ -107,6 +109,9 @@ export class ProjectChangesService implements IProjectChangesService { this._prepareInfo.changesRequireBuildTime = this._prepareInfo.time; } } + + this._changesInfo.nativePlatformStatus = this._prepareInfo.nativePlatformStatus; + return this._changesInfo; } @@ -134,24 +139,41 @@ export class ProjectChangesService implements IProjectChangesService { this.$fs.writeJson(prepareInfoFilePath, this._prepareInfo); } + public setNativePlatformStatus(platform: string, projectData: IProjectData, addedPlatform: IAddedNativePlatform): void { + this._prepareInfo = this._prepareInfo || this.getPrepareInfo(platform, projectData); + if (this._prepareInfo) { + this._prepareInfo.nativePlatformStatus = addedPlatform.nativePlatformStatus; + this.savePrepareInfo(platform, projectData); + } + } + private ensurePrepareInfo(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): boolean { this._prepareInfo = this.getPrepareInfo(platform, projectData); if (this._prepareInfo) { + this._prepareInfo.nativePlatformStatus = this._prepareInfo.nativePlatformStatus && this._prepareInfo.nativePlatformStatus < projectChangesOptions.nativePlatformStatus ? + projectChangesOptions.nativePlatformStatus : + this._prepareInfo.nativePlatformStatus || projectChangesOptions.nativePlatformStatus; + let platformData = this.$platformsData.getPlatformData(platform, projectData); let prepareInfoFile = path.join(platformData.projectRoot, prepareInfoFileName); this._outputProjectMtime = this.$fs.getFsStats(prepareInfoFile).mtime.getTime(); this._outputProjectCTime = this.$fs.getFsStats(prepareInfoFile).ctime.getTime(); return false; } + this._prepareInfo = { time: "", + nativePlatformStatus: projectChangesOptions.nativePlatformStatus, bundle: projectChangesOptions.bundle, release: projectChangesOptions.release, changesRequireBuild: true, + projectFileHash: this.getProjectFileStrippedHash(projectData, platform), changesRequireBuildTime: null }; + this._outputProjectMtime = 0; this._outputProjectCTime = 0; + this._changesInfo = this._changesInfo || new ProjectChangesInfo(); this._changesInfo.appFilesChanged = true; this._changesInfo.appResourcesChanged = true; this._changesInfo.modulesChanged = true; @@ -159,6 +181,27 @@ export class ProjectChangesService implements IProjectChangesService { return true; } + private getProjectFileStrippedHash(projectData: IProjectData, platform: string): string { + platform = platform.toLowerCase(); + const projectFilePath = path.join(projectData.projectDir, PACKAGE_JSON_FILE_NAME); + const projectFileContents = this.$fs.readJson(projectFilePath); + _(this.$devicePlatformsConstants) + .keys() + .map(k => k.toLowerCase()) + .difference([platform]) + .each(otherPlatform => { + delete projectFileContents.nativescript[`tns-${otherPlatform}`]; + }); + + return getHash(JSON.stringify(projectFileContents)); + } + + private isProjectFileChanged(projectData: IProjectData, platform: string): boolean { + const projectFileStrippedContentsHash = this.getProjectFileStrippedHash(projectData, platform); + const prepareInfo = this.getPrepareInfo(platform, projectData); + return projectFileStrippedContentsHash !== prepareInfo.projectFileHash; + } + private filesChanged(files: string[]): boolean { for (let file of files) { if (this.$fs.exists(file)) { @@ -168,6 +211,7 @@ export class ProjectChangesService implements IProjectChangesService { } } } + return false; } diff --git a/lib/tools/node-modules/node-modules-builder.ts b/lib/tools/node-modules/node-modules-builder.ts index ac1556151d..0143d437ab 100644 --- a/lib/tools/node-modules/node-modules-builder.ts +++ b/lib/tools/node-modules/node-modules-builder.ts @@ -9,13 +9,29 @@ export class NodeModulesBuilder implements INodeModulesBuilder { ) { } public async prepareNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData): Promise { + const productionDependencies = this.initialPrepareNodeModules(absoluteOutputPath, platform, lastModifiedTime, projectData); + const npmPluginPrepare: NpmPluginPrepare = this.$injector.resolve(NpmPluginPrepare); + await npmPluginPrepare.preparePlugins(productionDependencies, platform, projectData); + } + + public async prepareJSNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData): Promise { + const productionDependencies = this.initialPrepareNodeModules(absoluteOutputPath, platform, lastModifiedTime, projectData); + const npmPluginPrepare: NpmPluginPrepare = this.$injector.resolve(NpmPluginPrepare); + await npmPluginPrepare.prepareJSPlugins(productionDependencies, platform, projectData); + } + + public cleanNodeModules(absoluteOutputPath: string, platform: string): void { + shelljs.rm("-rf", absoluteOutputPath); + } + + private initialPrepareNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData, ): IDependencyData[] { + const productionDependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); + if (!this.$fs.exists(absoluteOutputPath)) { // Force copying if the destination doesn't exist. lastModifiedTime = null; } - let productionDependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); - if (!this.$options.bundle) { const tnsModulesCopy = this.$injector.resolve(TnsModulesCopy, { outputRoot: absoluteOutputPath @@ -25,12 +41,7 @@ export class NodeModulesBuilder implements INodeModulesBuilder { this.cleanNodeModules(absoluteOutputPath, platform); } - const npmPluginPrepare: NpmPluginPrepare = this.$injector.resolve(NpmPluginPrepare); - await npmPluginPrepare.preparePlugins(productionDependencies, platform, projectData); - } - - public cleanNodeModules(absoluteOutputPath: string, platform: string): void { - shelljs.rm("-rf", absoluteOutputPath); + return productionDependencies; } } diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts index a66fb2eebe..78d19c607c 100644 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ b/lib/tools/node-modules/node-modules-dest-copy.ts @@ -81,7 +81,8 @@ export class NpmPluginPrepare { constructor( private $fs: IFileSystem, private $pluginsService: IPluginsService, - private $platformsData: IPlatformsData + private $platformsData: IPlatformsData, + private $logger: ILogger ) { } @@ -146,10 +147,33 @@ export class NpmPluginPrepare { const dependency = dependencies[dependencyKey]; let isPlugin = !!dependency.nativescript; if (isPlugin) { - await this.$pluginsService.prepare(dependency, platform, projectData); + let pluginData = this.$pluginsService.convertToPluginData(dependency, projectData.projectDir); + await this.$pluginsService.preparePluginNativeCode(pluginData, platform, projectData); } } await this.afterPrepare(dependencies, platform, projectData); } + + public async prepareJSPlugins(dependencies: IDependencyData[], platform: string, projectData: IProjectData): Promise { + if (_.isEmpty(dependencies) || this.allPrepared(dependencies, platform, projectData)) { + return; + } + + for (let dependencyKey in dependencies) { + const dependency = dependencies[dependencyKey]; + let isPlugin = !!dependency.nativescript; + if (isPlugin) { + platform = platform.toLowerCase(); + let pluginData = this.$pluginsService.convertToPluginData(dependency, projectData.projectDir); + let platformData = this.$platformsData.getPlatformData(platform, projectData); + let appFolderExists = this.$fs.exists(path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME)); + if (appFolderExists) { + this.$pluginsService.preparePluginScripts(pluginData, platform, projectData); + // Show message + this.$logger.out(`Successfully prepared plugin ${pluginData.name} for ${platform}.`); + } + } + } + } } diff --git a/test/debug.ts b/test/debug.ts index d204cb3576..995c73c429 100644 --- a/test/debug.ts +++ b/test/debug.ts @@ -1,6 +1,6 @@ import * as stubs from "./stubs"; import * as yok from "../lib/common/yok"; -import { DebugAndroidCommand } from "../lib/commands/debug"; +import { DebugAndroidCommand, DebugPlatformCommand } from "../lib/commands/debug"; import { assert } from "chai"; import { Configuration, StaticConfig } from "../lib/config"; import { Options } from "../lib/options"; @@ -68,6 +68,11 @@ function createTestInjector(): IInjector { testInjector.register("prompter", {}); testInjector.registerCommand("debug|android", DebugAndroidCommand); + testInjector.register("liveSyncCommandHelper", { + getDevicesLiveSyncInfo: async (): Promise => { + return null; + } + }); return testInjector; } @@ -78,7 +83,7 @@ describe("debug command tests", () => { const testInjector = createTestInjector(); const options = testInjector.resolve("options"); options.forDevice = options.emulator = true; - const debugCommand = testInjector.resolveCommand("debug|android"); + const debugCommand = testInjector.resolve(DebugPlatformCommand, { debugService: {}, platform: "android" }); await assert.isRejected(debugCommand.getDeviceForDebug(), DebugCommandErrors.UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR); }); @@ -95,7 +100,7 @@ describe("debug command tests", () => { const options = testInjector.resolve("options"); options.device = specifiedDeviceOption; - const debugCommand = testInjector.resolveCommand("debug|android"); + const debugCommand = testInjector.resolve(DebugPlatformCommand, { debugService: {}, platform: "android" }); const selectedDeviceInstance = await debugCommand.getDeviceForDebug(); assert.deepEqual(selectedDeviceInstance, deviceInstance); }); @@ -111,7 +116,7 @@ describe("debug command tests", () => { const devicesService = testInjector.resolve("devicesService"); devicesService.getDeviceInstances = (): Mobile.IDevice[] => getDeviceInstancesResult; - const debugCommand = testInjector.resolveCommand("debug|android"); + const debugCommand = testInjector.resolve(DebugPlatformCommand, { debugService: {}, platform: "android" }); await assert.isRejected(debugCommand.getDeviceForDebug(), DebugCommandErrors.NO_DEVICES_EMULATORS_FOUND_FOR_OPTIONS); }; @@ -184,7 +189,7 @@ describe("debug command tests", () => { const devicesService = testInjector.resolve("devicesService"); devicesService.getDeviceInstances = (): Mobile.IDevice[] => [deviceInstance]; - const debugCommand = testInjector.resolveCommand("debug|android"); + const debugCommand = testInjector.resolve(DebugPlatformCommand, { debugService: {}, platform: "android" }); const actualDeviceInstance = await debugCommand.getDeviceForDebug(); assert.deepEqual(actualDeviceInstance, deviceInstance); }); @@ -243,7 +248,7 @@ describe("debug command tests", () => { return choices[1]; }; - const debugCommand = testInjector.resolveCommand("debug|android"); + const debugCommand = testInjector.resolve(DebugPlatformCommand, { debugService: {}, platform: "android" }); const actualDeviceInstance = await debugCommand.getDeviceForDebug(); const expectedChoicesPassedToPrompter = [deviceInstance1, deviceInstance2].map(d => `${d.deviceInfo.identifier} - ${d.deviceInfo.displayName}`); assert.deepEqual(choicesPassedToPrompter, expectedChoicesPassedToPrompter); @@ -304,7 +309,7 @@ describe("debug command tests", () => { devicesService.getDeviceInstances = (): Mobile.IDevice[] => deviceInstances; - const debugCommand = testInjector.resolveCommand("debug|android"); + const debugCommand = testInjector.resolve(DebugPlatformCommand, { debugService: {}, platform: "android" }); const actualDeviceInstance = await debugCommand.getDeviceForDebug(); assert.deepEqual(actualDeviceInstance, deviceInstance2); diff --git a/test/platform-service.ts b/test/platform-service.ts index 94ae456ac5..d017e07c76 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -49,6 +49,9 @@ function createTestInjector() { testInjector.register("nodeModulesBuilder", { prepareNodeModules: () => { return Promise.resolve(); + }, + prepareJSNodeModules: () => { + return Promise.resolve(); } }); testInjector.register("pluginsService", { @@ -382,6 +385,12 @@ describe('Platform Service Tests', () => { let appDestFolderPath = path.join(tempFolder, "appDest"); let appResourcesFolderPath = path.join(appDestFolderPath, "App_Resources"); + fs.writeJson(path.join(tempFolder, "package.json"), { + name: "testname", + nativescript: { + id: "org.nativescript.testname" + } + }); return { tempFolder, appFolderPath, app1FolderPath, appDestFolderPath, appResourcesFolderPath }; } diff --git a/test/plugin-prepare.ts b/test/plugin-prepare.ts index 45f3664147..6c07491663 100644 --- a/test/plugin-prepare.ts +++ b/test/plugin-prepare.ts @@ -7,7 +7,7 @@ class TestNpmPluginPrepare extends NpmPluginPrepare { public preparedDependencies: IDictionary = {}; constructor(private previouslyPrepared: IDictionary) { - super(null, null, null); + super(null, null, null, null); } protected getPreviouslyPreparedDependencies(platform: string): IDictionary { diff --git a/test/project-changes-service.ts b/test/project-changes-service.ts index 5fab8b9598..3593400340 100644 --- a/test/project-changes-service.ts +++ b/test/project-changes-service.ts @@ -32,6 +32,13 @@ class ProjectChangesServiceTest extends BaseServiceTest { this.injector.register("devicePlatformsConstants", {}); this.injector.register("projectChangesService", ProjectChangesService); + const fs = this.injector.resolve("fs"); + fs.writeJson(path.join(this.projectDir, Constants.PACKAGE_JSON_FILE_NAME), { + nativescript: { + id: "org.nativescript.test" + } + }); + } get projectChangesService(): IProjectChangesService { @@ -113,13 +120,15 @@ describe("Project Changes Service Tests", () => { // arrange const prepareInfoPath = path.join(serviceTest.projectDir, Constants.PLATFORMS_DIR_NAME, platform, ".nsprepareinfo"); - const expectedPrepareInfo = { + const expectedPrepareInfo: IPrepareInfo = { time: new Date().toString(), bundle: true, release: false, changesRequireBuild: true, changesRequireBuildTime: new Date().toString(), - iOSProvisioningProfileUUID: "provisioning_profile_test" + iOSProvisioningProfileUUID: "provisioning_profile_test", + projectFileHash: "", + nativePlatformStatus: Constants.NativePlatformStatus.requiresPlatformAdd }; fs.writeJson(prepareInfoPath, expectedPrepareInfo); diff --git a/test/stubs.ts b/test/stubs.ts index 8fa4e100a7..1a4902a444 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -206,7 +206,7 @@ export class ErrorsStub implements IErrors { } export class NpmInstallationManagerStub implements INpmInstallationManager { - async install(packageName: string, pathToSave?: string, options?: INpmInstallOptions ): Promise { + async install(packageName: string, pathToSave?: string, options?: INpmInstallOptions): Promise { return Promise.resolve(""); } @@ -574,6 +574,10 @@ export class ProjectChangesService implements IProjectChangesService { public get currentChanges(): IProjectChangesInfo { return {}; } + + public setNativePlatformStatus(platform: string, projectData: IProjectData, nativePlatformStatus: IAddedNativePlatform): void { + return; + } } export class CommandsService implements ICommandsService { From 52c88bb9218649de200ac8ca004316061984b47c Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Thu, 20 Jul 2017 11:41:47 +0300 Subject: [PATCH 153/212] Fix EPIPE during livesync on iOS (#2987) In case during `tns run ios` the application is uninstalled manually and after that a change is applied, CLI should install the app and continue with livesync operation. However it fails with EPIPE. The problem was in ios-device-lib - it is fixed in 0.4.8 version. --- npm-shrinkwrap.json | 2111 +++++++++++++++++++++++++++++++++++++------ package.json | 2 +- 2 files changed, 1848 insertions(+), 265 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index a6687e20b5..172a7e87ac 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2,6 +2,7 @@ "name": "nativescript", "version": "3.2.0", "lockfileVersion": 1, + "requires": true, "dependencies": { "@types/chai": { "version": "4.0.1", @@ -13,19 +14,28 @@ "version": "0.0.31", "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-0.0.31.tgz", "integrity": "sha512-DWT96QeM5AeASIkChnFtEOy6O45WvKmaw27MPhAKLkx06TaFNqrzJuWVurKjCEo3PqVV89YLR2iVON8PhTRaLg==", - "dev": true + "dev": true, + "requires": { + "@types/chai": "4.0.1" + } }, "@types/chokidar": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@types/chokidar/-/chokidar-1.6.0.tgz", "integrity": "sha1-2xhDNg1UjyZ+84o1+Tj+IkM8Uoc=", - "dev": true + "dev": true, + "requires": { + "@types/node": "6.0.61" + } }, "@types/form-data": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", "integrity": "sha1-yayFsqX9GENbjIXZ7LUObWyJP/g=", - "dev": true + "dev": true, + "requires": { + "@types/node": "6.0.61" + } }, "@types/node": { "version": "6.0.61", @@ -37,13 +47,20 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/@types/qr-image/-/qr-image-3.2.0.tgz", "integrity": "sha1-09XkVzWSOnIpoHbH/JC6ufDcYgs=", - "dev": true + "dev": true, + "requires": { + "@types/node": "6.0.61" + } }, "@types/request": { "version": "0.0.45", "resolved": "https://registry.npmjs.org/@types/request/-/request-0.0.45.tgz", "integrity": "sha512-OIIREjT58pnpfJjEY5PeBEuRtRR2ED4DF1Ez3Dj9474kCqEKfE+iNAYyM/P3RxxDjNxBhipo+peNBW0S/7Wrzg==", - "dev": true + "dev": true, + "requires": { + "@types/form-data": "0.0.33", + "@types/node": "6.0.61" + } }, "@types/semver": { "version": "5.3.32", @@ -74,6 +91,9 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", "dev": true, + "requires": { + "acorn": "3.3.0" + }, "dependencies": { "acorn": { "version": "3.3.0", @@ -86,7 +106,11 @@ "ajv": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=" + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } }, "ajv-keywords": { "version": "1.5.1", @@ -98,7 +122,12 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true + "dev": true, + "requires": { + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" + } }, "amdefine": { "version": "1.0.1", @@ -130,18 +159,28 @@ "anymatch": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz", - "integrity": "sha1-o+Uvo5FoyCX/V7AkgSbOWo/5VQc=" + "integrity": "sha1-o+Uvo5FoyCX/V7AkgSbOWo/5VQc=", + "requires": { + "arrify": "1.0.1", + "micromatch": "2.3.11" + } }, "argparse": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", - "dev": true + "dev": true, + "requires": { + "sprintf-js": "1.0.3" + } }, "arr-diff": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=" + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "requires": { + "arr-flatten": "1.1.0" + } }, "arr-flatten": { "version": "1.1.0", @@ -158,7 +197,10 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } }, "array-uniq": { "version": "1.0.3", @@ -221,7 +263,12 @@ "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=", - "dev": true + "dev": true, + "requires": { + "chalk": "1.1.0", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } }, "balanced-match": { "version": "1.0.0", @@ -237,7 +284,10 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "optional": true + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } }, "big-integer": { "version": "1.6.23", @@ -254,12 +304,27 @@ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.2.tgz", "integrity": "sha1-EBXLH+LEQ4WCWVgdtTMy+NDPUPk=", "dev": true, + "requires": { + "bytes": "2.2.0", + "content-type": "1.0.2", + "debug": "2.2.0", + "depd": "1.1.0", + "http-errors": "1.3.1", + "iconv-lite": "0.4.13", + "on-finished": "2.3.0", + "qs": "5.2.0", + "raw-body": "2.1.7", + "type-is": "1.6.15" + }, "dependencies": { "debug": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "dev": true + "dev": true, + "requires": { + "ms": "0.7.1" + } }, "iconv-lite": { "version": "0.4.13", @@ -284,12 +349,18 @@ "boom": { "version": "2.10.1", "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=" + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "requires": { + "hoek": "2.16.3" + } }, "bplist-creator": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.7.tgz", - "integrity": "sha1-N98VNgkoJLh8QvlXsBNEEXNyrkU=" + "integrity": "sha1-N98VNgkoJLh8QvlXsBNEEXNyrkU=", + "requires": { + "stream-buffers": "2.2.0" + } }, "bplist-parser": { "version": "0.1.0", @@ -299,12 +370,21 @@ "brace-expansion": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=" + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } }, "braces": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=" + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } }, "browser-stdout": { "version": "1.3.0", @@ -337,7 +417,10 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true + "dev": true, + "requires": { + "callsites": "0.2.0" + } }, "callsites": { "version": "0.2.0", @@ -355,6 +438,10 @@ "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, + "requires": { + "camelcase": "2.1.1", + "map-obj": "1.0.1" + }, "dependencies": { "camelcase": { "version": "2.1.1", @@ -367,7 +454,11 @@ "cardinal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-1.0.0.tgz", - "integrity": "sha1-UOIcGwqjdyn5N33vGWtanOyTLuk=" + "integrity": "sha1-UOIcGwqjdyn5N33vGWtanOyTLuk=", + "requires": { + "ansicolors": "0.2.1", + "redeyed": "1.0.1" + } }, "caseless": { "version": "0.12.0", @@ -379,24 +470,47 @@ "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", "dev": true, - "optional": true + "optional": true, + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } }, "chai": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/chai/-/chai-4.0.2.tgz", "integrity": "sha1-L3MnxN5vOF3XeHmZ4qsCaXoyuDs=", - "dev": true + "dev": true, + "requires": { + "assertion-error": "1.0.2", + "check-error": "1.0.2", + "deep-eql": "2.0.2", + "get-func-name": "2.0.0", + "pathval": "1.1.0", + "type-detect": "4.0.3" + } }, "chai-as-promised": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.0.0.tgz", "integrity": "sha512-7YYdnXPq2pV9nvRBb36Wi/MXfT8j2iL/H76GtenlOMatXbMoQLb+PonuVHGFsw5wE2M6R/VFciq8AnSSAix0GA==", - "dev": true + "dev": true, + "requires": { + "check-error": "1.0.2", + "eslint": "3.19.0" + } }, "chalk": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.0.tgz", - "integrity": "sha1-CbRTzsSXp1Ug5KYK5IIUqHAOCSE=" + "integrity": "sha1-CbRTzsSXp1Ug5KYK5IIUqHAOCSE=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } }, "check-error": { "version": "1.0.2", @@ -407,7 +521,18 @@ "chokidar": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", - "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=" + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "requires": { + "anymatch": "1.3.0", + "async-each": "1.0.1", + "fsevents": "1.1.2", + "glob-parent": "2.0.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "2.0.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0" + } }, "circular-json": { "version": "0.3.1", @@ -418,17 +543,31 @@ "cli-color": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-0.3.2.tgz", - "integrity": "sha1-dfpfcowwjMSsWUsF4GzF2A2szYY=" + "integrity": "sha1-dfpfcowwjMSsWUsF4GzF2A2szYY=", + "requires": { + "d": "0.1.1", + "es5-ext": "0.10.24", + "memoizee": "0.3.10", + "timers-ext": "0.1.2" + } }, "cli-cursor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true + "dev": true, + "requires": { + "restore-cursor": "1.0.1" + } }, "cli-table": { "version": "https://github.com/telerik/cli-table/tarball/v0.3.1.2", "integrity": "sha1-B+E8MRgVTFOJPTvnp+CNmQ6G2Fk=", + "requires": { + "colors": "1.0.3", + "lodash": "3.6.0", + "wcwidth": "1.0.1" + }, "dependencies": { "colors": { "version": "1.0.3", @@ -450,7 +589,12 @@ "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=" + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" + } }, "clone": { "version": "1.0.2", @@ -460,7 +604,10 @@ "clui": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/clui/-/clui-0.3.1.tgz", - "integrity": "sha1-AT0ILOht2/BguG05J/iauMM79CM=" + "integrity": "sha1-AT0ILOht2/BguG05J/iauMM79CM=", + "requires": { + "cli-color": "0.3.2" + } }, "co": { "version": "4.6.0", @@ -486,13 +633,19 @@ "combined-stream": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=" + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "requires": { + "delayed-stream": "1.0.0" + } }, "commander": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true + "dev": true, + "requires": { + "graceful-readlink": "1.0.1" + } }, "concat-map": { "version": "0.0.1", @@ -503,7 +656,12 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", - "dev": true + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "typedarray": "0.0.6" + } }, "content-type": { "version": "1.0.2", @@ -519,13 +677,22 @@ "cryptiles": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=" + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "requires": { + "boom": "2.10.1" + } }, "csproj2ts": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/csproj2ts/-/csproj2ts-0.0.8.tgz", "integrity": "sha1-nRxxniDELM6MTeKQCO/DVUn9Em8=", "dev": true, + "requires": { + "es6-promise": "4.1.1", + "lodash": "4.17.4", + "semver": "5.3.0", + "xml2js": "0.4.17" + }, "dependencies": { "es6-promise": { "version": "4.1.1", @@ -545,17 +712,26 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true + "dev": true, + "requires": { + "array-find-index": "1.0.2" + } }, "d": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz", - "integrity": "sha1-2hhMU10Y2O57oqoim5FACfrhEwk=" + "integrity": "sha1-2hhMU10Y2O57oqoim5FACfrhEwk=", + "requires": { + "es5-ext": "0.10.24" + } }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "1.0.0" + }, "dependencies": { "assert-plus": { "version": "1.0.0", @@ -573,12 +749,19 @@ "version": "1.0.12", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", - "dev": true + "dev": true, + "requires": { + "get-stdin": "4.0.1", + "meow": "3.7.0" + } }, "debug": { "version": "2.6.8", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=" + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "requires": { + "ms": "2.0.0" + } }, "decamelize": { "version": "1.2.0", @@ -590,6 +773,9 @@ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-2.0.2.tgz", "integrity": "sha1-sbrAblbwp2d3aG1Qyf63XC7XZ5o=", "dev": true, + "requires": { + "type-detect": "3.0.0" + }, "dependencies": { "type-detect": { "version": "3.0.0", @@ -608,13 +794,25 @@ "defaults": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=" + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "requires": { + "clone": "1.0.2" + } }, "del": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "dev": true + "dev": true, + "requires": { + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.0", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.2.8" + } }, "delayed-stream": { "version": "1.0.0", @@ -631,7 +829,10 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true + "dev": true, + "requires": { + "repeating": "2.0.1" + } }, "detect-newline": { "version": "2.1.0", @@ -649,13 +850,20 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", - "dev": true + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } }, "ecc-jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "optional": true + "optional": true, + "requires": { + "jsbn": "0.1.1" + } }, "ee-first": { "version": "1.1.1", @@ -671,22 +879,37 @@ "error-ex": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=" + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "requires": { + "is-arrayish": "0.2.1" + } }, "es5-ext": { "version": "0.10.24", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.24.tgz", - "integrity": "sha1-pVh3yZJLwMjZvTwsvhdJWsFwmxQ=" + "integrity": "sha1-pVh3yZJLwMjZvTwsvhdJWsFwmxQ=", + "requires": { + "es6-iterator": "2.0.1", + "es6-symbol": "3.1.1" + } }, "es6-iterator": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz", "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=", + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.24", + "es6-symbol": "3.1.1" + }, "dependencies": { "d": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=" + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "requires": { + "es5-ext": "0.10.24" + } } } }, @@ -695,12 +918,23 @@ "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.24", + "es6-iterator": "2.0.1", + "es6-set": "0.1.5", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + }, "dependencies": { "d": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "dev": true + "dev": true, + "requires": { + "es5-ext": "0.10.24" + } } } }, @@ -715,12 +949,22 @@ "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.24", + "es6-iterator": "2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + }, "dependencies": { "d": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "dev": true + "dev": true, + "requires": { + "es5-ext": "0.10.24" + } } } }, @@ -728,11 +972,18 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.24" + }, "dependencies": { "d": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=" + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "requires": { + "es5-ext": "0.10.24" + } } } }, @@ -740,16 +991,31 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-0.1.4.tgz", "integrity": "sha1-cGzvnpmqI2undmwjnIueKG6n0ig=", + "requires": { + "d": "0.1.1", + "es5-ext": "0.10.24", + "es6-iterator": "0.1.3", + "es6-symbol": "2.0.1" + }, "dependencies": { "es6-iterator": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-0.1.3.tgz", - "integrity": "sha1-1vWLjE/EE8JJtLqhl2j45NfIlE4=" + "integrity": "sha1-1vWLjE/EE8JJtLqhl2j45NfIlE4=", + "requires": { + "d": "0.1.1", + "es5-ext": "0.10.24", + "es6-symbol": "2.0.1" + } }, "es6-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz", - "integrity": "sha1-dhtcZ8/U8dGK+yNPaR1nhoLLO/M=" + "integrity": "sha1-dhtcZ8/U8dGK+yNPaR1nhoLLO/M=", + "requires": { + "d": "0.1.1", + "es5-ext": "0.10.24" + } } } }, @@ -763,6 +1029,13 @@ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", "dev": true, + "requires": { + "esprima": "2.7.3", + "estraverse": "1.9.3", + "esutils": "2.0.2", + "optionator": "0.8.2", + "source-map": "0.2.0" + }, "dependencies": { "esprima": { "version": "2.7.3", @@ -781,7 +1054,10 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", "dev": true, - "optional": true + "optional": true, + "requires": { + "amdefine": "1.0.1" + } } } }, @@ -790,18 +1066,33 @@ "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", "dev": true, + "requires": { + "es6-map": "0.1.5", + "es6-weak-map": "2.0.2", + "esrecurse": "4.2.0", + "estraverse": "4.2.0" + }, "dependencies": { "d": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "dev": true + "dev": true, + "requires": { + "es5-ext": "0.10.24" + } }, "es6-weak-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", - "dev": true + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.24", + "es6-iterator": "2.0.1", + "es6-symbol": "3.1.1" + } } } }, @@ -810,12 +1101,56 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", "dev": true, + "requires": { + "babel-code-frame": "6.22.0", + "chalk": "1.1.3", + "concat-stream": "1.6.0", + "debug": "2.6.8", + "doctrine": "2.0.0", + "escope": "3.6.0", + "espree": "3.4.3", + "esquery": "1.0.0", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "glob": "7.1.2", + "globals": "9.18.0", + "ignore": "3.3.3", + "imurmurhash": "0.1.4", + "inquirer": "0.12.0", + "is-my-json-valid": "2.16.0", + "is-resolvable": "1.0.0", + "js-yaml": "3.9.0", + "json-stable-stringify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.13.1", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "1.2.1", + "progress": "1.1.8", + "require-uncached": "1.0.3", + "shelljs": "0.7.6", + "strip-bom": "3.0.0", + "strip-json-comments": "2.0.1", + "table": "3.8.3", + "text-table": "0.2.0", + "user-home": "2.0.0" + }, "dependencies": { "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } }, "cli-width": { "version": "2.1.0", @@ -827,13 +1162,33 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", - "dev": true + "dev": true, + "requires": { + "ansi-escapes": "1.4.0", + "ansi-regex": "2.1.1", + "chalk": "1.1.3", + "cli-cursor": "1.0.2", + "cli-width": "2.1.0", + "figures": "1.7.0", + "lodash": "4.13.1", + "readline2": "1.0.1", + "run-async": "0.1.0", + "rx-lite": "3.1.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "through": "2.3.8" + } }, "readline2": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", - "dev": true + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "mute-stream": "0.0.5" + } }, "rx-lite": { "version": "3.1.2", @@ -853,7 +1208,11 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz", "integrity": "sha1-KRC1zNSc6JPC//+qtP2LOjG4I3Q=", - "dev": true + "dev": true, + "requires": { + "acorn": "5.1.1", + "acorn-jsx": "3.0.1" + } }, "esprima": { "version": "2.7.0", @@ -864,13 +1223,20 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", - "dev": true + "dev": true, + "requires": { + "estraverse": "4.2.0" + } }, "esrecurse": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", - "dev": true + "dev": true, + "requires": { + "estraverse": "4.2.0", + "object-assign": "4.1.1" + } }, "estraverse": { "version": "4.2.0", @@ -888,11 +1254,18 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.24" + }, "dependencies": { "d": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=" + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "requires": { + "es5-ext": "0.10.24" + } } } }, @@ -917,12 +1290,18 @@ "expand-brackets": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=" + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "requires": { + "is-posix-bracket": "0.1.1" + } }, "expand-range": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=" + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "requires": { + "fill-range": "2.2.3" + } }, "extend": { "version": "3.0.1", @@ -932,7 +1311,10 @@ "extglob": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=" + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "requires": { + "is-extglob": "1.0.0" + } }, "extsprintf": { "version": "1.0.2", @@ -949,18 +1331,29 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", - "dev": true + "dev": true, + "requires": { + "websocket-driver": "0.6.5" + } }, "figures": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=" + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" + } }, "file-entry-cache": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "dev": true + "dev": true, + "requires": { + "flat-cache": "1.2.2", + "object-assign": "4.1.1" + } }, "file-sync-cmp": { "version": "0.1.1", @@ -981,24 +1374,45 @@ "fill-range": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", - "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=" + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.7", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + } }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=" + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } }, "findup-sync": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", "dev": true, + "requires": { + "glob": "5.0.15" + }, "dependencies": { "glob": { "version": "5.0.15", "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true + "dev": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.2", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } } } }, @@ -1006,7 +1420,13 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz", "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=", - "dev": true + "dev": true, + "requires": { + "circular-json": "0.3.1", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" + } }, "for-in": { "version": "1.0.2", @@ -1016,7 +1436,10 @@ "for-own": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=" + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "requires": { + "for-in": "1.0.2" + } }, "forever-agent": { "version": "0.6.1", @@ -1026,7 +1449,12 @@ "form-data": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=" + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.15" + } }, "fs.realpath": { "version": "1.0.0", @@ -1038,6 +1466,10 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz", "integrity": "sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw==", "optional": true, + "requires": { + "nan": "2.6.2", + "node-pre-gyp": "0.6.36" + }, "dependencies": { "abbrev": { "version": "1.1.0", @@ -1047,7 +1479,11 @@ "ajv": { "version": "4.11.8", "bundled": true, - "optional": true + "optional": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } }, "ansi-regex": { "version": "2.1.1", @@ -1061,7 +1497,11 @@ "are-we-there-yet": { "version": "1.1.4", "bundled": true, - "optional": true + "optional": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.2.9" + } }, "asn1": { "version": "0.2.3", @@ -1095,19 +1535,32 @@ "bcrypt-pbkdf": { "version": "1.0.1", "bundled": true, - "optional": true + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } }, "block-stream": { "version": "0.0.9", - "bundled": true + "bundled": true, + "requires": { + "inherits": "2.0.3" + } }, "boom": { "version": "2.10.1", - "bundled": true + "bundled": true, + "requires": { + "hoek": "2.16.3" + } }, "brace-expansion": { "version": "1.1.7", - "bundled": true + "bundled": true, + "requires": { + "balanced-match": "0.4.2", + "concat-map": "0.0.1" + } }, "buffer-shims": { "version": "1.0.0", @@ -1129,7 +1582,10 @@ }, "combined-stream": { "version": "1.0.5", - "bundled": true + "bundled": true, + "requires": { + "delayed-stream": "1.0.0" + } }, "concat-map": { "version": "0.0.1", @@ -1146,12 +1602,18 @@ "cryptiles": { "version": "2.0.5", "bundled": true, - "optional": true + "optional": true, + "requires": { + "boom": "2.10.1" + } }, "dashdash": { "version": "1.14.1", "bundled": true, "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, "dependencies": { "assert-plus": { "version": "1.0.0", @@ -1163,7 +1625,10 @@ "debug": { "version": "2.6.8", "bundled": true, - "optional": true + "optional": true, + "requires": { + "ms": "2.0.0" + } }, "deep-extend": { "version": "0.4.2", @@ -1182,7 +1647,10 @@ "ecc-jsbn": { "version": "0.1.1", "bundled": true, - "optional": true + "optional": true, + "requires": { + "jsbn": "0.1.1" + } }, "extend": { "version": "3.0.1", @@ -1201,7 +1669,12 @@ "form-data": { "version": "2.1.4", "bundled": true, - "optional": true + "optional": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.15" + } }, "fs.realpath": { "version": "1.0.0", @@ -1209,22 +1682,46 @@ }, "fstream": { "version": "1.0.11", - "bundled": true + "bundled": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.1" + } }, "fstream-ignore": { "version": "1.0.5", "bundled": true, - "optional": true + "optional": true, + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } }, "gauge": { "version": "2.7.4", "bundled": true, - "optional": true + "optional": true, + "requires": { + "aproba": "1.1.1", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } }, "getpass": { "version": "0.1.7", "bundled": true, "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, "dependencies": { "assert-plus": { "version": "1.0.0", @@ -1235,7 +1732,15 @@ }, "glob": { "version": "7.1.2", - "bundled": true + "bundled": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } }, "graceful-fs": { "version": "4.1.11", @@ -1249,7 +1754,11 @@ "har-validator": { "version": "4.2.1", "bundled": true, - "optional": true + "optional": true, + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } }, "has-unicode": { "version": "2.0.1", @@ -1259,7 +1768,13 @@ "hawk": { "version": "3.1.3", "bundled": true, - "optional": true + "optional": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } }, "hoek": { "version": "2.16.3", @@ -1268,11 +1783,20 @@ "http-signature": { "version": "1.1.1", "bundled": true, - "optional": true + "optional": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.0", + "sshpk": "1.13.0" + } }, "inflight": { "version": "1.0.6", - "bundled": true + "bundled": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } }, "inherits": { "version": "2.0.3", @@ -1285,7 +1809,10 @@ }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true + "bundled": true, + "requires": { + "number-is-nan": "1.0.1" + } }, "is-typedarray": { "version": "1.0.0", @@ -1304,7 +1831,10 @@ "jodid25519": { "version": "1.0.2", "bundled": true, - "optional": true + "optional": true, + "requires": { + "jsbn": "0.1.1" + } }, "jsbn": { "version": "0.1.1", @@ -1319,7 +1849,10 @@ "json-stable-stringify": { "version": "1.0.1", "bundled": true, - "optional": true + "optional": true, + "requires": { + "jsonify": "0.0.0" + } }, "json-stringify-safe": { "version": "5.0.1", @@ -1335,6 +1868,12 @@ "version": "1.4.0", "bundled": true, "optional": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.0.2", + "json-schema": "0.2.3", + "verror": "1.3.6" + }, "dependencies": { "assert-plus": { "version": "1.0.0", @@ -1349,11 +1888,17 @@ }, "mime-types": { "version": "2.1.15", - "bundled": true + "bundled": true, + "requires": { + "mime-db": "1.27.0" + } }, "minimatch": { "version": "3.0.4", - "bundled": true + "bundled": true, + "requires": { + "brace-expansion": "1.1.7" + } }, "minimist": { "version": "0.0.8", @@ -1361,7 +1906,10 @@ }, "mkdirp": { "version": "0.5.1", - "bundled": true + "bundled": true, + "requires": { + "minimist": "0.0.8" + } }, "ms": { "version": "2.0.0", @@ -1371,17 +1919,38 @@ "node-pre-gyp": { "version": "0.6.36", "bundled": true, - "optional": true + "optional": true, + "requires": { + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.0", + "rc": "1.2.1", + "request": "2.81.0", + "rimraf": "2.6.1", + "semver": "5.3.0", + "tar": "2.2.1", + "tar-pack": "3.4.0" + } }, "nopt": { "version": "4.0.1", "bundled": true, - "optional": true + "optional": true, + "requires": { + "abbrev": "1.1.0", + "osenv": "0.1.4" + } }, "npmlog": { "version": "4.1.0", "bundled": true, - "optional": true + "optional": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } }, "number-is-nan": { "version": "1.0.1", @@ -1399,7 +1968,10 @@ }, "once": { "version": "1.4.0", - "bundled": true + "bundled": true, + "requires": { + "wrappy": "1.0.2" + } }, "os-homedir": { "version": "1.0.2", @@ -1414,7 +1986,11 @@ "osenv": { "version": "0.1.4", "bundled": true, - "optional": true + "optional": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } }, "path-is-absolute": { "version": "1.0.1", @@ -1443,6 +2019,12 @@ "version": "1.2.1", "bundled": true, "optional": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, "dependencies": { "minimist": { "version": "1.2.0", @@ -1453,16 +2035,52 @@ }, "readable-stream": { "version": "2.2.9", - "bundled": true + "bundled": true, + "requires": { + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "1.0.1", + "util-deprecate": "1.0.2" + } }, "request": { "version": "2.81.0", "bundled": true, - "optional": true + "optional": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.15", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.0.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.2", + "tunnel-agent": "0.6.0", + "uuid": "3.0.1" + } }, "rimraf": { "version": "2.6.1", - "bundled": true + "bundled": true, + "requires": { + "glob": "7.1.2" + } }, "safe-buffer": { "version": "5.0.1", @@ -1486,12 +2104,26 @@ "sntp": { "version": "1.0.9", "bundled": true, - "optional": true + "optional": true, + "requires": { + "hoek": "2.16.3" + } }, "sshpk": { "version": "1.13.0", "bundled": true, "optional": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jodid25519": "1.0.2", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, "dependencies": { "assert-plus": { "version": "1.0.0", @@ -1502,11 +2134,19 @@ }, "string_decoder": { "version": "1.0.1", - "bundled": true + "bundled": true, + "requires": { + "safe-buffer": "5.0.1" + } }, "string-width": { "version": "1.0.2", - "bundled": true + "bundled": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } }, "stringstream": { "version": "0.0.5", @@ -1515,7 +2155,10 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true + "bundled": true, + "requires": { + "ansi-regex": "2.1.1" + } }, "strip-json-comments": { "version": "2.0.1", @@ -1524,22 +2167,43 @@ }, "tar": { "version": "2.2.1", - "bundled": true + "bundled": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } }, "tar-pack": { "version": "3.4.0", "bundled": true, - "optional": true + "optional": true, + "requires": { + "debug": "2.6.8", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.2.9", + "rimraf": "2.6.1", + "tar": "2.2.1", + "uid-number": "0.0.6" + } }, "tough-cookie": { "version": "2.3.2", "bundled": true, - "optional": true + "optional": true, + "requires": { + "punycode": "1.4.1" + } }, "tunnel-agent": { "version": "0.6.0", "bundled": true, - "optional": true + "optional": true, + "requires": { + "safe-buffer": "5.0.1" + } }, "tweetnacl": { "version": "0.14.5", @@ -1563,12 +2227,18 @@ "verror": { "version": "1.3.6", "bundled": true, - "optional": true + "optional": true, + "requires": { + "extsprintf": "1.0.2" + } }, "wide-align": { "version": "1.1.2", "bundled": true, - "optional": true + "optional": true, + "requires": { + "string-width": "1.0.2" + } }, "wrappy": { "version": "1.0.2", @@ -1579,7 +2249,10 @@ "gaze": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.0.tgz", - "integrity": "sha1-dNP/sBEO3nFcnxW7pWxum4UdPOA=" + "integrity": "sha1-dNP/sBEO3nFcnxW7pWxum4UdPOA=", + "requires": { + "globule": "1.2.0" + } }, "generate-function": { "version": "2.0.0", @@ -1591,7 +2264,10 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "dev": true + "dev": true, + "requires": { + "is-property": "1.0.2" + } }, "get-caller-file": { "version": "1.0.2", @@ -1620,6 +2296,9 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "1.0.0" + }, "dependencies": { "assert-plus": { "version": "1.0.0", @@ -1632,23 +2311,41 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + }, "dependencies": { "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==" + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "1.1.8" + } } } }, "glob-base": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=" + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "requires": { + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + } }, "glob-parent": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=" + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "requires": { + "is-glob": "2.0.1" + } }, "globals": { "version": "9.18.0", @@ -1660,12 +2357,25 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true + "dev": true, + "requires": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } }, "globule": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz", "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=", + "requires": { + "glob": "7.1.2", + "lodash": "4.17.4", + "minimatch": "3.0.2" + }, "dependencies": { "lodash": { "version": "4.17.4", @@ -1696,18 +2406,50 @@ "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.1.tgz", "integrity": "sha1-6HeHZOlEsY8yuw8QuQeEdcnftWs=", "dev": true, + "requires": { + "coffee-script": "1.10.0", + "dateformat": "1.0.12", + "eventemitter2": "0.4.14", + "exit": "0.1.2", + "findup-sync": "0.3.0", + "glob": "7.0.6", + "grunt-cli": "1.2.0", + "grunt-known-options": "1.1.0", + "grunt-legacy-log": "1.0.0", + "grunt-legacy-util": "1.0.0", + "iconv-lite": "0.4.18", + "js-yaml": "3.5.5", + "minimatch": "3.0.2", + "nopt": "3.0.6", + "path-is-absolute": "1.0.1", + "rimraf": "2.2.8" + }, "dependencies": { "glob": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", - "dev": true + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.2", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } }, "grunt-cli": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", "integrity": "sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg=", - "dev": true + "dev": true, + "requires": { + "findup-sync": "0.3.0", + "grunt-known-options": "1.1.0", + "nopt": "3.0.6", + "resolve": "1.1.7" + } }, "iconv-lite": { "version": "0.4.18", @@ -1719,7 +2461,11 @@ "version": "3.5.5", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.5.5.tgz", "integrity": "sha1-A3fDgBfKvHMisNH7zSWkkWQfL74=", - "dev": true + "dev": true, + "requires": { + "argparse": "1.0.9", + "esprima": "2.7.0" + } }, "resolve": { "version": "1.1.7", @@ -1734,6 +2480,10 @@ "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-1.0.0.tgz", "integrity": "sha1-ay7ZQRfix//jLuBFeMlv5GJam20=", "dev": true, + "requires": { + "async": "1.5.2", + "rimraf": "2.6.1" + }, "dependencies": { "async": { "version": "1.5.2", @@ -1745,7 +2495,10 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", - "dev": true + "dev": true, + "requires": { + "glob": "7.1.2" + } } } }, @@ -1754,12 +2507,23 @@ "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz", "integrity": "sha1-cGDGWB6QS4qw0A8HbgqPbj58NXM=", "dev": true, + "requires": { + "chalk": "1.1.3", + "file-sync-cmp": "0.1.1" + }, "dependencies": { "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } } } }, @@ -1768,6 +2532,12 @@ "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.0.0.tgz", "integrity": "sha1-hKGnodar0m7VaEE0lscxM+mQAY8=", "dev": true, + "requires": { + "async": "1.5.2", + "gaze": "1.1.0", + "lodash": "3.10.1", + "tiny-lr": "0.2.1" + }, "dependencies": { "async": { "version": "1.5.2", @@ -1794,6 +2564,13 @@ "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-1.0.0.tgz", "integrity": "sha1-+4bxgJhHvAfcR4Q/ns1srLYt8tU=", "dev": true, + "requires": { + "colors": "1.1.2", + "grunt-legacy-log-utils": "1.0.0", + "hooker": "0.2.3", + "lodash": "3.10.1", + "underscore.string": "3.2.3" + }, "dependencies": { "lodash": { "version": "3.10.1", @@ -1808,12 +2585,23 @@ "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-1.0.0.tgz", "integrity": "sha1-p7ji0Ps1taUPSvmG/BEnSevJbz0=", "dev": true, + "requires": { + "chalk": "1.1.3", + "lodash": "4.3.0" + }, "dependencies": { "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } }, "lodash": { "version": "4.3.0", @@ -1828,6 +2616,15 @@ "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.0.0.tgz", "integrity": "sha1-OGqnjcbtUJhsKxiVcmWxtIq7m4Y=", "dev": true, + "requires": { + "async": "1.5.2", + "exit": "0.1.2", + "getobject": "0.1.0", + "hooker": "0.2.3", + "lodash": "4.3.0", + "underscore.string": "3.2.3", + "which": "1.2.14" + }, "dependencies": { "async": { "version": "1.5.2", @@ -1847,13 +2644,31 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/grunt-shell/-/grunt-shell-1.3.0.tgz", "integrity": "sha1-3lYGCpNN+OzuZAdLYcYwSQDXEVg=", - "dev": true + "dev": true, + "requires": { + "chalk": "1.1.0", + "npm-run-path": "1.0.0", + "object-assign": "4.1.1" + } }, "grunt-ts": { "version": "6.0.0-beta.16", "resolved": "https://registry.npmjs.org/grunt-ts/-/grunt-ts-6.0.0-beta.16.tgz", "integrity": "sha1-wC9P+cgRAE7suTOJBaBds/hpIMM=", "dev": true, + "requires": { + "chokidar": "1.7.0", + "csproj2ts": "0.0.8", + "detect-indent": "4.0.0", + "detect-newline": "2.1.0", + "es6-promise": "0.1.2", + "jsmin2": "1.2.1", + "lodash": "4.17.4", + "ncp": "0.5.1", + "rimraf": "2.2.6", + "semver": "5.3.0", + "strip-bom": "2.0.0" + }, "dependencies": { "lodash": { "version": "4.17.4", @@ -1874,6 +2689,12 @@ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.10.tgz", "integrity": "sha1-PTDHGLCaPZbyPqTMH0A8TTup/08=", "dev": true, + "requires": { + "async": "1.5.2", + "optimist": "0.6.1", + "source-map": "0.4.4", + "uglify-js": "2.8.29" + }, "dependencies": { "async": { "version": "1.5.2", @@ -1885,7 +2706,10 @@ "version": "0.4.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true + "dev": true, + "requires": { + "amdefine": "1.0.1" + } } } }, @@ -1897,12 +2721,19 @@ "har-validator": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", - "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=" + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } }, "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=" + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "2.1.1" + } }, "has-flag": { "version": "1.0.0", @@ -1913,7 +2744,13 @@ "hawk": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=" + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } }, "hoek": { "version": "2.16.3", @@ -1935,12 +2772,21 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", - "dev": true + "dev": true, + "requires": { + "inherits": "2.0.3", + "statuses": "1.3.1" + } }, "http-signature": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=" + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.0", + "sshpk": "1.13.1" + } }, "iconv-lite": { "version": "0.4.11", @@ -1963,12 +2809,19 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true + "dev": true, + "requires": { + "repeating": "2.0.1" + } }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=" + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } }, "inherits": { "version": "2.0.3", @@ -1979,6 +2832,18 @@ "version": "0.9.0", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.9.0.tgz", "integrity": "sha1-c2bjijMeYZBJWKzlstpKml9jZ5g=", + "requires": { + "ansi-regex": "2.1.1", + "chalk": "1.1.0", + "cli-width": "1.1.1", + "figures": "1.7.0", + "lodash": "3.10.1", + "readline2": "0.1.1", + "run-async": "0.1.0", + "rx-lite": "2.5.2", + "strip-ansi": "3.0.1", + "through": "2.3.8" + }, "dependencies": { "lodash": { "version": "3.10.1", @@ -1998,36 +2863,65 @@ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" }, "ios-device-lib": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/ios-device-lib/-/ios-device-lib-0.4.7.tgz", - "integrity": "sha1-E3G8cfGwnbICdtLcA1BR8OCTsIg=", - "dependencies": { - "node-uuid": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz", - "integrity": "sha1-baWhdmjEs91ZYjvaEc9/pMH2Cm8=" - } + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/ios-device-lib/-/ios-device-lib-0.4.8.tgz", + "integrity": "sha1-VJJWQk+3HzrCnJHuxTqVYrybxhY=", + "requires": { + "bufferpack": "0.0.6", + "node-uuid": "1.3.3" } }, "ios-mobileprovision-finder": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/ios-mobileprovision-finder/-/ios-mobileprovision-finder-1.0.9.tgz", "integrity": "sha1-Hc80ywKeP+oMhSkmu79K6GTswKc=", + "requires": { + "chalk": "1.1.3", + "plist": "2.1.0", + "yargs": "6.6.0" + }, "dependencies": { "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=" + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } }, "plist": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-2.1.0.tgz", - "integrity": "sha1-V8zbeggh3yGDEhejytVOPhRqECU=" + "integrity": "sha1-V8zbeggh3yGDEhejytVOPhRqECU=", + "requires": { + "base64-js": "1.2.0", + "xmlbuilder": "8.2.2", + "xmldom": "0.1.21" + } }, "yargs": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", - "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=" + "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", + "requires": { + "camelcase": "3.0.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "os-locale": "1.4.0", + "read-pkg-up": "1.0.1", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "1.0.2", + "which-module": "1.0.0", + "y18n": "3.2.1", + "yargs-parser": "4.2.1" + } } } }, @@ -2035,10 +2929,22 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/ios-sim-portable/-/ios-sim-portable-3.0.0.tgz", "integrity": "sha1-VOCVccIb+TNH8sfAFSno9fHEtfI=", + "requires": { + "bplist-parser": "https://github.com/telerik/node-bplist-parser/tarball/master", + "colors": "0.6.2", + "lodash": "3.2.0", + "osenv": "0.1.3", + "plist": "1.1.0", + "shelljs": "0.7.0", + "yargs": "4.7.1" + }, "dependencies": { "bplist-parser": { "version": "https://github.com/telerik/node-bplist-parser/tarball/master", - "integrity": "sha1-X7BVlo1KqtkGi+pkMyFRiYKHtFY=" + "integrity": "sha1-X7BVlo1KqtkGi+pkMyFRiYKHtFY=", + "requires": { + "big-integer": "1.6.23" + } }, "colors": { "version": "0.6.2", @@ -2058,17 +2964,41 @@ "shelljs": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.0.tgz", - "integrity": "sha1-P28uSWXOxWX2X/OGHWRPh5KBpXY=" + "integrity": "sha1-P28uSWXOxWX2X/OGHWRPh5KBpXY=", + "requires": { + "glob": "7.1.2", + "interpret": "1.0.3", + "rechoir": "0.6.2" + } }, "yargs": { "version": "4.7.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.7.1.tgz", - "integrity": "sha1-5gQyZYozh/8mnAKOrN5KUS5Djf8=" + "integrity": "sha1-5gQyZYozh/8mnAKOrN5KUS5Djf8=", + "requires": { + "camelcase": "3.0.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "lodash.assign": "4.2.0", + "os-locale": "1.4.0", + "pkg-conf": "1.1.3", + "read-pkg-up": "1.0.1", + "require-main-filename": "1.0.1", + "set-blocking": "1.0.0", + "string-width": "1.0.2", + "window-size": "0.2.0", + "y18n": "3.2.1", + "yargs-parser": "2.4.1" + } }, "yargs-parser": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz", - "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=" + "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=", + "requires": { + "camelcase": "3.0.0", + "lodash.assign": "4.2.0" + } } } }, @@ -2080,7 +3010,10 @@ "is-binary-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=" + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "requires": { + "binary-extensions": "1.8.0" + } }, "is-buffer": { "version": "1.1.5", @@ -2090,7 +3023,10 @@ "is-builtin-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=" + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "requires": { + "builtin-modules": "1.1.1" + } }, "is-dotfile": { "version": "1.0.3", @@ -2100,7 +3036,10 @@ "is-equal-shallow": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=" + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "requires": { + "is-primitive": "2.0.0" + } }, "is-extendable": { "version": "0.1.1", @@ -2116,23 +3055,38 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=" + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.1" + } }, "is-glob": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=" + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "requires": { + "is-extglob": "1.0.0" + } }, "is-my-json-valid": { "version": "2.16.0", "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz", "integrity": "sha1-8Hndm/2uZe4gOKrorLyGqxCeNpM=", "dev": true, + "requires": { + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "jsonpointer": "4.0.1", + "xtend": "4.0.1" + }, "dependencies": { "xtend": { "version": "4.0.1", @@ -2145,7 +3099,10 @@ "is-number": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=" + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "requires": { + "kind-of": "3.2.2" + } }, "is-path-cwd": { "version": "1.0.0", @@ -2157,13 +3114,19 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", - "dev": true + "dev": true, + "requires": { + "is-path-inside": "1.0.0" + } }, "is-path-inside": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", - "dev": true + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } }, "is-posix-bracket": { "version": "0.1.1", @@ -2185,7 +3148,10 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", - "dev": true + "dev": true, + "requires": { + "tryit": "1.0.3" + } }, "is-typedarray": { "version": "1.0.0", @@ -2211,7 +3177,10 @@ "isobject": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=" + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } }, "isstream": { "version": "0.1.2", @@ -2223,6 +3192,22 @@ "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", "dev": true, + "requires": { + "abbrev": "1.0.9", + "async": "1.5.2", + "escodegen": "1.8.1", + "esprima": "2.7.0", + "glob": "5.0.15", + "handlebars": "4.0.10", + "js-yaml": "3.9.0", + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "once": "1.4.0", + "resolve": "1.1.7", + "supports-color": "3.2.3", + "which": "1.2.14", + "wordwrap": "1.0.0" + }, "dependencies": { "abbrev": { "version": "1.0.9", @@ -2240,7 +3225,14 @@ "version": "5.0.15", "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true + "dev": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.2", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } }, "resolve": { "version": "1.1.7", @@ -2252,7 +3244,10 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true + "dev": true, + "requires": { + "has-flag": "1.0.0" + } } } }, @@ -2267,6 +3262,10 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.9.0.tgz", "integrity": "sha512-0LoUNELX4S+iofCT8f4uEHIiRBR+c2AINyC8qRWfC6QNruLtxVZRJaPcu/xwMgFIgDxF25tGHaDjvxzJCNE9yw==", "dev": true, + "requires": { + "argparse": "1.0.9", + "esprima": "4.0.0" + }, "dependencies": { "esprima": { "version": "4.0.0", @@ -2296,7 +3295,10 @@ "json-stable-stringify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=" + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "requires": { + "jsonify": "0.0.0" + } }, "json-stringify-safe": { "version": "5.0.1", @@ -2324,6 +3326,12 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.0.2", + "json-schema": "0.2.3", + "verror": "1.3.6" + }, "dependencies": { "assert-plus": { "version": "1.0.0", @@ -2335,7 +3343,10 @@ "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=" + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.5" + } }, "lazy-cache": { "version": "1.0.4", @@ -2347,13 +3358,20 @@ "lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=" + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "1.0.0" + } }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } }, "livereload-js": { "version": "2.2.2", @@ -2364,7 +3382,14 @@ "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=" + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + } }, "lockfile": { "version": "1.0.1", @@ -2385,7 +3410,11 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "dev": true + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash.keys": "3.1.2" + } }, "lodash._basecopy": { "version": "3.0.1", @@ -2420,7 +3449,12 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", - "dev": true + "dev": true, + "requires": { + "lodash._baseassign": "3.2.0", + "lodash._basecreate": "3.0.3", + "lodash._isiterateecall": "3.0.9" + } }, "lodash.isarguments": { "version": "3.1.0", @@ -2438,7 +3472,12 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } }, "lodash.toarray": { "version": "4.4.0", @@ -2448,7 +3487,12 @@ "log4js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/log4js/-/log4js-1.0.1.tgz", - "integrity": "sha1-+vZMEFa2NSpfu/CpO2Gpl5qEIsw=" + "integrity": "sha1-+vZMEFa2NSpfu/CpO2Gpl5qEIsw=", + "requires": { + "debug": "2.6.8", + "semver": "5.3.0", + "streamroller": "0.2.2" + } }, "longest": { "version": "1.0.1", @@ -2460,12 +3504,19 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true + "dev": true, + "requires": { + "currently-unhandled": "0.4.1", + "signal-exit": "3.0.2" + } }, "lru-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=" + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "requires": { + "es5-ext": "0.10.24" + } }, "map-obj": { "version": "1.0.1", @@ -2482,11 +3533,25 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-2.0.0.tgz", "integrity": "sha1-Xq9Wi+ZvaGVBr6UqVYKAMQox3i0=", + "requires": { + "cardinal": "1.0.0", + "chalk": "1.1.3", + "cli-table": "https://github.com/telerik/cli-table/tarball/v0.3.1.2", + "lodash.assign": "4.2.0", + "node-emoji": "1.7.0" + }, "dependencies": { "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=" + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } } } }, @@ -2499,13 +3564,34 @@ "memoizee": { "version": "0.3.10", "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.3.10.tgz", - "integrity": "sha1-TsoNiu057J0Bf0xcLy9kMvQuXI8=" + "integrity": "sha1-TsoNiu057J0Bf0xcLy9kMvQuXI8=", + "requires": { + "d": "0.1.1", + "es5-ext": "0.10.24", + "es6-weak-map": "0.1.4", + "event-emitter": "0.3.5", + "lru-queue": "0.1.0", + "next-tick": "0.2.2", + "timers-ext": "0.1.2" + } }, "meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, + "requires": { + "camelcase-keys": "2.1.0", + "decamelize": "1.2.0", + "loud-rejection": "1.6.0", + "map-obj": "1.0.1", + "minimist": "1.2.0", + "normalize-package-data": "2.4.0", + "object-assign": "4.1.1", + "read-pkg-up": "1.0.1", + "redent": "1.0.0", + "trim-newlines": "1.0.0" + }, "dependencies": { "minimist": { "version": "1.2.0", @@ -2518,7 +3604,22 @@ "micromatch": { "version": "2.3.11", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=" + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.3" + } }, "mime-db": { "version": "1.27.0", @@ -2528,12 +3629,18 @@ "mime-types": { "version": "2.1.15", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", - "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=" + "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", + "requires": { + "mime-db": "1.27.0" + } }, "minimatch": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.2.tgz", - "integrity": "sha1-DzmKcwDqRB6cNIyD2Yq4ydv5xAo=" + "integrity": "sha1-DzmKcwDqRB6cNIyD2Yq4ydv5xAo=", + "requires": { + "brace-expansion": "1.1.8" + } }, "minimist": { "version": "0.0.8", @@ -2543,25 +3650,52 @@ "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=" + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } }, "mocha": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.1.2.tgz", "integrity": "sha1-Ufk7Qyv34bF1/8Iog8zQvjLbprU=", "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.2.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.5", + "glob": "7.0.5", + "growl": "1.9.2", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + }, "dependencies": { "debug": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "dev": true + "dev": true, + "requires": { + "ms": "0.7.1" + } }, "glob": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.5.tgz", "integrity": "sha1-tCAqaQmbu00pKnwblbZoK2fr3JU=", - "dev": true + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.2", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } }, "ms": { "version": "0.7.1", @@ -2573,7 +3707,10 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", - "dev": true + "dev": true, + "requires": { + "has-flag": "1.0.0" + } } } }, @@ -2618,7 +3755,11 @@ "node-emoji": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.7.0.tgz", - "integrity": "sha512-dYx345sjhPJUpWaVQKjP0/43y+nTcfBRTZfSciM3ZEbRGaU/9AKaHBPf7AJ9vOKcK0W3v67AgI4m4oo02NLHhQ==" + "integrity": "sha512-dYx345sjhPJUpWaVQKjP0/43y+nTcfBRTZfSciM3ZEbRGaU/9AKaHBPf7AJ9vOKcK0W3v67AgI4m4oo02NLHhQ==", + "requires": { + "lodash.toarray": "4.4.0", + "string.prototype.codepointat": "0.2.0" + } }, "node-uuid": { "version": "1.3.3", @@ -2634,23 +3775,38 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true + "dev": true, + "requires": { + "abbrev": "1.1.0" + } }, "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==" + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "requires": { + "hosted-git-info": "2.5.0", + "is-builtin-module": "1.0.0", + "semver": "5.3.0", + "validate-npm-package-license": "3.0.1" + } }, "normalize-path": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=" + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "1.0.2" + } }, "npm-run-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-1.0.0.tgz", "integrity": "sha1-9cMr9ZX+ga6Sfa7FLoL4sACsPI8=", - "dev": true + "dev": true, + "requires": { + "path-key": "1.0.0" + } }, "number-is-nan": { "version": "1.0.1", @@ -2675,18 +3831,28 @@ "object.omit": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=" + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + } }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true + "dev": true, + "requires": { + "ee-first": "1.1.1" + } }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=" + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } }, "onetime": { "version": "1.1.0", @@ -2704,6 +3870,10 @@ "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, + "requires": { + "minimist": "0.0.8", + "wordwrap": "0.0.3" + }, "dependencies": { "wordwrap": { "version": "0.0.3", @@ -2717,7 +3887,15 @@ "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + } }, "os-homedir": { "version": "1.0.2", @@ -2727,7 +3905,10 @@ "os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=" + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "1.0.0" + } }, "os-tmpdir": { "version": "1.0.2", @@ -2737,17 +3918,30 @@ "osenv": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.3.tgz", - "integrity": "sha1-g88FxtZFj8TVrGNi6jJdkvJ1Qhc=" + "integrity": "sha1-g88FxtZFj8TVrGNi6jJdkvJ1Qhc=", + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } }, "parse-glob": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=" + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "requires": { + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" + } }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=" + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "1.3.1" + } }, "parseurl": { "version": "1.3.1", @@ -2758,7 +3952,10 @@ "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=" + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "2.0.1" + } }, "path-is-absolute": { "version": "1.0.1", @@ -2785,7 +3982,12 @@ "path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=" + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } }, "pathval": { "version": "1.1.0", @@ -2821,17 +4023,32 @@ "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=" + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "2.0.4" + } }, "pkg-conf": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-1.1.3.tgz", - "integrity": "sha1-N45W1v0T6Iv7b0ol33qD+qvduls=" + "integrity": "sha1-N45W1v0T6Iv7b0ol33qD+qvduls=", + "requires": { + "find-up": "1.1.2", + "load-json-file": "1.1.0", + "object-assign": "4.1.1", + "symbol": "0.2.3" + } }, "plist": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-1.1.0.tgz", "integrity": "sha1-/2cIWQyXzEOOe8Rd5SUb1yXz+J0=", + "requires": { + "base64-js": "0.0.6", + "util-deprecate": "1.0.0", + "xmlbuilder": "2.2.1", + "xmldom": "0.1.21" + }, "dependencies": { "base64-js": { "version": "0.0.6", @@ -2846,7 +4063,10 @@ "xmlbuilder": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.2.1.tgz", - "integrity": "sha1-kyZDDxMNh0NdTECGZDqikm4QWjI=" + "integrity": "sha1-kyZDDxMNh0NdTECGZDqikm4QWjI=", + "requires": { + "lodash-node": "2.4.1" + } } } }, @@ -2854,6 +4074,10 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/plist-merge-patch/-/plist-merge-patch-0.1.0.tgz", "integrity": "sha1-eOTAlL6dtLpHWdpktvGkz2rTIJE=", + "requires": { + "lodash": "4.17.4", + "plist": "2.1.0" + }, "dependencies": { "lodash": { "version": "4.17.4", @@ -2863,7 +4087,12 @@ "plist": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-2.1.0.tgz", - "integrity": "sha1-V8zbeggh3yGDEhejytVOPhRqECU=" + "integrity": "sha1-V8zbeggh3yGDEhejytVOPhRqECU=", + "requires": { + "base64-js": "1.2.0", + "xmlbuilder": "8.2.2", + "xmldom": "0.1.21" + } } } }, @@ -2871,6 +4100,13 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/plistlib/-/plistlib-0.2.1.tgz", "integrity": "sha1-RukssEmKCjrtO+5bXE2IKQVNqGw=", + "requires": { + "async": "0.2.10", + "moment": "2.4.0", + "node-xml": "1.0.2", + "underscore": "1.5.2", + "xml": "0.0.12" + }, "dependencies": { "moment": { "version": "2.4.0", @@ -2910,7 +4146,12 @@ "progress-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.1.1.tgz", - "integrity": "sha1-nsvxh5MsSUHVUCGRkNdN7ArEX1Q=" + "integrity": "sha1-nsvxh5MsSUHVUCGRkNdN7ArEX1Q=", + "requires": { + "single-line-log": "0.3.1", + "speedometer": "0.1.4", + "through2": "0.2.3" + } }, "properties-parser": { "version": "0.2.3", @@ -2936,23 +4177,36 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, "dependencies": { "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "3.2.2" + }, "dependencies": { "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=" + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.5" + } } } }, "kind-of": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=" + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "1.1.5" + } } } }, @@ -2961,6 +4215,11 @@ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", "dev": true, + "requires": { + "bytes": "2.4.0", + "iconv-lite": "0.4.13", + "unpipe": "1.0.0" + }, "dependencies": { "bytes": { "version": "2.4.0", @@ -2979,27 +4238,55 @@ "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=" + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" + } }, "read-pkg-up": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=" + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + } }, "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==" + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } }, "readdirp": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", - "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=" + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "requires": { + "graceful-fs": "4.1.11", + "minimatch": "3.0.2", + "readable-stream": "2.3.3", + "set-immediate-shim": "1.0.1" + } }, "readline2": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/readline2/-/readline2-0.1.1.tgz", "integrity": "sha1-mUQ7pug7gw7zBRv9fcJBqCco1Wg=", + "requires": { + "mute-stream": "0.0.4", + "strip-ansi": "2.0.1" + }, "dependencies": { "ansi-regex": { "version": "1.1.1", @@ -3014,25 +4301,38 @@ "strip-ansi": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz", - "integrity": "sha1-32LBqpTtLxFOHQ8h/R1QSCt5pg4=" + "integrity": "sha1-32LBqpTtLxFOHQ8h/R1QSCt5pg4=", + "requires": { + "ansi-regex": "1.1.1" + } } } }, "rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=" + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "requires": { + "resolve": "1.3.3" + } }, "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true + "dev": true, + "requires": { + "indent-string": "2.1.0", + "strip-indent": "1.0.1" + } }, "redeyed": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-1.0.1.tgz", "integrity": "sha1-6WwZO0DAgWsArshCaY5hGF5VSYo=", + "requires": { + "esprima": "3.0.0" + }, "dependencies": { "esprima": { "version": "3.0.0", @@ -3044,7 +4344,11 @@ "regex-cache": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz", - "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU=" + "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU=", + "requires": { + "is-equal-shallow": "0.1.3", + "is-primitive": "2.0.0" + } }, "remove-trailing-separator": { "version": "1.0.2", @@ -3065,12 +4369,39 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true + "dev": true, + "requires": { + "is-finite": "1.0.2" + } }, "request": { "version": "2.81.0", "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", - "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=" + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.15", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.2", + "tunnel-agent": "0.6.0", + "uuid": "3.0.1" + } }, "require-directory": { "version": "2.1.1", @@ -3086,12 +4417,19 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + } }, "resolve": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz", - "integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=" + "integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=", + "requires": { + "path-parse": "1.0.5" + } }, "resolve-from": { "version": "1.0.1", @@ -3103,14 +4441,21 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true + "dev": true, + "requires": { + "exit-hook": "1.1.1", + "onetime": "1.1.0" + } }, "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", "dev": true, - "optional": true + "optional": true, + "requires": { + "align-text": "0.1.4" + } }, "rimraf": { "version": "2.2.8", @@ -3120,7 +4465,10 @@ "run-async": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", - "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=" + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "requires": { + "once": "1.4.0" + } }, "rx-lite": { "version": "2.5.2", @@ -3156,25 +4504,41 @@ "shelljs": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.6.tgz", - "integrity": "sha1-N5zM+1a5HIYB5HkzVutTgpJN6a0=" + "integrity": "sha1-N5zM+1a5HIYB5HkzVutTgpJN6a0=", + "requires": { + "glob": "7.1.2", + "interpret": "1.0.3", + "rechoir": "0.6.2" + } }, "should": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/should/-/should-7.0.2.tgz", "integrity": "sha1-HfJOAqlxzx1ZWu0mfi2D8kw3CYM=", - "dev": true + "dev": true, + "requires": { + "should-equal": "0.5.0", + "should-format": "0.3.0", + "should-type": "0.2.0" + } }, "should-equal": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-0.5.0.tgz", "integrity": "sha1-x5fxNfMGf+tp6+zbMGscP+IbPm8=", - "dev": true + "dev": true, + "requires": { + "should-type": "0.2.0" + } }, "should-format": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/should-format/-/should-format-0.3.0.tgz", "integrity": "sha1-QgB+wKochupEkUzJER8bnyfTzqw=", - "dev": true + "dev": true, + "requires": { + "should-type": "0.2.0" + } }, "should-type": { "version": "0.2.0", @@ -3192,6 +4556,11 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-0.2.1.tgz", "integrity": "sha1-cXZts1IyaSjPOoByQrp2IyJjZyM=", + "requires": { + "bplist-creator": "0.0.7", + "bplist-parser": "0.1.1", + "plist": "2.0.1" + }, "dependencies": { "base64-js": { "version": "1.1.2", @@ -3201,12 +4570,20 @@ "bplist-parser": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.1.1.tgz", - "integrity": "sha1-1g1dzCDLptx+HymbNdPh+V2vuuY=" + "integrity": "sha1-1g1dzCDLptx+HymbNdPh+V2vuuY=", + "requires": { + "big-integer": "1.6.23" + } }, "plist": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/plist/-/plist-2.0.1.tgz", - "integrity": "sha1-CjLKlIGxw2TpLhjcVch23p0B2os=" + "integrity": "sha1-CjLKlIGxw2TpLhjcVch23p0B2os=", + "requires": { + "base64-js": "1.1.2", + "xmlbuilder": "8.2.2", + "xmldom": "0.1.21" + } } } }, @@ -3224,7 +4601,10 @@ "sntp": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=" + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "requires": { + "hoek": "2.16.3" + } }, "source-map": { "version": "0.5.6", @@ -3235,12 +4615,18 @@ "version": "0.4.15", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz", "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E=", - "dev": true + "dev": true, + "requires": { + "source-map": "0.5.6" + } }, "spdx-correct": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=" + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "requires": { + "spdx-license-ids": "1.2.2" + } }, "spdx-expression-parse": { "version": "1.0.4", @@ -3267,6 +4653,16 @@ "version": "1.13.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, "dependencies": { "assert-plus": { "version": "1.0.0", @@ -3290,6 +4686,11 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.2.2.tgz", "integrity": "sha1-oTQg4EFp5XPbBo9ZIO4j2IGr/jM=", + "requires": { + "date-format": "0.0.0", + "debug": "0.7.4", + "readable-stream": "1.1.14" + }, "dependencies": { "debug": { "version": "0.7.4", @@ -3304,7 +4705,13 @@ "readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=" + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } }, "string_decoder": { "version": "0.10.31", @@ -3316,12 +4723,20 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==" + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=" + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } }, "string.prototype.codepointat": { "version": "0.2.0", @@ -3336,18 +4751,27 @@ "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=" + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "2.1.1" + } }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=" + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "0.2.1" + } }, "strip-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true + "dev": true, + "requires": { + "get-stdin": "4.0.1" + } }, "strip-json-comments": { "version": "2.0.1", @@ -3370,6 +4794,14 @@ "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", "dev": true, + "requires": { + "ajv": "4.11.8", + "ajv-keywords": "1.5.1", + "chalk": "1.1.3", + "lodash": "4.13.1", + "slice-ansi": "0.0.4", + "string-width": "2.1.0" + }, "dependencies": { "ansi-regex": { "version": "3.0.0", @@ -3381,7 +4813,14 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } }, "is-fullwidth-code-point": { "version": "2.0.0", @@ -3394,12 +4833,19 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.0.tgz", "integrity": "sha1-AwZkVh/BRslCPsfZeP4kV0N/5tA=", "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, "dependencies": { "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } } } } @@ -3412,7 +4858,11 @@ "temp": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", - "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=" + "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", + "requires": { + "os-tmpdir": "1.0.2", + "rimraf": "2.2.8" + } }, "text-table": { "version": "0.2.0", @@ -3429,6 +4879,10 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", + "requires": { + "readable-stream": "1.1.14", + "xtend": "2.1.2" + }, "dependencies": { "isarray": { "version": "0.0.1", @@ -3438,7 +4892,13 @@ "readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=" + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } }, "string_decoder": { "version": "0.10.31", @@ -3451,6 +4911,10 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.2.tgz", "integrity": "sha1-YcxHp2wavTGV8UUn+XjViulMUgQ=", + "requires": { + "es5-ext": "0.10.24", + "next-tick": "1.0.0" + }, "dependencies": { "next-tick": { "version": "1.0.0", @@ -3464,12 +4928,23 @@ "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-0.2.1.tgz", "integrity": "sha1-s/26gC5dVqM8L28QeUsy5Hescp0=", "dev": true, + "requires": { + "body-parser": "1.14.2", + "debug": "2.2.0", + "faye-websocket": "0.10.0", + "livereload-js": "2.2.2", + "parseurl": "1.3.1", + "qs": "5.1.0" + }, "dependencies": { "debug": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "dev": true + "dev": true, + "requires": { + "ms": "0.7.1" + } }, "ms": { "version": "0.7.1", @@ -3488,7 +4963,10 @@ "tough-cookie": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", - "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=" + "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "requires": { + "punycode": "1.4.1" + } }, "trim-newlines": { "version": "1.0.0", @@ -3513,6 +4991,18 @@ "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.4.3.tgz", "integrity": "sha1-dhyEArgONHt3M6BDkKdXslNYBGc=", "dev": true, + "requires": { + "babel-code-frame": "6.22.0", + "colors": "1.1.2", + "commander": "2.9.0", + "diff": "3.3.0", + "glob": "7.1.2", + "minimatch": "3.0.4", + "resolve": "1.3.3", + "semver": "5.3.0", + "tslib": "1.7.1", + "tsutils": "2.6.1" + }, "dependencies": { "diff": { "version": "3.3.0", @@ -3524,7 +5014,10 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } } } }, @@ -3532,12 +5025,18 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.6.1.tgz", "integrity": "sha1-mOzwCVlPTkr4hAV75M9BpFC8djc=", - "dev": true + "dev": true, + "requires": { + "tslib": "1.7.1" + } }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=" + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.1.1" + } }, "tweetnacl": { "version": "0.14.5", @@ -3549,7 +5048,10 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } }, "type-detect": { "version": "4.0.3", @@ -3561,7 +5063,11 @@ "version": "1.6.15", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", - "dev": true + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "2.1.15" + } }, "typedarray": { "version": "0.0.6", @@ -3581,6 +5087,11 @@ "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", "dev": true, "optional": true, + "requires": { + "source-map": "0.5.6", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, "dependencies": { "camelcase": { "version": "1.2.1", @@ -3594,7 +5105,12 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", "dev": true, - "optional": true + "optional": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + } }, "window-size": { "version": "0.1.0", @@ -3615,7 +5131,13 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", "dev": true, - "optional": true + "optional": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } } } }, @@ -3652,7 +5174,10 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", - "dev": true + "dev": true, + "requires": { + "os-homedir": "1.0.2" + } }, "util-deprecate": { "version": "1.0.2", @@ -3667,23 +5192,36 @@ "validate-npm-package-license": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", - "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=" + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.4" + } }, "verror": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", - "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=" + "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", + "requires": { + "extsprintf": "1.0.2" + } }, "wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=" + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "requires": { + "defaults": "1.0.3" + } }, "websocket-driver": { "version": "0.6.5", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", - "dev": true + "dev": true, + "requires": { + "websocket-extensions": "0.1.1" + } }, "websocket-extensions": { "version": "0.1.1", @@ -3695,7 +5233,10 @@ "version": "1.2.14", "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", - "dev": true + "dev": true, + "requires": { + "isexe": "2.0.0" + } }, "which-module": { "version": "1.0.0", @@ -3721,7 +5262,11 @@ "wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=" + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + } }, "wrappy": { "version": "1.0.2", @@ -3732,16 +5277,26 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "dev": true + "dev": true, + "requires": { + "mkdirp": "0.5.1" + } }, "ws": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ws/-/ws-2.2.0.tgz", - "integrity": "sha1-MhinsevRWgnFa7EqPpQ6lg63veU=" + "integrity": "sha1-MhinsevRWgnFa7EqPpQ6lg63veU=", + "requires": { + "ultron": "1.1.0" + } }, "xcode": { "version": "https://github.com/NativeScript/node-xcode/archive/1.4.0.tar.gz", - "integrity": "sha1-pws+vYIXCzhk70x9hYy3eXmOHjg=" + "integrity": "sha1-pws+vYIXCzhk70x9hYy3eXmOHjg=", + "requires": { + "node-uuid": "1.3.3", + "pegjs": "0.6.2" + } }, "xml": { "version": "0.0.12", @@ -3753,12 +5308,19 @@ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", "dev": true, + "requires": { + "sax": "1.2.4", + "xmlbuilder": "4.2.1" + }, "dependencies": { "xmlbuilder": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", - "dev": true + "dev": true, + "requires": { + "lodash": "4.13.1" + } } } }, @@ -3779,7 +5341,10 @@ "xtend": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", - "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=" + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "requires": { + "object-keys": "0.4.0" + } }, "y18n": { "version": "3.2.1", @@ -3789,12 +5354,30 @@ "yargs": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.0.0.tgz", - "integrity": "sha1-kAR5306L9qsOhyFvXtKydguWg0U=" + "integrity": "sha1-kAR5306L9qsOhyFvXtKydguWg0U=", + "requires": { + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "os-locale": "1.4.0", + "read-pkg-up": "1.0.1", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "1.0.2", + "which-module": "1.0.0", + "window-size": "0.2.0", + "y18n": "3.2.1", + "yargs-parser": "4.2.1" + } }, "yargs-parser": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", - "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=" + "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", + "requires": { + "camelcase": "3.0.0" + } }, "zipstream": { "version": "https://github.com/Icenium/node-zipstream/tarball/master", diff --git a/package.json b/package.json index a1753d7b62..9b36164ee2 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "glob": "^7.0.3", "iconv-lite": "0.4.11", "inquirer": "0.9.0", - "ios-device-lib": "0.4.7", + "ios-device-lib": "0.4.8", "ios-mobileprovision-finder": "1.0.9", "ios-sim-portable": "~3.0.0", "lockfile": "1.0.1", From ac8cf2f88d16d8366325b547986102900e19e7f8 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Thu, 20 Jul 2017 17:41:25 +0300 Subject: [PATCH 154/212] Fix error handling during fast sync (#2991) During fast sync we shouldn't clear all listeners handling `error` event. If we do then any error raised is treated as unhandled and leads to a crash. --- lib/services/livesync/ios-device-livesync-service.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index 23ea563a4e..872d7af4e0 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -139,15 +139,16 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen payload.writeInt32BE(length, 0); payload.write(message, 4, length, "utf16le"); - this.socket.once("error", (error: Error) => { + const errorCallback = (error: Error) => { if (!isResolved) { isResolved = true; reject(error); } - }); + }; + this.socket.once("error", errorCallback); this.socket.write(payload, "utf16le", () => { - this.socket.removeAllListeners("error"); + this.socket.removeListener("error", errorCallback); if (!isResolved) { isResolved = true; From 199b293ebb6ebbf44d75b57f4ff8740ed23faa0a Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Thu, 20 Jul 2017 18:15:16 +0300 Subject: [PATCH 155/212] Move skipNativePrepare to deviceDescriptors (#2990) * Move skipNativePrepare to deviceDescriptors Each device can use different build types - cloud or local. So the `skipNativePrepare` option should be passed per each device, not for the whole livesync operation. * Validate native plugin prepare only when skipNativePrepare is not true --- lib/definitions/livesync.d.ts | 10 +++++----- lib/services/livesync/livesync-service.ts | 4 ++-- lib/services/platform-service.ts | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index d420e15a64..571b616525 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -88,6 +88,11 @@ interface ILiveSyncDeviceInfo { * In case it is not passed, the default output for local builds will be used. */ outputPath?: string; + + /** + * Whether to skip preparing the native platform. + */ + skipNativePrepare?: boolean; } /** @@ -120,11 +125,6 @@ interface ILiveSyncInfo { * Forces a build before the initial livesync. */ clean?: boolean; - - /** - * Whether to skip preparing the native platform. - */ - skipNativePrepare?: boolean; } interface ILatestAppPackageInstalledSettings extends IDictionary> { /* empty */ } diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index dae7ecf36a..8816c5686a 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -237,7 +237,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { deviceBuildInfoDescriptor, liveSyncData, settings - }, { skipNativePrepare: liveSyncData.skipNativePrepare }); + }, { skipNativePrepare: deviceBuildInfoDescriptor.skipNativePrepare }); const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ projectData, device, @@ -339,7 +339,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { deviceBuildInfoDescriptor, settings: latestAppPackageInstalledSettings, modifiedFiles: allModifiedFiles - }, { skipNativePrepare: liveSyncData.skipNativePrepare }); + }, { skipNativePrepare: deviceBuildInfoDescriptor.skipNativePrepare }); const service = this.getLiveSyncService(device.deviceInfo.platform); const settings: ILiveSyncWatchInfo = { diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 76e3bee56f..976c2a5e78 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -278,7 +278,6 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$errors.failWithoutHelp(`Unable to install dependencies. Make sure your package.json is valid and all dependencies are correct. Error is: ${err.message}`); } - await this.$pluginsService.validate(platformData, projectData); await this.ensurePlatformInstalled(platform, platformTemplate, projectData, config, nativePrepare); const bundle = appFilesUpdaterOptions.bundle; @@ -341,6 +340,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { } if (!changesInfo || changesInfo.modulesChanged || appFilesUpdaterOptions.bundle) { + await this.$pluginsService.validate(platformData, projectData); + let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); let lastModifiedTime = this.$fs.exists(appDestinationDirectoryPath) ? this.$fs.getFsStats(appDestinationDirectoryPath).mtime : null; From dc8af3dc3ba542e0bdf63f168d6cf7b0d83158ed Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Fri, 21 Jul 2017 13:36:51 +0300 Subject: [PATCH 156/212] Fix itunes error after run android (#2994) --- lib/commands/run.ts | 2 +- lib/common | 2 +- lib/services/livesync/livesync-command-helper.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/commands/run.ts b/lib/commands/run.ts index b966d73a3c..bdd9236c47 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -56,7 +56,7 @@ export class RunCommandBase implements ICommand { skipInferPlatform: !this.platform }); - await this.$devicesService.detectCurrentlyAttachedDevices(); + await this.$devicesService.detectCurrentlyAttachedDevices({ shouldReturnImmediateResult: false, platform: this.platform }); let devices = this.$devicesService.getDeviceInstances(); devices = devices.filter(d => !this.platform || d.deviceInfo.platform.toLowerCase() === this.platform.toLowerCase()); await this.$liveSyncCommandHelper.getDevicesLiveSyncInfo(devices, this.$liveSyncService, this.platform); diff --git a/lib/common b/lib/common index c33a233679..0ff4e5d858 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit c33a23367970966d1993749886fd689f3d048213 +Subproject commit 0ff4e5d8583f48f702d9a0c3c13c996f14b9694a diff --git a/lib/services/livesync/livesync-command-helper.ts b/lib/services/livesync/livesync-command-helper.ts index e37dd20b5f..784dcce81d 100644 --- a/lib/services/livesync/livesync-command-helper.ts +++ b/lib/services/livesync/livesync-command-helper.ts @@ -10,7 +10,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { } public async getDevicesLiveSyncInfo(devices: Mobile.IDevice[], liveSyncService: ILiveSyncService, platform: string): Promise { - await this.$devicesService.detectCurrentlyAttachedDevices(); + await this.$devicesService.detectCurrentlyAttachedDevices({ shouldReturnImmediateResult: false, platform }); const workingWithiOSDevices = !platform || this.$mobileHelper.isiOSPlatform(platform); const shouldKeepProcessAlive = this.$options.watch || !this.$options.justlaunch; From c2ff8122f6b65b1e6ed3cbaea396c4b48c6a7ff1 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Fri, 21 Jul 2017 15:47:13 +0300 Subject: [PATCH 157/212] Add error handling for debug sockets (#2995) --- lib/device-sockets/ios/socket-proxy-factory.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/device-sockets/ios/socket-proxy-factory.ts b/lib/device-sockets/ios/socket-proxy-factory.ts index 4f67a7a242..9a2cfb4022 100644 --- a/lib/device-sockets/ios/socket-proxy-factory.ts +++ b/lib/device-sockets/ios/socket-proxy-factory.ts @@ -113,6 +113,14 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact webSocket.send(buffer.toString(encoding)); }); + webSocket.on("error", err => { + this.$logger.trace("Error on debugger websocket", err); + }); + + deviceSocket.on("error", err => { + this.$logger.trace("Error on debugger deviceSocket", err); + }); + webSocket.on("message", (message, flags) => { let length = Buffer.byteLength(message, encoding); let payload = new Buffer(length + 4); @@ -121,9 +129,11 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact deviceSocket.write(payload); }); - deviceSocket.on("end", () => { + deviceSocket.on("close", () => { this.$logger.info("Backend socket closed!"); - process.exit(0); + if (!this.$options.watch) { + process.exit(0); + } }); webSocket.on("close", () => { From 54d1bcf0703af0ae306c5a7e4388a00343f6a33d Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Fri, 21 Jul 2017 18:38:20 +0300 Subject: [PATCH 158/212] Set correct timeout on LiveSync (#2996) --- lib/constants.ts | 1 + lib/services/ios-debug-service.ts | 7 +++---- lib/services/livesync/ios-device-livesync-service.ts | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/constants.ts b/lib/constants.ts index d7512a2f78..0b0d046507 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -17,6 +17,7 @@ export const LIVESYNC_EXCLUDED_FILE_PATTERNS = ["**/*.js.map", "**/*.ts"]; export const XML_FILE_EXTENSION = ".xml"; export const PLATFORMS_DIR_NAME = "platforms"; export const CODE_SIGN_ENTITLEMENTS = "CODE_SIGN_ENTITLEMENTS"; +export const AWAIT_NOTIFICATION_TIMEOUT_SECONDS = 9; export class PackageVersion { static NEXT = "next"; diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index b120065040..15c2f6bf1e 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -4,7 +4,7 @@ import * as path from "path"; import * as log4js from "log4js"; import { ChildProcess } from "child_process"; import { DebugServiceBase } from "./debug-service-base"; -import { CONNECTION_ERROR_EVENT_NAME } from "../constants"; +import { CONNECTION_ERROR_EVENT_NAME, AWAIT_NOTIFICATION_TIMEOUT_SECONDS } from "../constants"; import { getPidFromiOSSimulatorLogs } from "../common/helpers"; import byline = require("byline"); @@ -13,7 +13,6 @@ const inspectorBackendPort = 18181; const inspectorAppName = "NativeScript Inspector.app"; const inspectorNpmPackageName = "tns-ios-inspector"; const inspectorUiDir = "WebInspectorUI/"; -const TIMEOUT_SECONDS = 9; class IOSDebugService extends DebugServiceBase implements IPlatformDebugService { private _lldbProcess: ChildProcess; @@ -176,7 +175,7 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService } private async debugBrkCore(device: Mobile.IiOSDevice, debugData: IDebugData, debugOptions: IDebugOptions): Promise { - await this.$iOSSocketRequestExecutor.executeLaunchRequest(device.deviceInfo.identifier, TIMEOUT_SECONDS, TIMEOUT_SECONDS, debugData.applicationIdentifier, debugOptions.debugBrk); + await this.$iOSSocketRequestExecutor.executeLaunchRequest(device.deviceInfo.identifier, AWAIT_NOTIFICATION_TIMEOUT_SECONDS, AWAIT_NOTIFICATION_TIMEOUT_SECONDS, debugData.applicationIdentifier, debugOptions.debugBrk); return this.wireDebuggerClient(debugData, debugOptions, device); } @@ -188,7 +187,7 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService } private async deviceStartCore(device: Mobile.IiOSDevice, debugData: IDebugData, debugOptions: IDebugOptions): Promise { - await this.$iOSSocketRequestExecutor.executeAttachRequest(device, TIMEOUT_SECONDS, debugData.applicationIdentifier); + await this.$iOSSocketRequestExecutor.executeAttachRequest(device, AWAIT_NOTIFICATION_TIMEOUT_SECONDS, debugData.applicationIdentifier); return this.wireDebuggerClient(debugData, debugOptions, device); } diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index 872d7af4e0..976376a39f 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -37,8 +37,7 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen return false; } } else { - let timeout = 9000; - await this.$iOSSocketRequestExecutor.executeAttachRequest(this.device, timeout, projectId); + await this.$iOSSocketRequestExecutor.executeAttachRequest(this.device, constants.AWAIT_NOTIFICATION_TIMEOUT_SECONDS, projectId); this.socket = await this.device.connectToPort(IOSDeviceLiveSyncService.BACKEND_PORT); } From e5f5a54e00c3b770afb721200fdcdd936126e98e Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Sat, 22 Jul 2017 10:43:23 +0300 Subject: [PATCH 159/212] Fix run without platform on ios. (#2982) * Fix run without platform on ios. * Make `tns run` without platform working Introduce new method in livesync-command-helper to get platforms on which to execute action. Rename a method in the helper as its purpose is to execute livesync, not to get params for livesync operation. Pass correct args to detectCurrentlyAttachedDevices in debug command. Make `tns run` to fail in case there's no running emulators and no devices attached as we are unable to determine for which platform to stat emulator. --- docs/man_pages/project/testing/run.md | 21 ++++++++++++------- lib/commands/debug.ts | 4 ++-- lib/commands/run.ts | 4 ++-- lib/definitions/livesync.d.ts | 3 ++- .../livesync/livesync-command-helper.ts | 16 +++++++++++--- test/debug.ts | 2 +- 6 files changed, 33 insertions(+), 17 deletions(-) diff --git a/docs/man_pages/project/testing/run.md b/docs/man_pages/project/testing/run.md index 7d5172706f..851cf43611 100644 --- a/docs/man_pages/project/testing/run.md +++ b/docs/man_pages/project/testing/run.md @@ -3,19 +3,24 @@ run Usage | Synopsis ---|--- -<% if((isConsole && isMacOS) || isHtml) { %>General | `$ tns run `<% } %><% if(isConsole && (isLinux || isWindows)) { %>General | `$ tns run android`<% } %> +Run on all connected devices | `$ tns run [--release] [--justlaunch]` +Run on a selected connected device or running emulator. Will start emulator with specified `Device Identifier`, if not already running. | `$ tns run --device [--release] [--justlaunch]` -Runs your project on all connected devices or in native emulators for the selected platform.<% if(isMacOS) { %> You must specify the target platform on which you want to run your project.<% } %><% if(isConsole && (isLinux || isWindows)) { %>You must run `$ tns run android`<% } %> The command will prepare, build and deploy the app when necessary. By default listens for changes in your code, synchronizes those changes and refreshes all selected devices. +Runs your project on all connected devices or in native emulators for the selected platform.<% if(isConsole && (isLinux || isWindows)) { %>The command will work with all currently running Android devices and emulators.<% } %> The command will prepare, build and deploy the app when necessary. By default listens for changes in your code, synchronizes those changes and refreshes all selected devices. -<% if((isConsole && isMacOS) || isHtml) { %>### Attributes -`` is the target mobile platform on which you want to run your project. You can set the following target platforms. -* `android` - Runs your project on a connected Android device, in the native emulator. -* `ios` - Runs your project on a connected iOS device or in the iOS Simulator.<% } %> +### Options +* `--justlaunch` - If set, does not print the application output in the console. +* `--release` - If set, produces a release build. Otherwise, produces a debug build. +* `--device` - Specifies a connected device/emulator to start and run the app. + +### Attributes +* `` is the index or `Device Identifier` of the target device as listed by `$ tns device --available-devices` <% if(isHtml) { %> ### Command Limitations -* You can run `$ tns run ios` only on OS X systems. +* The command will work with all connected devices and running emulators on macOS. On Windows and Linux the command will work with Android devices only. +* In case a platform is not specified and there's no running devices and emulators, the command will fail. ### Related Commands @@ -35,4 +40,4 @@ Command | Description [test init](test-init.html) | Configures your project for unit testing with a selected framework. [test android](test-android.html) | Runs the tests in your project on Android devices or native emulators. [test ios](test-ios.html) | Runs the tests in your project on iOS devices or the iOS Simulator. -<% } %> \ No newline at end of file +<% } %> diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 5c1ff1b5a7..54f947de7e 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -37,7 +37,7 @@ export class DebugPlatformCommand implements ICommand { const selectedDeviceForDebug = await this.getDeviceForDebug(); - await this.$liveSyncCommandHelper.getDevicesLiveSyncInfo([selectedDeviceForDebug], this.$debugLiveSyncService, this.platform); + await this.$liveSyncCommandHelper.executeLiveSyncOperation([selectedDeviceForDebug], this.$debugLiveSyncService, this.platform); } public async getDeviceForDebug(): Promise { @@ -45,7 +45,7 @@ export class DebugPlatformCommand implements ICommand { this.$errors.fail(DebugCommandErrors.UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR); } - await this.$devicesService.detectCurrentlyAttachedDevices(); + await this.$devicesService.detectCurrentlyAttachedDevices({ platform: this.platform, shouldReturnImmediateResult: false }); if (this.$options.device) { const device = await this.$devicesService.getDevice(this.$options.device); diff --git a/lib/commands/run.ts b/lib/commands/run.ts index bdd9236c47..2f30032475 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -33,7 +33,7 @@ export class RunCommandBase implements ICommand { this.platform = this.$devicePlatformsConstants.Android; } - const availablePlatforms = this.platform ? [this.platform] : this.$platformsData.availablePlatforms; + const availablePlatforms = this.$liveSyncCommandHelper.getPlatformsForOperation(this.platform); for (let platform of availablePlatforms) { const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); const platformProjectService = platformData.platformProjectService; @@ -59,7 +59,7 @@ export class RunCommandBase implements ICommand { await this.$devicesService.detectCurrentlyAttachedDevices({ shouldReturnImmediateResult: false, platform: this.platform }); let devices = this.$devicesService.getDeviceInstances(); devices = devices.filter(d => !this.platform || d.deviceInfo.platform.toLowerCase() === this.platform.toLowerCase()); - await this.$liveSyncCommandHelper.getDevicesLiveSyncInfo(devices, this.$liveSyncService, this.platform); + await this.$liveSyncCommandHelper.executeLiveSyncOperation(devices, this.$liveSyncService, this.platform); } } diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 571b616525..11dea74d7e 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -256,5 +256,6 @@ interface IDevicePathProvider { } interface ILiveSyncCommandHelper { - getDevicesLiveSyncInfo(devices: Mobile.IDevice[], liveSyncService: ILiveSyncService, platform: string): Promise; + executeLiveSyncOperation(devices: Mobile.IDevice[], liveSyncService: ILiveSyncService, platform: string): Promise; + getPlatformsForOperation(platform: string): string[]; } diff --git a/lib/services/livesync/livesync-command-helper.ts b/lib/services/livesync/livesync-command-helper.ts index 784dcce81d..c896d6d478 100644 --- a/lib/services/livesync/livesync-command-helper.ts +++ b/lib/services/livesync/livesync-command-helper.ts @@ -6,10 +6,20 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { protected $devicesService: Mobile.IDevicesService, private $iosDeviceOperations: IIOSDeviceOperations, private $mobileHelper: Mobile.IMobileHelper, - private $platformsData: IPlatformsData) { + private $platformsData: IPlatformsData, + private $errors: IErrors) { } - public async getDevicesLiveSyncInfo(devices: Mobile.IDevice[], liveSyncService: ILiveSyncService, platform: string): Promise { + public getPlatformsForOperation(platform: string): string[] { + const availablePlatforms = platform ? [platform] : _.values(this.$platformsData.availablePlatforms); + return availablePlatforms; + } + + public async executeLiveSyncOperation(devices: Mobile.IDevice[], liveSyncService: ILiveSyncService, platform: string): Promise { + if (!devices || !devices.length) { + this.$errors.failWithoutHelp("Unable to find applicable devices to execute operation and unable to start emulator when platform is not specified."); + } + await this.$devicesService.detectCurrentlyAttachedDevices({ shouldReturnImmediateResult: false, platform }); const workingWithiOSDevices = !platform || this.$mobileHelper.isiOSPlatform(platform); @@ -75,7 +85,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { clean: true, }, this.$options.argv); - const availablePlatforms = platform ? [platform] : _.values(this.$platformsData.availablePlatforms); + const availablePlatforms = this.getPlatformsForOperation(platform); for (const currentPlatform of availablePlatforms) { await this.$platformService.deployPlatform(currentPlatform, this.$options, deployOptions, this.$projectData, this.$options); await this.$platformService.startApplication(currentPlatform, runPlatformOptions, this.$projectData.projectId); diff --git a/test/debug.ts b/test/debug.ts index 995c73c429..4a89464944 100644 --- a/test/debug.ts +++ b/test/debug.ts @@ -69,7 +69,7 @@ function createTestInjector(): IInjector { testInjector.register("prompter", {}); testInjector.registerCommand("debug|android", DebugAndroidCommand); testInjector.register("liveSyncCommandHelper", { - getDevicesLiveSyncInfo: async (): Promise => { + executeLiveSyncOperation: async (): Promise => { return null; } }); From abb0aa8bfeb7331c16f2a45c58ee9685bbd12aa3 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Sat, 22 Jul 2017 11:05:55 +0300 Subject: [PATCH 160/212] Set version to 3.1.3 and update CHANGELOG --- CHANGELOG.md | 22 ++++++++++++++++++++++ package.json | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b014a88ed..35a9bee4b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,28 @@ NativeScript CLI Changelog ================ +3.1.3 (2017, July 25) +== + +### New + +* [Implemented #2470](https://github.com/NativeScript/nativescript-cli/issues/2470): Establish a recommended workflow for simultaneous Android & iOS development - use `tns run` command. +* [Implemented #2873](https://github.com/NativeScript/nativescript-cli/issues/2873): Add official support for Node 8. +* [Implemented #2894](https://github.com/NativeScript/nativescript-cli/issues/2894): Support for Android 8 (API-26) and Android Build Tools 26.0.0. + +### Fixed +* [Fixed #2361](https://github.com/NativeScript/nativescript-cli/issues/2361): 'iTunes is not installed...' on Windows when using `tns run android`. +* [Fixed #2870](https://github.com/NativeScript/nativescript-cli/issues/2870): CLI can not create projects with custom templates when npm 5 is used . +* [Fixed #2871](https://github.com/NativeScript/nativescript-cli/issues/2871): CLI can not add platform from local tgz package when npm 5 is used . +* [Fixed #2889](https://github.com/NativeScript/nativescript-cli/issues/2889): `tns prepare ios --provision` starts simulator. +* [Fixed #2936](https://github.com/NativeScript/nativescript-cli/issues/2936): CFBundleURLTypes cannot be overridden from a plugin. +* [Fixed #2941](https://github.com/NativeScript/nativescript-cli/issues/2941): Duplicate console logs with LiveSync in 3.1. +* [Fixed #2965](https://github.com/NativeScript/nativescript-cli/issues/2965): Unmet peerDependencies break adding of platform. +* [Fixed #2966](https://github.com/NativeScript/nativescript-cli/issues/2966): Improve selection of device/emulator for debugging. +* [Fixed #2975](https://github.com/NativeScript/nativescript-cli/issues/2975): CLI Process hangs on native build failure. +* [Fixed #2986](https://github.com/NativeScript/nativescript-cli/issues/2986): Preparing a project for a platform causes changes for the other platform. +* [Fixed #2988](https://github.com/NativeScript/nativescript-cli/issues/2988): CLI fails with EPIPE during `$ tns run ios`. + 3.1.2 (2017, July 06) == diff --git a/package.json b/package.json index 9b36164ee2..4ec6eb3c85 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "3.2.0", + "version": "3.1.3", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { From 8512415dd105373823a2cb5e13ab8931010a6ad8 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Tue, 25 Jul 2017 13:36:18 +0300 Subject: [PATCH 161/212] Fix rebuilding application every time when a change is applied (#3002) In case you have built your application and you add a new npm package as dependency, the LiveSync will always rebuild the app, no matter of the change applied afterwards. The problem is in our project-changes-service that persists shasum of the package.json. However it is never reset, so we always have the hashed shasum of the package.json when the applcation has been build for the first time. Fix this by setting the correct value for projectFileHash - this way the checks are correct and applying change in .js (.xml, .css) files will not trigger rebuild of app. --- lib/services/project-changes-service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index 77c6d536ee..5fbfa2a892 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -108,6 +108,8 @@ export class ProjectChangesService implements IProjectChangesService { if (this._prepareInfo.changesRequireBuild) { this._prepareInfo.changesRequireBuildTime = this._prepareInfo.time; } + + this._prepareInfo.projectFileHash = this.getProjectFileStrippedHash(projectData, platform); } this._changesInfo.nativePlatformStatus = this._prepareInfo.nativePlatformStatus; From f51a72ce19226a6c80ea3d0ec330a52adada141f Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 25 Jul 2017 13:38:27 +0300 Subject: [PATCH 162/212] Set version to 3.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4ec6eb3c85..9b36164ee2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "3.1.3", + "version": "3.2.0", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { From b62c92597daba231990a17aa472f5d32835a6222 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Wed, 26 Jul 2017 18:18:43 +0300 Subject: [PATCH 163/212] Fix LiveSync on iOS when app is uninstalled manually (#3005) Whenever application is reinstalled on iOS device, we have to execute full sync operation due to iOS runtime requirements. However, in case the application is uninstalled manually during LiveSync, CLI will reinstall it, but the code that checks if a full sync is required will return false. The problem is that the `isRebuilt` property (renamed to `isReinstalled` now) really checks if the application has been rebuild during current check and expectedly this returns false. In order to fix this, change the return type of `ensureLatestAppPackageIsInstalledOnDevice`, so it will give the required information if a full sync should be executed. Also improve the information that we emit in liveSyncExecuted event: * add new property - `isFullSync` - it can be used to indicate the current operation * ensure the `syncedFiles` property contains the really synced files - for Android devices the full sync is not always a full sync - it checks the shasums of files and uploads only the modified ones. At the moment we've been returning all project files in the array, even when we upload only a few (or event none) of them. In order to fix this respect the return type of the `transferDirectory` method and use it in LiveSync operations. --- PublicAPI.md | 1 + lib/common | 2 +- lib/definitions/livesync.d.ts | 12 +++++++++++- lib/services/livesync/ios-livesync-service.ts | 2 +- lib/services/livesync/livesync-service.ts | 17 +++++++++++------ .../livesync/platform-livesync-service-base.ts | 15 +++++++++------ 6 files changed, 34 insertions(+), 15 deletions(-) diff --git a/PublicAPI.md b/PublicAPI.md index ce9e13e09c..0164703980 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -635,6 +635,7 @@ tns.liveSyncService.on("liveSyncStarted", data => { * Full paths to files synced during the operation. In case the `syncedFiles.length` is 0, the operation is "fullSync" (i.e. all project files are synced). */ syncedFiles: string[]; + isFullSync: boolean; } ``` diff --git a/lib/common b/lib/common index 0ff4e5d858..359f7508cb 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 0ff4e5d8583f48f702d9a0c3c13c996f14b9694a +Subproject commit 359f7508cbf174452c5afa213337805e1ab50860 diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 11dea74d7e..6f05f94aeb 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -149,6 +149,16 @@ interface IEnsureLatestAppPackageIsInstalledOnDeviceOptions { modifiedFiles?: string[]; } +/** + * Describes the action that has been executed during ensureLatestAppPackageIsInstalledOnDevice execution. + */ +interface IAppInstalledOnDeviceResult { + /** + * Defines if the app has been installed on device from the ensureLatestAppPackageIsInstalledOnDevice method. + */ + appInstalled: boolean; +} + /** * Describes LiveSync operations. */ @@ -187,7 +197,7 @@ interface ILiveSyncWatchInfo { projectData: IProjectData; filesToRemove: string[]; filesToSync: string[]; - isRebuilt: boolean; + isReinstalled: boolean; syncAllFiles: boolean; useLiveEdit?: boolean; } diff --git a/lib/services/livesync/ios-livesync-service.ts b/lib/services/livesync/ios-livesync-service.ts index 3efc0bb3e1..da1e921fe0 100644 --- a/lib/services/livesync/ios-livesync-service.ts +++ b/lib/services/livesync/ios-livesync-service.ts @@ -59,7 +59,7 @@ export class IOSLiveSyncService extends PlatformLiveSyncServiceBase implements I } public liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise { - if (liveSyncInfo.isRebuilt) { + if (liveSyncInfo.isReinstalled) { // In this case we should execute fullsync because iOS Runtime requires the full content of app dir to be extracted in the root of sync dir. return this.fullSync({ projectData: liveSyncInfo.projectData, device, syncAllFiles: liveSyncInfo.syncAllFiles, watch: true }); } else { diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 8816c5686a..e135a871ef 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -109,7 +109,8 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { projectDir: projectData.projectDir, applicationIdentifier: projectData.projectId, syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), - deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier + deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, + isFullSync: liveSyncResultInfo.isFullSync }); this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); @@ -154,8 +155,9 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { throw new Error(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); } - private async ensureLatestAppPackageIsInstalledOnDevice(options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions, nativePrepare?: INativePrepare): Promise { + private async ensureLatestAppPackageIsInstalledOnDevice(options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions, nativePrepare?: INativePrepare): Promise { const platform = options.device.deviceInfo.platform; + const appInstalledOnDeviceResult: IAppInstalledOnDeviceResult = { appInstalled: false }; if (options.preparedPlatforms.indexOf(platform) === -1) { options.preparedPlatforms.push(platform); // TODO: Pass provision and sdk as a fifth argument here @@ -167,7 +169,8 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const buildResult = await this.installedCachedAppPackage(platform, options); if (buildResult) { - return; + appInstalledOnDeviceResult.appInstalled = true; + return appInstalledOnDeviceResult; } // TODO: Pass provision and sdk as a fifth argument here @@ -176,7 +179,6 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { let action = LiveSyncTrackActionNames.LIVESYNC_OPERATION; if (shouldBuild) { pathToBuildItem = await options.deviceBuildInfoDescriptor.buildAction(); - // Is it possible to return shouldBuild for two devices? What about android device and android emulator? options.rebuiltInformation.push({ isEmulator: options.device.isEmulator, platform, pathToBuildItem }); action = LiveSyncTrackActionNames.LIVESYNC_OPERATION_BUILD; } @@ -186,7 +188,10 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const shouldInstall = await this.$platformService.shouldInstall(options.device, options.projectData, options.deviceBuildInfoDescriptor.outputPath); if (shouldInstall) { await this.$platformService.installApplication(options.device, { release: false }, options.projectData, pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); + appInstalledOnDeviceResult.appInstalled = true; } + + return appInstalledOnDeviceResult; } private async trackAction(action: string, platform: string, options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions): Promise { @@ -331,7 +336,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; const deviceBuildInfoDescriptor = _.find(liveSyncProcessInfo.deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - await this.ensureLatestAppPackageIsInstalledOnDevice({ + const appInstalledOnDeviceResult = await this.ensureLatestAppPackageIsInstalledOnDevice({ device, preparedPlatforms, rebuiltInformation, @@ -346,7 +351,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { projectData, filesToRemove: currentFilesToRemove, filesToSync: currentFilesToSync, - isRebuilt: !!_.find(rebuiltInformation, info => info.isEmulator === device.isEmulator && info.platform === device.deviceInfo.platform), + isReinstalled: appInstalledOnDeviceResult.appInstalled, syncAllFiles: liveSyncData.watchAllFiles, useLiveEdit: liveSyncData.useLiveEdit }; diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index 04a3883f20..c8beb26240 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -44,10 +44,10 @@ export abstract class PlatformLiveSyncServiceBase { const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, null, []); - await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, true); + const modifiedFilesData = await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, true); return { - modifiedFilesData: localToDevicePaths, + modifiedFilesData, isFullSync: true, deviceAppData }; @@ -96,19 +96,22 @@ export abstract class PlatformLiveSyncServiceBase { return { modifiedFilesData: modifiedLocalToDevicePaths, - isFullSync: liveSyncInfo.isRebuilt, + isFullSync: liveSyncInfo.isReinstalled, deviceAppData }; } - protected async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise { + protected async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise { + let transferredFiles = localToDevicePaths; if (isFullSync) { - await deviceAppData.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath); + transferredFiles = await deviceAppData.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath); } else { await deviceAppData.device.fileSystem.transferFiles(deviceAppData, localToDevicePaths); } - this.logFilesSyncInformation(localToDevicePaths, "Successfully transferred %s.", this.$logger.info); + this.logFilesSyncInformation(transferredFiles, "Successfully transferred %s.", this.$logger.info); + + return transferredFiles; } protected async getAppData(syncInfo: IFullSyncInfo): Promise { From 184a91b3c97b1e27ab90029d4c310cdd61906cd2 Mon Sep 17 00:00:00 2001 From: Nadezhda Atanasova Date: Thu, 27 Jul 2017 17:51:10 +0300 Subject: [PATCH 164/212] Respect configuration when preparing project files (#2958) --- lib/commands/test.ts | 11 ++++++++--- lib/common | 2 +- lib/definitions/platform.d.ts | 4 ++-- lib/definitions/plugins.d.ts | 4 ++-- lib/definitions/project.d.ts | 4 ++-- lib/providers/project-files-provider.ts | 4 ++-- lib/services/platform-service.ts | 19 ++++++++++--------- lib/services/plugins-service.ts | 8 ++++---- lib/services/test-execution-service.ts | 4 ++-- .../node-modules/node-modules-builder.ts | 8 ++++---- .../node-modules/node-modules-dest-copy.ts | 8 ++++---- test/plugin-prepare.ts | 6 +++--- test/plugins-service.ts | 2 +- test/project-files-provider.ts | 12 ++++++------ 14 files changed, 51 insertions(+), 45 deletions(-) diff --git a/lib/commands/test.ts b/lib/commands/test.ts index 9e6f197120..774d028ffc 100644 --- a/lib/commands/test.ts +++ b/lib/commands/test.ts @@ -1,9 +1,13 @@ +import * as helpers from "../common/helpers"; + function RunTestCommandFactory(platform: string) { return function RunTestCommand( + $options: IOptions, $testExecutionService: ITestExecutionService, $projectData: IProjectData) { $projectData.initializeProjectData(); - this.execute = (args: string[]): Promise => $testExecutionService.startTestRunner(platform, $projectData); + const projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: this.$options.release }); + this.execute = (args: string[]): Promise => $testExecutionService.startTestRunner(platform, $projectData, projectFilesConfig); this.allowedParameters = []; }; } @@ -12,9 +16,10 @@ $injector.registerCommand("dev-test|android", RunTestCommandFactory('android')); $injector.registerCommand("dev-test|ios", RunTestCommandFactory('iOS')); function RunKarmaTestCommandFactory(platform: string) { - return function RunKarmaTestCommand($testExecutionService: ITestExecutionService, $projectData: IProjectData) { + return function RunKarmaTestCommand($options: IOptions, $testExecutionService: ITestExecutionService, $projectData: IProjectData) { $projectData.initializeProjectData(); - this.execute = (args: string[]): Promise => $testExecutionService.startKarmaServer(platform, $projectData); + const projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: this.$options.release }); + this.execute = (args: string[]): Promise => $testExecutionService.startKarmaServer(platform, $projectData, projectFilesConfig); this.allowedParameters = []; }; } diff --git a/lib/common b/lib/common index 359f7508cb..fa89ea0c2d 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 359f7508cbf174452c5afa213337805e1ab50860 +Subproject commit fa89ea0c2d5eb555e703f878ce20edeccd4706c7 diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 5645e49f21..fed7c2fd15 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -287,8 +287,8 @@ interface IPlatformsData { } interface INodeModulesBuilder { - prepareNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData): Promise; - prepareJSNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData): Promise; + prepareNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise; + prepareJSNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise; cleanNodeModules(absoluteOutputPath: string, platform: string): void; } diff --git a/lib/definitions/plugins.d.ts b/lib/definitions/plugins.d.ts index 7feb3d5bc6..ded728e3cb 100644 --- a/lib/definitions/plugins.d.ts +++ b/lib/definitions/plugins.d.ts @@ -1,10 +1,10 @@ interface IPluginsService { add(plugin: string, projectData: IProjectData): Promise; // adds plugin by name, github url, local path and et. remove(pluginName: string, projectData: IProjectData): Promise; // removes plugin only by name - prepare(pluginData: IDependencyData, platform: string, projectData: IProjectData): Promise; + prepare(pluginData: IDependencyData, platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise; getAllInstalledPlugins(projectData: IProjectData): Promise; ensureAllDependenciesAreInstalled(projectData: IProjectData): Promise; - preparePluginScripts(pluginData: IPluginData, platform: string, projectData: IProjectData): void + preparePluginScripts(pluginData: IPluginData, platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): void /** * Returns all dependencies and devDependencies from pacakge.json file. diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 2656ddf251..b8a73670af 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -279,8 +279,8 @@ interface IAndroidProjectPropertiesManager { } interface ITestExecutionService { - startTestRunner(platform: string, projectData: IProjectData): Promise; - startKarmaServer(platform: string, projectData: IProjectData): Promise; + startTestRunner(platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise; + startKarmaServer(platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise; } /** diff --git a/lib/providers/project-files-provider.ts b/lib/providers/project-files-provider.ts index afe422d537..6a70fb2592 100644 --- a/lib/providers/project-files-provider.ts +++ b/lib/providers/project-files-provider.ts @@ -12,9 +12,9 @@ export class ProjectFilesProvider extends ProjectFilesProviderBase { private static INTERNAL_NONPROJECT_FILES = [ "**/*.ts" ]; - public mapFilePath(filePath: string, platform: string, projectData: IProjectData): string { + public mapFilePath(filePath: string, platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): string { let platformData = this.$platformsData.getPlatformData(platform.toLowerCase(), projectData); - let parsedFilePath = this.getPreparedFilePath(filePath); + let parsedFilePath = this.getPreparedFilePath(filePath, projectFilesConfig); let mappedFilePath = ""; if (parsedFilePath.indexOf(constants.NODE_MODULES_FOLDER_NAME) > -1) { let relativePath = path.relative(path.join(projectData.projectDir, constants.NODE_MODULES_FOLDER_NAME), parsedFilePath); diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 976c2a5e78..c829e1b02c 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -294,10 +294,11 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$logger.out("Preparing project..."); let platformData = this.$platformsData.getPlatformData(platform, projectData); - await this.preparePlatformCoreJS(platform, platformData, appFilesUpdaterOptions, projectData, platformSpecificData, changesInfo); + const projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: appFilesUpdaterOptions.release }); + await this.preparePlatformCoreJS(platform, platformData, appFilesUpdaterOptions, projectData, platformSpecificData, changesInfo, filesToSync, projectFilesConfig); if (!nativePrepare || !nativePrepare.skipNativePrepare) { - await this.preparePlatformCoreNative(platform, platformData, appFilesUpdaterOptions, projectData, platformSpecificData, changesInfo); + await this.preparePlatformCoreNative(platform, platformData, appFilesUpdaterOptions, projectData, platformSpecificData, changesInfo, projectFilesConfig); } let directoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); @@ -306,12 +307,12 @@ export class PlatformService extends EventEmitter implements IPlatformService { excludedDirs.push(constants.TNS_MODULES_FOLDER_NAME); } - this.$projectFilesManager.processPlatformSpecificFiles(directoryPath, platform, excludedDirs); + this.$projectFilesManager.processPlatformSpecificFiles(directoryPath, platform, projectFilesConfig, excludedDirs); this.$logger.out(`Project successfully prepared (${platform})`); } - private async preparePlatformCoreJS(platform: string, platformData: IPlatformData, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, changesInfo?: IProjectChangesInfo, filesToSync?: Array, ): Promise { + private async preparePlatformCoreJS(platform: string, platformData: IPlatformData, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, changesInfo?: IProjectChangesInfo, filesToSync?: Array, projectFilesConfig?: IProjectFilesConfig): Promise { if (!changesInfo || changesInfo.appFilesChanged) { await this.copyAppFiles(platformData, appFilesUpdaterOptions, projectData); @@ -324,11 +325,11 @@ export class PlatformService extends EventEmitter implements IPlatformService { } if (!changesInfo || changesInfo.modulesChanged) { - await this.copyTnsModules(platform, platformData, projectData); + await this.copyTnsModules(platform, platformData, projectData, projectFilesConfig); } } - public async preparePlatformCoreNative(platform: string, platformData: IPlatformData, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, changesInfo?: IProjectChangesInfo): Promise { + public async preparePlatformCoreNative(platform: string, platformData: IPlatformData, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, changesInfo?: IProjectChangesInfo, projectFilesConfig?: IProjectFilesConfig): Promise { if (changesInfo.hasChanges) { await this.cleanProject(platform, appFilesUpdaterOptions, platformData, projectData); } @@ -347,7 +348,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { let tnsModulesDestinationPath = path.join(appDestinationDirectoryPath, constants.TNS_MODULES_FOLDER_NAME); // Process node_modules folder - await this.$nodeModulesBuilder.prepareNodeModules(tnsModulesDestinationPath, platform, lastModifiedTime, projectData); + await this.$nodeModulesBuilder.prepareNodeModules(tnsModulesDestinationPath, platform, lastModifiedTime, projectData, projectFilesConfig); } if (!changesInfo || changesInfo.configChanged || changesInfo.modulesChanged) { @@ -385,14 +386,14 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - private async copyTnsModules(platform: string, platformData: IPlatformData, projectData: IProjectData): Promise { + private async copyTnsModules(platform: string, platformData: IPlatformData, projectData: IProjectData, projectFilesConfig?: IProjectFilesConfig): Promise { let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); let lastModifiedTime = this.$fs.exists(appDestinationDirectoryPath) ? this.$fs.getFsStats(appDestinationDirectoryPath).mtime : null; try { let tnsModulesDestinationPath = path.join(appDestinationDirectoryPath, constants.TNS_MODULES_FOLDER_NAME); // Process node_modules folder - await this.$nodeModulesBuilder.prepareJSNodeModules(tnsModulesDestinationPath, platform, lastModifiedTime, projectData); + await this.$nodeModulesBuilder.prepareJSNodeModules(tnsModulesDestinationPath, platform, lastModifiedTime, projectData, projectFilesConfig); } catch (error) { this.$logger.debug(error); shell.rm("-rf", appDestinationDirectoryPath); diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index 57d07d132b..0b460da5db 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -107,14 +107,14 @@ export class PluginsService implements IPluginsService { return await platformData.platformProjectService.validatePlugins(projectData); } - public async prepare(dependencyData: IDependencyData, platform: string, projectData: IProjectData): Promise { + public async prepare(dependencyData: IDependencyData, platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise { platform = platform.toLowerCase(); let platformData = this.$platformsData.getPlatformData(platform, projectData); let pluginData = this.convertToPluginData(dependencyData, projectData.projectDir); let appFolderExists = this.$fs.exists(path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME)); if (appFolderExists) { - this.preparePluginScripts(pluginData, platform, projectData); + this.preparePluginScripts(pluginData, platform, projectData, projectFilesConfig); await this.preparePluginNativeCode(pluginData, platform, projectData); // Show message @@ -122,7 +122,7 @@ export class PluginsService implements IPluginsService { } } - public preparePluginScripts(pluginData: IPluginData, platform: string, projectData: IProjectData): void { + public preparePluginScripts(pluginData: IPluginData, platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): void { let platformData = this.$platformsData.getPlatformData(platform, projectData); let pluginScriptsDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, "tns_modules"); let scriptsDestinationExists = this.$fs.exists(pluginScriptsDestinationPath); @@ -136,7 +136,7 @@ export class PluginsService implements IPluginsService { } //prepare platform speciffic files, .map and .ts files - this.$projectFilesManager.processPlatformSpecificFiles(pluginScriptsDestinationPath, platform); + this.$projectFilesManager.processPlatformSpecificFiles(pluginScriptsDestinationPath, platform, projectFilesConfig); } public async preparePluginNativeCode(pluginData: IPluginData, platform: string, projectData: IProjectData): Promise { diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index f6bcc41a45..376490882c 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -33,7 +33,7 @@ class TestExecutionService implements ITestExecutionService { public platform: string; - public async startTestRunner(platform: string, projectData: IProjectData): Promise { + public async startTestRunner(platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise { this.platform = platform; this.$options.justlaunch = true; await new Promise((resolve, reject) => { @@ -133,7 +133,7 @@ class TestExecutionService implements ITestExecutionService { }); } - public async startKarmaServer(platform: string, projectData: IProjectData): Promise { + public async startKarmaServer(platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise { platform = platform.toLowerCase(); this.platform = platform; diff --git a/lib/tools/node-modules/node-modules-builder.ts b/lib/tools/node-modules/node-modules-builder.ts index 0143d437ab..8e84bb8031 100644 --- a/lib/tools/node-modules/node-modules-builder.ts +++ b/lib/tools/node-modules/node-modules-builder.ts @@ -8,16 +8,16 @@ export class NodeModulesBuilder implements INodeModulesBuilder { private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder ) { } - public async prepareNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData): Promise { + public async prepareNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise { const productionDependencies = this.initialPrepareNodeModules(absoluteOutputPath, platform, lastModifiedTime, projectData); const npmPluginPrepare: NpmPluginPrepare = this.$injector.resolve(NpmPluginPrepare); - await npmPluginPrepare.preparePlugins(productionDependencies, platform, projectData); + await npmPluginPrepare.preparePlugins(productionDependencies, platform, projectData, projectFilesConfig); } - public async prepareJSNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData): Promise { + public async prepareJSNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise { const productionDependencies = this.initialPrepareNodeModules(absoluteOutputPath, platform, lastModifiedTime, projectData); const npmPluginPrepare: NpmPluginPrepare = this.$injector.resolve(NpmPluginPrepare); - await npmPluginPrepare.prepareJSPlugins(productionDependencies, platform, projectData); + await npmPluginPrepare.prepareJSPlugins(productionDependencies, platform, projectData, projectFilesConfig); } public cleanNodeModules(absoluteOutputPath: string, platform: string): void { diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts index 78d19c607c..f80b0f175b 100644 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ b/lib/tools/node-modules/node-modules-dest-copy.ts @@ -137,7 +137,7 @@ export class NpmPluginPrepare { return result; } - public async preparePlugins(dependencies: IDependencyData[], platform: string, projectData: IProjectData): Promise { + public async preparePlugins(dependencies: IDependencyData[], platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise { if (_.isEmpty(dependencies) || this.allPrepared(dependencies, platform, projectData)) { return; } @@ -155,10 +155,10 @@ export class NpmPluginPrepare { await this.afterPrepare(dependencies, platform, projectData); } - public async prepareJSPlugins(dependencies: IDependencyData[], platform: string, projectData: IProjectData): Promise { + public async prepareJSPlugins(dependencies: IDependencyData[], platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise { if (_.isEmpty(dependencies) || this.allPrepared(dependencies, platform, projectData)) { return; - } +} for (let dependencyKey in dependencies) { const dependency = dependencies[dependencyKey]; @@ -169,7 +169,7 @@ export class NpmPluginPrepare { let platformData = this.$platformsData.getPlatformData(platform, projectData); let appFolderExists = this.$fs.exists(path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME)); if (appFolderExists) { - this.$pluginsService.preparePluginScripts(pluginData, platform, projectData); + this.$pluginsService.preparePluginScripts(pluginData, platform, projectData, projectFilesConfig); // Show message this.$logger.out(`Successfully prepared plugin ${pluginData.name} for ${platform}.`); } diff --git a/test/plugin-prepare.ts b/test/plugin-prepare.ts index 6c07491663..bca777f0c8 100644 --- a/test/plugin-prepare.ts +++ b/test/plugin-prepare.ts @@ -28,7 +28,7 @@ class TestNpmPluginPrepare extends NpmPluginPrepare { describe("Plugin preparation", () => { it("skips prepare if no plugins", async () => { const pluginPrepare = new TestNpmPluginPrepare({}); - await pluginPrepare.preparePlugins([], "android", null); + await pluginPrepare.preparePlugins([], "android", null, {}); assert.deepEqual({}, pluginPrepare.preparedDependencies); }); @@ -42,7 +42,7 @@ describe("Plugin preparation", () => { nativescript: null, } ]; - await pluginPrepare.preparePlugins(testDependencies, "android", null); + await pluginPrepare.preparePlugins(testDependencies, "android", null, {}); assert.deepEqual({}, pluginPrepare.preparedDependencies); }); @@ -62,7 +62,7 @@ describe("Plugin preparation", () => { nativescript: null, } ]; - await pluginPrepare.preparePlugins(testDependencies, "android", null); + await pluginPrepare.preparePlugins(testDependencies, "android", null, {}); const prepareData = { "tns-core-modules-widgets": true, "nativescript-calendar": true }; assert.deepEqual(prepareData, pluginPrepare.preparedDependencies); }); diff --git a/test/plugins-service.ts b/test/plugins-service.ts index 4cad62b65b..2744d5f055 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -516,7 +516,7 @@ describe("Plugins service", () => { `\n@#[line:1,col:39].` + `\n@#[line:1,col:39].`; mockBeginCommand(testInjector, expectedErrorMessage); - await pluginsService.prepare(pluginJsonData, "android", projectData); + await pluginsService.prepare(pluginJsonData, "android", projectData, {}); }); }); }); diff --git a/test/project-files-provider.ts b/test/project-files-provider.ts index 8d21a50966..2c910275e7 100644 --- a/test/project-files-provider.ts +++ b/test/project-files-provider.ts @@ -57,37 +57,37 @@ describe("project-files-provider", () => { describe("mapFilePath", () => { it("returns file path from prepared project when path from app dir is passed", () => { let projectData: IProjectData = testInjector.resolve("projectData"); - let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "test.js"), "android", projectData); + let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "test.js"), "android", projectData, {}); assert.deepEqual(mappedFilePath, path.join(appDestinationDirectoryPath, "app", "test.js")); }); it("returns file path from prepared project when path from app/App_Resources/platform dir is passed", () => { let projectData: IProjectData = testInjector.resolve("projectData"); - let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "App_Resources", "android", "test.js"), "android", projectData); + let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "App_Resources", "android", "test.js"), "android", projectData, {}); assert.deepEqual(mappedFilePath, path.join(appResourcesDestinationDirectoryPath, "test.js")); }); it("returns null when path from app/App_Resources/android dir is passed and iOS platform is specified", () => { let projectData: IProjectData = testInjector.resolve("projectData"); - let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "App_Resources", "android", "test.js"), "iOS", projectData); + let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "App_Resources", "android", "test.js"), "iOS", projectData, {}); assert.deepEqual(mappedFilePath, null); }); it("returns null when path from app/App_Resources/ dir (not platform specific) is passed", () => { let projectData: IProjectData = testInjector.resolve("projectData"); - let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "App_Resources", "test.js"), "android", projectData); + let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "App_Resources", "test.js"), "android", projectData, {}); assert.deepEqual(mappedFilePath, null); }); it("returns file path from prepared project when path from app dir is passed and it contains platform in its name", () => { let projectData: IProjectData = testInjector.resolve("projectData"); - let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "test.android.js"), "android", projectData); + let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "test.android.js"), "android", projectData, {}); assert.deepEqual(mappedFilePath, path.join(appDestinationDirectoryPath, "app", "test.js")); }); it("returns file path from prepared project when path from app dir is passed and it contains configuration in its name", () => { let projectData: IProjectData = testInjector.resolve("projectData"); - let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "test.debug.js"), "android", projectData); + let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "test.debug.js"), "android", projectData, {}); assert.deepEqual(mappedFilePath, path.join(appDestinationDirectoryPath, "app", "test.js")); }); }); From afa5102d785dffdd0e486939f42e3a4b2b78bb8b Mon Sep 17 00:00:00 2001 From: Nadezhda Atanasova Date: Fri, 28 Jul 2017 15:44:38 +0300 Subject: [PATCH 165/212] "This" is not the correct property invoker in current context (#3013) Options are passed az function argument and should not be called with this. --- lib/commands/test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/commands/test.ts b/lib/commands/test.ts index 774d028ffc..b990a02d44 100644 --- a/lib/commands/test.ts +++ b/lib/commands/test.ts @@ -6,7 +6,7 @@ function RunTestCommandFactory(platform: string) { $testExecutionService: ITestExecutionService, $projectData: IProjectData) { $projectData.initializeProjectData(); - const projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: this.$options.release }); + const projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: $options.release }); this.execute = (args: string[]): Promise => $testExecutionService.startTestRunner(platform, $projectData, projectFilesConfig); this.allowedParameters = []; }; @@ -18,7 +18,7 @@ $injector.registerCommand("dev-test|ios", RunTestCommandFactory('iOS')); function RunKarmaTestCommandFactory(platform: string) { return function RunKarmaTestCommand($options: IOptions, $testExecutionService: ITestExecutionService, $projectData: IProjectData) { $projectData.initializeProjectData(); - const projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: this.$options.release }); + const projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: $options.release }); this.execute = (args: string[]): Promise => $testExecutionService.startKarmaServer(platform, $projectData, projectFilesConfig); this.allowedParameters = []; }; From ac4ee247a8e61c52a28bde7f24e06b99fcd5ecf0 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Fri, 28 Jul 2017 19:22:30 +0300 Subject: [PATCH 166/212] Ensure platforms directory exists prior to preparing (#3014) --- lib/common | 2 +- lib/services/platform-service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/common b/lib/common index fa89ea0c2d..512ae8ef52 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit fa89ea0c2d5eb555e703f878ce20edeccd4706c7 +Subproject commit 512ae8ef520deac25f0f3c3533edecb6b4534fc8 diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index c829e1b02c..1275f1e1db 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -133,8 +133,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { spinner.stop(); } + this.$fs.ensureDirectoryExists(platformPath); this.$logger.out("Project successfully created."); - } private async addPlatformCore(platformData: IPlatformData, frameworkDir: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, nativePrepare?: INativePrepare): Promise { From 2d02102602f39a5f8d665b247b22bfd38793c593 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Mon, 31 Jul 2017 01:13:44 +0300 Subject: [PATCH 167/212] Emit liveSyncStopped event when LiveSync operation is really stopped (#3011) In case `stopLiveSync` is called during livesync, we should await the current action and emit `liveSyncStopped` event. At the moment, we have the following behavior: 1. In case stopLiveSync is called with deviceIdentifiers, we emit `liveSyncStopped` immediately for all of them. However there may be pending actions, for example build + deploy for current device. We cannot stop the actions, so you receive `liveSyncStopped` and application is installed/updated on device after that. Fix this by persisting the current action and await it before emitting `liveSyncStopped` event. 2. In case we are currently rebuilding the application and some changes are applied during build, we'll update the `actionsChain`. At this point we'll have several actions in the chain. In case stopLiveSync method is called (without deviceIdentifiers), we will await all pending actions, which will result in awaiting the build, deploy and all other actions and we'll emit the `liveSyncStopped` after that. However we should not execute all pending actions - we should just execute the current one and skip the rest. In order to fix this, just set isStopped to true before awaiting the actions. This way only the current action will be executed. --- lib/definitions/livesync.d.ts | 1 + lib/services/livesync/livesync-service.ts | 35 +++++++++++++---------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 6f05f94aeb..7f504ff5a6 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -65,6 +65,7 @@ interface ILiveSyncProcessInfo { actionsChain: Promise; isStopped: boolean; deviceDescriptors: ILiveSyncDeviceInfo[]; + currentSyncAction: Promise; } /** diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index e135a871ef..08ea9ae0c6 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -44,15 +44,15 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectDir]; if (liveSyncProcessInfo) { - _.each(deviceIdentifiers, deviceId => { - _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => { - const shouldRemove = descriptor.identifier === deviceId; - if (shouldRemove) { - this.emit(LiveSyncEvents.liveSyncStopped, { projectDir, deviceIdentifier: descriptor.identifier }); - } + // In case we are coming from error during livesync, the current action is the one that erred (but we are still executing it), + // so we cannot await it as this will cause infinite loop. + const shouldAwaitPendingOperation = !stopOptions || stopOptions.shouldAwaitAllActions; - return shouldRemove; - }); + let removedDeviceIdentifiers: string[] = deviceIdentifiers || []; + + _.each(deviceIdentifiers, deviceId => { + removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => descriptor.identifier === deviceId) + .map(deviceDescriptor => deviceDescriptor.identifier); }); // In case deviceIdentifiers are not passed, we should stop the whole LiveSync. @@ -66,16 +66,12 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { } liveSyncProcessInfo.watcherInfo = null; + liveSyncProcessInfo.isStopped = true; - if (liveSyncProcessInfo.actionsChain && (!stopOptions || stopOptions.shouldAwaitAllActions)) { + if (liveSyncProcessInfo.actionsChain && shouldAwaitPendingOperation) { await liveSyncProcessInfo.actionsChain; } - _.each(liveSyncProcessInfo.deviceDescriptors, descriptor => { - this.emit(LiveSyncEvents.liveSyncStopped, { projectDir, deviceIdentifier: descriptor.identifier }); - }); - - liveSyncProcessInfo.isStopped = true; liveSyncProcessInfo.deviceDescriptors = []; // Kill typescript watcher @@ -85,7 +81,14 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { projectData } }); + } else if (liveSyncProcessInfo.currentSyncAction && shouldAwaitPendingOperation) { + await liveSyncProcessInfo.currentSyncAction; } + + // Emit LiveSync stopped when we've really stopped. + _.each(removedDeviceIdentifiers, deviceIdentifier => { + this.emit(LiveSyncEvents.liveSyncStopped, { projectDir, deviceIdentifier }); + }); } } @@ -139,6 +142,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { private setLiveSyncProcessInfo(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[]): void { this.liveSyncProcessesInfo[projectDir] = this.liveSyncProcessesInfo[projectDir] || Object.create(null); this.liveSyncProcessesInfo[projectDir].actionsChain = this.liveSyncProcessesInfo[projectDir].actionsChain || Promise.resolve(); + this.liveSyncProcessesInfo[projectDir].currentSyncAction = this.liveSyncProcessesInfo[projectDir].actionsChain; this.liveSyncProcessesInfo[projectDir].isStopped = false; const currentDeviceDescriptors = this.liveSyncProcessesInfo[projectDir].deviceDescriptors || []; @@ -446,7 +450,8 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { if (liveSyncInfo) { liveSyncInfo.actionsChain = liveSyncInfo.actionsChain.then(async () => { if (!liveSyncInfo.isStopped) { - const res = await action(); + liveSyncInfo.currentSyncAction = action(); + const res = await liveSyncInfo.currentSyncAction; return res; } }); From 03882730392808eb8904c67ccb133ffc6fd5c681 Mon Sep 17 00:00:00 2001 From: Plamen Petkov Date: Mon, 31 Jul 2017 08:06:56 +0300 Subject: [PATCH 168/212] move device discovery to debug command as it is done for run command (#3008) * move device discovery to debug command as it is done for run command * update after review --- lib/commands/debug.ts | 2 ++ lib/definitions/livesync.d.ts | 7 +++++++ lib/services/livesync/livesync-command-helper.ts | 2 -- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 54f947de7e..b0322844f6 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -37,6 +37,8 @@ export class DebugPlatformCommand implements ICommand { const selectedDeviceForDebug = await this.getDeviceForDebug(); + await this.$devicesService.detectCurrentlyAttachedDevices({ shouldReturnImmediateResult: false, platform: this.platform }); + await this.$liveSyncCommandHelper.executeLiveSyncOperation([selectedDeviceForDebug], this.$debugLiveSyncService, this.platform); } diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 7f504ff5a6..892b8d455a 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -267,6 +267,13 @@ interface IDevicePathProvider { } interface ILiveSyncCommandHelper { + /** + * Method sets up configuration, before calling livesync and expects that devices are already discovered. + * @param {Mobile.IDevice[]} devices List of discovered devices + * @param {ILiveSyncService} liveSyncService Service expected to do the actual livesyncing + * @param {string} platform The platform for which the livesync will be ran + * @returns {Promise} + */ executeLiveSyncOperation(devices: Mobile.IDevice[], liveSyncService: ILiveSyncService, platform: string): Promise; getPlatformsForOperation(platform: string): string[]; } diff --git a/lib/services/livesync/livesync-command-helper.ts b/lib/services/livesync/livesync-command-helper.ts index c896d6d478..b1854e4e7a 100644 --- a/lib/services/livesync/livesync-command-helper.ts +++ b/lib/services/livesync/livesync-command-helper.ts @@ -20,8 +20,6 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { this.$errors.failWithoutHelp("Unable to find applicable devices to execute operation and unable to start emulator when platform is not specified."); } - await this.$devicesService.detectCurrentlyAttachedDevices({ shouldReturnImmediateResult: false, platform }); - const workingWithiOSDevices = !platform || this.$mobileHelper.isiOSPlatform(platform); const shouldKeepProcessAlive = this.$options.watch || !this.$options.justlaunch; if (workingWithiOSDevices && shouldKeepProcessAlive) { From 0a7408aba37c4e82940c6d68ab13828f891ab601 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Thu, 3 Aug 2017 09:49:55 +0300 Subject: [PATCH 169/212] Emit liveSyncStopped properly when stopping on all devices (#3030) Whenever stopping on all devices we should emit `liveSyncStopped` for each device. --- lib/services/livesync/livesync-service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 08ea9ae0c6..1cdb9d6a1d 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -72,6 +72,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { await liveSyncProcessInfo.actionsChain; } + removedDeviceIdentifiers = _.map(liveSyncProcessInfo.deviceDescriptors, d => d.identifier); liveSyncProcessInfo.deviceDescriptors = []; // Kill typescript watcher From e35de421061258bac88e04c4a307d5e1b4afc4d9 Mon Sep 17 00:00:00 2001 From: Plamen Petkov Date: Fri, 4 Aug 2017 17:49:33 +0300 Subject: [PATCH 170/212] livesync deletes files and cleans temp folder afterwards (#3027) * livesync deletes files and cleans temp folder afterwards * Add await for livesync reload files. --- .../android-device-livesync-service.ts | 30 +++++++++++++------ .../platform-livesync-service-base.ts | 2 +- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/services/livesync/android-device-livesync-service.ts b/lib/services/livesync/android-device-livesync-service.ts index bf1c0edbd9..b50a35513e 100644 --- a/lib/services/livesync/android-device-livesync-service.ts +++ b/lib/services/livesync/android-device-livesync-service.ts @@ -36,14 +36,25 @@ export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase impl `${deviceProjectRootDirname}/sync`] ); + await this.reloadResources(deviceAppData, localToDevicePaths); + const canExecuteFastSync = !liveSyncInfo.isFullSync && !_.some(localToDevicePaths, (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.canExecuteFastSync(localToDevicePath.getLocalPath(), projectData, this.device.deviceInfo.platform)); - if (canExecuteFastSync) { - return this.reloadPage(deviceAppData, localToDevicePaths); + if (!canExecuteFastSync) { + return this.restartApplication(deviceAppData); } + } + + private async cleanLivesyncDirectories(deviceAppData: Mobile.IDeviceAppData): Promise { + const deviceRootPath = await this.$devicePathProvider.getDeviceProjectRootPath(deviceAppData.device, { + appIdentifier: deviceAppData.appIdentifier, + getDirname: true + }); - return this.restartApplication(deviceAppData); + await this.device.adb.executeShellCommand(["rm", "-rf", await this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.FULLSYNC_DIR_NAME), + this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.SYNC_DIR_NAME), + await this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.REMOVEDSYNC_DIR_NAME)]); } private async restartApplication(deviceAppData: Mobile.IDeviceAppData): Promise { @@ -70,15 +81,16 @@ export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase impl await this.device.adb.executeShellCommand(["rm", "-f", deviceRootPath]); } - this.device.adb.executeShellCommand(["rm", "-rf", this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.FULLSYNC_DIR_NAME), - this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.SYNC_DIR_NAME), - await this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.REMOVEDSYNC_DIR_NAME)]); + await this.cleanLivesyncDirectories(deviceAppData); } - private async reloadPage(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { + private async reloadResources(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { await this.device.adb.executeCommand(["forward", `tcp:${AndroidDeviceLiveSyncService.BACKEND_PORT.toString()}`, `localabstract:${deviceAppData.appIdentifier}-livesync`]); - if (!await this.sendPageReloadMessage()) { - await this.restartApplication(deviceAppData); + + if (await this.sendPageReloadMessage()) { + await this.cleanLivesyncDirectories(deviceAppData); + } else { + await this.restartApplication(deviceAppData); //in case runtime socket error/close } } diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index c8beb26240..298d34c902 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -91,7 +91,7 @@ export abstract class PlatformLiveSyncServiceBase { modifiedLocalToDevicePaths.push(...localToDevicePaths); const deviceLiveSyncService = this.getDeviceLiveSyncService(device, projectData.projectId); - deviceLiveSyncService.removeFiles(deviceAppData, localToDevicePaths); + await deviceLiveSyncService.removeFiles(deviceAppData, localToDevicePaths); } return { From 81488deda5a73afc7e764f65b98cb7571e132ff6 Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Mon, 7 Aug 2017 13:45:42 +0300 Subject: [PATCH 171/212] Add check for application identifier ios (#3033) --- lib/services/ios-project-service.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index db60e0f23f..d083538223 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -314,6 +314,8 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ handler, this.buildForSimulator(projectRoot, basicArgs, projectData, buildConfig.buildOutputStdio)); } + + this.validateApplicationIdentifier(projectData); } public async validatePlugins(projectData: IProjectData): Promise { @@ -1312,6 +1314,23 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } return teamId; } + + private validateApplicationIdentifier(projectData: IProjectData): void { + const projectDir = projectData.projectDir; + const infoPlistPath = path.join(projectDir, constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME, this.getPlatformData(projectData).normalizedPlatformName, this.getPlatformData(projectData).configurationFileName); + const mergedPlistPath = this.getPlatformData(projectData).configurationFilePath; + + if (!this.$fs.exists(infoPlistPath) || !this.$fs.exists(mergedPlistPath)) { + return; + } + + const infoPlist = plist.parse(this.$fs.readText(infoPlistPath)); + const mergedPlist = plist.parse(this.$fs.readText(mergedPlistPath)); + + if (infoPlist.CFBundleIdentifier && infoPlist.CFBundleIdentifier !== mergedPlist.CFBundleIdentifier) { + this.$logger.warnWithLabel("The CFBundleIdentifier key inside the 'Info.plist' will be overriden by the 'id' inside 'package.json'."); + } + } } $injector.register("iOSProjectService", IOSProjectService); From b1c6f18a1629fae3204769cdb4c82380b5fc5992 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Wed, 9 Aug 2017 15:46:44 +0300 Subject: [PATCH 172/212] Fix tns run ios on case sensitive file system (#3045) In case the file system is case sensitive, `tns run ios` command fails to execute as the `entitlements` file from `app/App_Resources/iOS/` (in case there's such file) is copied to `/platforms/ios` dir (the name of iOS dir inside platforms is lowercased). However the entitlements service expects it to be inside `/platforms/iOS` dir. On case sensitive file systems we fail with ENOENT error, as there's no iOS directory. Fix this inside `entitlements service` to use lowercased value. This way it will successfully find the entitlements file inside platforms dir. --- lib/services/ios-entitlements-service.ts | 2 +- test/ios-entitlements-service.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/services/ios-entitlements-service.ts b/lib/services/ios-entitlements-service.ts index e30be51f54..74662bea78 100644 --- a/lib/services/ios-entitlements-service.ts +++ b/lib/services/ios-entitlements-service.ts @@ -22,7 +22,7 @@ export class IOSEntitlementsService { } public getPlatformsEntitlementsPath(projectData: IProjectData) : string { - return path.join(projectData.platformsDir, this.$devicePlatformsConstants.iOS, + return path.join(projectData.platformsDir, this.$devicePlatformsConstants.iOS.toLowerCase(), projectData.projectName, projectData.projectName + ".entitlements"); } public getPlatformsEntitlementsRelativePath(projectData: IProjectData): string { diff --git a/test/ios-entitlements-service.ts b/test/ios-entitlements-service.ts index 5c9f950531..0987039ad6 100644 --- a/test/ios-entitlements-service.ts +++ b/test/ios-entitlements-service.ts @@ -64,6 +64,12 @@ describe("IOSEntitlements Service Tests", () => { let actual = iOSEntitlementsService.getPlatformsEntitlementsRelativePath(projectData); assert.equal(actual, expected); }); + + it("Ensure full path to entitlements in platforms dir is correct", () => { + const expected = path.join(projectData.platformsDir, "ios", "testApp", "testApp.entitlements"); + const actual = iOSEntitlementsService.getPlatformsEntitlementsPath(projectData); + assert.equal(actual, expected); + }); }); describe("Merge", () => { From cc9945157a660e9d2d3df52d59f4fb684a9d5a63 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Thu, 10 Aug 2017 16:27:48 +0300 Subject: [PATCH 173/212] Fix `tns debug --start` inconsistent behavior (#3047) `tns debug ` will check all available devices and will prompt the user to select device for debugging. In case the terminal is not interactive, it will select the emulator/device with highest API level. However, adding `--start` causes failure as the described logic is not used. So ensure we have this behavior even when `--start` is used. --- lib/commands/debug.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index b0322844f6..144ae61d23 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -29,14 +29,15 @@ export class DebugPlatformCommand implements ICommand { await this.$platformService.trackProjectType(this.$projectData); + const selectedDeviceForDebug = await this.getDeviceForDebug(); + debugData.deviceIdentifier = selectedDeviceForDebug.deviceInfo.identifier; + if (this.$options.start) { return this.$debugLiveSyncService.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); } this.$config.debugLivesync = true; - const selectedDeviceForDebug = await this.getDeviceForDebug(); - await this.$devicesService.detectCurrentlyAttachedDevices({ shouldReturnImmediateResult: false, platform: this.platform }); await this.$liveSyncCommandHelper.executeLiveSyncOperation([selectedDeviceForDebug], this.$debugLiveSyncService, this.platform); From 91844342c552d45cb0d733c9e8a77e859d68274b Mon Sep 17 00:00:00 2001 From: Plamen Petkov Date: Fri, 11 Aug 2017 08:18:18 +0300 Subject: [PATCH 174/212] doctor doesn't crash on missing xcode tools (#3043) * doctor doesn't crash on missing xcode tools set a better message when components are up to date * renamed method and fixed check * added method return type --- lib/declarations.d.ts | 5 ++--- lib/services/doctor-service.ts | 17 ++--------------- lib/services/versions-service.ts | 19 ++++++++++++++++--- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 8a6bfea5b6..9c19342437 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -604,10 +604,9 @@ interface IVersionsService { getRuntimesVersions(): Promise; /** - * Gets versions information about the nativescript components with new. - * @return {Promise} The version information. + * Checks version information about the nativescript components and prints available updates if any. */ - getComponentsForUpdate(): Promise; + checkComponentsForUpdate(): Promise; /** * Gets versions information about all nativescript components. diff --git a/lib/services/doctor-service.ts b/lib/services/doctor-service.ts index a96e206c95..5b8df741a6 100644 --- a/lib/services/doctor-service.ts +++ b/lib/services/doctor-service.ts @@ -95,7 +95,7 @@ class DoctorService implements IDoctorService { result = true; } - if (await this.$xcprojService.verifyXcproj(false)) { + if (sysInfo.xcodeVer && sysInfo.cocoapodVer && await this.$xcprojService.verifyXcproj(false)) { result = true; } } else { @@ -123,10 +123,8 @@ class DoctorService implements IDoctorService { } } - let versionsInformation: IVersionInformation[] = []; try { - versionsInformation = await this.$versionsService.getComponentsForUpdate(); - this.printVersionsInformation(versionsInformation); + await this.$versionsService.checkComponentsForUpdate(); } catch (err) { this.$logger.error("Cannot get the latest versions information from npm. Please try again later."); } @@ -134,17 +132,6 @@ class DoctorService implements IDoctorService { return doctorResult; } - private printVersionsInformation(versionsInformation: IVersionInformation[]) { - if (versionsInformation && versionsInformation.length) { - let table: any = this.$versionsService.createTableWithVersionsInformation(versionsInformation); - - this.$logger.warn("Updates available"); - this.$logger.out(table.toString() + EOL); - } else { - this.$logger.out("Your components are up-to-date." + EOL); - } - } - private async promptForDocs(link: string): Promise { if (await this.$prompter.confirm("Do you want to visit the official documentation?", () => helpers.isInteractive())) { this.$opener.open(link); diff --git a/lib/services/versions-service.ts b/lib/services/versions-service.ts index 2334713cd8..03e39414a4 100644 --- a/lib/services/versions-service.ts +++ b/lib/services/versions-service.ts @@ -1,3 +1,4 @@ +import { EOL } from "os"; import * as constants from "../constants"; import * as semver from "semver"; import * as path from "path"; @@ -14,7 +15,8 @@ class VersionsService implements IVersionsService { private $npmInstallationManager: INpmInstallationManager, private $injector: IInjector, private $staticConfig: Config.IStaticConfig, - private $pluginsService: IPluginsService) { + private $pluginsService: IPluginsService, + private $logger: ILogger) { this.projectData = this.getProjectData(); } @@ -84,7 +86,7 @@ class VersionsService implements IVersionsService { return runtimesVersions; } - public async getComponentsForUpdate(): Promise { + public async checkComponentsForUpdate(): Promise { let allComponents: IVersionInformation[] = await this.getAllComponentsVersions(); let componentsForUpdate: IVersionInformation[] = []; @@ -94,7 +96,18 @@ class VersionsService implements IVersionsService { } }); - return componentsForUpdate; + this.printVersionsInformation(componentsForUpdate, allComponents); + } + + private printVersionsInformation(versionsInformation: IVersionInformation[], allComponents: IVersionInformation[]): void { + if (versionsInformation && versionsInformation.length) { + let table: any = this.createTableWithVersionsInformation(versionsInformation); + + this.$logger.warn("Updates available"); + this.$logger.out(table.toString() + EOL); + } else { + this.$logger.out(`Your components are up-to-date: ${EOL}${allComponents.map(component => component.componentName)}${EOL}`); + } } public async getAllComponentsVersions(): Promise { From 1a7e27d8c752dc3a5010b3dc800c14e3f9c823ee Mon Sep 17 00:00:00 2001 From: Vasil Chimev Date: Fri, 11 Aug 2017 14:10:16 +0300 Subject: [PATCH 175/212] Update .travis.yml (#3050) --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index dd0e46bfed..5e4ee5d250 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,11 +46,11 @@ deploy: on: branch: master api_key: - secure: KzzsvF3eA3j4gRQa8tO//+XWNSR3XiX8Sa18o3PyKyG9/cBZ6PQ3Te74cNS1C3ZiLUOgs5dWA6/TmRVPci4XjvFaWo/B6e2fuVSl5H94Od99bkeBHJsbLSEkLN4ClV/YbGuyKgA5Q2yIFt6p2EJjL90RjbbIk7I4YuyG2Mo3j0Q= + secure: "g7Bpo7zX9kHaX8BcrnT/6S9/uuscAb2t+5Zr6okHCTaJXuLGOvzeV9KLFRyKKn93/o6sPlRIVA9welsYhUhdIlOUKz3jlPzejoaURhEY3xFrDWX29beho1Q88/AM5idGtosyElxvpw435WYeu/JlAu3DoYtCQavNXeEz5dY8cY0=" - provider: npm skip_cleanup: true email: nativescript@telerik.com on: branch: release api_key: - secure: KzzsvF3eA3j4gRQa8tO//+XWNSR3XiX8Sa18o3PyKyG9/cBZ6PQ3Te74cNS1C3ZiLUOgs5dWA6/TmRVPci4XjvFaWo/B6e2fuVSl5H94Od99bkeBHJsbLSEkLN4ClV/YbGuyKgA5Q2yIFt6p2EJjL90RjbbIk7I4YuyG2Mo3j0Q= + secure: "g7Bpo7zX9kHaX8BcrnT/6S9/uuscAb2t+5Zr6okHCTaJXuLGOvzeV9KLFRyKKn93/o6sPlRIVA9welsYhUhdIlOUKz3jlPzejoaURhEY3xFrDWX29beho1Q88/AM5idGtosyElxvpw435WYeu/JlAu3DoYtCQavNXeEz5dY8cY0=" From 815f0a9dc072944d3a244ba418266d131e9d79f8 Mon Sep 17 00:00:00 2001 From: Peter Staev Date: Fri, 11 Aug 2017 16:00:50 +0300 Subject: [PATCH 176/212] Fix IPA export (#3036) * fixes #3020 * changes requested from review * require new version of ios-mobileprovision-finder module * Update shrinkwrap, so ios-mobileprovision-finder 1.0.10 will be used --- lib/services/ios-project-service.ts | 30 ++++++++++++++++++++++++++++- npm-shrinkwrap.json | 6 +++--- package.json | 2 +- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index d083538223..5409d353f0 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -251,6 +251,22 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ let projectRoot = platformData.projectRoot; let archivePath = options.archivePath; let buildOutputPath = path.join(projectRoot, "build", "device"); + let exportOptionsMethod = await this.getExportOptionsMethod(projectData); + let plistTemplate = ` + + + + method + ${exportOptionsMethod} + uploadBitcode + + +`; + + // Save the options... + temp.track(); + let exportOptionsPlist = temp.path({ prefix: "export-", suffix: ".plist" }); + this.$fs.writeFile(exportOptionsPlist, plistTemplate); // The xcodebuild exportPath expects directory and writes the .ipa at that directory. let exportPath = path.resolve(options.exportDir || buildOutputPath); @@ -259,7 +275,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ let args = ["-exportArchive", "-archivePath", archivePath, "-exportPath", exportPath, - "-exportOptionsPlist", platformData.configurationFilePath + "-exportOptionsPlist", exportOptionsPlist ]; await this.$childProcess.spawnFromEvent("xcodebuild", args, "exit", { stdio: buildConfig.buildOutputStdio || 'inherit', cwd: this.getPlatformData(projectData).projectRoot }, @@ -1331,6 +1347,18 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.$logger.warnWithLabel("The CFBundleIdentifier key inside the 'Info.plist' will be overriden by the 'id' inside 'package.json'."); } } + + private getExportOptionsMethod(projectData: IProjectData): string { + const embeddedMobileProvisionPath = path.join(this.getPlatformData(projectData).deviceBuildOutputPath, `${projectData.projectName}.app`, "embedded.mobileprovision"); + const provision = mobileprovision.provision.readFromFile(embeddedMobileProvisionPath); + + return { + "Development": "development", + "AdHoc": "ad-hoc", + "Distribution": "app-store", + "Enterprise": "enterprise" + }[provision.Type]; + } } $injector.register("iOSProjectService", IOSProjectService); diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 172a7e87ac..b834b74b20 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2872,9 +2872,9 @@ } }, "ios-mobileprovision-finder": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/ios-mobileprovision-finder/-/ios-mobileprovision-finder-1.0.9.tgz", - "integrity": "sha1-Hc80ywKeP+oMhSkmu79K6GTswKc=", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/ios-mobileprovision-finder/-/ios-mobileprovision-finder-1.0.10.tgz", + "integrity": "sha1-UaXn+TzUCwN/fI8+JwXjSI11VgE=", "requires": { "chalk": "1.1.3", "plist": "2.1.0", diff --git a/package.json b/package.json index 9b36164ee2..af34c0b271 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "iconv-lite": "0.4.11", "inquirer": "0.9.0", "ios-device-lib": "0.4.8", - "ios-mobileprovision-finder": "1.0.9", + "ios-mobileprovision-finder": "1.0.10", "ios-sim-portable": "~3.0.0", "lockfile": "1.0.1", "lodash": "4.13.1", From 7605f81aec6d3939d33092e6e11941250a53f3d0 Mon Sep 17 00:00:00 2001 From: Plamen Petkov Date: Mon, 14 Aug 2017 10:46:49 +0300 Subject: [PATCH 177/212] don't restart application twice on crash (#3051) --- .../livesync/android-device-livesync-service.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/services/livesync/android-device-livesync-service.ts b/lib/services/livesync/android-device-livesync-service.ts index b50a35513e..985a5f347e 100644 --- a/lib/services/livesync/android-device-livesync-service.ts +++ b/lib/services/livesync/android-device-livesync-service.ts @@ -36,9 +36,9 @@ export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase impl `${deviceProjectRootDirname}/sync`] ); - await this.reloadResources(deviceAppData, localToDevicePaths); + const reloadedSuccessfully = await this.reloadApplicationFiles(deviceAppData, localToDevicePaths); - const canExecuteFastSync = !liveSyncInfo.isFullSync && !_.some(localToDevicePaths, + const canExecuteFastSync = reloadedSuccessfully && !liveSyncInfo.isFullSync && !_.some(localToDevicePaths, (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.canExecuteFastSync(localToDevicePath.getLocalPath(), projectData, this.device.deviceInfo.platform)); if (!canExecuteFastSync) { @@ -84,14 +84,15 @@ export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase impl await this.cleanLivesyncDirectories(deviceAppData); } - private async reloadResources(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { + private async reloadApplicationFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { await this.device.adb.executeCommand(["forward", `tcp:${AndroidDeviceLiveSyncService.BACKEND_PORT.toString()}`, `localabstract:${deviceAppData.appIdentifier}-livesync`]); - if (await this.sendPageReloadMessage()) { + if (await this.awaitRuntimeReloadSuccessMessage()) { await this.cleanLivesyncDirectories(deviceAppData); } else { - await this.restartApplication(deviceAppData); //in case runtime socket error/close + return false; } + return true; } public async removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { @@ -115,7 +116,7 @@ export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase impl return this.$injector.resolve(AndroidDeviceHashService, { adb, appIdentifier }); } - private async sendPageReloadMessage(): Promise { + private async awaitRuntimeReloadSuccessMessage(): Promise { return new Promise((resolve, reject) => { let isResolved = false; let socket = new net.Socket(); From ffc7661f38c955d1c0afb2715d7261736ef3c8a0 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Mon, 14 Aug 2017 11:02:57 +0300 Subject: [PATCH 178/212] Fix message when unable to find devices for LiveSync (#3058) In case there are attached, but not trusted devices, `tns run ` command prints error that it is unable to find devices and cannot start emulator when platform is not specified. But the platform has been specified, so improve the error message in such cases. --- lib/services/livesync/livesync-command-helper.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/services/livesync/livesync-command-helper.ts b/lib/services/livesync/livesync-command-helper.ts index b1854e4e7a..c6e0334898 100644 --- a/lib/services/livesync/livesync-command-helper.ts +++ b/lib/services/livesync/livesync-command-helper.ts @@ -17,7 +17,11 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { public async executeLiveSyncOperation(devices: Mobile.IDevice[], liveSyncService: ILiveSyncService, platform: string): Promise { if (!devices || !devices.length) { - this.$errors.failWithoutHelp("Unable to find applicable devices to execute operation and unable to start emulator when platform is not specified."); + if (platform) { + this.$errors.failWithoutHelp("Unable to find applicable devices to execute operation. Ensure connected devices are trusted and try again."); + } else { + this.$errors.failWithoutHelp("Unable to find applicable devices to execute operation and unable to start emulator when platform is not specified."); + } } const workingWithiOSDevices = !platform || this.$mobileHelper.isiOSPlatform(platform); From 381b35ebd276c7e1eb412f95e268626902efca58 Mon Sep 17 00:00:00 2001 From: Robin Naundorf Date: Mon, 14 Aug 2017 10:11:27 +0200 Subject: [PATCH 179/212] Update ios-project-service.ts (#3056) Fix small typo --- lib/services/ios-project-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 5409d353f0..72a4379006 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -1293,7 +1293,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f if (!teamId) { let teams = this.getDevelopmentTeams(); this.$logger.warn("Xcode 8 requires a team id to be specified when building for device."); - this.$logger.warn("You can specify the team id by setting the DEVELOPMENT_TEAM setting in build.xcconfig file located in App_Resources folder of your app, or by using the --teamId option when calling run, debug or livesync commnads."); + this.$logger.warn("You can specify the team id by setting the DEVELOPMENT_TEAM setting in build.xcconfig file located in App_Resources folder of your app, or by using the --teamId option when calling run, debug or livesync commands."); if (teams.length === 1) { teamId = teams[0].id; this.$logger.warn("Found and using the following development team installed on your system: " + teams[0].name + " (" + teams[0].id + ")"); From 1ba4656828b697f720533b4559a0b3013722a4ce Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Mon, 14 Aug 2017 13:31:32 +0300 Subject: [PATCH 180/212] Improve get latest compatible version to search for major.minor.*x* versions (#3052) * Improve get latest compatible version to search for major.minor.*any* versions * Update submodule --- lib/common | 2 +- lib/npm-installation-manager.ts | 16 ++++++++++++---- test/npm-installation-manager.ts | 6 ++++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/common b/lib/common index 512ae8ef52..4de56f7d90 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 512ae8ef520deac25f0f3c3533edecb6b4534fc8 +Subproject commit 4de56f7d90a32913c4307fce0efe796eb1c3bd17 diff --git a/lib/npm-installation-manager.ts b/lib/npm-installation-manager.ts index 89b698470e..7a4728912c 100644 --- a/lib/npm-installation-manager.ts +++ b/lib/npm-installation-manager.ts @@ -20,15 +20,23 @@ export class NpmInstallationManager implements INpmInstallationManager { } public async getLatestCompatibleVersion(packageName: string): Promise { + const configVersion = this.$staticConfig.version; + const isPreReleaseVersion = semver.prerelease(configVersion) !== null; + let cliVersionRange = `~${semver.major(configVersion)}.${semver.minor(configVersion)}.0`; + if (isPreReleaseVersion) { + // if the user has some 0-19 pre-release version, include pre-release versions in the search query. + cliVersionRange = `~${configVersion}`; + } - let cliVersionRange = `~${this.$staticConfig.version}`; - let latestVersion = await this.getLatestVersion(packageName); + const latestVersion = await this.getLatestVersion(packageName); if (semver.satisfies(latestVersion, cliVersionRange)) { return latestVersion; } - let data = await this.$npm.view(packageName, { "versions": true }); - return semver.maxSatisfying(data, cliVersionRange) || latestVersion; + const data = await this.$npm.view(packageName, { "versions": true }); + + const maxSatisfying = semver.maxSatisfying(data, cliVersionRange); + return maxSatisfying || latestVersion; } public async install(packageName: string, projectDir: string, opts?: INpmInstallOptions): Promise { diff --git a/test/npm-installation-manager.ts b/test/npm-installation-manager.ts index a65ca053b4..ef11d4098d 100644 --- a/test/npm-installation-manager.ts +++ b/test/npm-installation-manager.ts @@ -146,6 +146,12 @@ describe("Npm installation manager tests", () => { packageLatestVersion: "1.4.0", cliVersion: "1.6.0-2016-10-01-182", expectedResult: "1.4.0" + }, + "When CLI Version has patch version larger than an existing package, should return max compliant package from the same major.minor version": { + versions: ["1.0.0", "1.0.1", "1.4.0", "2.5.0", "2.5.1", "2.5.2", "3.0.0"], + packageLatestVersion: "3.0.0", + cliVersion: "2.5.4", + expectedResult: "2.5.2" } }; From 39ae515feefaa3f670cfaf20c429d1ff948b04fe Mon Sep 17 00:00:00 2001 From: Tsvetan Milanov Date: Mon, 14 Aug 2017 14:08:29 +0300 Subject: [PATCH 181/212] Fix the error handling in hooks execution (#3055) * Fix the error handling in hooks execution Some hooks can reject their promises without error. We need to check if we have an error before we try to get some information from it. * Update submodule SHA --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index 4de56f7d90..0f10a81439 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 4de56f7d90a32913c4307fce0efe796eb1c3bd17 +Subproject commit 0f10a81439cc459db67815c85029291d285e77c7 From 973fc8c5f29a16111ffce1ee0d3027901b3e13c5 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Tue, 15 Aug 2017 13:07:31 +0300 Subject: [PATCH 182/212] Update ios-sim-portable to 3.1.0 (#3060) This will fix issue with starting iOS Simulator when a fake device id is passed. Instead we will fail with correct error from now on. --- lib/common | 2 +- npm-shrinkwrap.json | 6 +++--- package.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/common b/lib/common index 0f10a81439..b218c1863f 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 0f10a81439cc459db67815c85029291d285e77c7 +Subproject commit b218c1863fa7ba76d05db3642ee26c5f27ed760d diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index b834b74b20..036158e9f0 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2926,9 +2926,9 @@ } }, "ios-sim-portable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ios-sim-portable/-/ios-sim-portable-3.0.0.tgz", - "integrity": "sha1-VOCVccIb+TNH8sfAFSno9fHEtfI=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ios-sim-portable/-/ios-sim-portable-3.1.0.tgz", + "integrity": "sha1-IgAOTTUYgvQz8DtS2TqPICZm/f4=", "requires": { "bplist-parser": "https://github.com/telerik/node-bplist-parser/tarball/master", "colors": "0.6.2", diff --git a/package.json b/package.json index af34c0b271..000411318e 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "inquirer": "0.9.0", "ios-device-lib": "0.4.8", "ios-mobileprovision-finder": "1.0.10", - "ios-sim-portable": "~3.0.0", + "ios-sim-portable": "3.1.0", "lockfile": "1.0.1", "lodash": "4.13.1", "log4js": "1.0.1", From 4d5bcfb8eb9829e21b82dab0f32130de35031751 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Tue, 15 Aug 2017 17:34:10 +0300 Subject: [PATCH 183/212] Fix tns run ios when no simulator is running (#3064) In case when there's no device attached and no simulator running, `tns run ios` should start default iOS Simulator. However, this fails. The problem is resolved in `ios-sim-portable` 3.1.1. Bump the version, so the issue will be resolved. --- npm-shrinkwrap.json | 56 ++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 036158e9f0..78883fb1e0 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -7,13 +7,13 @@ "@types/chai": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.0.1.tgz", - "integrity": "sha512-DWrdkraJO+KvBB7+Jc6AuDd2+fwV6Z9iK8cqEEoYpcurYrH7GiUZmwjFuQIIWj5HhFz6NsSxdN72YMIHT7Fy2Q==", + "integrity": "sha1-N/6neWF8/sP9KxmgJH6LvdUTO/Y=", "dev": true }, "@types/chai-as-promised": { "version": "0.0.31", "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-0.0.31.tgz", - "integrity": "sha512-DWT96QeM5AeASIkChnFtEOy6O45WvKmaw27MPhAKLkx06TaFNqrzJuWVurKjCEo3PqVV89YLR2iVON8PhTRaLg==", + "integrity": "sha1-4ekF6m2XHa/K02VgyPH3p9aQxeU=", "dev": true, "requires": { "@types/chai": "4.0.1" @@ -55,7 +55,7 @@ "@types/request": { "version": "0.0.45", "resolved": "https://registry.npmjs.org/@types/request/-/request-0.0.45.tgz", - "integrity": "sha512-OIIREjT58pnpfJjEY5PeBEuRtRR2ED4DF1Ez3Dj9474kCqEKfE+iNAYyM/P3RxxDjNxBhipo+peNBW0S/7Wrzg==", + "integrity": "sha1-xuUr6LEI6wNcNaqa9Wo4omDD5+Y=", "dev": true, "requires": { "@types/form-data": "0.0.33", @@ -65,7 +65,7 @@ "@types/semver": { "version": "5.3.32", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.3.32.tgz", - "integrity": "sha512-MdbWERx4JWmN4zP+skJy9Kio+Cddvmyn1k8x0S8UAqDoMgOJeobQo7yhlE4BfiimonHirgixWfva/hKUlXBsrw==", + "integrity": "sha1-p6/cS+5xPAgRFM2BG1G+EJB9e64=", "dev": true }, "@types/source-map": { @@ -83,7 +83,7 @@ "acorn": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.1.tgz", - "integrity": "sha512-vOk6uEMctu0vQrvuSqFdJyqj1Q0S5VTDL79qtjo+DhRr+1mmaD+tluFSCZqhvi/JUhXSzoZN2BhtstaPEeE8cw==", + "integrity": "sha1-U/4WERH5EquZnuiHqQoLxSgi/XU=", "dev": true }, "acorn-jsx": { @@ -185,7 +185,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=" }, "array-find-index": { "version": "1.0.2", @@ -493,7 +493,7 @@ "chai-as-promised": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.0.0.tgz", - "integrity": "sha512-7YYdnXPq2pV9nvRBb36Wi/MXfT8j2iL/H76GtenlOMatXbMoQLb+PonuVHGFsw5wE2M6R/VFciq8AnSSAix0GA==", + "integrity": "sha1-yH7mE+qhlnZjk9pvu0BS8RKs9nU=", "dev": true, "requires": { "check-error": "1.0.2", @@ -697,7 +697,7 @@ "es6-promise": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.1.1.tgz", - "integrity": "sha512-OaU1hHjgJf+b0NzsxCg7NdIYERD6Hy/PEmFLTjw+b65scuisG3Kt4QoTvJ66BBkPZ581gr0kpoVzKnxniM8nng==", + "integrity": "sha1-iBHpCRXZoNujYnTwskLb2nj5ySo=", "dev": true }, "lodash": { @@ -1464,7 +1464,7 @@ "fsevents": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz", - "integrity": "sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw==", + "integrity": "sha1-MoK3E/s62A7eDp/PRhG1qm/AM/Q=", "optional": true, "requires": { "nan": "2.6.2", @@ -2310,7 +2310,7 @@ "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -2323,7 +2323,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "requires": { "brace-expansion": "1.1.8" } @@ -2350,7 +2350,7 @@ "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=", "dev": true }, "globby": { @@ -2454,7 +2454,7 @@ "iconv-lite": { "version": "0.4.18", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", - "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==", + "integrity": "sha1-I9hlaxaq5nQqwpcy6o8DNqR4nPI=", "dev": true }, "js-yaml": { @@ -2766,7 +2766,7 @@ "hosted-git-info": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", - "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==" + "integrity": "sha1-bWDjSzq7yDEwYsO3mO+NkBoHrzw=" }, "http-errors": { "version": "1.3.1", @@ -2926,9 +2926,9 @@ } }, "ios-sim-portable": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ios-sim-portable/-/ios-sim-portable-3.1.0.tgz", - "integrity": "sha1-IgAOTTUYgvQz8DtS2TqPICZm/f4=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/ios-sim-portable/-/ios-sim-portable-3.1.1.tgz", + "integrity": "sha1-AmL3x3N6ZnfyAI48rem2KMEIN6c=", "requires": { "bplist-parser": "https://github.com/telerik/node-bplist-parser/tarball/master", "colors": "0.6.2", @@ -3260,7 +3260,7 @@ "js-yaml": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.9.0.tgz", - "integrity": "sha512-0LoUNELX4S+iofCT8f4uEHIiRBR+c2AINyC8qRWfC6QNruLtxVZRJaPcu/xwMgFIgDxF25tGHaDjvxzJCNE9yw==", + "integrity": "sha1-T/u/JcKsljuCmdx02n43QN4cGM4=", "dev": true, "requires": { "argparse": "1.0.9", @@ -3270,7 +3270,7 @@ "esprima": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "integrity": "sha1-RJnt3NERDgshi6zy+n9/WfVcqAQ=", "dev": true } } @@ -3755,7 +3755,7 @@ "node-emoji": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.7.0.tgz", - "integrity": "sha512-dYx345sjhPJUpWaVQKjP0/43y+nTcfBRTZfSciM3ZEbRGaU/9AKaHBPf7AJ9vOKcK0W3v67AgI4m4oo02NLHhQ==", + "integrity": "sha1-pABJCqxAm2FtE5QVMiAPEorwN/k=", "requires": { "lodash.toarray": "4.4.0", "string.prototype.codepointat": "0.2.0" @@ -3783,7 +3783,7 @@ "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=", "requires": { "hosted-git-info": "2.5.0", "is-builtin-module": "1.0.0", @@ -4176,7 +4176,7 @@ "randomatic": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=", "requires": { "is-number": "3.0.0", "kind-of": "4.0.0" @@ -4257,7 +4257,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", @@ -4478,12 +4478,12 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=", "dev": true }, "semver": { @@ -4723,7 +4723,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "requires": { "safe-buffer": "5.1.1" } @@ -5007,13 +5007,13 @@ "diff": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.0.tgz", - "integrity": "sha512-w0XZubFWn0Adlsapj9EAWX0FqWdO4tz8kc3RiYdWLh4k/V8PTb6i0SMgXt0vRM3zyKnT8tKO7mUlieRQHIjMNg==", + "integrity": "sha1-BWaVFQ16qTI3yn43isOxaCt5Y7k=", "dev": true }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "dev": true, "requires": { "brace-expansion": "1.1.8" diff --git a/package.json b/package.json index 000411318e..254c0143d9 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "inquirer": "0.9.0", "ios-device-lib": "0.4.8", "ios-mobileprovision-finder": "1.0.10", - "ios-sim-portable": "3.1.0", + "ios-sim-portable": "3.1.1", "lockfile": "1.0.1", "lodash": "4.13.1", "log4js": "1.0.1", From 1f46ca1c0de1de6bf19ddedffc56b3c8e0fb3760 Mon Sep 17 00:00:00 2001 From: Plamen Petkov Date: Thu, 17 Aug 2017 09:15:35 +0300 Subject: [PATCH 184/212] make livesync work with renamed files and folders (#3042) * added special check for file changes on Mac * fix missing imports in tests * add dir check, modified file check * check isDirModified before anything else to save time * updates after review * bring back check for symlinks * add check for modification in bot files and folders * fixed lint --- lib/services/project-changes-service.ts | 29 +++++++++++++++++++------ test/project-changes-service.ts | 1 - 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index 5fbfa2a892..08c87a3df5 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -68,6 +68,7 @@ export class ProjectChangesService implements IProjectChangesService { path.join(projectData.projectDir, NODE_MODULES_FOLDER_NAME, "tns-ios-inspector"), projectData, this.fileChangeRequiresBuild); + if (this._newFiles > 0) { this._changesInfo.modulesChanged = true; } @@ -218,6 +219,11 @@ export class ProjectChangesService implements IProjectChangesService { } private containsNewerFiles(dir: string, skipDir: string, projectData: IProjectData, processFunc?: (filePath: string, projectData: IProjectData) => boolean): boolean { + const dirFileStat = this.$fs.getFsStats(dir); + if (this.isFileModified(dirFileStat, dir)) { + return true; + } + let files = this.$fs.readDirectory(dir); for (let file of files) { let filePath = path.join(dir, file); @@ -225,13 +231,8 @@ export class ProjectChangesService implements IProjectChangesService { continue; } - let fileStats = this.$fs.getFsStats(filePath); - - let changed = fileStats.mtime.getTime() >= this._outputProjectMtime || fileStats.ctime.getTime() >= this._outputProjectCTime; - if (!changed) { - let lFileStats = this.$fs.getLsStats(filePath); - changed = lFileStats.mtime.getTime() >= this._outputProjectMtime || lFileStats.ctime.getTime() >= this._outputProjectCTime; - } + const fileStats = this.$fs.getFsStats(filePath); + let changed = this.isFileModified(fileStats, filePath); if (changed) { if (processFunc) { @@ -250,10 +251,24 @@ export class ProjectChangesService implements IProjectChangesService { return true; } } + } return false; } + private isFileModified(filePathStat: IFsStats, filePath: string): boolean { + let changed = filePathStat.mtime.getTime() >= this._outputProjectMtime || + filePathStat.ctime.getTime() >= this._outputProjectCTime; + + if (!changed) { + let lFileStats = this.$fs.getLsStats(filePath); + changed = lFileStats.mtime.getTime() >= this._outputProjectMtime || + lFileStats.ctime.getTime() >= this._outputProjectCTime; + } + + return changed; + } + private fileChangeRequiresBuild(file: string, projectData: IProjectData) { if (path.basename(file) === "package.json") { return true; diff --git a/test/project-changes-service.ts b/test/project-changes-service.ts index 3593400340..cc29656ebd 100644 --- a/test/project-changes-service.ts +++ b/test/project-changes-service.ts @@ -26,7 +26,6 @@ class ProjectChangesServiceTest extends BaseServiceTest { this.injector.register("platformsData", PlatformsData); this.injector.register("androidProjectService", {}); this.injector.register("iOSProjectService", {}); - this.injector.register("fs", FileSystem); this.injector.register("devicePlatformsConstants", {}); this.injector.register("devicePlatformsConstants", {}); From 445b463239b86c4fae36606ae44482227b92e0d7 Mon Sep 17 00:00:00 2001 From: Emil Tabakov Date: Thu, 17 Aug 2017 13:00:51 +0300 Subject: [PATCH 185/212] Fix setup script for Windows #3048 (#3049) * Change android command line tool * Fix the default path to android tools to the actual installation point * Replace x86 image with ARM, when HAXM is not available * Install x86 with HAMX and otherwise arm image --- setup/native-script.ps1 | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/setup/native-script.ps1 b/setup/native-script.ps1 index cb5f028724..1543c6924f 100644 --- a/setup/native-script.ps1 +++ b/setup/native-script.ps1 @@ -81,7 +81,7 @@ if (!$env:ANDROID_HOME) { $androidHome = Resolve-Path $androidHomeJoinedPath | Select-Object -ExpandProperty Path } else { - $androidHome = "$env:localappdata\Android\android-sdk" + $androidHome = "${Env:SystemDrive}\Android\android-sdk" } $env:ANDROID_HOME = $androidHome; @@ -97,29 +97,25 @@ if (!$env:JAVA_HOME) { # setup android sdk # following commands are separated in case of having to answer to license agreements -# the android tool will introduce a --accept-license option in subsequent releases -$androidExecutable = [io.path]::combine($env:ANDROID_HOME, "tools", "android") -echo y | cmd /c "$androidExecutable" update sdk --filter "platform-tools" --all --no-ui -echo y | cmd /c "$androidExecutable" update sdk --filter "tools" --all --no-ui -echo y | cmd /c "$androidExecutable" update sdk --filter "android-23" --all --no-ui -echo y | cmd /c "$androidExecutable" update sdk --filter "build-tools-25.0.2" --all --no-ui -echo y | cmd /c "$androidExecutable" update sdk --filter "build-tools-23.0.3" --all --no-ui -echo y | cmd /c "$androidExecutable" update sdk --filter "extra-android-m2repository" --all --no-ui +$androidExecutable = [io.path]::combine($env:ANDROID_HOME, "tools", "bin", "sdkmanager") +echo y | cmd /c "$androidExecutable" "platform-tools" +echo y | cmd /c "$androidExecutable" "tools" +echo y | cmd /c "$androidExecutable" "build-tools;25.0.2" +echo y | cmd /c "$androidExecutable" "platforms;android-23" +echo y | cmd /c "$androidExecutable" "extras;android;m2repository" +echo y | cmd /c "$androidExecutable" "extras;google;m2repository" if ((Read-Host "Do you want to install Android emulator?") -eq 'y') { if ((Read-Host "Do you want to install HAXM (Hardware accelerated Android emulator)?") -eq 'y') { - echo y | cmd /c "$androidExecutable" update sdk --filter extra-intel-Hardware_Accelerated_Execution_Manager --all --no-ui - + echo y | cmd /c "$androidExecutable" "extras;intel;Hardware_Accelerated_Execution_Manager" $haxmSilentInstaller = [io.path]::combine($env:ANDROID_HOME, "extras", "intel", "Hardware_Accelerated_Execution_Manager", "silent_install.bat") cmd /c "$haxmSilentInstaller" - - echo y | cmd /c "$androidExecutable" update sdk --filter sys-img-x86-android-23 --all --no-ui - echo no | cmd /c "$androidExecutable" create avd -n Emulator-Api23-Default -t android-23 --abi default/x86 -c 12M -f - } else { - echo y | cmd /c "$androidExecutable" update sdk --filter sys-img-armeabi-v7a-android-23 --all --no-ui - echo no | cmd /c "$androidExecutable" create avd -n Emulator-Api23-Default -t android-23 --abi default/armeabi-v7a -c 12M -f + echo y | cmd /c "$androidExecutable" "system-images;android-25;google_apis;x86" + } + else { + echo y | cmd /c "$androidExecutable" "system-images;android-25;google_apis;armeabi-v7a" } } Write-Host -ForegroundColor Green "This script has modified your environment. You need to log off and log back on for the changes to take effect." -Pause +Pause \ No newline at end of file From f99762c183cc6a185a002a88aa1d29c8ec0d76b0 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Thu, 17 Aug 2017 16:51:22 +0300 Subject: [PATCH 186/212] Fix passing provision to `tns run ios` and passsing `--teamId` (#3069) Passing `--provision` to `tns run ` is not respected. Fix this by including additional option in the deviceDescriptors. This allows us to pass different provisions for each device, however in CLI's commands we'll set one provision for all devices. Also fix passing `--teamId` option - during previous refactoring we've broken the code for it and it has not been respected. Remove the unused alias `teamIdentifier` and use `teamId` in all cases. --- lib/definitions/livesync.d.ts | 5 ++++ lib/definitions/platform.d.ts | 21 ++++++--------- lib/definitions/project.d.ts | 4 --- lib/services/ios-project-service.ts | 17 +++++++----- .../livesync/livesync-command-helper.ts | 2 ++ lib/services/livesync/livesync-service.ts | 11 +++++--- lib/services/platform-service.ts | 26 +++++++++---------- test/platform-service.ts | 2 +- 8 files changed, 47 insertions(+), 41 deletions(-) diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 892b8d455a..ec89823dbe 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -94,6 +94,11 @@ interface ILiveSyncDeviceInfo { * Whether to skip preparing the native platform. */ skipNativePrepare?: boolean; + + /** + * Describes options specific for each platform, like provision for iOS, target sdk for Android, etc. + */ + platformSpecificOptions?: IPlatformOptions; } /** diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index fed7c2fd15..058f33fffa 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -1,7 +1,7 @@ interface IPlatformService extends NodeJS.EventEmitter { - cleanPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, framework?: string): Promise; + cleanPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, framework?: string): Promise; - addPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, frameworkPath?: string): Promise; + addPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, frameworkPath?: string): Promise; /** * Gets list of all installed platforms (the ones for which /platforms/ exists). @@ -32,7 +32,7 @@ interface IPlatformService extends NodeJS.EventEmitter { */ removePlatforms(platforms: string[], projectData: IProjectData): Promise; - updatePlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise; + updatePlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IPlatformOptions): Promise; /** * Ensures that the specified platform and its dependencies are installed. @@ -47,7 +47,7 @@ interface IPlatformService extends NodeJS.EventEmitter { * @param {Array} filesToSync Files about to be synced to device. * @returns {boolean} true indicates that the platform was prepared. */ - preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, filesToSync?: Array, nativePrepare?: INativePrepare): Promise; + preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, filesToSync?: Array, nativePrepare?: INativePrepare): Promise; /** * Determines whether a build is necessary. A build is necessary when one of the following is true: @@ -113,7 +113,7 @@ interface IPlatformService extends NodeJS.EventEmitter { * @param {IAddPlatformCoreOptions} config Options required for project preparation/creation. * @returns {void} */ - deployPlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, deployOptions: IDeployPlatformOptions, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise; + deployPlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, deployOptions: IDeployPlatformOptions, projectData: IProjectData, config: IPlatformOptions): Promise; /** * Runs the application on specified platform. Assumes that the application is already build and installed. Fails if this is not true. @@ -124,7 +124,7 @@ interface IPlatformService extends NodeJS.EventEmitter { */ startApplication(platform: string, runOptions: IRunPlatformOptions, projectId: string): Promise; - cleanDestinationApp(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise; + cleanDestinationApp(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IPlatformOptions): Promise; validatePlatformInstalled(platform: string, projectData: IProjectData): void; /** @@ -213,17 +213,12 @@ interface IPlatformService extends NodeJS.EventEmitter { saveBuildInfoFile(platform: string, projectDir: string, buildInfoFileDirname: string): void } -interface IAddPlatformCoreOptions extends IPlatformSpecificData, ICreateProjectOptions { } +interface IPlatformOptions extends IPlatformSpecificData, ICreateProjectOptions { } /** * Platform specific data required for project preparation. */ -interface IPlatformSpecificData { - /** - * UUID of the provisioning profile used in iOS project preparation. - */ - provision: any; - +interface IPlatformSpecificData extends IProvision { /** * Target SDK for Android. */ diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index b8a73670af..9a328d9255 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -155,10 +155,6 @@ interface IiOSBuildConfig extends IBuildForDevice, IDeviceIdentifier, IProvision * Code sign identity used for build. If not set iPhone Developer is used as a default when building for device. */ codeSignIdentity?: string; - /** - * Team identifier. - */ - teamIdentifier?: string; } interface IPlatformProjectService extends NodeJS.EventEmitter { diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 72a4379006..cd75de7dad 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -396,8 +396,8 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ args.push(`PROVISIONING_PROFILE=${buildConfig.mobileProvisionIdentifier}`); } - if (buildConfig && buildConfig.teamIdentifier) { - args.push(`DEVELOPMENT_TEAM=${buildConfig.teamIdentifier}`); + if (buildConfig && buildConfig.teamId) { + args.push(`DEVELOPMENT_TEAM=${buildConfig.teamId}`); } // this.$logger.out("xcodebuild..."); @@ -470,12 +470,14 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ xcode.setManualSigningStyle(projectData.projectName); xcode.save(); } else if (!buildConfig.provision && !(signing && signing.style === "Manual" && !buildConfig.teamId)) { - if (buildConfig) { - delete buildConfig.teamIdentifier; - } - const teamId = await this.getDevelopmentTeam(projectData, buildConfig.teamId); + // Remove teamId from build config as we'll set the signing in Xcode project directly. + // In case we do not remove it, we'll pass DEVELOPMENT_TEAM= to xcodebuild, which is unnecessary. + if (buildConfig.teamId) { + delete buildConfig.teamId; + } + xcode.setAutomaticSigningStyle(projectData.projectName, teamId); xcode.save(); this.$logger.trace("Set Automatic signing style and team."); @@ -1328,6 +1330,9 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } } } + + this.$logger.trace(`Selected teamId is '${teamId}'.`); + return teamId; } diff --git a/lib/services/livesync/livesync-command-helper.ts b/lib/services/livesync/livesync-command-helper.ts index c6e0334898..869c148160 100644 --- a/lib/services/livesync/livesync-command-helper.ts +++ b/lib/services/livesync/livesync-command-helper.ts @@ -40,6 +40,8 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { .map(d => { const info: ILiveSyncDeviceInfo = { identifier: d.deviceInfo.identifier, + platformSpecificOptions: this.$options, + buildAction: async (): Promise => { const buildConfig: IBuildConfig = { buildForDevice: !d.isEmulator, diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 1cdb9d6a1d..69f112b8af 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -165,11 +165,12 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const appInstalledOnDeviceResult: IAppInstalledOnDeviceResult = { appInstalled: false }; if (options.preparedPlatforms.indexOf(platform) === -1) { options.preparedPlatforms.push(platform); - // TODO: Pass provision and sdk as a fifth argument here + + const platformSpecificOptions = options.deviceBuildInfoDescriptor.platformSpecificOptions || {}; await this.$platformService.preparePlatform(platform, { bundle: false, release: false, - }, null, options.projectData, {}, options.modifiedFiles, nativePrepare); + }, null, options.projectData, platformSpecificOptions, options.modifiedFiles, nativePrepare); } const buildResult = await this.installedCachedAppPackage(platform, options); @@ -178,8 +179,10 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { return appInstalledOnDeviceResult; } - // TODO: Pass provision and sdk as a fifth argument here - const shouldBuild = await this.$platformService.shouldBuild(platform, options.projectData, { buildForDevice: !options.device.isEmulator, clean: options.liveSyncData && options.liveSyncData.clean }, options.deviceBuildInfoDescriptor.outputPath); + const shouldBuild = await this.$platformService.shouldBuild(platform, + options.projectData, + { buildForDevice: !options.device.isEmulator, clean: options.liveSyncData && options.liveSyncData.clean }, + options.deviceBuildInfoDescriptor.outputPath); let pathToBuildItem = null; let action = LiveSyncTrackActionNames.LIVESYNC_OPERATION; if (shouldBuild) { diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 1275f1e1db..1d072f6592 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -43,7 +43,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { super(); } - public async cleanPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, framworkPath?: string): Promise { + public async cleanPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, framworkPath?: string): Promise { for (let platform of platforms) { let version: string = this.getCurrentPlatformVersion(platform, projectData); @@ -57,7 +57,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - public async addPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, frameworkPath?: string): Promise { + public async addPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, frameworkPath?: string): Promise { const platformsDir = projectData.platformsDir; this.$fs.ensureDirectoryExists(platformsDir); @@ -84,7 +84,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { return version; } - private async addPlatform(platformParam: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, frameworkPath?: string, nativePrepare?: INativePrepare): Promise { + private async addPlatform(platformParam: string, platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, frameworkPath?: string, nativePrepare?: INativePrepare): Promise { let data = platformParam.split("@"), platform = data[0].toLowerCase(), version = data[1]; @@ -137,7 +137,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$logger.out("Project successfully created."); } - private async addPlatformCore(platformData: IPlatformData, frameworkDir: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, nativePrepare?: INativePrepare): Promise { + private async addPlatformCore(platformData: IPlatformData, frameworkDir: string, platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, nativePrepare?: INativePrepare): Promise { const coreModuleData = this.$fs.readJson(path.join(frameworkDir, "..", "package.json")); const installedVersion = coreModuleData.version; const customTemplateOptions = await this.getPathToPlatformTemplate(platformTemplate, platformData.frameworkPackageName, projectData.projectDir); @@ -159,7 +159,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } - private async addPlatformCoreNative(platformData: IPlatformData, frameworkDir: string, installedVersion: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { + private async addPlatformCoreNative(platformData: IPlatformData, frameworkDir: string, installedVersion: string, projectData: IProjectData, config: IPlatformOptions): Promise { await platformData.platformProjectService.createProject(path.resolve(frameworkDir), installedVersion, projectData, config); platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData); await platformData.platformProjectService.interpolateData(projectData, config); @@ -213,7 +213,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { public getPreparedPlatforms(projectData: IProjectData): string[] { return _.filter(this.$platformsData.platformsNames, p => { return this.isPlatformPrepared(p, projectData); }); } - public async preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, filesToSync?: Array, nativePrepare?: INativePrepare): Promise { + public async preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, filesToSync?: Array, nativePrepare?: INativePrepare): Promise { const platformData = this.$platformsData.getPlatformData(platform, projectData); const changesInfo = await this.initialPrepare(platform, platformData, appFilesUpdaterOptions, platformTemplate, projectData, config, nativePrepare); const requiresNativePrepare = (!nativePrepare || !nativePrepare.skipNativePrepare) && changesInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPrepare; @@ -265,7 +265,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - private async initialPrepare(platform: string, platformData: IPlatformData, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, nativePrepare?: INativePrepare): Promise { + private async initialPrepare(platform: string, platformData: IPlatformData, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, nativePrepare?: INativePrepare): Promise { this.validatePlatform(platform, projectData); await this.trackProjectType(projectData); @@ -532,7 +532,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$logger.out(`Successfully installed on device with identifier '${device.deviceInfo.identifier}'.`); } - public async deployPlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, deployOptions: IDeployPlatformOptions, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { + public async deployPlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, deployOptions: IDeployPlatformOptions, projectData: IProjectData, config: IPlatformOptions): Promise { await this.preparePlatform(platform, appFilesUpdaterOptions, deployOptions.platformTemplate, projectData, config); let options: Mobile.IDevicesServicesInitializationOptions = { platform: platform, deviceId: deployOptions.device, emulator: deployOptions.emulator @@ -623,7 +623,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { return null; } - public async cleanDestinationApp(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { + public async cleanDestinationApp(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IPlatformOptions): Promise { await this.ensurePlatformInstalled(platform, platformTemplate, projectData, config); const appSourceDirectoryPath = path.join(projectData.projectDir, constants.APP_FOLDER_NAME); @@ -679,7 +679,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - public async updatePlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { + public async updatePlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IPlatformOptions): Promise { for (let platformParam of platforms) { let data = platformParam.split("@"), platform = data[0], @@ -736,7 +736,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - public async ensurePlatformInstalled(platform: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, nativePrepare?: INativePrepare): Promise { + public async ensurePlatformInstalled(platform: string, platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, nativePrepare?: INativePrepare): Promise { let requiresNativePlatformAdd = false; if (!this.isPlatformInstalled(platform, projectData)) { @@ -808,7 +808,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { return this.getLatestApplicationPackage(outputPath || platformData.emulatorBuildOutputPath || platformData.deviceBuildOutputPath, platformData.getValidPackageNames({ isForDevice: false, isReleaseBuild: buildConfig.release })); } - private async updatePlatform(platform: string, version: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { + private async updatePlatform(platform: string, version: string, platformTemplate: string, projectData: IProjectData, config: IPlatformOptions): Promise { let platformData = this.$platformsData.getPlatformData(platform, projectData); let data = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); @@ -841,7 +841,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } - private async updatePlatformCore(platformData: IPlatformData, updateOptions: IUpdatePlatformOptions, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { + private async updatePlatformCore(platformData: IPlatformData, updateOptions: IUpdatePlatformOptions, projectData: IProjectData, config: IPlatformOptions): Promise { let packageName = platformData.normalizedPlatformName.toLowerCase(); await this.removePlatforms([packageName], projectData); packageName = updateOptions.newVersion ? `${packageName}@${updateOptions.newVersion}` : packageName; diff --git a/test/platform-service.ts b/test/platform-service.ts index d017e07c76..22d5bf78d0 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -147,7 +147,7 @@ class DestinationFolderVerifier { describe('Platform Service Tests', () => { let platformService: IPlatformService, testInjector: IInjector; - const config: IAddPlatformCoreOptions = { + const config: IPlatformOptions = { ignoreScripts: false, provision: null, sdk: null, From db969b65d6ee6bfccf827f981c82c0593249ebf2 Mon Sep 17 00:00:00 2001 From: pkoleva Date: Fri, 18 Aug 2017 09:34:33 +0300 Subject: [PATCH 187/212] Documented `--provision` option in build-ios.md (#3068) Documented `--provision` option in build-ios.md --- docs/man_pages/project/testing/build-ios.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/man_pages/project/testing/build-ios.md b/docs/man_pages/project/testing/build-ios.md index 57511c2817..9e6127ee8b 100644 --- a/docs/man_pages/project/testing/build-ios.md +++ b/docs/man_pages/project/testing/build-ios.md @@ -3,7 +3,7 @@ build ios Usage | Synopsis ---|--- -General | `$ tns build ios [--for-device] [--release] [--copy-to ]` +General | `$ tns build ios [--for-device] [--release] [--copy-to ] [--provision []]` Builds the project for iOS and produces an `APP` or `IPA` that you can manually deploy in the iOS Simulator or on device, respectively. @@ -15,6 +15,7 @@ Builds the project for iOS and produces an `APP` or `IPA` that you can manually * `--release` - If set, produces a release build. Otherwise, produces a debug build. * `--for-device` - If set, produces an application package that you can deploy on device. Otherwise, produces a build that you can run only in the native iOS Simulator. * `--copy-to` - Specifies the file path where the built `.ipa` will be copied. If it points to a non-existent directory, it will be created. If the specified value is directory, the original file name will be used. +* `--provision` - If used without parameter, lists all eligible provisioning profiles. If used with UUID or name of your provisioning profile, it will switch to manual signing mode and configure the .xcodeproj file of your app. In this case xcconfig should not contain any provisioning/team id flags. This provisioning profile will be further used for codesigning the app. <% } %> <% if(isHtml) { %> ### Command Limitations From ff5d1e5bfb4b63d777e282681cfff14827c76592 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Mon, 21 Aug 2017 12:23:28 +0300 Subject: [PATCH 188/212] Add option to use chrome-devtools fronted from appspot (#3071) * Add option to use chrome-devtools fronted from appspot URLs starting with `chrome-devtools://devtools/` cannot be opened in chrome with code. However, we can open the urls in https://chrome-devtools-frontend.appspot.com/, so add new option in Public API that allows using the remote version. Keep the current default behavior, i.e. for Android we are using the `bundled` DevTools, for iOS we are using a commit pointing to Chrome 55. In case both `useBundledDevTools` and `useHttpUrl` are passed, the value of `useBundledDevTools` is disregarded. Add unit tests for all of the cases. * Add `devToolsCommit` to debug options (and public API) Currently CLI uses specific SHA of dev tools, that corresponds to Chrome 55. However, we may need different version for Android or iOS, so add new option. This will also allow specifying it from public API, so external calls may pass the commit SHA. --- PublicAPI.md | 18 ++- lib/definitions/debug.d.ts | 18 ++- lib/services/android-debug-service.ts | 17 ++- lib/services/debug-service-base.ts | 19 +++ lib/services/ios-debug-service.ts | 22 ++- test/services/android-debug-service.ts | 162 +++++++++++++++++++++ test/services/ios-debug-service.ts | 187 +++++++++++++++++++++++++ 7 files changed, 425 insertions(+), 18 deletions(-) create mode 100644 test/services/android-debug-service.ts create mode 100644 test/services/ios-debug-service.ts diff --git a/PublicAPI.md b/PublicAPI.md index 0164703980..6104b7a7c8 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -496,9 +496,25 @@ interface IDebugData { */ interface IDebugOptions { /** - * Defines if bundled Chrome DevTools should be used or specific commit. Valid for iOS only. + * Defines if bundled Chrome DevTools should be used or specific commit. + * Default value is true for Android and false for iOS. */ useBundledDevTools?: boolean; + + /** + * Defines if https://chrome-devtools-frontend.appspot.com should be used instead of chrome-devtools://devtools + * In case it is passed, the value of `useBundledDevTools` is disregarded. + * Default value is false. + */ + useHttpUrl?: boolean; + + /** + * Defines the commit that will be used in cases where remote protocol is required. + * For Android this is the case when useHttpUrl is set to true or useBundledDevTools is set to false. + * For iOS the value is used by default and when useHttpUrl is set to true. + * Default value is 02e6bde1bbe34e43b309d4ef774b1168d25fd024 which corresponds to 55.0.2883 Chrome version + */ + devToolsCommit?: string; } ``` diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 5e9cfde535..50bd31b886 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -68,9 +68,25 @@ interface IDebugOptions { justlaunch?: boolean; /** - * Defines if bundled Chrome DevTools should be used or specific commit. Valid for iOS only. + * Defines if bundled Chrome DevTools should be used or specific commit. + * Default value is true for Android and false for iOS. */ useBundledDevTools?: boolean; + + /** + * Defines if https://chrome-devtools-frontend.appspot.com should be used instead of chrome-devtools://devtools + * In case it is passed, the value of `useBundledDevTools` is disregarded. + * Default value is false. + */ + useHttpUrl?: boolean; + + /** + * Defines the commit that will be used in cases where remote protocol is required. + * For Android this is the case when useHttpUrl is set to true or useBundledDevTools is set to false. + * For iOS the value is used by default and when useHttpUrl is set to true. + * Default value is 02e6bde1bbe34e43b309d4ef774b1168d25fd024 which corresponds to 55.0.2883 Chrome version + */ + devToolsCommit?: string; } /** diff --git a/lib/services/android-debug-service.ts b/lib/services/android-debug-service.ts index 04713f1752..ce08d73d4c 100644 --- a/lib/services/android-debug-service.ts +++ b/lib/services/android-debug-service.ts @@ -2,7 +2,7 @@ import { sleep } from "../common/helpers"; import { ChildProcess } from "child_process"; import { DebugServiceBase } from "./debug-service-base"; -class AndroidDebugService extends DebugServiceBase implements IPlatformDebugService { +export class AndroidDebugService extends DebugServiceBase implements IPlatformDebugService { private _device: Mobile.IAndroidDevice = null; private _debuggerClientProcess: ChildProcess; @@ -49,6 +49,14 @@ class AndroidDebugService extends DebugServiceBase implements IPlatformDebugServ return; } + protected getChromeDebugUrl(debugOptions: IDebugOptions, port: number): string { + const debugOpts = _.cloneDeep(debugOptions); + debugOpts.useBundledDevTools = debugOpts.useBundledDevTools === undefined ? true : debugOpts.useBundledDevTools; + + const chromeDebugUrl = super.getChromeDebugUrl(debugOpts, port); + return chromeDebugUrl; + } + private async debugOnEmulator(debugData: IDebugData, debugOptions: IDebugOptions): Promise { // Assure we've detected the emulator as device // For example in case deployOnEmulator had stated new emulator instance @@ -124,11 +132,12 @@ class AndroidDebugService extends DebugServiceBase implements IPlatformDebugServ this.$errors.failWithoutHelp(`The application ${packageName} does not appear to be running on ${deviceId} or is not built with debugging enabled.`); } - let startDebuggerCommand = ["am", "broadcast", "-a", `\"${packageName}-debug\"`, "--ez", "enable", "true"]; + const startDebuggerCommand = ["am", "broadcast", "-a", `\"${packageName}-debug\"`, "--ez", "enable", "true"]; await this.device.adb.executeShellCommand(startDebuggerCommand); - let port = await this.getForwardedLocalDebugPortForPackageName(deviceId, packageName); - return `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=localhost:${port}`; + const port = await this.getForwardedLocalDebugPortForPackageName(deviceId, packageName); + + return this.getChromeDebugUrl(debugOptions, port); } private detachDebugger(packageName: string): Promise { diff --git a/lib/services/debug-service-base.ts b/lib/services/debug-service-base.ts index d50327c442..3429e18e35 100644 --- a/lib/services/debug-service-base.ts +++ b/lib/services/debug-service-base.ts @@ -26,4 +26,23 @@ export abstract class DebugServiceBase extends EventEmitter implements IPlatform } }; } + + protected getChromeDebugUrl(debugOptions: IDebugOptions, port: number): string { + // corresponds to 55.0.2883 Chrome version + const commitSHA = debugOptions.devToolsCommit || "02e6bde1bbe34e43b309d4ef774b1168d25fd024"; + debugOptions.useHttpUrl = debugOptions.useHttpUrl === undefined ? false : debugOptions.useHttpUrl; + + let chromeDevToolsPrefix = `chrome-devtools://devtools/remote/serve_file/@${commitSHA}`; + + if (debugOptions.useBundledDevTools) { + chromeDevToolsPrefix = "chrome-devtools://devtools/bundled"; + } + + if (debugOptions.useHttpUrl) { + chromeDevToolsPrefix = `https://chrome-devtools-frontend.appspot.com/serve_file/@${commitSHA}`; + } + + const chromeUrl = `${chromeDevToolsPrefix}/inspector.html?experiments=true&ws=localhost:${port}`; + return chromeUrl; + } } diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index 15c2f6bf1e..bb14ef2db1 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -14,7 +14,7 @@ const inspectorAppName = "NativeScript Inspector.app"; const inspectorNpmPackageName = "tns-ios-inspector"; const inspectorUiDir = "WebInspectorUI/"; -class IOSDebugService extends DebugServiceBase implements IPlatformDebugService { +export class IOSDebugService extends DebugServiceBase implements IPlatformDebugService { private _lldbProcess: ChildProcess; private _sockets: net.Socket[] = []; private _childProcess: ChildProcess; @@ -93,6 +93,14 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService } } + protected getChromeDebugUrl(debugOptions: IDebugOptions, port: number): string { + const debugOpts = _.cloneDeep(debugOptions); + debugOpts.useBundledDevTools = debugOpts.useBundledDevTools === undefined ? false : debugOpts.useBundledDevTools; + + const chromeDebugUrl = super.getChromeDebugUrl(debugOpts, port); + return chromeDebugUrl; + } + private async killProcess(childProcess: ChildProcess): Promise { if (childProcess) { return new Promise((resolve, reject) => { @@ -195,17 +203,7 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService if (debugOptions.chrome) { this._socketProxy = await this.$socketProxyFactory.createWebSocketProxy(this.getSocketFactory(device)); - let chromeDevToolsPrefix = `chrome-devtools://devtools/`; - - if (debugOptions.useBundledDevTools) { - chromeDevToolsPrefix += "bundled"; - } else { - // corresponds to 55.0.2883 Chrome version - const commitSHA = "02e6bde1bbe34e43b309d4ef774b1168d25fd024"; - chromeDevToolsPrefix += `remote/serve_file/@${commitSHA}`; - } - - return `${chromeDevToolsPrefix}/inspector.html?experiments=true&ws=localhost:${this._socketProxy.options.port}`; + return this.getChromeDebugUrl(debugOptions, this._socketProxy.options.port); } else { this._socketProxy = await this.$socketProxyFactory.createTCPSocketProxy(this.getSocketFactory(device)); await this.openAppInspector(this._socketProxy.address(), debugData, debugOptions); diff --git a/test/services/android-debug-service.ts b/test/services/android-debug-service.ts new file mode 100644 index 0000000000..d7f239136f --- /dev/null +++ b/test/services/android-debug-service.ts @@ -0,0 +1,162 @@ +import { AndroidDebugService } from "../../lib/services/android-debug-service"; +import { Yok } from "../../lib/common/yok"; +import * as stubs from "../stubs"; +import { assert } from "chai"; + +const expectedDevToolsCommitSha = "02e6bde1bbe34e43b309d4ef774b1168d25fd024"; + +class AndroidDebugServiceInheritor extends AndroidDebugService { + constructor(protected $devicesService: Mobile.IDevicesService, + $errors: IErrors, + $logger: ILogger, + $config: IConfiguration, + $androidDeviceDiscovery: Mobile.IDeviceDiscovery, + $androidProcessService: Mobile.IAndroidProcessService, + $net: INet) { + super($devicesService, $errors, $logger, $config, $androidDeviceDiscovery, $androidProcessService, $net); + } + + public getChromeDebugUrl(debugOptions: IDebugOptions, port: number): string { + return super.getChromeDebugUrl(debugOptions, port); + } +} + +const createTestInjector = (): IInjector => { + const testInjector = new Yok(); + testInjector.register("devicesService", {}); + testInjector.register("errors", stubs.ErrorsStub); + testInjector.register("logger", stubs.LoggerStub); + testInjector.register("config", {}); + testInjector.register("androidDeviceDiscovery", {}); + testInjector.register("androidProcessService", {}); + testInjector.register("net", {}); + + return testInjector; +}; + +interface IChromeUrlTestCase { + debugOptions: IDebugOptions; + expectedChromeUrl: string; + scenarioName: string; +} + +describe("androidDebugService", () => { + describe("getChromeDebugUrl", () => { + const expectedPort = 12345; + const customDevToolsCommit = "customDevToolsCommit"; + + const chromUrlTestCases: IChromeUrlTestCase[] = [ + // Default CLI behavior: + { + scenarioName: "useBundledDevTools and useHttpUrl are not passed", + debugOptions: {}, + expectedChromeUrl: `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + + // When useBundledDevTools is true + { + scenarioName: "useBundledDevTools is true and useHttpUrl is not passed", + debugOptions: { + useBundledDevTools: true + }, + expectedChromeUrl: `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + { + scenarioName: "useBundledDevTools is true and useHttpUrl is false", + debugOptions: { + useBundledDevTools: true, + useHttpUrl: false + }, + expectedChromeUrl: `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + { + scenarioName: "useBundledDevTools is true and useHttpUrl is true", + debugOptions: { + useBundledDevTools: true, + useHttpUrl: true + }, + expectedChromeUrl: `https://chrome-devtools-frontend.appspot.com/serve_file/@${expectedDevToolsCommitSha}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + + // When useBundledDevTools is false + { + scenarioName: "useBundledDevTools is false and useHttpUrl is not passed", + debugOptions: { + useBundledDevTools: false + }, + expectedChromeUrl: `chrome-devtools://devtools/remote/serve_file/@${expectedDevToolsCommitSha}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + { + scenarioName: "useBundledDevTools is false and useHttpUrl is false", + debugOptions: { + useBundledDevTools: false, + useHttpUrl: false + }, + expectedChromeUrl: `chrome-devtools://devtools/remote/serve_file/@${expectedDevToolsCommitSha}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + { + scenarioName: "useBundledDevTools is false and useHttpUrl is true", + debugOptions: { + useBundledDevTools: false, + useHttpUrl: true + }, + expectedChromeUrl: `https://chrome-devtools-frontend.appspot.com/serve_file/@${expectedDevToolsCommitSha}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + + // When useBundledDevTools is not passed + { + scenarioName: "useBundledDevTools is not passed and useHttpUrl is false", + debugOptions: { + useHttpUrl: false + }, + expectedChromeUrl: `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + { + scenarioName: "useBundledDevTools is not passed and useHttpUrl is true", + debugOptions: { + useHttpUrl: true + }, + expectedChromeUrl: `https://chrome-devtools-frontend.appspot.com/serve_file/@${expectedDevToolsCommitSha}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + + // devToolsCommit tests + { + scenarioName: "devToolsCommit defaults to ${expectedDevToolsCommitSha} when useBundledDevTools is set to false", + debugOptions: { + useBundledDevTools: false + }, + expectedChromeUrl: `chrome-devtools://devtools/remote/serve_file/@${expectedDevToolsCommitSha}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + { + scenarioName: "devToolsCommit is disregarded when useBundledDevTools is not passed", + debugOptions: {}, + expectedChromeUrl: `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + { + scenarioName: "devToolsCommit is set to passed value when useBundledDevTools is set to false", + debugOptions: { + useBundledDevTools: false, + devToolsCommit: customDevToolsCommit + }, + expectedChromeUrl: `chrome-devtools://devtools/remote/serve_file/@${customDevToolsCommit}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + { + scenarioName: "devToolsCommit is set to passed value when useHttpUrl is set to true", + debugOptions: { + useHttpUrl: true, + devToolsCommit: customDevToolsCommit + }, + expectedChromeUrl: `https://chrome-devtools-frontend.appspot.com/serve_file/@${customDevToolsCommit}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + } + ]; + + for (const testCase of chromUrlTestCases) { + it(`returns correct url when ${testCase.scenarioName}`, () => { + const testInjector = createTestInjector(); + const androidDebugService = testInjector.resolve(AndroidDebugServiceInheritor); + const actualChromeUrl = androidDebugService.getChromeDebugUrl(testCase.debugOptions, expectedPort); + assert.equal(actualChromeUrl, testCase.expectedChromeUrl); + }); + } + }); +}); diff --git a/test/services/ios-debug-service.ts b/test/services/ios-debug-service.ts new file mode 100644 index 0000000000..2f4898cfcb --- /dev/null +++ b/test/services/ios-debug-service.ts @@ -0,0 +1,187 @@ +import { IOSDebugService } from "../../lib/services/ios-debug-service"; +import { Yok } from "../../lib/common/yok"; +import * as stubs from "../stubs"; +import { assert } from "chai"; + +const expectedDevToolsCommitSha = "02e6bde1bbe34e43b309d4ef774b1168d25fd024"; + +class IOSDebugServiceInheritor extends IOSDebugService { + constructor(protected $devicesService: Mobile.IDevicesService, + $platformService: IPlatformService, + $iOSEmulatorServices: Mobile.IEmulatorPlatformServices, + $childProcess: IChildProcess, + $logger: ILogger, + $errors: IErrors, + $npmInstallationManager: INpmInstallationManager, + $iOSNotification: IiOSNotification, + $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, + $processService: IProcessService, + $socketProxyFactory: ISocketProxyFactory) { + super($devicesService, $platformService, $iOSEmulatorServices, $childProcess, $logger, $errors, + $npmInstallationManager, $iOSNotification, $iOSSocketRequestExecutor, $processService, $socketProxyFactory); + } + + public getChromeDebugUrl(debugOptions: IDebugOptions, port: number): string { + return super.getChromeDebugUrl(debugOptions, port); + } +} + +const createTestInjector = (): IInjector => { + const testInjector = new Yok(); + testInjector.register("devicesService", {}); + testInjector.register("platformService", {}); + testInjector.register("iOSEmulatorServices", {}); + testInjector.register("childProcess", {}); + + testInjector.register("errors", stubs.ErrorsStub); + testInjector.register("logger", stubs.LoggerStub); + testInjector.register("npmInstallationManager", {}); + testInjector.register("iOSNotification", {}); + testInjector.register("iOSSocketRequestExecutor", {}); + testInjector.register("processService", { + attachToProcessExitSignals: (context: any, callback: () => void): void => undefined + }); + + testInjector.register("socketProxyFactory", { + on: (event: string | symbol, listener: Function): any => undefined + }); + + return testInjector; +}; + +interface IChromeUrlTestCase { + debugOptions: IDebugOptions; + expectedChromeUrl: string; + scenarioName: string; +} + +describe("iOSDebugService", () => { + describe("getChromeDebugUrl", () => { + const expectedPort = 12345; + const customDevToolsCommit = "customDevToolsCommit"; + + const chromUrlTestCases: IChromeUrlTestCase[] = [ + // Default CLI behavior: + { + scenarioName: "useBundledDevTools and useHttpUrl are not passed", + debugOptions: {}, + expectedChromeUrl: `chrome-devtools://devtools/remote/serve_file/@${expectedDevToolsCommitSha}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + + // When useBundledDevTools is true + { + scenarioName: "useBundledDevTools is true and useHttpUrl is not passed", + debugOptions: { + useBundledDevTools: true + }, + expectedChromeUrl: `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + { + scenarioName: "useBundledDevTools is true and useHttpUrl is false", + debugOptions: { + useBundledDevTools: true, + useHttpUrl: false + }, + expectedChromeUrl: `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + { + scenarioName: "useBundledDevTools is true and useHttpUrl is true", + debugOptions: { + useBundledDevTools: true, + useHttpUrl: true + }, + expectedChromeUrl: `https://chrome-devtools-frontend.appspot.com/serve_file/@${expectedDevToolsCommitSha}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + + // When useBundledDevTools is false + { + scenarioName: "useBundledDevTools is false and useHttpUrl is not passed", + debugOptions: { + useBundledDevTools: false + }, + expectedChromeUrl: `chrome-devtools://devtools/remote/serve_file/@${expectedDevToolsCommitSha}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + { + scenarioName: "useBundledDevTools is false and useHttpUrl is false", + debugOptions: { + useBundledDevTools: false, + useHttpUrl: false + }, + expectedChromeUrl: `chrome-devtools://devtools/remote/serve_file/@${expectedDevToolsCommitSha}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + { + scenarioName: "useBundledDevTools is false and useHttpUrl is true", + debugOptions: { + useBundledDevTools: false, + useHttpUrl: true + }, + expectedChromeUrl: `https://chrome-devtools-frontend.appspot.com/serve_file/@${expectedDevToolsCommitSha}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + + // When useBundledDevTools is not passed + { + scenarioName: "useBundledDevTools is not passed and useHttpUrl is false", + debugOptions: { + useHttpUrl: false + }, + expectedChromeUrl: `chrome-devtools://devtools/remote/serve_file/@${expectedDevToolsCommitSha}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + { + scenarioName: "useBundledDevTools is not passed and useHttpUrl is true", + debugOptions: { + useHttpUrl: true + }, + expectedChromeUrl: `https://chrome-devtools-frontend.appspot.com/serve_file/@${expectedDevToolsCommitSha}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + + // devToolsCommit tests + { + scenarioName: "devToolsCommit defaults to ${expectedDevToolsCommitSha} and is used in result when useBundledDevTools is not passed", + debugOptions: {}, + expectedChromeUrl: `chrome-devtools://devtools/remote/serve_file/@${expectedDevToolsCommitSha}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + { + scenarioName: "devToolsCommit is set to passed value when useBundledDevTools is not passed", + debugOptions: { + devToolsCommit: customDevToolsCommit + }, + expectedChromeUrl: `chrome-devtools://devtools/remote/serve_file/@${customDevToolsCommit}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + { + scenarioName: "devToolsCommit is set to passed value when useBundledDevTools is set to false", + debugOptions: { + useBundledDevTools: false, + devToolsCommit: customDevToolsCommit + }, + expectedChromeUrl: `chrome-devtools://devtools/remote/serve_file/@${customDevToolsCommit}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + { + scenarioName: "devToolsCommit is set to passed value when useHttpUrl is set to true", + debugOptions: { + useHttpUrl: true, + devToolsCommit: customDevToolsCommit + }, + expectedChromeUrl: `https://chrome-devtools-frontend.appspot.com/serve_file/@${customDevToolsCommit}/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + }, + { + scenarioName: "devToolsCommit is disregarded when useBundledDevTools is set to true", + debugOptions: { + useBundledDevTools: true, + devToolsCommit: customDevToolsCommit + }, + expectedChromeUrl: `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=localhost:${expectedPort}`, + } + + ]; + + for (const testCase of chromUrlTestCases) { + it(`returns correct url when ${testCase.scenarioName}`, () => { + const testInjector = createTestInjector(); + const iOSDebugService = testInjector.resolve(IOSDebugServiceInheritor); + const actualChromeUrl = iOSDebugService.getChromeDebugUrl(testCase.debugOptions, expectedPort); + assert.equal(actualChromeUrl, testCase.expectedChromeUrl); + }); + } + + }); +}); From a25fa758e202a0721d2770b01bc81e7854171536 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Tue, 22 Aug 2017 14:06:16 +0300 Subject: [PATCH 189/212] Fix stopLiveSync method (#3077) * Fix stopLiveSync method Currently stopLiveSync method does not emit correct events in some cases. For example, when there's only one attached device and its identifier is passed to `stopLiveSync` method, we do not emit `liveSyncStopped` event. Fix the code and add unit tests to assure `liveSyncStopped` event is raised correctly. * Expose getLiveSyncDeviceDescriptors method Add new public method `getLiveSyncDeviceDescriptors` to `liveSyncService` - its purpose is to get information for current LiveSync operation. --- PublicAPI.md | 23 ++++ lib/definitions/livesync.d.ts | 9 ++ lib/services/livesync/livesync-service.ts | 22 ++-- test/services/livesync-service.ts | 147 ++++++++++++++++++++++ test/stubs.ts | 4 + 5 files changed, 196 insertions(+), 9 deletions(-) create mode 100644 test/services/livesync-service.ts diff --git a/PublicAPI.md b/PublicAPI.md index 6104b7a7c8..dce94a45d4 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -623,6 +623,29 @@ tns.liveSyncService.stopLiveSync(projectDir, deviceIdentifiers) }); ``` +### getLiveSyncDeviceDescriptors +Gives information for currently running LiveSync operation and parameters used to start it on each device. + +* Definition +```TypeScript +/** + * Returns the device information for current LiveSync operation of specified project. + * In case LiveSync has been started on many devices, but stopped for some of them at a later point, + * calling the method after that will return information only for devices for which LiveSync operation is in progress. + * @param {string} projectDir The path to project for which the LiveSync operation is executed + * @returns {ILiveSyncDeviceInfo[]} Array of elements describing parameters used to start LiveSync on each device. +*/ +getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[]; +``` + +* Usage +```JavaScript +const projectDir = "myProjectDir"; +const deviceIdentifiers = [ "4df18f307d8a8f1b", "12318af23ebc0e25" ]; +const currentlyRunningDescriptors = tns.liveSyncService.getLiveSyncDeviceDescriptors(projectDir); +console.log(`LiveSync for ${projectDir} is currently running on the following devices: ${currentlyRunningDescriptors.map(descriptor => descriptor.identifier)}`); +``` + ### Events `liveSyncService` raises several events in order to provide information for current state of the operation. * liveSyncStarted - raised whenever CLI starts a LiveSync operation for specific device. When `liveSync` method is called, the initial LiveSync operation will emit `liveSyncStarted` for each specified device. After that the event will be emitted only in case when liveSync method is called again with different device instances. The event is raised with the following data: diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index ec89823dbe..4216a9bbeb 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -185,6 +185,15 @@ interface ILiveSyncService { * @returns {Promise} */ stopLiveSync(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise; + + /** + * Returns the device information for current LiveSync operation of specified project. + * In case LiveSync has been started on many devices, but stopped for some of them at a later point, + * calling the method after that will return information only for devices for which LiveSync operation is in progress. + * @param {string} projectDir The path to project for which the LiveSync operation is executed + * @returns {ILiveSyncDeviceInfo[]} Array of elements describing parameters used to start LiveSync on each device. + */ + getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[]; } /** diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 69f112b8af..b8b0829979 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -17,7 +17,7 @@ const LiveSyncEvents = { export class LiveSyncService extends EventEmitter implements ILiveSyncService { // key is projectDir - private liveSyncProcessesInfo: IDictionary = {}; + protected liveSyncProcessesInfo: IDictionary = {}; constructor(protected $platformService: IPlatformService, private $projectDataService: IProjectDataService, @@ -48,12 +48,10 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { // so we cannot await it as this will cause infinite loop. const shouldAwaitPendingOperation = !stopOptions || stopOptions.shouldAwaitAllActions; - let removedDeviceIdentifiers: string[] = deviceIdentifiers || []; + const deviceIdentifiersToRemove = deviceIdentifiers || _.map(liveSyncProcessInfo.deviceDescriptors, d => d.identifier); - _.each(deviceIdentifiers, deviceId => { - removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => descriptor.identifier === deviceId) - .map(deviceDescriptor => deviceDescriptor.identifier); - }); + const removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => _.includes(deviceIdentifiersToRemove, descriptor.identifier)) + .map(descriptor => descriptor.identifier); // In case deviceIdentifiers are not passed, we should stop the whole LiveSync. if (!deviceIdentifiers || !deviceIdentifiers.length || !liveSyncProcessInfo.deviceDescriptors || !liveSyncProcessInfo.deviceDescriptors.length) { @@ -72,7 +70,6 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { await liveSyncProcessInfo.actionsChain; } - removedDeviceIdentifiers = _.map(liveSyncProcessInfo.deviceDescriptors, d => d.identifier); liveSyncProcessInfo.deviceDescriptors = []; // Kill typescript watcher @@ -93,6 +90,12 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { } } + public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { + const liveSyncProcessesInfo = this.liveSyncProcessesInfo[projectDir] || {}; + const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors; + return currentDescriptors || []; + } + protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo): Promise { const platformLiveSyncService = this.getLiveSyncService(liveSyncResultInfo.deviceAppData.platform); try { @@ -127,7 +130,8 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const isAlreadyLiveSyncing = this.liveSyncProcessesInfo[projectData.projectDir] && !this.liveSyncProcessesInfo[projectData.projectDir].isStopped; // Prevent cases where liveSync is called consecutive times with the same device, for example [ A, B, C ] and then [ A, B, D ] - we want to execute initialSync only for D. - const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors; + const currentlyRunningDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectData.projectDir); + const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentlyRunningDeviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors; this.setLiveSyncProcessInfo(liveSyncData.projectDir, deviceDescriptors); await this.initialSync(projectData, deviceDescriptorsForInitialSync, liveSyncData); @@ -146,7 +150,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { this.liveSyncProcessesInfo[projectDir].currentSyncAction = this.liveSyncProcessesInfo[projectDir].actionsChain; this.liveSyncProcessesInfo[projectDir].isStopped = false; - const currentDeviceDescriptors = this.liveSyncProcessesInfo[projectDir].deviceDescriptors || []; + const currentDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectDir); this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), deviceDescriptorPrimaryKey); } diff --git a/test/services/livesync-service.ts b/test/services/livesync-service.ts new file mode 100644 index 0000000000..75d2073bec --- /dev/null +++ b/test/services/livesync-service.ts @@ -0,0 +1,147 @@ +import { Yok } from "../../lib/common/yok"; +import { assert } from "chai"; +import { LiveSyncService } from "../../lib/services/livesync/livesync-service"; +import { LoggerStub } from "../stubs"; + +const createTestInjector = (): IInjector => { + const testInjector = new Yok(); + + testInjector.register("platformService", {}); + testInjector.register("projectDataService", { + getProjectData: (projectDir: string): IProjectData => ({}) + }); + + testInjector.register("devicesService", {}); + testInjector.register("mobileHelper", {}); + testInjector.register("devicePlatformsConstants", {}); + testInjector.register("nodeModulesDependenciesBuilder", {}); + testInjector.register("logger", LoggerStub); + testInjector.register("processService", {}); + testInjector.register("hooksService", { + executeAfterHooks: (commandName: string, hookArguments?: IDictionary): Promise => Promise.resolve() + }); + + testInjector.register("pluginsService", {}); + testInjector.register("injector", testInjector); + + return testInjector; +}; + +class LiveSyncServiceInheritor extends LiveSyncService { + constructor($platformService: IPlatformService, + $projectDataService: IProjectDataService, + $devicesService: Mobile.IDevicesService, + $mobileHelper: Mobile.IMobileHelper, + $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, + $logger: ILogger, + $processService: IProcessService, + $hooksService: IHooksService, + $pluginsService: IPluginsService, + $injector: IInjector) { + + super( + $platformService, + $projectDataService, + $devicesService, + $mobileHelper, + $devicePlatformsConstants, + $nodeModulesDependenciesBuilder, + $logger, + $processService, + $hooksService, + $pluginsService, + $injector + ); + } + + public liveSyncProcessesInfo: IDictionary = {}; +} + +interface IStopLiveSyncTestCase { + name: string; + currentDeviceIdentifiers: string[]; + expectedDeviceIdentifiers: string[]; + deviceIdentifiersToBeStopped?: string[]; +} + +describe("liveSyncService", () => { + describe("stopLiveSync", () => { + const getLiveSyncProcessInfo = (): ILiveSyncProcessInfo => ({ + actionsChain: Promise.resolve(), + currentSyncAction: Promise.resolve(), + isStopped: false, + timer: setTimeout(() => undefined, 1000), + watcherInfo: { + watcher: { + close: (): any => undefined + }, + pattern: "pattern" + }, + deviceDescriptors: [] + }); + + const getDeviceDescriptor = (identifier: string): ILiveSyncDeviceInfo => ({ + identifier, + outputPath: "", + skipNativePrepare: false, + platformSpecificOptions: null, + buildAction: () => Promise.resolve("") + }); + + const testCases: IStopLiveSyncTestCase[] = [ + { + name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers", + currentDeviceIdentifiers: ["device1", "device2", "device3"], + expectedDeviceIdentifiers: ["device1", "device2", "device3"] + }, + { + name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers (when a single device is attached)", + currentDeviceIdentifiers: ["device1"], + expectedDeviceIdentifiers: ["device1"] + }, + { + name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them (when a single device is attached)", + currentDeviceIdentifiers: ["device1"], + expectedDeviceIdentifiers: ["device1"], + deviceIdentifiersToBeStopped: ["device1"] + }, + { + name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them", + currentDeviceIdentifiers: ["device1", "device2", "device3"], + expectedDeviceIdentifiers: ["device1", "device3"], + deviceIdentifiersToBeStopped: ["device1", "device3"] + }, + { + name: "does not raise liveSyncStopped event for device, which is not currently being liveSynced", + currentDeviceIdentifiers: ["device1", "device2", "device3"], + expectedDeviceIdentifiers: ["device1"], + deviceIdentifiersToBeStopped: ["device1", "device4"] + } + ]; + + for (const testCase of testCases) { + it(testCase.name, async () => { + const testInjector = createTestInjector(); + const liveSyncService = testInjector.resolve(LiveSyncServiceInheritor); + const projectDir = "projectDir"; + const emittedDeviceIdentifiersForLiveSyncStoppedEvent: string[] = []; + liveSyncService.on("liveSyncStopped", (data: { projectDir: string, deviceIdentifier: string }) => { + assert.equal(data.projectDir, projectDir); + emittedDeviceIdentifiersForLiveSyncStoppedEvent.push(data.deviceIdentifier); + }); + + // Setup liveSyncProcessesInfo for current test + liveSyncService.liveSyncProcessesInfo[projectDir] = getLiveSyncProcessInfo(); + const deviceDescriptors = testCase.currentDeviceIdentifiers.map(d => getDeviceDescriptor(d)); + liveSyncService.liveSyncProcessesInfo[projectDir].deviceDescriptors.push(...deviceDescriptors); + + await liveSyncService.stopLiveSync(projectDir, testCase.deviceIdentifiersToBeStopped); + + assert.deepEqual(emittedDeviceIdentifiersForLiveSyncStoppedEvent, testCase.expectedDeviceIdentifiers); + }); + } + + }); + +}); diff --git a/test/stubs.ts b/test/stubs.ts index 1a4902a444..cfb77841db 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -500,6 +500,10 @@ export class LiveSyncServiceStub implements ILiveSyncService { public async stopLiveSync(projectDir: string): Promise { return; } + + public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { + return []; + } } export class AndroidToolsInfoStub implements IAndroidToolsInfo { From f762d8819438bf62658de4a3e08250eee2db09ba Mon Sep 17 00:00:00 2001 From: Emil Tabakov Date: Tue, 22 Aug 2017 14:57:56 +0300 Subject: [PATCH 190/212] Fix typo in the messages (#3078) --- setup/native-script.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup/native-script.ps1 b/setup/native-script.ps1 index 1543c6924f..605fd226a4 100644 --- a/setup/native-script.ps1 +++ b/setup/native-script.ps1 @@ -55,8 +55,8 @@ function Pause { } # Actually installing all other dependencies -# Install Chocolately -Install "Chocolately(It's mandatory for the rest of the script)" "Installing Chocolately" "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" +# Install Chocolatey +Install "Chocolatey (It's mandatory for the rest of the script)" "Installing Chocolatey" "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" if ((Get-Command "cinst" -ErrorAction SilentlyContinue) -eq $null) { Write-Host -ForegroundColor Red "Chocolatey is not installed or not configured properly. Download it from https://chocolatey.org/, install, set it up and run this script again." @@ -64,7 +64,7 @@ if ((Get-Command "cinst" -ErrorAction SilentlyContinue) -eq $null) { exit 1 } -# Install dependenciess with Chocolately +# Install dependenciess with Chocolatey Install "Google Chrome" "Installing Google Chrome (required to debug NativeScript apps)" "cinst googlechrome --force --yes" From 8223f4f1994b5dbfbc65478a658f1fce0a141081 Mon Sep 17 00:00:00 2001 From: Toma Nikolov Date: Tue, 22 Aug 2017 15:48:54 +0300 Subject: [PATCH 191/212] Fix native platform add after cloud build. (#3075) When we prepare the platform for cloud build we skip the native prepare. After this partial prepare, the platform directory contains only .js files. In order to make a local build we should add the native part of the platform which fails with `ENOTEMPTY: directory not empty, rename`. --- lib/services/platform-service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 1d072f6592..3f02b9c341 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -144,6 +144,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { config.pathToTemplate = customTemplateOptions && customTemplateOptions.pathToTemplate; if (!nativePrepare || !nativePrepare.skipNativePrepare) { + const platformDir = path.join(projectData.platformsDir, platformData.normalizedPlatformName.toLowerCase()); + this.$fs.deleteDirectory(platformDir); await this.addPlatformCoreNative(platformData, frameworkDir, installedVersion, projectData, config); } From 5fc07a7bc46c8d4aa7c10ef0eccbc1d7a1a6f9ad Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Tue, 22 Aug 2017 17:25:55 +0300 Subject: [PATCH 192/212] Fix simultaneous livesync error (#3074) --- lib/common | 2 +- lib/services/livesync/platform-livesync-service-base.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/common b/lib/common index b218c1863f..604a1ddccd 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit b218c1863fa7ba76d05db3642ee26c5f27ed760d +Subproject commit 604a1ddccd6e3bcc3e8beb4dee0d658e99bc2bdd diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index 298d34c902..bb25daf456 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -64,7 +64,7 @@ export abstract class PlatformLiveSyncServiceBase { const mappedFiles = _.map(filesToSync, filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)); // Some plugins modify platforms dir on afterPrepare (check nativescript-dev-sass) - we want to sync only existing file. - const existingFiles = mappedFiles.filter(m => this.$fs.exists(m)); + const existingFiles = mappedFiles.filter(m => m && this.$fs.exists(m)); this.$logger.trace("Will execute livesync for files: ", existingFiles); const skippedFiles = _.difference(mappedFiles, existingFiles); if (skippedFiles.length) { @@ -75,7 +75,7 @@ export abstract class PlatformLiveSyncServiceBase { const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, - projectFilesPath, mappedFiles, []); + projectFilesPath, existingFiles, []); modifiedLocalToDevicePaths.push(...localToDevicePaths); await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, false); } @@ -85,7 +85,10 @@ export abstract class PlatformLiveSyncServiceBase { const filePaths = liveSyncInfo.filesToRemove; const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); - const mappedFiles = _.map(filePaths, filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)); + const mappedFiles = _(filePaths) + .map(filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)) + .filter(filePath => !!filePath) + .value(); const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, mappedFiles, []); modifiedLocalToDevicePaths.push(...localToDevicePaths); From dd9aa325ca5f4985e083bfda4be720108bb783ff Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Wed, 23 Aug 2017 08:37:11 +0300 Subject: [PATCH 193/212] Fix prepare of scoped plugins (#3080) In case a plugin depends on a scoped package, for example `@angular/core`, trying to use the plugin from local path with npm 5 will fail. The recommended approach is to declare the scoped package (`@angular/core`) as a devDependency of the plugin. After that, declare both the plugin and the scoped package as dependencies of your project. In case the plugin is installed from local directory, npm 5 creates symlink in node_modules. This means that all files inside `/node_modules` will be visible for CLI, including the dev dependencies. During project preparation CLI copies `node_modules` from the project to `/platforms/...` directory. We have a specific logic to process only dependencies of each package, i.e. we should remove `@angular/core` from the plugin's node_modules direcotry. However, the logic is not working correctly as instead of using the name `@angular/core` it's been trying to remove `core` only. Fix getting names of the dependencies, so we'll be able to remove the scoped packages as well. --- lib/tools/node-modules/node-modules-dest-copy.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts index f80b0f175b..f3f534c373 100644 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ b/lib/tools/node-modules/node-modules-dest-copy.ts @@ -65,7 +65,9 @@ export class TnsModulesCopy { const dependencies = _.flatten(this.$fs.readDirectory(dependenciesFolder) .map(dir => { if (_.startsWith(dir, "@")) { - return this.$fs.readDirectory(path.join(dependenciesFolder, dir)); + const pathToDir = path.join(dependenciesFolder, dir); + const contents = this.$fs.readDirectory(pathToDir); + return _.map(contents, subDir => `${dir}/${subDir}`); } return dir; From e6eda4177f608f864f0a0bd5bbed94577a656cb7 Mon Sep 17 00:00:00 2001 From: Toma Nikolov Date: Wed, 23 Aug 2017 09:55:52 +0300 Subject: [PATCH 194/212] Improve iOS log filter regex. (#3079) --- lib/common | 2 +- lib/services/ios-log-filter.ts | 2 +- test/services/ios-log-filter.ts | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/common b/lib/common index 604a1ddccd..2287fd9efa 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 604a1ddccd6e3bcc3e8beb4dee0d658e99bc2bdd +Subproject commit 2287fd9efa56ee985d720d49f8aef4e7cd362da2 diff --git a/lib/services/ios-log-filter.ts b/lib/services/ios-log-filter.ts index 55aed86a16..713122e1ea 100644 --- a/lib/services/ios-log-filter.ts +++ b/lib/services/ios-log-filter.ts @@ -4,7 +4,7 @@ import { cache } from "../common/decorators"; import * as iOSLogFilterBase from "../common/mobile/ios/ios-log-filter"; export class IOSLogFilter extends iOSLogFilterBase.IOSLogFilter implements Mobile.IPlatformLogFilter { - protected infoFilterRegex = /^.*?(:.*?(((?:CONSOLE|JS) (?:LOG|ERROR)).*?))$/im; + protected infoFilterRegex = /^.*?((?::)?.*?(((?:CONSOLE|JS) (?:LOG|ERROR)).*?))$/im; private partialLine: string = null; diff --git a/test/services/ios-log-filter.ts b/test/services/ios-log-filter.ts index c8e2588275..eba20f5ffe 100644 --- a/test/services/ios-log-filter.ts +++ b/test/services/ios-log-filter.ts @@ -32,6 +32,7 @@ describe("iOSLogFilter", () => { "May 24 14:44:59 iPad-90 mobile_installation_proxy[355] : 0x1f197000 LoadInfoPlist: Failed to create CFBundle from URL file:///private/var/mobile/Containers/Bundle/Application/0DA02818-DCAE-407C-979D-D55F4F36F8D2/NativeScript300.app", " May 24 14:44:59 iPad-90 mobile_installation_proxy[355] : 0x1f197000 LoadInfoPlist: Failed to create CFBundle from URL file:///private/var/mobile/Containers/Bundle/Application/B0EE9362-7BDD-4FF2-868F-857B76D9D8D3/Cordova370.app", " May 24 14:44:59 iPad-90 NativeScript250[790] : CONSOLE ERROR file:///app/tns_modules/@angular/core/bundles/core.umd.js:3472:32: EXCEPTION: Uncaught (in promise): Error: CUSTOM EXCEPTION", + "Aug 22 10:59:20 MCSOFAPPBLD TestApp[52946]: CONSOLE LOG file:///app/home/home-view-model.js:6:20: CUSTOM CONSOLE LOG", "" ], infoExpectedArr: [ @@ -43,6 +44,7 @@ describe("iOSLogFilter", () => { null, null, "CONSOLE ERROR file:///app/tns_modules/@angular/core/bundles/core.umd.js:3472:32: EXCEPTION: Uncaught (in promise): Error: CUSTOM EXCEPTION", + "CONSOLE LOG file:///app/home/home-view-model.js:6:20: CUSTOM CONSOLE LOG", "" ] }, @@ -65,6 +67,7 @@ describe("iOSLogFilter", () => { "UIApplicationMain@[native code]", "start@file:///app/tns_modules/tns-core-modules/application/application.js:251:26", "bootstrapApp@file:///app/tns_module", + "Aug 22 10:59:20 MCSOFAPPBLD TestApp[52946]: CONSOLE LOG file:///app/home/home-view-model.js:6:20: CUSTOM CONSOLE LOG", "" ], infoExpectedArr: [ @@ -84,6 +87,7 @@ describe("iOSLogFilter", () => { null, null, null, + "CONSOLE LOG file:///app/home/home-view-model.js:6:20: CUSTOM CONSOLE LOG", "" ] } From 0d536f43acd9ae2dcfb8e0e319e1ff6768a40c15 Mon Sep 17 00:00:00 2001 From: Emil Tabakov Date: Wed, 23 Aug 2017 15:30:55 +0300 Subject: [PATCH 195/212] Install correct emulators if/not HAMX is supported (#3072) --- setup/native-script.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup/native-script.rb b/setup/native-script.rb index 8bdc7fb51f..8687c50336 100755 --- a/setup/native-script.rb +++ b/setup/native-script.rb @@ -143,8 +143,10 @@ def install_environment_variable(name, value) execute("echo y | #{sdk_manager} \"extras;intel;Hardware_Accelerated_Execution_Manager\"", error_msg) haxm_silent_installer = File.join(ENV["ANDROID_HOME"], "extras", "intel", "Hardware_Accelerated_Execution_Manager", "silent_install.sh") execute("sudo #{haxm_silent_installer}", "There seem to be some problems with the Android configuration") - end - execute("echo y | #{sdk_manager} \"system-images;android-23;default;x86\"", error_msg) + execute("echo y | #{sdk_manager} \"system-images;android-23;default;x86\"", error_msg) + else + execute("echo y | #{sdk_manager} \"system-images;android-25;google_apis;armeabi-v7a\"", error_msg) + end end puts "The ANDROID_HOME and JAVA_HOME environment variables have been added to your .bash_profile/.zprofile" From 9fb0efcbb8215b18073f606a94e33d8622995be9 Mon Sep 17 00:00:00 2001 From: Panayot Cankov Date: Wed, 23 Aug 2017 17:15:09 +0300 Subject: [PATCH 196/212] Fix xcode9 provision, provide --quiet by default, enable -allowProvisioningUpdates (#3070) * Setup the provisioningProfiles in the exportOptions plist, provide quiet flags to xcodebuild and gradle, provide -allowProvisioningUpdates to xcodebuild > 9.0 * Using teamId without arg will print all teams, teamId and provision are mutually exclusive, teamId now should work with name if a provision that has the team id for the provided team name is found * Add documentation on --team-id near --provision * Fix versionCompare for the '8.0' and '9.0' versions we compare in ios-project-service * Address PR review * Address another PR comment, native builds will now print short friendly message... --- docs/man_pages/project/testing/build-ios.md | 1 + lib/commands/appstore-upload.ts | 2 +- lib/commands/build.ts | 4 +- lib/commands/debug.ts | 4 +- lib/commands/deploy.ts | 2 +- lib/commands/prepare.ts | 2 +- lib/commands/run.ts | 4 +- lib/definitions/platform.d.ts | 4 +- lib/definitions/project-changes.d.ts | 4 +- lib/definitions/project.d.ts | 4 +- lib/options.ts | 2 +- lib/services/android-project-service.ts | 10 +- lib/services/ios-project-service.ts | 273 +++++++++++--------- lib/services/ios-provision-service.ts | 38 ++- lib/services/local-build-service.ts | 1 + lib/services/platform-service.ts | 8 +- lib/services/project-changes-service.ts | 4 +- test/ios-project-service.ts | 24 +- test/platform-commands.ts | 8 +- test/platform-service.ts | 5 +- test/project-changes-service.ts | 6 +- test/stubs.ts | 12 +- 22 files changed, 243 insertions(+), 179 deletions(-) diff --git a/docs/man_pages/project/testing/build-ios.md b/docs/man_pages/project/testing/build-ios.md index 9e6127ee8b..de0ee38850 100644 --- a/docs/man_pages/project/testing/build-ios.md +++ b/docs/man_pages/project/testing/build-ios.md @@ -15,6 +15,7 @@ Builds the project for iOS and produces an `APP` or `IPA` that you can manually * `--release` - If set, produces a release build. Otherwise, produces a debug build. * `--for-device` - If set, produces an application package that you can deploy on device. Otherwise, produces a build that you can run only in the native iOS Simulator. * `--copy-to` - Specifies the file path where the built `.ipa` will be copied. If it points to a non-existent directory, it will be created. If the specified value is directory, the original file name will be used. +* `--team-id` - If used without parameter, lists all team names and ids. If used with team name or id, it will switch to automatic signing mode and configure the .xcodeproj file of your app. In this case .xcconfig should not contain any provisioning/team id flags. This team id will be further used for codesigning the app. For Xcode 9.0+, xcodebuild will be allowed to update and modify automatically managed provisioning profiles. * `--provision` - If used without parameter, lists all eligible provisioning profiles. If used with UUID or name of your provisioning profile, it will switch to manual signing mode and configure the .xcodeproj file of your app. In this case xcconfig should not contain any provisioning/team id flags. This provisioning profile will be further used for codesigning the app. <% } %> <% if(isHtml) { %> diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index dfca00ff9c..48671fea98 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -83,7 +83,7 @@ export class PublishIOS implements ICommand { let archivePath = await iOSProjectService.archive(this.$projectData); this.$logger.info("Archive at: " + archivePath); - let exportPath = await iOSProjectService.exportArchive(this.$projectData, { archivePath, teamID }); + let exportPath = await iOSProjectService.exportArchive(this.$projectData, { archivePath, teamID, provision: mobileProvisionIdentifier || this.$options.provision }); this.$logger.info("Export at: " + exportPath); ipaFilePath = exportPath; diff --git a/lib/commands/build.ts b/lib/commands/build.ts index d0185286d2..04b86c12e4 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -57,7 +57,7 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { public canExecute(args: string[]): Promise { super.validatePlatform(this.$devicePlatformsConstants.iOS); - return args.length === 0 && this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); + return args.length === 0 && this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.iOS); } } @@ -89,7 +89,7 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { const platformProjectService = platformData.platformProjectService; await platformProjectService.validate(this.$projectData); - return args.length === 0 && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.Android); + return args.length === 0 && await this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.Android); } } diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 144ae61d23..cc04b09411 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -154,7 +154,7 @@ export class DebugIOSCommand implements ICommand { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } - return await this.debugPlatformCommand.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); + return await this.debugPlatformCommand.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.iOS); } public platform = this.$devicePlatformsConstants.iOS; @@ -186,7 +186,7 @@ export class DebugAndroidCommand implements ICommand { return this.debugPlatformCommand.execute(args); } public async canExecute(args: string[]): Promise { - return await this.debugPlatformCommand.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.Android); + return await this.debugPlatformCommand.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.Android); } public platform = this.$devicePlatformsConstants.Android; diff --git a/lib/commands/deploy.ts b/lib/commands/deploy.ts index 3625fb8ae2..3fc90bc5c7 100644 --- a/lib/commands/deploy.ts +++ b/lib/commands/deploy.ts @@ -48,7 +48,7 @@ export class DeployOnDeviceCommand implements ICommand { const platformProjectService = platformData.platformProjectService; await platformProjectService.validate(this.$projectData); - return this.$platformService.validateOptions(this.$options.provision, this.$projectData, args[0]); + return this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, args[0]); } } diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts index bf1d775c9a..489ebcd358 100644 --- a/lib/commands/prepare.ts +++ b/lib/commands/prepare.ts @@ -16,7 +16,7 @@ export class PrepareCommand implements ICommand { public async canExecute(args: string[]): Promise { const platform = args[0]; - const result = await this.$platformCommandParameter.validate(platform) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, platform); + const result = await this.$platformCommandParameter.validate(platform) && await this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, platform); if (result) { const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); const platformProjectService = platformData.platformProjectService; diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 2f30032475..76a974a185 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -97,7 +97,7 @@ export class RunIosCommand implements ICommand { } public async canExecute(args: string[]): Promise { - return await this.runCommand.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); + return await this.runCommand.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.iOS); } } @@ -140,7 +140,7 @@ export class RunAndroidCommand implements ICommand { if (this.$options.release && (!this.$options.keyStorePath || !this.$options.keyStorePassword || !this.$options.keyStoreAlias || !this.$options.keyStoreAliasPassword)) { this.$errors.fail("When producing a release build, you need to specify all --key-store-* options."); } - return this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.Android); + return this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.Android); } } diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 058f33fffa..3c5e8cd5d1 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -101,7 +101,7 @@ interface IPlatformService extends NodeJS.EventEmitter { * If no platform is provided or a falsy (null, undefined, "", false...) platform is provided, * the options will be validated for all available platforms. */ - validateOptions(provision: any, projectData: IProjectData, platform?: string): Promise; + validateOptions(provision: true | string, teamId: true | string, projectData: IProjectData, platform?: string): Promise; /** * Executes prepare, build and installOnPlatform when necessary to ensure that the latest version of the app is installed on specified platform. @@ -218,7 +218,7 @@ interface IPlatformOptions extends IPlatformSpecificData, ICreateProjectOptions /** * Platform specific data required for project preparation. */ -interface IPlatformSpecificData extends IProvision { +interface IPlatformSpecificData extends IProvision, ITeamIdentifier { /** * Target SDK for Android. */ diff --git a/lib/definitions/project-changes.d.ts b/lib/definitions/project-changes.d.ts index 04ae5edab4..0f2143102a 100644 --- a/lib/definitions/project-changes.d.ts +++ b/lib/definitions/project-changes.d.ts @@ -22,12 +22,12 @@ interface IProjectChangesInfo extends IAddedNativePlatform { readonly changesRequirePrepare: boolean; } -interface IProjectChangesOptions extends IAppFilesUpdaterOptions, IProvision { +interface IProjectChangesOptions extends IAppFilesUpdaterOptions, IProvision, ITeamIdentifier { nativePlatformStatus?: "1" | "2" | "3"; } interface IProjectChangesService { - checkForChanges(platform: string, projectData: IProjectData, buildOptions: IProjectChangesOptions): IProjectChangesInfo; + checkForChanges(platform: string, projectData: IProjectData, buildOptions: IProjectChangesOptions): Promise; getPrepareInfo(platform: string, projectData: IProjectData): IPrepareInfo; savePrepareInfo(platform: string, projectData: IProjectData): void; getPrepareInfoFilePath(platform: string, projectData: IProjectData): string; diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 9a328d9255..f8d4cbd076 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -178,7 +178,7 @@ interface IPlatformProjectService extends NodeJS.EventEmitter { * @param {any} provision UUID of the provisioning profile used in iOS option validation. * @returns {void} */ - validateOptions(projectId?: string, provision?: true | string): Promise; + validateOptions(projectId?: string, provision?: true | string, teamId?: true | string): Promise; validatePlugins(projectData: IProjectData): Promise; @@ -265,7 +265,7 @@ interface IPlatformProjectService extends NodeJS.EventEmitter { * Check the current state of the project, and validate against the options. * If there are parts in the project that are inconsistent with the desired options, marks them in the changeset flags. */ - checkForChanges(changeset: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): void; + checkForChanges(changeset: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): Promise; } interface IAndroidProjectPropertiesManager { diff --git a/lib/options.ts b/lib/options.ts index 7f71b3dcbb..a5c960007a 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -34,7 +34,7 @@ export class Options extends commonOptionsLibPath.OptionsBase { androidTypings: { type: OptionType.Boolean }, bundle: { type: OptionType.Boolean }, all: { type: OptionType.Boolean }, - teamId: { type: OptionType.String }, + teamId: { type: OptionType.Object }, syncAllFiles: { type: OptionType.Boolean, default: false }, liveEdit: { type: OptionType.Boolean }, chrome: { type: OptionType.Boolean }, diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 4dabcefe6d..8b7c53afea 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -447,7 +447,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject await adb.executeShellCommand(["rm", "-rf", deviceRootPath]); } - public checkForChanges(changesInfo: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): void { + public async checkForChanges(changesInfo: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): Promise { // Nothing android specific to check yet. } @@ -517,12 +517,18 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject if (this.$androidToolsInfo.getToolsInfo().androidHomeEnvVar) { const gradlew = this.$hostInfo.isWindows ? "gradlew.bat" : "./gradlew"; + const localArgs = [...gradleArgs]; + if (this.$logger.getLevel() === "INFO") { + localArgs.push("--quiet"); + this.$logger.info("Gradle build..."); + } + childProcessOpts = childProcessOpts || {}; childProcessOpts.cwd = childProcessOpts.cwd || projectRoot; childProcessOpts.stdio = childProcessOpts.stdio || "inherit"; return await this.spawn(gradlew, - gradleArgs, + localArgs, childProcessOpts, spawnFromEventOptions); } diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index cd75de7dad..65c2fe47d3 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -61,7 +61,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } if (projectData && projectData.platformsDir && this._platformsDirCache !== projectData.platformsDir) { - let projectRoot = path.join(projectData.platformsDir, "ios"); + let projectRoot = path.join(projectData.platformsDir, this.$devicePlatformsConstants.iOS.toLowerCase()); this._platformData = { frameworkPackageName: "tns-ios", @@ -93,13 +93,23 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return this._platformData; } - public async validateOptions(projectId: string, provision: true | string): Promise { + public async validateOptions(projectId: string, provision: true | string, teamId: true | string): Promise { + if (provision && teamId) { + this.$errors.failWithoutHelp("The options --provision and --teamId are mutually exclusive."); + } + if (provision === true) { - await this.$iOSProvisionService.list(projectId); + await this.$iOSProvisionService.listProvisions(projectId); this.$errors.failWithoutHelp("Please provide provisioning profile uuid or name with the --provision option."); return false; } + if (teamId === true) { + await this.$iOSProvisionService.listTeams(); + this.$errors.failWithoutHelp("Please provide team id or team name with the --teamId options."); + return false; + } + return true; } @@ -194,19 +204,19 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ let args = ["archive", "-archivePath", archivePath, "-configuration", (!buildConfig || buildConfig.release) ? "Release" : "Debug"] .concat(this.xcbuildProjectArgs(projectRoot, projectData, "scheme")); - await this.$childProcess.spawnFromEvent("xcodebuild", args, "exit", { stdio: 'inherit' }); + await this.xcodebuild(args, projectRoot, buildConfig && buildConfig.buildOutputStdio); return archivePath; } /** * Exports .xcarchive for AppStore distribution. */ - public async exportArchive(projectData: IProjectData, options: { archivePath: string, exportDir?: string, teamID?: string }): Promise { - let projectRoot = this.getPlatformData(projectData).projectRoot; - let archivePath = options.archivePath; + public async exportArchive(projectData: IProjectData, options: { archivePath: string, exportDir?: string, teamID?: string, provision?: string }): Promise { + const projectRoot = this.getPlatformData(projectData).projectRoot; + const archivePath = options.archivePath; // The xcodebuild exportPath expects directory and writes the .ipa at that directory. - let exportPath = path.resolve(options.exportDir || path.join(projectRoot, "/build/archive")); - let exportFile = path.join(exportPath, projectData.projectName + ".ipa"); + const exportPath = path.resolve(options.exportDir || path.join(projectRoot, "/build/archive")); + const exportFile = path.join(exportPath, projectData.projectName + ".ipa"); // These are the options that you can set in the Xcode UI when exporting for AppStore deployment. let plistTemplate = ` @@ -218,6 +228,13 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ plistTemplate += ` teamID ${options.teamID} `; + } + if (options && options.provision) { + plistTemplate += ` provisioningProfiles + + ${projectData.projectId} + ${options.provision} + `; } plistTemplate += ` method app-store @@ -230,23 +247,24 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ // Save the options... temp.track(); - let exportOptionsPlist = temp.path({ prefix: "export-", suffix: ".plist" }); + const exportOptionsPlist = temp.path({ prefix: "export-", suffix: ".plist" }); this.$fs.writeFile(exportOptionsPlist, plistTemplate); - let args = ["-exportArchive", - "-archivePath", archivePath, - "-exportPath", exportPath, - "-exportOptionsPlist", exportOptionsPlist - ]; - await this.$childProcess.spawnFromEvent("xcodebuild", args, "exit", { stdio: 'inherit' }); - + await this.xcodebuild( + [ + "-exportArchive", + "-archivePath", archivePath, + "-exportPath", exportPath, + "-exportOptionsPlist", exportOptionsPlist + ], + projectRoot); return exportFile; } /** * Exports .xcarchive for a development device. */ - private async exportDevelopmentArchive(projectData: IProjectData, buildConfig: IBuildConfig, options: { archivePath: string, exportDir?: string, teamID?: string }): Promise { + private async exportDevelopmentArchive(projectData: IProjectData, buildConfig: IBuildConfig, options: { archivePath: string, exportDir?: string, teamID?: string, provision?: string }): Promise { let platformData = this.getPlatformData(projectData); let projectRoot = platformData.projectRoot; let archivePath = options.archivePath; @@ -257,7 +275,15 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ method - ${exportOptionsMethod} + ${exportOptionsMethod}`; + if (options && options.provision) { + plistTemplate += ` provisioningProfiles + + ${projectData.projectId} + ${options.provision} +`; + } + plistTemplate += ` uploadBitcode @@ -272,15 +298,14 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ let exportPath = path.resolve(options.exportDir || buildOutputPath); let exportFile = path.join(exportPath, projectData.projectName + ".ipa"); - let args = ["-exportArchive", - "-archivePath", archivePath, - "-exportPath", exportPath, - "-exportOptionsPlist", exportOptionsPlist - ]; - await this.$childProcess.spawnFromEvent("xcodebuild", args, "exit", - { stdio: buildConfig.buildOutputStdio || 'inherit', cwd: this.getPlatformData(projectData).projectRoot }, - { emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true }); - + await this.xcodebuild( + [ + "-exportArchive", + "-archivePath", archivePath, + "-exportPath", exportPath, + "-exportOptionsPlist", exportOptionsPlist + ], + projectRoot, buildConfig.buildOutputStdio); return exportFile; } @@ -388,27 +413,62 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ await this.setupSigningForDevice(projectRoot, buildConfig, projectData); } - if (buildConfig && buildConfig.codeSignIdentity) { - args.push(`CODE_SIGN_IDENTITY=${buildConfig.codeSignIdentity}`); - } + await this.xcodebuild(args, projectRoot, buildConfig.buildOutputStdio); + await this.createIpa(projectRoot, projectData, buildConfig); + } - if (buildConfig && buildConfig.mobileProvisionIdentifier) { - args.push(`PROVISIONING_PROFILE=${buildConfig.mobileProvisionIdentifier}`); + private async xcodebuild(args: string[], cwd: string, stdio: any = "inherit"): Promise { + const localArgs = [...args]; + const xcodeBuildVersion = await this.getXcodeVersion(); + try { + if (helpers.versionCompare(xcodeBuildVersion, "9.0") >= 0) { + localArgs.push("-allowProvisioningUpdates"); + } + } catch (e) { + this.$logger.warn("Failed to detect whether -allowProvisioningUpdates can be used with your xcodebuild version due to error: " + e); } - - if (buildConfig && buildConfig.teamId) { - args.push(`DEVELOPMENT_TEAM=${buildConfig.teamId}`); + if (this.$logger.getLevel() === "INFO") { + localArgs.push("-quiet"); + this.$logger.info("Xcode build..."); } - - // this.$logger.out("xcodebuild..."); - await this.$childProcess.spawnFromEvent("xcodebuild", - args, + return this.$childProcess.spawnFromEvent("xcodebuild", + localArgs, "exit", - { stdio: buildConfig.buildOutputStdio || "inherit", cwd: this.getPlatformData(projectData).projectRoot }, + { stdio: stdio || "inherit", cwd }, { emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true }); - // this.$logger.out("xcodebuild build succeded."); + } - await this.createIpa(projectRoot, projectData, buildConfig); + private async setupSigningFromTeam(projectRoot: string, projectData: IProjectData, teamId: string) { + const xcode = this.$pbxprojDomXcode.Xcode.open(this.getPbxProjPath(projectData)); + const signing = xcode.getSigning(projectData.projectName); + + let shouldUpdateXcode = false; + if (signing && signing.style === "Automatic") { + if (signing.team !== teamId) { + // Maybe the provided team is name such as "Telerik AD" and we need to convert it to CH******37 + const teamIdsForName = await this.$iOSProvisionService.getTeamIdsWithName(teamId); + if (!teamIdsForName.some(id => id === signing.team)) { + shouldUpdateXcode = true; + } + } + } else { + shouldUpdateXcode = true; + } + + if (shouldUpdateXcode) { + const teamIdsForName = await this.$iOSProvisionService.getTeamIdsWithName(teamId); + if (teamIdsForName.length > 0) { + this.$logger.trace(`Team id ${teamIdsForName[0]} will be used for team name "${teamId}".`); + teamId = teamIdsForName[0]; + } + + xcode.setAutomaticSigningStyle(projectData.projectName, teamId); + xcode.save(); + + this.$logger.trace(`Set Automatic signing style and team id ${teamId}.`); + } else { + this.$logger.trace(`The specified ${teamId} is already set in the Xcode.`); + } } private async setupSigningFromProvision(projectRoot: string, projectData: IProjectData, provision?: string, mobileProvisionData?: mobileprovision.provision.MobileProvision): Promise { @@ -447,9 +507,9 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ xcode.save(); // this.cache(uuid); - this.$logger.trace("Set Manual signing style and provisioning profile."); + this.$logger.trace(`Set Manual signing style and provisioning profile: ${mobileprovision.Name} (${mobileprovision.UUID})`); } else { - this.$logger.trace("The specified provisioning profile allready set in the Xcode."); + this.$logger.trace(`The specified provisioning profile is already set in the Xcode: ${provision}`); } } else { // read uuid from Xcode and cache... @@ -471,16 +531,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ xcode.save(); } else if (!buildConfig.provision && !(signing && signing.style === "Manual" && !buildConfig.teamId)) { const teamId = await this.getDevelopmentTeam(projectData, buildConfig.teamId); - - // Remove teamId from build config as we'll set the signing in Xcode project directly. - // In case we do not remove it, we'll pass DEVELOPMENT_TEAM= to xcodebuild, which is unnecessary. - if (buildConfig.teamId) { - delete buildConfig.teamId; - } - - xcode.setAutomaticSigningStyle(projectData.projectName, teamId); - xcode.save(); - this.$logger.trace("Set Automatic signing style and team."); + await this.setupSigningFromTeam(projectRoot, projectData, teamId); } } @@ -493,20 +544,12 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ "CONFIGURATION_BUILD_DIR=" + path.join(projectRoot, "build", "emulator"), "CODE_SIGN_IDENTITY=" ]); - - await this.$childProcess.spawnFromEvent("xcodebuild", args, "exit", - { stdio: buildOutputStdio || "inherit", cwd: this.getPlatformData(projectData).projectRoot }, - { emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true }); + await this.xcodebuild(args, projectRoot, buildOutputStdio); } private async createIpa(projectRoot: string, projectData: IProjectData, buildConfig: IBuildConfig): Promise { - let xarchivePath = await this.archive(projectData, buildConfig); - let exportFileIpa = await this.exportDevelopmentArchive(projectData, - buildConfig, - { - archivePath: xarchivePath, - }); - + const archivePath = await this.archive(projectData, buildConfig); + const exportFileIpa = await this.exportDevelopmentArchive(projectData, buildConfig, { archivePath, provision: buildConfig.provision || buildConfig.mobileProvisionIdentifier }); return exportFileIpa; } @@ -655,12 +698,16 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } public async prepareProject(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise { - let provision = platformSpecificData && platformSpecificData.provision; + const projectRoot = path.join(projectData.platformsDir, "ios"); + const provision = platformSpecificData && platformSpecificData.provision; + const teamId = platformSpecificData && platformSpecificData.teamId; if (provision) { - let projectRoot = path.join(projectData.platformsDir, "ios"); await this.setupSigningFromProvision(projectRoot, projectData, provision, platformSpecificData.mobileProvisionData); } + if (teamId) { + await this.setupSigningFromTeam(projectRoot, projectData, teamId); + } let project = this.createPbxProj(projectData); @@ -910,28 +957,46 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f return Promise.resolve(); } - public checkForChanges(changesInfo: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): void { - const provision = options.provision; - if (provision !== undefined) { + public async checkForChanges(changesInfo: IProjectChangesInfo, {provision, teamId}: IProjectChangesOptions, projectData: IProjectData): Promise { + const hasProvision = provision !== undefined; + const hasTeamId = teamId !== undefined; + if (hasProvision || hasTeamId) { // Check if the native project's signing is set to the provided provision... const pbxprojPath = this.getPbxProjPath(projectData); if (this.$fs.exists(pbxprojPath)) { const xcode = this.$pbxprojDomXcode.Xcode.open(pbxprojPath); const signing = xcode.getSigning(projectData.projectName); - if (signing && signing.style === "Manual") { - for (let name in signing.configurations) { - let config = signing.configurations[name]; - if (config.uuid !== provision && config.name !== provision) { - changesInfo.signingChanged = true; - break; + + if (hasProvision) { + if (signing && signing.style === "Manual") { + for (const name in signing.configurations) { + const config = signing.configurations[name]; + if (config.uuid !== provision && config.name !== provision) { + changesInfo.signingChanged = true; + break; + } } + } else { + // Specifying provisioning profile requires "Manual" signing style. + // If the current signing style was not "Manual" it was probably "Automatic" or, + // it was not uniform for the debug and release build configurations. + changesInfo.signingChanged = true; + } + } + if (hasTeamId) { + if (signing && signing.style === "Automatic") { + if (signing.team !== teamId) { + const teamIdsForName = await this.$iOSProvisionService.getTeamIdsWithName(teamId); + if (!teamIdsForName.some(id => id === signing.team)) { + changesInfo.signingChanged = true; + } + } + } else { + // Specifying team id or name requires "Automatic" signing style. + // If the current signing style was not "Automatic" it was probably "Manual". + changesInfo.signingChanged = true; } - } else { - // Specifying provisioning profile requires "Manual" signing style. - // If the current signing style was not "Manual" it was probably "Automatic" or, - // it was not uniform for the debug and release build configurations. - changesInfo.signingChanged = true; } } else { changesInfo.signingChanged = true; @@ -1211,51 +1276,11 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } let splitedXcodeBuildVersion = xcodeBuildVersion.split("."); - if (splitedXcodeBuildVersion.length === 3) { - xcodeBuildVersion = `${splitedXcodeBuildVersion[0]}.${splitedXcodeBuildVersion[1]}`; - } + xcodeBuildVersion = `${splitedXcodeBuildVersion[0] || 0}.${splitedXcodeBuildVersion[1] || 0}`; return xcodeBuildVersion; } - private getDevelopmentTeams(): Array<{ id: string, name: string }> { - let dir = path.join(process.env.HOME, "Library/MobileDevice/Provisioning Profiles/"); - let files = this.$fs.readDirectory(dir); - let teamIds: any = {}; - for (let file of files) { - let filePath = path.join(dir, file); - let data = this.$fs.readText(filePath, "utf8"); - let teamId = this.getProvisioningProfileValue("TeamIdentifier", data); - let teamName = this.getProvisioningProfileValue("TeamName", data); - if (teamId) { - teamIds[teamId] = teamName; - } - } - - let teamIdsArray = new Array<{ id: string, name: string }>(); - for (let teamId in teamIds) { - teamIdsArray.push({ id: teamId, name: teamIds[teamId] }); - } - - return teamIdsArray; - } - - private getProvisioningProfileValue(name: string, text: string): string { - let findStr = "" + name + ""; - let index = text.indexOf(findStr); - if (index > 0) { - index = text.indexOf("", index + findStr.length); - if (index > 0) { - index += "".length; - let endIndex = text.indexOf("", index); - let result = text.substring(index, endIndex); - return result; - } - } - - return null; - } - private getBuildXCConfigFilePath(projectData: IProjectData): string { let buildXCConfig = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, "build.xcconfig"); @@ -1293,7 +1318,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f teamId = teamId || this.readTeamId(projectData); if (!teamId) { - let teams = this.getDevelopmentTeams(); + let teams = await this.$iOSProvisionService.getDevelopmentTeams(); this.$logger.warn("Xcode 8 requires a team id to be specified when building for device."); this.$logger.warn("You can specify the team id by setting the DEVELOPMENT_TEAM setting in build.xcconfig file located in App_Resources folder of your app, or by using the --teamId option when calling run, debug or livesync commands."); if (teams.length === 1) { diff --git a/lib/services/ios-provision-service.ts b/lib/services/ios-provision-service.ts index 65622ac9e5..19c0ba380c 100644 --- a/lib/services/ios-provision-service.ts +++ b/lib/services/ios-provision-service.ts @@ -1,7 +1,7 @@ import * as mobileprovision from "ios-mobileprovision-finder"; -import { createTable } from "../common/helpers"; +import { createTable, quoteString } from "../common/helpers"; -const months = ["Jan", "Feb", "Marc", "Apr", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec"]; +const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; function formatDate(date: Date): string { return `${date.getDay()} ${months[date.getMonth()]} ${date.getFullYear()}`; } @@ -22,7 +22,7 @@ export class IOSProvisionService { || match.nonEligable.find(prov => prov.Name === uuidOrName); } - public async list(projectId: string): Promise { + public async listProvisions(projectId: string): Promise { const data = await this.queryProvisioningProfilesAndDevices(projectId); const devices = data.devices; const match = data.match; @@ -49,7 +49,7 @@ export class IOSProvisionService { function pushProvision(prov: mobileprovision.provision.MobileProvision) { table.push(["", "", "", ""]); - table.push(["\"" + prov.Name + "\"", prov.TeamName, prov.Type, formatTotalDeviceCount(prov)]); + table.push([quoteString(prov.Name), prov.TeamName, prov.Type, formatTotalDeviceCount(prov)]); table.push([prov.UUID, prov.TeamIdentifier && prov.TeamIdentifier.length > 0 ? "(" + prov.TeamIdentifier[0] + ")" : "", formatDate(prov.ExpirationDate), formatSupportedDeviceCount(prov)]); table.push([prov.Entitlements["application-identifier"], "", "", ""]); } @@ -59,7 +59,12 @@ export class IOSProvisionService { this.$logger.out(); this.$logger.out("There are also " + match.nonEligable.length + " non-eligable provisioning profiles."); this.$logger.out(); + } + public async listTeams(): Promise { + const teams = await this.getDevelopmentTeams(); + const table = createTable(["Team Name", "Team ID"], teams.map(team => [quoteString(team.name), team.id])); + this.$logger.out(table.toString()); } private async queryProvisioningProfilesAndDevices(projectId: string): Promise<{ devices: string[], match: mobileprovision.provision.Result }> { @@ -90,6 +95,31 @@ export class IOSProvisionService { return { devices, match }; } + + public async getDevelopmentTeams(): Promise<{ id: string, name: string }[]> { + const teams: { [teamName: string]: Set } = {}; + // NOTE: We are reading all provisioning profiles and collect team information from them. + // It would be better if we can check the Apple ID registered in Xcode and read the teams associated with it. + mobileprovision.provision.read().forEach(provision => + provision.TeamIdentifier && provision.TeamIdentifier.forEach(id => { + if (!teams[provision.TeamName]) { + teams[provision.TeamName] = new Set(); + } + teams[provision.TeamName].add(id); + }) + ); + const teamsArray = Object.keys(teams).reduce((arr, name) => { + teams[name].forEach(id => arr.push({ id, name })); + return arr; + }, []); + return teamsArray; + } + + public async getTeamIdsWithName(teamName: string): Promise { + const allTeams = await this.getDevelopmentTeams(); + const matchingTeamIds = allTeams.filter(team => team.name === teamName).map(team => team.id); + return matchingTeamIds; + } } $injector.register("iOSProvisionService", IOSProvisionService); diff --git a/lib/services/local-build-service.ts b/lib/services/local-build-service.ts index 8bf4cb0bb9..c705416d1f 100644 --- a/lib/services/local-build-service.ts +++ b/lib/services/local-build-service.ts @@ -12,6 +12,7 @@ export class LocalBuildService extends EventEmitter { this.$projectData.initializeProjectData(platformBuildOptions.projectDir); await this.$platformService.preparePlatform(platform, platformBuildOptions, platformTemplate, this.$projectData, { provision: platformBuildOptions.provision, + teamId: platformBuildOptions.teamId, sdk: null, frameworkPath: null, ignoreScripts: false diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 3f02b9c341..e6b546e211 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -230,18 +230,18 @@ export class PlatformService extends EventEmitter implements IPlatformService { return true; } - public async validateOptions(provision: true | string, projectData: IProjectData, platform?: string): Promise { + public async validateOptions(provision: true | string, teamId: true | string, projectData: IProjectData, platform?: string): Promise { if (platform) { platform = this.$mobileHelper.normalizePlatformName(platform); this.$logger.trace("Validate options for platform: " + platform); let platformData = this.$platformsData.getPlatformData(platform, projectData); - return await platformData.platformProjectService.validateOptions(projectData.projectId, provision); + return await platformData.platformProjectService.validateOptions(projectData.projectId, provision, teamId); } else { let valid = true; for (let availablePlatform in this.$platformsData.availablePlatforms) { this.$logger.trace("Validate options for platform: " + availablePlatform); let platformData = this.$platformsData.getPlatformData(availablePlatform, projectData); - valid = valid && await platformData.platformProjectService.validateOptions(projectData.projectId, provision); + valid = valid && await platformData.platformProjectService.validateOptions(projectData.projectId, provision, teamId); } return valid; @@ -284,7 +284,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { const bundle = appFilesUpdaterOptions.bundle; const nativePlatformStatus = (nativePrepare && nativePrepare.skipNativePrepare) ? constants.NativePlatformStatus.requiresPlatformAdd : constants.NativePlatformStatus.requiresPrepare; - const changesInfo = this.$projectChangesService.checkForChanges(platform, projectData, { bundle, release: appFilesUpdaterOptions.release, provision: config.provision, nativePlatformStatus }); + const changesInfo = await this.$projectChangesService.checkForChanges(platform, projectData, { bundle, release: appFilesUpdaterOptions.release, provision: config.provision, teamId: config.teamId, nativePlatformStatus }); this.$logger.trace("Changes info in prepare platform:", changesInfo); return changesInfo; diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index 08c87a3df5..e9982cae43 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -54,7 +54,7 @@ export class ProjectChangesService implements IProjectChangesService { return this._changesInfo; } - public checkForChanges(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): IProjectChangesInfo { + public async checkForChanges(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): Promise { let platformData = this.$platformsData.getPlatformData(platform, projectData); this._changesInfo = new ProjectChangesInfo(); if (!this.ensurePrepareInfo(platform, projectData, projectChangesOptions)) { @@ -87,7 +87,7 @@ export class ProjectChangesService implements IProjectChangesService { } let projectService = platformData.platformProjectService; - projectService.checkForChanges(this._changesInfo, projectChangesOptions, projectData); + await projectService.checkForChanges(this._changesInfo, projectChangesOptions, projectData); if (projectChangesOptions.bundle !== this._prepareInfo.bundle || projectChangesOptions.release !== this._prepareInfo.release) { this._changesInfo.appFilesChanged = true; diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index d0c97cde5f..956c4343a8 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -611,12 +611,12 @@ describe("iOS Project Service Signing", () => { }); describe("Check for Changes", () => { - it("sets signingChanged if no Xcode project exists", () => { + it("sets signingChanged if no Xcode project exists", async () => { let changes = {}; - iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev" }, projectData); + await iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev", teamId: undefined }, projectData); assert.isTrue(!!changes.signingChanged); }); - it("sets signingChanged if the Xcode projects is configured with Automatic signing, but proivsion is specified", () => { + it("sets signingChanged if the Xcode projects is configured with Automatic signing, but proivsion is specified", async () => { files[pbxproj] = ""; pbxprojDomXcode.Xcode.open = function (path: string) { assert.equal(path, pbxproj); @@ -627,10 +627,10 @@ describe("iOS Project Service Signing", () => { }; }; let changes = {}; - iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev" }, projectData); + await iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev", teamId: undefined }, projectData); assert.isTrue(!!changes.signingChanged); }); - it("sets signingChanged if the Xcode projects is configured with Manual signing, but the proivsion specified differs the selected in the pbxproj", () => { + it("sets signingChanged if the Xcode projects is configured with Manual signing, but the proivsion specified differs the selected in the pbxproj", async () => { files[pbxproj] = ""; pbxprojDomXcode.Xcode.open = function (path: string) { assert.equal(path, pbxproj); @@ -646,10 +646,10 @@ describe("iOS Project Service Signing", () => { }; }; let changes = {}; - iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev" }, projectData); + await iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev", teamId: undefined }, projectData); assert.isTrue(!!changes.signingChanged); }); - it("does not set signingChanged if the Xcode projects is configured with Manual signing and proivsion matches", () => { + it("does not set signingChanged if the Xcode projects is configured with Manual signing and proivsion matches", async () => { files[pbxproj] = ""; pbxprojDomXcode.Xcode.open = function (path: string) { assert.equal(path, pbxproj); @@ -665,7 +665,7 @@ describe("iOS Project Service Signing", () => { }; }; let changes = {}; - iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev" }, projectData); + await iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev", teamId: undefined }, projectData); assert.isFalse(!!changes.signingChanged); }); }); @@ -684,7 +684,7 @@ describe("iOS Project Service Signing", () => { }); it("fails with proper error if the provision can not be found", async () => { try { - await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptDev2" }); + await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptDev2", teamId: undefined }); } catch (e) { assert.isTrue(e.toString().indexOf("Failed to find mobile provision with UUID or Name: NativeScriptDev2") >= 0); } @@ -705,7 +705,7 @@ describe("iOS Project Service Signing", () => { } }; }; - await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptDev" }); + await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptDev", teamId: undefined }); assert.deepEqual(stack, [{ targetName: projectDirName, manualSigning: { team: "TKID101", uuid: "12345", name: "NativeScriptDev", identity: "iPhone Developer" } }, "save()"]); }); it("succeds if the provision name is provided for distribution cert", async () => { @@ -724,7 +724,7 @@ describe("iOS Project Service Signing", () => { } }; }; - await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptDist" }); + await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptDist", teamId: undefined }); assert.deepEqual(stack, [{ targetName: projectDirName, manualSigning: { team: "TKID202", uuid: "6789", name: "NativeScriptDist", identity: "iPhone Distribution" } }, "save()"]); }); it("succeds if the provision name is provided for adhoc cert", async () => { @@ -743,7 +743,7 @@ describe("iOS Project Service Signing", () => { } }; }; - await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptAdHoc" }); + await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptAdHoc", teamId: undefined }); assert.deepEqual(stack, [{ targetName: projectDirName, manualSigning: { team: "TKID303", uuid: "1010", name: "NativeScriptAdHoc", identity: "iPhone Distribution" } }, "save()"]); }); }); diff --git a/test/platform-commands.ts b/test/platform-commands.ts index cf7ff0e1e8..52a5695698 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -46,11 +46,11 @@ class PlatformData implements IPlatformData { class ErrorsNoFailStub implements IErrors { printCallStack: boolean = false; - fail(formatStr: string, ...args: any[]): void; - fail(opts: { formatStr?: string; errorCode?: number; suppressCommandHelp?: boolean }, ...args: any[]): void; + fail(formatStr: string, ...args: any[]): never; + fail(opts: { formatStr?: string; errorCode?: number; suppressCommandHelp?: boolean }, ...args: any[]): never; - fail(...args: any[]) { throw new Error(); } - failWithoutHelp(message: string, ...args: any[]): void { + fail(...args: any[]): never { throw new Error(); } + failWithoutHelp(message: string, ...args: any[]): never { throw new Error(); } diff --git a/test/platform-service.ts b/test/platform-service.ts index 22d5bf78d0..9333bb297e 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -150,6 +150,7 @@ describe('Platform Service Tests', () => { const config: IPlatformOptions = { ignoreScripts: false, provision: null, + teamId: null, sdk: null, frameworkPath: null }; @@ -441,7 +442,7 @@ describe('Platform Service Tests', () => { platformService = testInjector.resolve("platformService"); const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: false, release: release }; - await platformService.preparePlatform(platformToTest, appFilesUpdaterOptions, "", projectData, { provision: null, sdk: null, frameworkPath: null, ignoreScripts: false }); + await platformService.preparePlatform(platformToTest, appFilesUpdaterOptions, "", projectData, { provision: null, teamId: null, sdk: null, frameworkPath: null, ignoreScripts: false }); } async function testPreparePlatform(platformToTest: string, release?: boolean): Promise { @@ -868,7 +869,7 @@ describe('Platform Service Tests', () => { try { testInjector.resolve("$logger").warn = (text: string) => warnings += text; const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: false, release: false }; - await platformService.preparePlatform("android", appFilesUpdaterOptions, "", projectData, { provision: null, sdk: null, frameworkPath: null, ignoreScripts: false }); + await platformService.preparePlatform("android", appFilesUpdaterOptions, "", projectData, { provision: null, teamId: null, sdk: null, frameworkPath: null, ignoreScripts: false }); } finally { testInjector.resolve("$logger").warn = oldLoggerWarner; } diff --git a/test/project-changes-service.ts b/test/project-changes-service.ts index cc29656ebd..f4b091bb73 100644 --- a/test/project-changes-service.ts +++ b/test/project-changes-service.ts @@ -141,10 +141,10 @@ describe("Project Changes Service Tests", () => { }); describe("Accumulates Changes From Project Services", () => { - it("accumulates changes from the project service", () => { - let iOSChanges = serviceTest.projectChangesService.checkForChanges("ios", serviceTest.projectData, { bundle: false, release: false, provision: undefined }); + it("accumulates changes from the project service", async () => { + let iOSChanges = await serviceTest.projectChangesService.checkForChanges("ios", serviceTest.projectData, { bundle: false, release: false, provision: undefined, teamId: undefined }); assert.isTrue(!!iOSChanges.signingChanged, "iOS signingChanged expected to be true"); - let androidChanges = serviceTest.projectChangesService.checkForChanges("android", serviceTest.projectData, { bundle: false, release: false, provision: undefined }); + let androidChanges = await serviceTest.projectChangesService.checkForChanges("android", serviceTest.projectData, { bundle: false, release: false, provision: undefined, teamId: undefined }); assert.isFalse(!!androidChanges.signingChanged, "Android signingChanged expected to be false"); }); }); diff --git a/test/stubs.ts b/test/stubs.ts index cfb77841db..2a9569410e 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -178,14 +178,14 @@ export class FileSystemStub implements IFileSystem { } export class ErrorsStub implements IErrors { - fail(formatStr: string, ...args: any[]): void; - fail(opts: { formatStr?: string; errorCode?: number; suppressCommandHelp?: boolean }, ...args: any[]): void; + fail(formatStr: string, ...args: any[]): never; + fail(opts: { formatStr?: string; errorCode?: number; suppressCommandHelp?: boolean }, ...args: any[]): never; - fail(...args: any[]) { + fail(...args: any[]): never { throw new Error(require("util").format.apply(null, args || [])); } - failWithoutHelp(message: string, ...args: any[]): void { + failWithoutHelp(message: string, ...args: any[]): never { throw new Error(message); } @@ -333,7 +333,7 @@ export class PlatformProjectServiceStub extends EventEmitter implements IPlatfor async cleanProject(projectRoot: string, projectData: IProjectData): Promise { return Promise.resolve(); } - checkForChanges(changesInfo: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): void { + async checkForChanges(changesInfo: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): Promise { // Nothing yet. } } @@ -560,7 +560,7 @@ export class ChildProcessStub { } export class ProjectChangesService implements IProjectChangesService { - public checkForChanges(platform: string): IProjectChangesInfo { + public async checkForChanges(platform: string): Promise { return {}; } From 0b89d3efe4630feb3270babf6294857bac93bee5 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Wed, 23 Aug 2017 22:08:19 +0300 Subject: [PATCH 197/212] Expose enable/disable debugging (#3065) Expose enable/disable debugging when CLI is required as a library. Includes: * Unify `debug-livesync-service` and `livesync-service` into one class. Enable said class to attach a debugger at runtime during LiveSync * Remove redundant `debugLivesync` property from `$config` and its every usage * Remove redundant `debugStop` code in `android-debug-service` * Raise events in the event of inability to start/stop the app on iOS --- PublicAPI.md | 98 +++++++++ lib/bootstrap.ts | 1 - lib/commands/debug.ts | 30 +-- lib/commands/run.ts | 2 +- lib/common | 2 +- lib/config.ts | 1 - lib/constants.ts | 3 + lib/declarations.d.ts | 4 +- lib/definitions/debug.d.ts | 25 +-- .../emulator-platform-service.d.ts | 3 +- lib/definitions/livesync.d.ts | 99 +++++++-- lib/definitions/platform.d.ts | 7 +- lib/definitions/project.d.ts | 6 +- .../ios/socket-proxy-factory.ts | 5 +- lib/services/android-debug-service.ts | 67 ++---- lib/services/android-project-service.ts | 29 ++- lib/services/debug-service-base.ts | 7 +- lib/services/debug-service.ts | 51 +++-- lib/services/ios-debug-service.ts | 16 +- .../livesync/debug-livesync-service.ts | 79 ------- .../livesync/livesync-command-helper.ts | 18 +- lib/services/livesync/livesync-service.ts | 204 ++++++++++++++++-- lib/services/test-execution-service.ts | 33 ++- test/debug.ts | 37 +--- test/nativescript-cli-lib.ts | 2 +- test/services/android-debug-service.ts | 4 +- test/services/debug-service.ts | 78 +------ test/services/ios-debug-service.ts | 4 +- test/services/livesync-service.ts | 9 + test/stubs.ts | 8 + 30 files changed, 532 insertions(+), 400 deletions(-) delete mode 100644 lib/services/livesync/debug-livesync-service.ts diff --git a/PublicAPI.md b/PublicAPI.md index dce94a45d4..a2056851cc 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -552,6 +552,8 @@ After calling the method once, you can add new devices to the same LiveSync oper > NOTE: In case a consecutive call to `liveSync` method requires change in the pattern for watching files (i.e. `liveSyncData.syncAllFiles` option has changed), current watch operation will be stopped and a new one will be started. +> NOTE: In case `debugggingEnabled` is set to `true` in a deviceDescriptor, debugging will initially be enabled for that device and a debugger will be attached after a successful livesync operation. + * Definition ```TypeScript /** @@ -623,6 +625,92 @@ tns.liveSyncService.stopLiveSync(projectDir, deviceIdentifiers) }); ``` +### enableDebugging +Enables debugging during a LiveSync operation. This method will try to attach a debugger to the application. Note that `userInteractionNeeded` event may be raised. Additional details about the arguments can be seen [here](https://github.com/NativeScript/nativescript-cli/blob/master/lib/definitions/livesync.d.ts). + +* Definition +```TypeScript +/** +* Enables debugging for the specified devices +* @param {IEnableDebuggingDeviceOptions[]} deviceOpts Settings used for enabling debugging for each device. +* @param {IDebuggingAdditionalOptions} enableDebuggingOptions Settings used for enabling debugging. +* @returns {Promise[]} Array of promises for each device. +*/ +enableDebugging(deviceOpts: IEnableDebuggingDeviceOptions[], enableDebuggingOptions: IDebuggingAdditionalOptions): Promise[]; +``` + +* Usage +```JavaScript +const projectDir = "/tmp/myProject"; +const liveSyncData = { projectDir }; +const devices = [androidDeviceDescriptor, iOSDeviceDescriptor]; +tns.liveSyncService.liveSync(devices, liveSyncData) + .then(() => { + console.log("LiveSync operation started."); + devices.forEach(device => { + tns.liveSyncService.enableDebugging([{ + deviceIdentifier: device.identifier + }], { projectDir }); + }); + }); +``` + +### attachDebugger +Attaches a debugger to the specified device. Additional details about the argument can be seen [here](https://github.com/NativeScript/nativescript-cli/blob/master/lib/definitions/livesync.d.ts). + +* Definition +```TypeScript +/** +* Attaches a debugger to the specified device. +* @param {IAttachDebuggerOptions} settings Settings used for controling the attaching process. +* @returns {Promise} +*/ +attachDebugger(settings: IAttachDebuggerOptions): Promise; +``` + +* Usage +```JavaScript +tns.liveSyncService.on("userInteractionNeeded", data => { + console.log("Please restart the app manually"); + return tns.liveSyncService.attachDebugger(data); +}); +``` + +### disableDebugging +Disables debugging during a LiveSync operation. This method will try to detach a debugger from the application. Additional details about the arguments can be seen [here](https://github.com/NativeScript/nativescript-cli/blob/master/lib/definitions/livesync.d.ts). + +* Definition +```TypeScript +/** +* Disables debugging for the specified devices +* @param {IDisableDebuggingDeviceOptions[]} deviceOptions Settings used for disabling debugging for each device. +* @param {IDebuggingAdditionalOptions} debuggingAdditionalOptions Settings used for disabling debugging. +* @returns {Promise[]} Array of promises for each device. +*/ +disableDebugging(deviceOptions: IDisableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[]; +``` + +* Usage +```JavaScript +const projectDir = "/tmp/myProject"; +const liveSyncData = { projectDir }; +const devices = [androidDeviceDescriptor, iOSDeviceDescriptor]; +tns.liveSyncService.liveSync(devices, liveSyncData) + .then(() => { + console.log("LiveSync operation started."); + devices.forEach(device => { + tns.liveSyncService.enableDebugging([{ + deviceIdentifier: device.identifier + }], { projectDir }); + setTimeout(() => { + tns.liveSyncService.disableDebugging([{ + deviceIdentifier: device.identifier + }], { projectDir }); + }, 1000 * 30); + }); + }); +``` + ### getLiveSyncDeviceDescriptors Gives information for currently running LiveSync operation and parameters used to start it on each device. @@ -741,6 +829,16 @@ tns.liveSyncService.on("notify", data => { }); ``` +* userInteractionNeeded - raised whenever CLI needs to restart an application but cannot so the user has to restart it manually. The event is raised with an object, which can later be passed to `attachDebugger` method of `liveSyncService`: + +Example: +```JavaScript +tns.liveSyncService.on("userInteractionNeeded", data => { + console.log("Please restart the app manually"); + return tns.liveSyncService.attachDebugger(data); +}); +``` + ## How to add a new method to Public API CLI is designed as command line tool and when it is used as a library, it does not give you access to all of the methods. This is mainly implementation detail. Most of the CLI's code is created to work in command line, not as a library, so before adding method to public API, most probably it will require some modification. For example the `$options` injected module contains information about all `--` options passed on the terminal. When the CLI is used as a library, the options are not populated. Before adding method to public API, make sure its implementation does not rely on `$options`. diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 4beee8e2c0..96c6f24eda 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -106,7 +106,6 @@ $injector.requireCommand("platform|clean", "./commands/platform-clean"); $injector.requirePublicClass("liveSyncService", "./services/livesync/livesync-service"); $injector.require("liveSyncCommandHelper", "./services/livesync/livesync-command-helper"); -$injector.require("debugLiveSyncService", "./services/livesync/debug-livesync-service"); $injector.require("androidLiveSyncService", "./services/livesync/android-livesync-service"); $injector.require("iOSLiveSyncService", "./services/livesync/ios-livesync-service"); $injector.require("usbLiveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index cc04b09411..6ac49ad201 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -6,8 +6,8 @@ import { DebugCommandErrors } from "../constants"; export class DebugPlatformCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; - constructor(private debugService: IPlatformDebugService, - private platform: string, + constructor(private platform: string, + private $debugService: IDebugService, protected $devicesService: Mobile.IDevicesService, protected $platformService: IPlatformService, protected $projectData: IProjectData, @@ -16,31 +16,29 @@ export class DebugPlatformCommand implements ICommand { protected $logger: ILogger, protected $errors: IErrors, private $debugDataService: IDebugDataService, - private $debugLiveSyncService: IDebugLiveSyncService, - private $config: IConfiguration, + private $liveSyncService: IDebugLiveSyncService, private $prompter: IPrompter, private $liveSyncCommandHelper: ILiveSyncCommandHelper) { } public async execute(args: string[]): Promise { - const debugOptions = this.$options; + const debugOptions = _.cloneDeep(this.$options.argv); let debugData = this.$debugDataService.createDebugData(this.$projectData, this.$options); await this.$platformService.trackProjectType(this.$projectData); - const selectedDeviceForDebug = await this.getDeviceForDebug(); debugData.deviceIdentifier = selectedDeviceForDebug.deviceInfo.identifier; if (this.$options.start) { - return this.$debugLiveSyncService.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); + return this.$liveSyncService.printDebugInformation(await this.$debugService.debug(debugData, debugOptions)); } - this.$config.debugLivesync = true; - await this.$devicesService.detectCurrentlyAttachedDevices({ shouldReturnImmediateResult: false, platform: this.platform }); - await this.$liveSyncCommandHelper.executeLiveSyncOperation([selectedDeviceForDebug], this.$debugLiveSyncService, this.platform); + await this.$liveSyncCommandHelper.executeLiveSyncOperation([selectedDeviceForDebug], this.platform, { + [selectedDeviceForDebug.deviceInfo.identifier]: true + }); } public async getDeviceForDebug(): Promise { @@ -104,6 +102,10 @@ export class DebugPlatformCommand implements ICommand { this.$errors.fail(`Applications for platform ${this.platform} can not be built on this OS`); } + if (this.$options.release) { + this.$errors.fail("--release flag is not applicable to this command"); + } + const platformData = this.$platformsData.getPlatformData(this.platform, this.$projectData); const platformProjectService = platformData.platformProjectService; await platformProjectService.validate(this.$projectData); @@ -123,7 +125,7 @@ export class DebugIOSCommand implements ICommand { @cache() private get debugPlatformCommand(): DebugPlatformCommand { - return this.$injector.resolve(DebugPlatformCommand, { debugService: this.$iOSDebugService, platform: this.platform }); + return this.$injector.resolve(DebugPlatformCommand, { platform: this.platform }); } public allowedParameters: ICommandParameter[] = []; @@ -135,7 +137,6 @@ export class DebugIOSCommand implements ICommand { private $injector: IInjector, private $projectData: IProjectData, private $platformsData: IPlatformsData, - private $iOSDebugService: IDebugService, $iosDeviceOperations: IIOSDeviceOperations) { this.$projectData.initializeProjectData(); // Do not dispose ios-device-lib, so the process will remain alive and the debug application (NativeScript Inspector or Chrome DevTools) will be able to connect to the socket. @@ -166,7 +167,7 @@ export class DebugAndroidCommand implements ICommand { @cache() private get debugPlatformCommand(): DebugPlatformCommand { - return this.$injector.resolve(DebugPlatformCommand, { debugService: this.$androidDebugService, platform: this.platform }); + return this.$injector.resolve(DebugPlatformCommand, { platform: this.platform }); } public allowedParameters: ICommandParameter[] = []; @@ -177,8 +178,7 @@ export class DebugAndroidCommand implements ICommand { private $options: IOptions, private $injector: IInjector, private $projectData: IProjectData, - private $platformsData: IPlatformsData, - private $androidDebugService: IDebugService) { + private $platformsData: IPlatformsData) { this.$projectData.initializeProjectData(); } diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 76a974a185..28b76c92de 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -59,7 +59,7 @@ export class RunCommandBase implements ICommand { await this.$devicesService.detectCurrentlyAttachedDevices({ shouldReturnImmediateResult: false, platform: this.platform }); let devices = this.$devicesService.getDeviceInstances(); devices = devices.filter(d => !this.platform || d.deviceInfo.platform.toLowerCase() === this.platform.toLowerCase()); - await this.$liveSyncCommandHelper.executeLiveSyncOperation(devices, this.$liveSyncService, this.platform); + await this.$liveSyncCommandHelper.executeLiveSyncOperation(devices, this.platform); } } diff --git a/lib/common b/lib/common index 2287fd9efa..ef87c15950 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 2287fd9efa56ee985d720d49f8aef4e7cd362da2 +Subproject commit ef87c15950c43c8ebf6c217f3ec192cf9e18af94 diff --git a/lib/config.ts b/lib/config.ts index a3b9d77e49..52265023cb 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -8,7 +8,6 @@ export class Configuration extends ConfigBase implements IConfiguration { // Use TYPESCRIPT_COMPILER_OPTIONS = {}; ANDROID_DEBUG_UI: string = null; USE_POD_SANDBOX: boolean = false; - debugLivesync: boolean = false; /*don't require logger and everything that has logger as dependency in config.js due to cyclic dependency*/ constructor(protected $fs: IFileSystem) { diff --git a/lib/constants.ts b/lib/constants.ts index 0b0d046507..2ce856a677 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -85,6 +85,8 @@ export const ANGULAR_NAME = "angular"; export const TYPESCRIPT_NAME = "typescript"; export const BUILD_OUTPUT_EVENT_NAME = "buildOutput"; export const CONNECTION_ERROR_EVENT_NAME = "connectionError"; +export const USER_INTERACTION_NEEDED_EVENT_NAME = "userInteractionNeeded"; +export const DEBUGGER_ATTACHED_EVENT_NAME = "debuggerAttached"; export const VERSION_STRING = "version"; export const INSPECTOR_CACHE_DIRNAME = "ios-inspector"; export const POST_INSTALL_COMMAND_NAME = "post-install-cli"; @@ -92,6 +94,7 @@ export const POST_INSTALL_COMMAND_NAME = "post-install-cli"; export class DebugCommandErrors { public static UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR = "The options --for-device and --emulator cannot be used simultaneously. Please use only one of them."; public static NO_DEVICES_EMULATORS_FOUND_FOR_OPTIONS = "Unable to find device or emulator for specified options."; + public static UNSUPPORTED_DEVICE_OS_FOR_DEBUGGING = "Unsupported device OS for debugging"; } export const enum NativePlatformStatus { diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 9c19342437..f26c55ba52 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -311,7 +311,6 @@ interface IStaticConfig extends Config.IStaticConfig { } interface IConfiguration extends Config.IConfig { ANDROID_DEBUG_UI: string; USE_POD_SANDBOX: boolean; - debugLivesync: boolean; } interface IApplicationPackage { @@ -405,8 +404,7 @@ interface IDeviceEmulator extends IEmulator, IDeviceIdentifier { } interface IRunPlatformOptions extends IJustLaunch, IDeviceEmulator { } -interface IDeployPlatformOptions extends IAndroidReleaseOptions, IPlatformTemplate, IRelease, IClean, IDeviceEmulator, IProvision, ITeamIdentifier { - projectDir: string; +interface IDeployPlatformOptions extends IAndroidReleaseOptions, IPlatformTemplate, IRelease, IClean, IDeviceEmulator, IProvision, ITeamIdentifier, IProjectDir { forceInstall?: boolean; } diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 50bd31b886..37640c0848 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -1,12 +1,7 @@ /** * Describes information for starting debug process. */ -interface IDebugData { - /** - * Id of the device on which the debug process will be started. - */ - deviceIdentifier: string; - +interface IDebugData extends Mobile.IDeviceIdentifier { /** * Application identifier of the app that it will be debugged. */ @@ -115,14 +110,19 @@ interface IDebugServiceBase extends NodeJS.EventEmitter { debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; } -interface IDebugService { - getDebugService(device: Mobile.IDevice): IPlatformDebugService; +interface IDebugService extends IDebugServiceBase { + /** + * Stops debug operation for a specific device. + * @param {string} deviceIdentifier Identifier of the device fo which debugging will be stopped. + * @returns {Promise} + */ + debugStop(deviceIdentifier: string): Promise; } /** * Describes actions required for debugging on specific platform (Android or iOS). */ -interface IPlatformDebugService extends IDebugServiceBase { +interface IPlatformDebugService extends IDebugServiceBase, IPlatform { /** * Starts debug operation. * @param {IDebugData} debugData Describes information for device and application that will be debugged. @@ -135,10 +135,5 @@ interface IPlatformDebugService extends IDebugServiceBase { * Stops debug operation. * @returns {Promise} */ - debugStop(): Promise - - /** - * Mobile platform of the device - Android or iOS. - */ - platform: string; + debugStop(): Promise; } diff --git a/lib/definitions/emulator-platform-service.d.ts b/lib/definitions/emulator-platform-service.d.ts index 259bbabbb2..e088a2efc8 100644 --- a/lib/definitions/emulator-platform-service.d.ts +++ b/lib/definitions/emulator-platform-service.d.ts @@ -1,7 +1,6 @@ -interface IEmulatorInfo { +interface IEmulatorInfo extends IPlatform { name: string; version: string; - platform: string; id: string; type: string; isRunning?: boolean; diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 4216a9bbeb..1ba0230268 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -68,10 +68,19 @@ interface ILiveSyncProcessInfo { currentSyncAction: Promise; } +interface IOptionalOutputPath { + /** + * Path where the build result is located (directory containing .ipa, .apk or .zip). + * This is required for initial checks where LiveSync will skip the rebuild in case there's already a build result and no change requiring rebuild is made since then. + * In case it is not passed, the default output for local builds will be used. + */ + outputPath?: string; +} + /** * Describes information for LiveSync on a device. */ -interface ILiveSyncDeviceInfo { +interface ILiveSyncDeviceInfo extends IOptionalOutputPath { /** * Device identifier. */ @@ -84,16 +93,14 @@ interface ILiveSyncDeviceInfo { buildAction: () => Promise; /** - * Path where the build result is located (directory containing .ipa, .apk or .zip). - * This is required for initial checks where LiveSync will skip the rebuild in case there's already a build result and no change requiring rebuild is made since then. - * In case it is not passed, the default output for local builds will be used. + * Whether to skip preparing the native platform. */ - outputPath?: string; + skipNativePrepare?: boolean; /** - * Whether to skip preparing the native platform. + * Whether debugging has been enabled for this device or not */ - skipNativePrepare?: boolean; + debugggingEnabled?: boolean; /** * Describes options specific for each platform, like provision for iOS, target sdk for Android, etc. @@ -104,12 +111,7 @@ interface ILiveSyncDeviceInfo { /** * Describes a LiveSync operation. */ -interface ILiveSyncInfo { - /** - * Directory of the project that will be synced. - */ - projectDir: string; - +interface ILiveSyncInfo extends IProjectDir, IOptionalDebuggingOptions { /** * Defines if the watcher should be skipped. If not passed, fs.Watcher will be started. */ @@ -135,9 +137,11 @@ interface ILiveSyncInfo { interface ILatestAppPackageInstalledSettings extends IDictionary> { /* empty */ } -interface ILiveSyncBuildInfo { - platform: string; +interface IIsEmulator { isEmulator: boolean; +} + +interface ILiveSyncBuildInfo extends IIsEmulator, IPlatform { pathToBuildItem: string; } @@ -206,6 +210,67 @@ interface IDebugLiveSyncService extends ILiveSyncService { * @returns {void} */ printDebugInformation(information: string): void; + + /** + * Enables debugging for the specified devices + * @param {IEnableDebuggingDeviceOptions[]} deviceOpts Settings used for enabling debugging for each device. + * @param {IDebuggingAdditionalOptions} enableDebuggingOptions Settings used for enabling debugging. + * @returns {Promise[]} Array of promises for each device. + */ + enableDebugging(deviceOpts: IEnableDebuggingDeviceOptions[], enableDebuggingOptions: IDebuggingAdditionalOptions): Promise[]; + + /** + * Disables debugging for the specified devices + * @param {IDisableDebuggingDeviceOptions[]} deviceOptions Settings used for disabling debugging for each device. + * @param {IDebuggingAdditionalOptions} debuggingAdditionalOptions Settings used for disabling debugging. + * @returns {Promise[]} Array of promises for each device. + */ + disableDebugging(deviceOptions: IDisableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[]; + + /** + * Attaches a debugger to the specified device. + * @param {IAttachDebuggerOptions} settings Settings used for controling the attaching process. + * @returns {Promise} + */ + attachDebugger(settings: IAttachDebuggerOptions): Promise; +} + +/** + * Describes additional debugging settings. + */ +interface IDebuggingAdditionalOptions extends IProjectDir { } + +/** + * Describes settings used when disabling debugging. + */ +interface IDisableDebuggingDeviceOptions extends Mobile.IDeviceIdentifier { } + +interface IOptionalDebuggingOptions { + /** + * Optional debug options - can be used to control the start of a debug process. + */ + debugOptions?: IDebugOptions; +} + +/** + * Describes settings used when enabling debugging. + */ +interface IEnableDebuggingDeviceOptions extends Mobile.IDeviceIdentifier, IOptionalDebuggingOptions { } + +/** + * Describes settings passed to livesync service in order to control event emitting during refresh application. + */ +interface IShouldSkipEmitLiveSyncNotification { + /** + * Whether to skip emitting an event during refresh. Default is false. + */ + shouldSkipEmitLiveSyncNotification: boolean; +} + +/** + * Describes settings used for attaching a debugger. + */ +interface IAttachDebuggerOptions extends IDebuggingAdditionalOptions, IEnableDebuggingDeviceOptions, IIsEmulator, IPlatform, IOptionalOutputPath { } interface ILiveSyncWatchInfo { @@ -284,10 +349,10 @@ interface ILiveSyncCommandHelper { /** * Method sets up configuration, before calling livesync and expects that devices are already discovered. * @param {Mobile.IDevice[]} devices List of discovered devices - * @param {ILiveSyncService} liveSyncService Service expected to do the actual livesyncing * @param {string} platform The platform for which the livesync will be ran + * @param {IDictionary} deviceDebugMap @optional A map representing devices which have debugging enabled initially. * @returns {Promise} */ - executeLiveSyncOperation(devices: Mobile.IDevice[], liveSyncService: ILiveSyncService, platform: string): Promise; + executeLiveSyncOperation(devices: Mobile.IDevice[], platform: string, deviceDebugMap?: IDictionary): Promise; getPlatformsForOperation(platform: string): string[]; } diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 3c5e8cd5d1..535680ba60 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -233,17 +233,12 @@ interface IPlatformSpecificData extends IProvision, ITeamIdentifier { /** * Describes information that will be tracked for specific action related for platforms - build, livesync, etc. */ -interface ITrackPlatformAction { +interface ITrackPlatformAction extends IPlatform { /** * Name of the action. */ action: string; - /** - * Platform for which the action will be executed. - */ - platform: string; - /** * Defines if the action is for device or emulator. */ diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index f8d4cbd076..e88532323e 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -53,8 +53,7 @@ interface IProjectService { isValidNativeScriptProject(pathToProject?: string): boolean; } -interface IProjectData { - projectDir: string; +interface IProjectData extends IProjectDir { projectName: string; platformsDir: string; projectFilePath: string; @@ -136,8 +135,7 @@ interface INativePrepare { skipNativePrepare: boolean; } -interface IBuildConfig extends IAndroidBuildOptionsSettings, IiOSBuildConfig { - projectDir: string; +interface IBuildConfig extends IAndroidBuildOptionsSettings, IiOSBuildConfig, IProjectDir { clean?: boolean; architectures?: string[]; buildOutputStdio?: string; diff --git a/lib/device-sockets/ios/socket-proxy-factory.ts b/lib/device-sockets/ios/socket-proxy-factory.ts index 9a2cfb4022..7cace33215 100644 --- a/lib/device-sockets/ios/socket-proxy-factory.ts +++ b/lib/device-sockets/ios/socket-proxy-factory.ts @@ -9,7 +9,6 @@ import temp = require("temp"); export class SocketProxyFactory extends EventEmitter implements ISocketProxyFactory { constructor(private $logger: ILogger, private $errors: IErrors, - private $config: IConfiguration, private $options: IOptions, private $net: INet) { super(); @@ -29,7 +28,7 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact frontendSocket.on("end", () => { this.$logger.info('Frontend socket closed!'); - if (!(this.$config.debugLivesync && this.$options.watch)) { + if (!this.$options.watch) { process.exit(0); } }); @@ -39,7 +38,7 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact backendSocket.on("end", () => { this.$logger.info("Backend socket closed!"); - if (!(this.$config.debugLivesync && this.$options.watch)) { + if (!this.$options.watch) { process.exit(0); } }); diff --git a/lib/services/android-debug-service.ts b/lib/services/android-debug-service.ts index ce08d73d4c..65b3cd25d7 100644 --- a/lib/services/android-debug-service.ts +++ b/lib/services/android-debug-service.ts @@ -1,34 +1,24 @@ import { sleep } from "../common/helpers"; -import { ChildProcess } from "child_process"; import { DebugServiceBase } from "./debug-service-base"; export class AndroidDebugService extends DebugServiceBase implements IPlatformDebugService { - private _device: Mobile.IAndroidDevice = null; - private _debuggerClientProcess: ChildProcess; - + private _packageName: string; public get platform() { return "android"; } - private get device(): Mobile.IAndroidDevice { - return this._device; - } - - private set device(newDevice) { - this._device = newDevice; - } - - constructor(protected $devicesService: Mobile.IDevicesService, + constructor(protected device: Mobile.IAndroidDevice, + protected $devicesService: Mobile.IDevicesService, private $errors: IErrors, private $logger: ILogger, - private $config: IConfiguration, private $androidDeviceDiscovery: Mobile.IDeviceDiscovery, private $androidProcessService: Mobile.IAndroidProcessService, private $net: INet) { - super($devicesService); + super(device, $devicesService); } public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + this._packageName = debugData.applicationIdentifier; return debugOptions.emulator ? this.debugOnEmulator(debugData, debugOptions) : this.debugOnDevice(debugData, debugOptions); @@ -36,17 +26,13 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe public async debugStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise { await this.$devicesService.initialize({ platform: this.platform, deviceId: debugData.deviceIdentifier }); - let action = (device: Mobile.IAndroidDevice): Promise => { - this.device = device; - return this.debugStartCore(debugData.applicationIdentifier, debugOptions); - }; + const action = (device: Mobile.IAndroidDevice): Promise => this.debugStartCore(debugData.applicationIdentifier, debugOptions); await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); } - public async debugStop(): Promise { - this.stopDebuggerClient(); - return; + public debugStop(): Promise { + return this.removePortForwarding(); } protected getChromeDebugUrl(debugOptions: IDebugOptions, port: number): string { @@ -65,6 +51,11 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe return this.debugOnDevice(debugData, debugOptions); } + private async removePortForwarding(packageName?: string): Promise { + const port = await this.getForwardedLocalDebugPortForPackageName(this.device.deviceInfo.identifier, packageName || this._packageName); + return this.device.adb.executeCommand(["forward", "--remove", `tcp:${port}`]); + } + private async getForwardedLocalDebugPortForPackageName(deviceId: string, packageName: string): Promise { let port = -1; let forwardsResult = await this.device.adb.executeCommand(["forward", "--list"]); @@ -100,24 +91,22 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe await this.$devicesService.initialize({ platform: this.platform, deviceId: debugData.deviceIdentifier }); - let action = (device: Mobile.IAndroidDevice): Promise => this.debugCore(device, packageFile, debugData.applicationIdentifier, debugOptions); + const action = (device: Mobile.IAndroidDevice): Promise => this.debugCore(device, packageFile, debugData.applicationIdentifier, debugOptions); const deviceActionResult = await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); return deviceActionResult[0].result; } private async debugCore(device: Mobile.IAndroidDevice, packageFile: string, packageName: string, debugOptions: IDebugOptions): Promise { - this.device = device; - await this.printDebugPort(device.deviceInfo.identifier, packageName); if (debugOptions.start) { return await this.attachDebugger(device.deviceInfo.identifier, packageName, debugOptions); } else if (debugOptions.stop) { - await this.detachDebugger(packageName); + await this.removePortForwarding(); return null; } else { - await this.startAppWithDebugger(packageFile, packageName, debugOptions); + await this.debugStartCore(packageName, debugOptions); return await this.attachDebugger(device.deviceInfo.identifier, packageName, debugOptions); } } @@ -132,26 +121,11 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe this.$errors.failWithoutHelp(`The application ${packageName} does not appear to be running on ${deviceId} or is not built with debugging enabled.`); } - const startDebuggerCommand = ["am", "broadcast", "-a", `\"${packageName}-debug\"`, "--ez", "enable", "true"]; - await this.device.adb.executeShellCommand(startDebuggerCommand); - const port = await this.getForwardedLocalDebugPortForPackageName(deviceId, packageName); return this.getChromeDebugUrl(debugOptions, port); } - private detachDebugger(packageName: string): Promise { - return this.device.adb.executeShellCommand(["am", "broadcast", "-a", `${packageName}-debug`, "--ez", "enable", "false"]); - } - - private async startAppWithDebugger(packageFile: string, packageName: string, debugOptions: IDebugOptions): Promise { - if (!debugOptions.emulator && !this.$config.debugLivesync) { - await this.device.applicationManager.uninstallApplication(packageName); - await this.device.applicationManager.installApplication(packageFile); - } - await this.debugStartCore(packageName, debugOptions); - } - private async debugStartCore(packageName: string, debugOptions: IDebugOptions): Promise { // Arguments passed to executeShellCommand must be in array ([]), but it turned out adb shell "arg with intervals" still works correctly. // As we need to redirect output of a command on the device, keep using only one argument. @@ -198,13 +172,6 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe return !!_.find(debuggableApps, a => a.appIdentifier === appIdentifier); } - - private stopDebuggerClient(): void { - if (this._debuggerClientProcess) { - this._debuggerClientProcess.kill(); - this._debuggerClientProcess = null; - } - } } -$injector.register("androidDebugService", AndroidDebugService); +$injector.register("androidDebugService", AndroidDebugService, false); diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 8b7c53afea..4406bf8064 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -35,7 +35,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject private $injector: IInjector, private $pluginVariablesService: IPluginVariablesService, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - private $config: IConfiguration, private $npm: INodePackageManager) { super($fs, $projectDataService); this._androidProjectPropertiesManagers = Object.create(null); @@ -408,25 +407,23 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } public async beforePrepareAllPlugins(projectData: IProjectData, dependencies?: IDependencyData[]): Promise { - if (!this.$config.debugLivesync) { - if (dependencies) { - let platformDir = path.join(projectData.platformsDir, "android"); - let buildDir = path.join(platformDir, "build-tools"); - let checkV8dependants = path.join(buildDir, "check-v8-dependants.js"); - if (this.$fs.exists(checkV8dependants)) { - let stringifiedDependencies = JSON.stringify(dependencies); - try { - await this.spawn('node', [checkV8dependants, stringifiedDependencies, projectData.platformsDir], { stdio: "inherit" }); - } catch (e) { - this.$logger.info("Checking for dependants on v8 public API failed. This is likely caused because of cyclic production dependencies. Error code: " + e.code + "\nMore information: https://github.com/NativeScript/nativescript-cli/issues/2561"); - } + if (dependencies) { + let platformDir = path.join(projectData.platformsDir, "android"); + let buildDir = path.join(platformDir, "build-tools"); + let checkV8dependants = path.join(buildDir, "check-v8-dependants.js"); + if (this.$fs.exists(checkV8dependants)) { + let stringifiedDependencies = JSON.stringify(dependencies); + try { + await this.spawn('node', [checkV8dependants, stringifiedDependencies, projectData.platformsDir], { stdio: "inherit" }); + } catch (e) { + this.$logger.info("Checking for dependants on v8 public API failed. This is likely caused because of cyclic production dependencies. Error code: " + e.code + "\nMore information: https://github.com/NativeScript/nativescript-cli/issues/2561"); } } + } - let projectRoot = this.getPlatformData(projectData).projectRoot; + let projectRoot = this.getPlatformData(projectData).projectRoot; - await this.cleanProject(projectRoot, projectData); - } + await this.cleanProject(projectRoot, projectData); } public stopServices(projectRoot: string): Promise { diff --git a/lib/services/debug-service-base.ts b/lib/services/debug-service-base.ts index 3429e18e35..054c8f8578 100644 --- a/lib/services/debug-service-base.ts +++ b/lib/services/debug-service-base.ts @@ -1,7 +1,12 @@ import { EventEmitter } from "events"; export abstract class DebugServiceBase extends EventEmitter implements IPlatformDebugService { - constructor(protected $devicesService: Mobile.IDevicesService) { super(); } + constructor( + protected device: Mobile.IDevice, + protected $devicesService: Mobile.IDevicesService + ) { + super(); + } public abstract get platform(): string; diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index 303177379b..ab7151baca 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -1,17 +1,17 @@ import { platform } from "os"; import { EventEmitter } from "events"; -import { CONNECTION_ERROR_EVENT_NAME } from "../constants"; +import { CONNECTION_ERROR_EVENT_NAME, DebugCommandErrors } from "../constants"; import { CONNECTED_STATUS } from "../common/constants"; export class DebugService extends EventEmitter implements IDebugService { + private _platformDebugServices: IDictionary; constructor(private $devicesService: Mobile.IDevicesService, - private $androidDebugService: IPlatformDebugService, - private $iOSDebugService: IPlatformDebugService, private $errors: IErrors, + private $injector: IInjector, private $hostInfo: IHostInfo, private $mobileHelper: Mobile.IMobileHelper) { super(); - this.attachConnectionErrorHandlers(); + this._platformDebugServices = {}; } public async debug(debugData: IDebugData, options: IDebugOptions): Promise { @@ -29,15 +29,12 @@ export class DebugService extends EventEmitter implements IDebugService { this.$errors.failWithoutHelp(`The application ${debugData.applicationIdentifier} is not installed on device with identifier ${debugData.deviceIdentifier}.`); } - const debugOptions: IDebugOptions = _.merge({}, options); - debugOptions.start = true; + const debugOptions: IDebugOptions = _.cloneDeep(options); // TODO: Check if app is running. // For now we can only check if app is running on Android. // After we find a way to check on iOS we should use it here. - const isAppRunning = true; let result: string; - debugOptions.chrome = true; const debugService = this.getDebugService(device); if (!debugService) { @@ -50,10 +47,6 @@ export class DebugService extends EventEmitter implements IDebugService { } if (this.$hostInfo.isWindows) { - if (!isAppRunning) { - this.$errors.failWithoutHelp(`Application ${debugData.applicationIdentifier} is not running. To be able to debug the application on Windows you must run it.`); - } - debugOptions.emulator = false; } else if (!this.$hostInfo.isDarwin) { this.$errors.failWithoutHelp(`Debugging on iOS devices is not supported for ${platform()} yet.`); @@ -67,19 +60,37 @@ export class DebugService extends EventEmitter implements IDebugService { return result; } - public getDebugService(device: Mobile.IDevice): IPlatformDebugService { - if (this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform)) { - return this.$iOSDebugService; - } else if (this.$mobileHelper.isAndroidPlatform(device.deviceInfo.platform)) { - return this.$androidDebugService; + public debugStop(deviceIdentifier: string): Promise { + const debugService = this.getDebugServiceByIdentifier(deviceIdentifier); + return debugService.debugStop(); + } + + protected getDebugService(device: Mobile.IDevice): IPlatformDebugService { + if (!this._platformDebugServices[device.deviceInfo.identifier]) { + const platform = device.deviceInfo.platform; + if (this.$mobileHelper.isiOSPlatform(platform)) { + this._platformDebugServices[device.deviceInfo.identifier] = this.$injector.resolve("iOSDebugService", { device }); + } else if (this.$mobileHelper.isAndroidPlatform(platform)) { + this._platformDebugServices[device.deviceInfo.identifier] = this.$injector.resolve("androidDebugService", { device }); + } else { + this.$errors.failWithoutHelp(DebugCommandErrors.UNSUPPORTED_DEVICE_OS_FOR_DEBUGGING); + } + + this.attachConnectionErrorHandlers(this._platformDebugServices[device.deviceInfo.identifier]); } + + return this._platformDebugServices[device.deviceInfo.identifier]; + } + + private getDebugServiceByIdentifier(deviceIdentifier: string): IPlatformDebugService { + const device = this.$devicesService.getDeviceByIdentifier(deviceIdentifier); + return this.getDebugService(device); } - private attachConnectionErrorHandlers() { + private attachConnectionErrorHandlers(platformDebugService: IPlatformDebugService) { let connectionErrorHandler = (e: Error) => this.emit(CONNECTION_ERROR_EVENT_NAME, e); connectionErrorHandler = connectionErrorHandler.bind(this); - this.$androidDebugService.on(CONNECTION_ERROR_EVENT_NAME, connectionErrorHandler); - this.$iOSDebugService.on(CONNECTION_ERROR_EVENT_NAME, connectionErrorHandler); + platformDebugService.on(CONNECTION_ERROR_EVENT_NAME, connectionErrorHandler); } } diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index bb14ef2db1..03fa2f651a 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -20,10 +20,12 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS private _childProcess: ChildProcess; private _socketProxy: any; - constructor(protected $devicesService: Mobile.IDevicesService, + constructor(protected device: Mobile.IDevice, + protected $devicesService: Mobile.IDevicesService, private $platformService: IPlatformService, private $iOSEmulatorServices: Mobile.IEmulatorPlatformServices, private $childProcess: IChildProcess, + private $hostInfo: IHostInfo, private $logger: ILogger, private $errors: IErrors, private $npmInstallationManager: INpmInstallationManager, @@ -31,7 +33,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, private $processService: IProcessService, private $socketProxyFactory: ISocketProxyFactory) { - super($devicesService); + super(device, $devicesService); this.$processService.attachToProcessExitSignals(this, this.debugStop); this.$socketProxyFactory.on(CONNECTION_ERROR_EVENT_NAME, (e: Error) => this.emit(CONNECTION_ERROR_EVENT_NAME, e)); } @@ -40,7 +42,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS return "ios"; } - public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + public debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { if (debugOptions.debugBrk && debugOptions.start) { this.$errors.failWithoutHelp("Expected exactly one of the --debug-brk or --start options."); } @@ -51,9 +53,9 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS if (debugOptions.emulator) { if (debugOptions.start) { - return await this.emulatorStart(debugData, debugOptions); + return this.emulatorStart(debugData, debugOptions); } else { - return await this.emulatorDebugBrk(debugData, debugOptions); + return this.emulatorDebugBrk(debugData, debugOptions); } } else { if (debugOptions.start) { @@ -200,7 +202,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } private async wireDebuggerClient(debugData: IDebugData, debugOptions: IDebugOptions, device?: Mobile.IiOSDevice): Promise { - if (debugOptions.chrome) { + if (debugOptions.chrome || !this.$hostInfo.isDarwin) { this._socketProxy = await this.$socketProxyFactory.createWebSocketProxy(this.getSocketFactory(device)); return this.getChromeDebugUrl(debugOptions, this._socketProxy.options.port); @@ -237,4 +239,4 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } } -$injector.register("iOSDebugService", IOSDebugService); +$injector.register("iOSDebugService", IOSDebugService, false); diff --git a/lib/services/livesync/debug-livesync-service.ts b/lib/services/livesync/debug-livesync-service.ts deleted file mode 100644 index 568ba3a3ea..0000000000 --- a/lib/services/livesync/debug-livesync-service.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { EOL } from "os"; -import { LiveSyncService } from "./livesync-service"; - -export class DebugLiveSyncService extends LiveSyncService implements IDebugLiveSyncService { - - constructor(protected $platformService: IPlatformService, - $projectDataService: IProjectDataService, - protected $devicesService: Mobile.IDevicesService, - $mobileHelper: Mobile.IMobileHelper, - $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, - protected $logger: ILogger, - $processService: IProcessService, - $hooksService: IHooksService, - $pluginsService: IPluginsService, - protected $injector: IInjector, - private $options: IOptions, - private $debugDataService: IDebugDataService, - private $projectData: IProjectData, - private $debugService: IDebugService, - private $config: IConfiguration) { - - super($platformService, - $projectDataService, - $devicesService, - $mobileHelper, - $devicePlatformsConstants, - $nodeModulesDependenciesBuilder, - $logger, - $processService, - $hooksService, - $pluginsService, - $injector); - } - - protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo): Promise { - const debugOptions = this.$options; - const deployOptions: IDeployPlatformOptions = { - clean: this.$options.clean, - device: this.$options.device, - emulator: this.$options.emulator, - platformTemplate: this.$options.platformTemplate, - projectDir: this.$options.path, - release: this.$options.release, - provision: this.$options.provision, - teamId: this.$options.teamId - }; - - let debugData = this.$debugDataService.createDebugData(this.$projectData, { device: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier }); - const debugService = this.$debugService.getDebugService(liveSyncResultInfo.deviceAppData.device); - - await this.$platformService.trackProjectType(this.$projectData); - - if (this.$options.start) { - return this.printDebugInformation(await debugService.debug(debugData, debugOptions)); - } - - const deviceAppData = liveSyncResultInfo.deviceAppData; - this.$config.debugLivesync = true; - - await debugService.debugStop(); - - let applicationId = deviceAppData.appIdentifier; - await deviceAppData.device.applicationManager.stopApplication(applicationId, projectData.projectName); - - const buildConfig: IBuildConfig = _.merge({ buildForDevice: !deviceAppData.device.isEmulator }, deployOptions); - debugData.pathToAppPackage = this.$platformService.lastOutputPath(debugService.platform, buildConfig, projectData); - - this.printDebugInformation(await debugService.debug(debugData, debugOptions)); - } - - public printDebugInformation(information: string): void { - if (!!information) { - this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${information}${EOL}`.cyan); - } - } -} - -$injector.register("debugLiveSyncService", DebugLiveSyncService); diff --git a/lib/services/livesync/livesync-command-helper.ts b/lib/services/livesync/livesync-command-helper.ts index 869c148160..fae89af3c9 100644 --- a/lib/services/livesync/livesync-command-helper.ts +++ b/lib/services/livesync/livesync-command-helper.ts @@ -1,9 +1,9 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { - constructor(protected $platformService: IPlatformService, - protected $projectData: IProjectData, - protected $options: IOptions, - protected $devicesService: Mobile.IDevicesService, + constructor(private $platformService: IPlatformService, + private $projectData: IProjectData, + private $options: IOptions, + private $liveSyncService: ILiveSyncService, private $iosDeviceOperations: IIOSDeviceOperations, private $mobileHelper: Mobile.IMobileHelper, private $platformsData: IPlatformsData, @@ -15,7 +15,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { return availablePlatforms; } - public async executeLiveSyncOperation(devices: Mobile.IDevice[], liveSyncService: ILiveSyncService, platform: string): Promise { + public async executeLiveSyncOperation(devices: Mobile.IDevice[], platform: string, deviceDebugMap?: IDictionary): Promise { if (!devices || !devices.length) { if (platform) { this.$errors.failWithoutHelp("Unable to find applicable devices to execute operation. Ensure connected devices are trusted and try again."); @@ -60,7 +60,8 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { await this.$platformService.buildPlatform(d.deviceInfo.platform, buildConfig, this.$projectData); const result = await this.$platformService.lastOutputPath(d.deviceInfo.platform, buildConfig, this.$projectData); return result; - } + }, + debugggingEnabled: deviceDebugMap && deviceDebugMap[d.deviceInfo.identifier] }; return info; @@ -70,10 +71,11 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { projectDir: this.$projectData.projectDir, skipWatcher: !this.$options.watch, watchAllFiles: this.$options.syncAllFiles, - clean: this.$options.clean + clean: this.$options.clean, + debugOptions: this.$options }; - await liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); + await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index b8b0829979..7b0d9379d8 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -1,8 +1,10 @@ import * as path from "path"; import * as choki from "chokidar"; +import { parse } from "url"; +import { EOL } from "os"; import { EventEmitter } from "events"; import { hook } from "../../common/helpers"; -import { APP_FOLDER_NAME, PACKAGE_JSON_FILE_NAME, LiveSyncTrackActionNames } from "../../constants"; +import { APP_FOLDER_NAME, PACKAGE_JSON_FILE_NAME, LiveSyncTrackActionNames, USER_INTERACTION_NEEDED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME } from "../../constants"; import { FileExtensions, DeviceTypes } from "../../common/constants"; const deviceDescriptorPrimaryKey = "identifier"; @@ -15,21 +17,24 @@ const LiveSyncEvents = { liveSyncNotification: "notify" }; -export class LiveSyncService extends EventEmitter implements ILiveSyncService { +export class LiveSyncService extends EventEmitter implements IDebugLiveSyncService { // key is projectDir protected liveSyncProcessesInfo: IDictionary = {}; - constructor(protected $platformService: IPlatformService, + constructor(private $platformService: IPlatformService, private $projectDataService: IProjectDataService, - protected $devicesService: Mobile.IDevicesService, + private $devicesService: Mobile.IDevicesService, private $mobileHelper: Mobile.IMobileHelper, - protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, - protected $logger: ILogger, + private $logger: ILogger, private $processService: IProcessService, private $hooksService: IHooksService, private $pluginsService: IPluginsService, - protected $injector: IInjector) { + private $debugService: IDebugService, + private $errors: IErrors, + private $debugDataService: IDebugDataService, + private $injector: IInjector) { super(); } @@ -96,7 +101,15 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { return currentDescriptors || []; } - protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo): Promise { + private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string): Promise { + const deviceDescriptor = this.getDeviceDescriptor(liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, projectData.projectDir); + + return deviceDescriptor && deviceDescriptor.debugggingEnabled ? + this.refreshApplicationWithDebug(projectData, liveSyncResultInfo, debugOpts, outputPath) : + this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOpts, outputPath); + } + + private async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string, settings?: IShouldSkipEmitLiveSyncNotification): Promise { const platformLiveSyncService = this.getLiveSyncService(liveSyncResultInfo.deviceAppData.platform); try { await platformLiveSyncService.refreshApplication(projectData, liveSyncResultInfo); @@ -104,12 +117,14 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { this.$logger.info(`Error while trying to start application ${projectData.projectId} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Error is: ${err.message || err}`); const msg = `Unable to start application ${projectData.projectId} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Try starting it manually.`; this.$logger.warn(msg); - this.emit(LiveSyncEvents.liveSyncNotification, { - projectDir: projectData.projectDir, - applicationIdentifier: projectData.projectId, - deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, - notification: msg - }); + if (!settings || !settings.shouldSkipEmitLiveSyncNotification) { + this.emit(LiveSyncEvents.liveSyncNotification, { + projectDir: projectData.projectDir, + applicationIdentifier: projectData.projectId, + deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, + notification: msg + }); + } } this.emit(LiveSyncEvents.liveSyncExecuted, { @@ -123,6 +138,161 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); } + private async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOptions: IDebugOptions, outputPath?: string): Promise { + await this.$platformService.trackProjectType(projectData); + + const deviceAppData = liveSyncResultInfo.deviceAppData; + + const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; + await this.$debugService.debugStop(deviceIdentifier); + + let applicationId = deviceAppData.appIdentifier; + const attachDebuggerOptions: IAttachDebuggerOptions = { + platform: liveSyncResultInfo.deviceAppData.device.deviceInfo.platform, + isEmulator: liveSyncResultInfo.deviceAppData.device.isEmulator, + projectDir: projectData.projectDir, + deviceIdentifier, + debugOptions, + outputPath + }; + + try { + await deviceAppData.device.applicationManager.stopApplication(applicationId, projectData.projectName); + // Now that we've stopped the application we know it isn't started, so set debugOptions.start to false + // so that it doesn't default to true in attachDebugger method + debugOptions = debugOptions || {}; + debugOptions.start = false; + } catch (err) { + this.$logger.trace("Could not stop application during debug livesync. Will try to restart app instead.", err); + if ((err.message || err) === "Could not find developer disk image") { + await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, outputPath, { shouldSkipEmitLiveSyncNotification: true }); + this.emit(USER_INTERACTION_NEEDED_EVENT_NAME, attachDebuggerOptions); + return; + } else { + throw err; + } + } + + const deviceOption = { + deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, + debugOptions: debugOptions, + }; + + return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, { projectDir: projectData.projectDir }); + } + + public async attachDebugger(settings: IAttachDebuggerOptions): Promise { + // Default values + if (settings.debugOptions) { + settings.debugOptions.chrome = settings.debugOptions.chrome === undefined ? true : settings.debugOptions.chrome; + settings.debugOptions.start = settings.debugOptions.start === undefined ? true : settings.debugOptions.start; + } else { + settings.debugOptions = { + chrome: true, + start: true + }; + } + + const projectData = this.$projectDataService.getProjectData(settings.projectDir); + let debugData = this.$debugDataService.createDebugData(projectData, { device: settings.deviceIdentifier }); + + // Of the properties below only `buildForDevice` and `release` are currently used. + // Leaving the others with placeholder values so that they may not be forgotten in future implementations. + const buildConfig: IBuildConfig = { + buildForDevice: !settings.isEmulator, + release: false, + device: settings.deviceIdentifier, + provision: null, + teamId: null, + projectDir: settings.projectDir + }; + debugData.pathToAppPackage = this.$platformService.lastOutputPath(settings.platform, buildConfig, projectData, settings.outputPath); + + this.printDebugInformation(await this.$debugService.debug(debugData, settings.debugOptions)); + } + + public printDebugInformation(information: string): void { + if (!!information) { + const wsQueryParam = parse(information).query.ws; + const hostPortSplit = wsQueryParam && wsQueryParam.split(":"); + this.emit(DEBUGGER_ATTACHED_EVENT_NAME, { + url: information, + port: hostPortSplit && hostPortSplit[1] + }); + this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${information}${EOL}`.cyan); + } + } + + public enableDebugging(deviceOpts: IEnableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[] { + return _.map(deviceOpts, d => this.enableDebuggingCore(d, debuggingAdditionalOptions)); + } + + private getDeviceDescriptor(deviceIdentifier: string, projectDir: string) { + const deviceDescriptors = this.getLiveSyncDeviceDescriptors(projectDir); + + return _.find(deviceDescriptors, d => d.identifier === deviceIdentifier); + } + + private async enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption: IEnableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { + const currentDeviceDescriptor = this.getDeviceDescriptor(deviceOption.deviceIdentifier, debuggingAdditionalOptions.projectDir); + if (!currentDeviceDescriptor) { + this.$errors.failWithoutHelp(`Couldn't enable debugging for ${deviceOption.deviceIdentifier}`); + } + + currentDeviceDescriptor.debugggingEnabled = true; + const currentDeviceInstance = this.$devicesService.getDeviceByIdentifier(deviceOption.deviceIdentifier); + const attachDebuggerOptions: IAttachDebuggerOptions = { + deviceIdentifier: deviceOption.deviceIdentifier, + isEmulator: currentDeviceInstance.isEmulator, + outputPath: currentDeviceDescriptor.outputPath, + platform: currentDeviceInstance.deviceInfo.platform, + projectDir: debuggingAdditionalOptions.projectDir, + debugOptions: deviceOption.debugOptions + }; + + try { + await this.attachDebugger(attachDebuggerOptions); + } catch (err) { + this.$logger.trace("Couldn't attach debugger, will modify options and try again.", err); + attachDebuggerOptions.debugOptions.start = false; + await this.attachDebugger(attachDebuggerOptions); + } + } + + private async enableDebuggingCore(deviceOption: IEnableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { + const liveSyncProcessInfo: ILiveSyncProcessInfo = this.liveSyncProcessesInfo[debuggingAdditionalOptions.projectDir]; + if (liveSyncProcessInfo && liveSyncProcessInfo.currentSyncAction) { + await liveSyncProcessInfo.currentSyncAction; + } + + return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, debuggingAdditionalOptions); + } + + public disableDebugging(deviceOptions: IDisableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[] { + return _.map(deviceOptions, d => this.disableDebuggingCore(d, debuggingAdditionalOptions)); + } + + public async disableDebuggingCore(deviceOption: IDisableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { + const liveSyncProcessInfo = this.liveSyncProcessesInfo[debuggingAdditionalOptions.projectDir]; + if (liveSyncProcessInfo.currentSyncAction) { + await liveSyncProcessInfo.currentSyncAction; + } + + const currentDeviceDescriptor = this.getDeviceDescriptor(deviceOption.deviceIdentifier, debuggingAdditionalOptions.projectDir); + if (currentDeviceDescriptor) { + currentDeviceDescriptor.debugggingEnabled = false; + } else { + this.$errors.failWithoutHelp(`Couldn't disable debugging for ${deviceOption.deviceIdentifier}`); + } + + const currentDevice = this.$devicesService.getDeviceByIdentifier(currentDeviceDescriptor.identifier); + if (!currentDevice) { + this.$errors.failWithoutHelp(`Couldn't disable debugging for ${deviceOption.deviceIdentifier}. Could not find device.`); + } + + return this.$debugService.debugStop(currentDevice.deviceInfo.identifier); + } + @hook("liveSync") private async liveSyncOperation(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo, projectData: IProjectData): Promise { @@ -161,7 +331,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { return this.$injector.resolve("androidLiveSyncService"); } - throw new Error(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); + this.$errors.failWithoutHelp(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); } private async ensureLatestAppPackageIsInstalledOnDevice(options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions, nativePrepare?: INativePrepare): Promise { @@ -263,7 +433,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { watch: !liveSyncData.skipWatcher }); await this.$platformService.trackActionForPlatform({ action: "LiveSync", platform: device.deviceInfo.platform, isForDevice: !device.isEmulator, deviceOsVersion: device.deviceInfo.version }); - await this.refreshApplication(projectData, liveSyncResultInfo); + await this.refreshApplication(projectData, liveSyncResultInfo, liveSyncData.debugOptions, deviceBuildInfoDescriptor.outputPath); this.emit(LiveSyncEvents.liveSyncStarted, { projectDir: projectData.projectDir, @@ -369,7 +539,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { }; const liveSyncResultInfo = await service.liveSyncWatchAction(device, settings); - await this.refreshApplication(projectData, liveSyncResultInfo); + await this.refreshApplication(projectData, liveSyncResultInfo, liveSyncData.debugOptions, deviceBuildInfoDescriptor.outputPath); }, (device: Mobile.IDevice) => { const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index 376490882c..eaee3ee9cd 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -16,7 +16,6 @@ class TestExecutionService implements ITestExecutionService { private $platformService: IPlatformService, private $platformsData: IPlatformsData, private $liveSyncService: ILiveSyncService, - private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $debugDataService: IDebugDataService, private $httpClient: Server.IHttpClient, private $config: IConfiguration, @@ -25,8 +24,7 @@ class TestExecutionService implements ITestExecutionService { private $options: IOptions, private $pluginsService: IPluginsService, private $errors: IErrors, - private $androidDebugService: IPlatformDebugService, - private $iOSDebugService: IPlatformDebugService, + private $debugService: IDebugService, private $devicesService: Mobile.IDevicesService, private $childProcess: IChildProcess) { } @@ -112,7 +110,12 @@ class TestExecutionService implements ITestExecutionService { return info; }); - const liveSyncInfo: ILiveSyncInfo = { projectDir: projectData.projectDir, skipWatcher: !this.$options.watch || this.$options.justlaunch, watchAllFiles: this.$options.syncAllFiles }; + const liveSyncInfo: ILiveSyncInfo = { + projectDir: projectData.projectDir, + skipWatcher: !this.$options.watch || this.$options.justlaunch, + watchAllFiles: this.$options.syncAllFiles, + debugOptions: this.$options + }; await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); @@ -188,9 +191,8 @@ class TestExecutionService implements ITestExecutionService { }; if (this.$options.debugBrk) { - const debugService = this.getDebugService(platform); const debugData = this.getDebugData(platform, projectData, deployOptions); - await debugService.debug(debugData, this.$options); + await this.$debugService.debug(debugData, this.$options); } else { const devices = this.$devicesService.getDeviceInstances(); // Now let's take data for each device: @@ -223,7 +225,13 @@ class TestExecutionService implements ITestExecutionService { return info; }); - const liveSyncInfo: ILiveSyncInfo = { projectDir: projectData.projectDir, skipWatcher: !this.$options.watch || this.$options.justlaunch, watchAllFiles: this.$options.syncAllFiles }; + const liveSyncInfo: ILiveSyncInfo = { + projectDir: projectData.projectDir, + skipWatcher: !this.$options.watch || this.$options.justlaunch, + watchAllFiles: this.$options.syncAllFiles, + debugOptions: this.$options + }; + await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } }; @@ -277,17 +285,6 @@ class TestExecutionService implements ITestExecutionService { return 'module.exports = ' + JSON.stringify(config); } - private getDebugService(platform: string): IPlatformDebugService { - let lowerCasedPlatform = platform.toLowerCase(); - if (lowerCasedPlatform === this.$devicePlatformsConstants.iOS.toLowerCase()) { - return this.$iOSDebugService; - } else if (lowerCasedPlatform === this.$devicePlatformsConstants.Android.toLowerCase()) { - return this.$androidDebugService; - } - - throw new Error(`Invalid platform ${platform}. Valid platforms are ${this.$devicePlatformsConstants.iOS} and ${this.$devicePlatformsConstants.Android}`); - } - private getKarmaConfiguration(platform: string, projectData: IProjectData): any { let karmaConfig: any = { browsers: [platform], diff --git a/test/debug.ts b/test/debug.ts index 4a89464944..f004e806ff 100644 --- a/test/debug.ts +++ b/test/debug.ts @@ -24,7 +24,6 @@ function createTestInjector(): IInjector { testInjector.register("options", Options); testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); testInjector.register('childProcess', stubs.ChildProcessStub); - testInjector.register('androidDebugService', stubs.DebugServiceStub); testInjector.register('fs', FileSystem); testInjector.register('errors', stubs.ErrorsStub); testInjector.register('hostInfo', {}); @@ -39,7 +38,7 @@ function createTestInjector(): IInjector { getDeviceInstances: (): any[] => { return []; }, execute: async (): Promise => ({}) }); - testInjector.register("debugLiveSyncService", stubs.LiveSyncServiceStub); + testInjector.register("liveSyncService", stubs.LiveSyncServiceStub); testInjector.register("androidProjectService", AndroidProjectService); testInjector.register("androidToolsInfo", stubs.AndroidToolsInfoStub); testInjector.register("hostInfo", {}); @@ -50,6 +49,7 @@ function createTestInjector(): IInjector { testInjector.register("pluginVariablesService", {}); testInjector.register("deviceAppDataFactory", {}); testInjector.register("projectTemplatesService", {}); + testInjector.register("debugService", {}); testInjector.register("xmlValidator", {}); testInjector.register("npm", {}); testInjector.register("debugDataService", { @@ -341,40 +341,7 @@ describe("debug command tests", () => { testInjector = createTestInjector(); }); - it("Ensures that debugLivesync flag is true when executing debug --watch command", async () => { - const debugCommand = testInjector.resolveCommand("debug|android"); - const options: IOptions = testInjector.resolve("options"); - options.watch = true; - const devicesService = testInjector.resolve("devicesService"); - devicesService.getDeviceInstances = (): Mobile.IDevice[] => { - return [{ deviceInfo: { status: "Connected", platform: "android" } }]; - }; - - const debugLiveSyncService = testInjector.resolve("debugLiveSyncService"); - debugLiveSyncService.liveSync = async (deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise => { - return null; - }; - - await debugCommand.execute(["android", "--watch"]); - const config: IConfiguration = testInjector.resolve("config"); - assert.isTrue(config.debugLivesync); - }); - - it("Ensures that beforePrepareAllPlugins will not call gradle when livesyncing", async () => { - let config: IConfiguration = testInjector.resolve("config"); - config.debugLivesync = true; - let childProcess: stubs.ChildProcessStub = testInjector.resolve("childProcess"); - let androidProjectService: IPlatformProjectService = testInjector.resolve("androidProjectService"); - let projectData: IProjectData = testInjector.resolve("projectData"); - let spawnFromEventCount = childProcess.spawnFromEventCount; - await androidProjectService.beforePrepareAllPlugins(projectData); - assert.isTrue(spawnFromEventCount === 0); - assert.isTrue(spawnFromEventCount === childProcess.spawnFromEventCount); - }); - it("Ensures that beforePrepareAllPlugins will call gradle with clean option when *NOT* livesyncing", async () => { - let config: IConfiguration = testInjector.resolve("config"); - config.debugLivesync = false; let childProcess: stubs.ChildProcessStub = testInjector.resolve("childProcess"); let androidProjectService: IPlatformProjectService = testInjector.resolve("androidProjectService"); let projectData: IProjectData = testInjector.resolve("projectData"); diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index 3bcc81522c..2168f38da0 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -20,7 +20,7 @@ describe("nativescript-cli-lib", () => { deviceLogProvider: null, npm: ["install", "uninstall", "view", "search"], extensibilityService: ["loadExtensions", "loadExtension", "getInstalledExtensions", "installExtension", "uninstallExtension"], - liveSyncService: ["liveSync", "stopLiveSync"], + liveSyncService: ["liveSync", "stopLiveSync", "enableDebugging", "disableDebugging", "attachDebugger"], analyticsService: ["startEqatecMonitor"], debugService: ["debug"] }; diff --git a/test/services/android-debug-service.ts b/test/services/android-debug-service.ts index d7f239136f..478575b9af 100644 --- a/test/services/android-debug-service.ts +++ b/test/services/android-debug-service.ts @@ -9,11 +9,10 @@ class AndroidDebugServiceInheritor extends AndroidDebugService { constructor(protected $devicesService: Mobile.IDevicesService, $errors: IErrors, $logger: ILogger, - $config: IConfiguration, $androidDeviceDiscovery: Mobile.IDeviceDiscovery, $androidProcessService: Mobile.IAndroidProcessService, $net: INet) { - super($devicesService, $errors, $logger, $config, $androidDeviceDiscovery, $androidProcessService, $net); + super({}, $devicesService, $errors, $logger, $androidDeviceDiscovery, $androidProcessService, $net); } public getChromeDebugUrl(debugOptions: IDebugOptions, port: number): string { @@ -26,7 +25,6 @@ const createTestInjector = (): IInjector => { testInjector.register("devicesService", {}); testInjector.register("errors", stubs.ErrorsStub); testInjector.register("logger", stubs.LoggerStub); - testInjector.register("config", {}); testInjector.register("androidDeviceDiscovery", {}); testInjector.register("androidProcessService", {}); testInjector.register("net", {}); diff --git a/test/services/debug-service.ts b/test/services/debug-service.ts index 259550d481..b320178890 100644 --- a/test/services/debug-service.ts +++ b/test/services/debug-service.ts @@ -4,7 +4,7 @@ import * as stubs from "../stubs"; import { assert } from "chai"; import { EventEmitter } from "events"; import * as constants from "../../lib/common/constants"; -import { CONNECTION_ERROR_EVENT_NAME } from "../../lib/constants"; +import { CONNECTION_ERROR_EVENT_NAME, DebugCommandErrors } from "../../lib/constants"; const fakeChromeDebugUrl = "fakeChromeDebugUrl"; class PlatformDebugService extends EventEmitter /* implements IPlatformDebugService */ { @@ -57,7 +57,7 @@ describe("debugService", () => { testInjector.register("devicesService", { getDeviceByIdentifier: (identifier: string): Mobile.IDevice => { return testData.isDeviceFound ? - { + { deviceInfo: testData.deviceInformation.deviceInfo, applicationManager: { @@ -141,7 +141,7 @@ describe("debugService", () => { const testData = getDefaultTestData(); testData.deviceInformation.deviceInfo.platform = "WP8"; - await assertIsRejected(testData, "Unsupported device OS:"); + await assertIsRejected(testData, DebugCommandErrors.UNSUPPORTED_DEVICE_OS_FOR_DEBUGGING); }); it("when trying to debug on iOS Simulator on macOS, debug-brk is passed, but pathToAppPackage is not", async () => { @@ -178,78 +178,6 @@ describe("debugService", () => { }); }); - describe("passes correct args to", () => { - const assertPassedDebugOptions = async (platform: string, userSpecifiedOptions?: IDebugOptions, hostInfo?: { isWindows: boolean, isDarwin: boolean }): Promise => { - const testData = getDefaultTestData(); - testData.deviceInformation.deviceInfo.platform = platform; - if (hostInfo) { - testData.hostInfo = hostInfo; - } - - const testInjector = getTestInjectorForTestConfiguration(testData); - const platformDebugService = testInjector.resolve(`${platform}DebugService`); - let passedDebugOptions: IDebugOptions = null; - platformDebugService.debug = async (debugData: IDebugData, debugOptions: IDebugOptions): Promise => { - passedDebugOptions = debugOptions; - return []; - }; - - const debugService = testInjector.resolve(DebugService); - - const debugData = getDebugData(); - await assert.isFulfilled(debugService.debug(debugData, userSpecifiedOptions)); - assert.isTrue(passedDebugOptions.chrome); - assert.isTrue(passedDebugOptions.start); - - return passedDebugOptions; - }; - - _.each(["android", "iOS"], platform => { - describe(`${platform}DebugService's debug method`, () => { - describe("on macOS", () => { - it("passes chrome and start as true, when no debugOptions are passed to debugService", async () => { - await assertPassedDebugOptions(platform); - }); - - it("when calling debug service with chrome and start set to false, should disregard them and set both to true", async () => { - await assertPassedDebugOptions(platform, { chrome: false, start: false }); - }); - - it("passes other custom options without modification", async () => { - const passedDebugOptions = await assertPassedDebugOptions(platform, { emulator: true, useBundledDevTools: true }); - assert.isTrue(passedDebugOptions.useBundledDevTools); - assert.isTrue(passedDebugOptions.emulator); - }); - }); - - describe("on Windows", () => { - const assertEmulatorOption = (passedDebugOptions: IDebugOptions) => { - if (platform === "iOS") { - assert.isFalse(passedDebugOptions.emulator); - } - }; - - it("passes chrome and start as true, when no debugOptions are passed to debugService", async () => { - const passedDebugOptions = await assertPassedDebugOptions(platform, null, { isWindows: true, isDarwin: false }); - assertEmulatorOption(passedDebugOptions); - }); - - it("when calling debug service with chrome and start set to false, should disregard them and set both to true", async () => { - const passedDebugOptions = await assertPassedDebugOptions(platform, { chrome: false, start: false }, { isWindows: true, isDarwin: false }); - assertEmulatorOption(passedDebugOptions); - }); - - it("passes other custom options without modification", async () => { - const passedDebugOptions = await assertPassedDebugOptions(platform, { debugBrk: true, useBundledDevTools: true }); - assert.isTrue(passedDebugOptions.useBundledDevTools); - assert.isTrue(passedDebugOptions.debugBrk); - }); - }); - - }); - }); - }); - describe(`raises ${CONNECTION_ERROR_EVENT_NAME} event`, () => { _.each(["android", "iOS"], platform => { it(`when ${platform}DebugService raises ${CONNECTION_ERROR_EVENT_NAME} event`, async () => { diff --git a/test/services/ios-debug-service.ts b/test/services/ios-debug-service.ts index 2f4898cfcb..23af05478b 100644 --- a/test/services/ios-debug-service.ts +++ b/test/services/ios-debug-service.ts @@ -10,6 +10,7 @@ class IOSDebugServiceInheritor extends IOSDebugService { $platformService: IPlatformService, $iOSEmulatorServices: Mobile.IEmulatorPlatformServices, $childProcess: IChildProcess, + $hostInfo: IHostInfo, $logger: ILogger, $errors: IErrors, $npmInstallationManager: INpmInstallationManager, @@ -17,7 +18,7 @@ class IOSDebugServiceInheritor extends IOSDebugService { $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, $processService: IProcessService, $socketProxyFactory: ISocketProxyFactory) { - super($devicesService, $platformService, $iOSEmulatorServices, $childProcess, $logger, $errors, + super({}, $devicesService, $platformService, $iOSEmulatorServices, $childProcess, $hostInfo, $logger, $errors, $npmInstallationManager, $iOSNotification, $iOSSocketRequestExecutor, $processService, $socketProxyFactory); } @@ -35,6 +36,7 @@ const createTestInjector = (): IInjector => { testInjector.register("errors", stubs.ErrorsStub); testInjector.register("logger", stubs.LoggerStub); + testInjector.register("hostInfo", {}); testInjector.register("npmInstallationManager", {}); testInjector.register("iOSNotification", {}); testInjector.register("iOSSocketRequestExecutor", {}); diff --git a/test/services/livesync-service.ts b/test/services/livesync-service.ts index 75d2073bec..0dde25150b 100644 --- a/test/services/livesync-service.ts +++ b/test/services/livesync-service.ts @@ -17,6 +17,9 @@ const createTestInjector = (): IInjector => { testInjector.register("nodeModulesDependenciesBuilder", {}); testInjector.register("logger", LoggerStub); testInjector.register("processService", {}); + testInjector.register("debugService", {}); + testInjector.register("errors", {}); + testInjector.register("debugDataService", {}); testInjector.register("hooksService", { executeAfterHooks: (commandName: string, hookArguments?: IDictionary): Promise => Promise.resolve() }); @@ -38,6 +41,9 @@ class LiveSyncServiceInheritor extends LiveSyncService { $processService: IProcessService, $hooksService: IHooksService, $pluginsService: IPluginsService, + $debugService: IDebugService, + $errors: IErrors, + $debugDataService: IDebugDataService, $injector: IInjector) { super( @@ -51,6 +57,9 @@ class LiveSyncServiceInheritor extends LiveSyncService { $processService, $hooksService, $pluginsService, + $debugService, + $errors, + $debugDataService, $injector ); } diff --git a/test/stubs.ts b/test/stubs.ts index 2a9569410e..8c98f21c2f 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -35,6 +35,14 @@ export class LoggerStub implements ILogger { printMarkdown(message: string): void { } } +export class ProcessServiceStub implements IProcessService { + public listenersCount: number; + + public attachToProcessExitSignals(context: any, callback: () => void): void { + return undefined; + } +} + export class FileSystemStub implements IFileSystem { async zipFiles(zipFile: string, files: string[], zipPathCallback: (path: string) => string): Promise { return undefined; From 164b687e03343befbafc9818f968be58782d41d3 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Thu, 24 Aug 2017 14:29:37 +0300 Subject: [PATCH 198/212] Fix unit-tests on Windows (#3086) Due to recent changes in `ios-project-service`, some unit tests cannot work on Windows and Linux, so ignore them in case the OS is not macOS. --- test/ios-project-service.ts | 41 ++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index 956c4343a8..aa9f32a494 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -199,16 +199,21 @@ describe("iOSProjectService", () => { } }; } - it("by default exports xcodearchive to platforms/ios/build/archive/.xcarchive", async () => { - let setup = await setupArchive(); - await setup.run(); - setup.assert(); - }); - it("can pass archivePath to xcodebuild -archivePath", async () => { - let setup = await setupArchive({ archivePath: "myarchive.xcarchive" }); - await setup.run(); - setup.assert(); - }); + + if (require("os").platform() !== "darwin") { + console.log("Skipping iOS archive tests. They can work only on macOS"); + } else { + it("by default exports xcodearchive to platforms/ios/build/archive/.xcarchive", async () => { + let setup = await setupArchive(); + await setup.run(); + setup.assert(); + }); + it("can pass archivePath to xcodebuild -archivePath", async () => { + let setup = await setupArchive({ archivePath: "myarchive.xcarchive" }); + await setup.run(); + setup.assert(); + }); + } }); describe("exportArchive", () => { @@ -282,13 +287,17 @@ describe("iOSProjectService", () => { assert.ok(xcodebuildExeced, "Expected xcodebuild to be executed"); } - it("calls xcodebuild -exportArchive to produce .IPA", async () => { - await testExportArchive({}, noTeamPlist); - }); + if (require("os").platform() !== "darwin") { + console.log("Skipping iOS export archive tests. They can work only on macOS"); + } else { + it("calls xcodebuild -exportArchive to produce .IPA", async () => { + await testExportArchive({}, noTeamPlist); + }); - it("passes the --team-id option down the xcodebuild -exportArchive throug the -exportOptionsPlist", async () => { - await testExportArchive({ teamID: "MyTeam" }, myTeamPlist); - }); + it("passes the --team-id option down the xcodebuild -exportArchive throug the -exportOptionsPlist", async () => { + await testExportArchive({ teamID: "MyTeam" }, myTeamPlist); + }); + } }); }); From 9cde85233add31b3c69a6435c7a699935086b3bc Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Mon, 28 Aug 2017 14:07:56 +0300 Subject: [PATCH 199/212] Add prefer-const rule to tslint (#3090) Enable `prefer-const` rule in tslint and fix the errors: The rule requires that variable declarations use const instead of let and var if possible. If a variable is only assigned to once when it is declared, it should be declared using `const` Add new npm script for automatic fix of tslint: `npm run tslint-fix`. NOTE: In case you have 3 errors that the script can fix, when you execute `npm run tslint-fix`, it will fix them, but will print them as errors again. Run the command again to verify everything is ok. --- lib/android-tools-info.ts | 56 ++-- lib/commands/add-platform.ts | 2 +- lib/commands/appstore-list.ts | 8 +- lib/commands/appstore-upload.ts | 24 +- lib/commands/build.ts | 2 +- lib/commands/clean-app.ts | 4 +- lib/commands/debug.ts | 4 +- lib/commands/install.ts | 8 +- lib/commands/list-platforms.ts | 6 +- lib/commands/platform-clean.ts | 2 +- lib/commands/plugin/add-plugin.ts | 4 +- lib/commands/plugin/list-plugins.ts | 16 +- lib/commands/plugin/remove-plugin.ts | 4 +- lib/commands/plugin/update-plugin.ts | 10 +- lib/commands/remove-platform.ts | 2 +- lib/commands/run.ts | 2 +- lib/commands/test-init.ts | 22 +- lib/commands/update-platform.ts | 2 +- lib/commands/update.ts | 26 +- lib/common | 2 +- lib/config.ts | 2 +- lib/device-path-provider.ts | 2 +- lib/device-sockets/ios/packet-stream.ts | 4 +- .../ios/socket-proxy-factory.ts | 16 +- lib/dynamic-help-provider.ts | 2 +- lib/node-package-manager.ts | 24 +- lib/npm-installation-manager.ts | 24 +- lib/options.ts | 6 +- lib/providers/project-files-provider.ts | 12 +- lib/services/analytics-settings-service.ts | 2 +- lib/services/android-debug-service.ts | 14 +- .../android-project-properties-manager.ts | 18 +- lib/services/android-project-service.ts | 104 +++---- lib/services/app-files-updater.ts | 6 +- lib/services/cocoapods-service.ts | 18 +- lib/services/doctor-service.ts | 28 +- lib/services/emulator-platform-service.ts | 50 ++-- lib/services/emulator-settings-service.ts | 4 +- lib/services/info-service.ts | 4 +- lib/services/init-service.ts | 34 +-- lib/services/ios-debug-service.ts | 22 +- lib/services/ios-entitlements-service.ts | 16 +- lib/services/ios-log-filter.ts | 28 +- lib/services/ios-project-service.ts | 280 +++++++++--------- lib/services/itmstransporter-service.ts | 26 +- lib/services/karma-execution.ts | 2 +- .../android-device-livesync-service.ts | 8 +- .../livesync/ios-device-livesync-service.ts | 12 +- lib/services/livesync/livesync-service.ts | 18 +- .../platform-livesync-service-base.ts | 2 +- lib/services/platform-project-service-base.ts | 6 +- lib/services/platform-service.ts | 172 +++++------ lib/services/plugin-variables-service.ts | 20 +- lib/services/plugins-service.ts | 74 ++--- lib/services/project-changes-service.ts | 40 +-- lib/services/project-data-service.ts | 6 +- lib/services/project-name-service.ts | 6 +- lib/services/project-service.ts | 36 +-- lib/services/project-templates-service.ts | 2 +- lib/services/subscription-service.ts | 10 +- lib/services/test-execution-service.ts | 42 +-- lib/services/user-settings-service.ts | 2 +- lib/services/versions-service.ts | 46 +-- lib/services/xcconfig-service.ts | 4 +- lib/services/xcproj-service.ts | 12 +- lib/sys-info.ts | 2 +- .../node-modules-dependencies-builder.ts | 6 +- .../node-modules/node-modules-dest-copy.ts | 28 +- lib/xml-validator.ts | 12 +- package.json | 3 +- test/android-project-properties-manager.ts | 72 ++--- test/cocoapods-service.ts | 8 +- test/debug.ts | 10 +- test/ios-entitlements-service.ts | 40 +-- test/ios-project-service.ts | 214 ++++++------- test/npm-installation-manager.ts | 12 +- test/npm-support.ts | 102 +++---- test/platform-commands.ts | 8 +- test/platform-service.ts | 188 ++++++------ test/plugin-variables-service.ts | 196 ++++++------ test/plugins-service.ts | 178 +++++------ test/post-install.ts | 2 +- test/project-changes-service.ts | 16 +- test/project-commands.ts | 4 +- test/project-files-provider.ts | 32 +- test/project-name-service.ts | 18 +- test/project-service.ts | 150 +++++----- test/project-templates-service.ts | 18 +- test/services/extensibility-service.ts | 4 +- test/services/project-data-service.ts | 6 +- test/services/subscription-service.ts | 2 +- test/stubs.ts | 12 +- test/tns-appstore-upload.ts | 4 +- .../node-modules-dependencies-builder.ts | 10 +- test/xcconfig-service.ts | 58 ++-- tslint.json | 1 + 96 files changed, 1445 insertions(+), 1443 deletions(-) diff --git a/lib/android-tools-info.ts b/lib/android-tools-info.ts index 32667f66b4..020b1b3dcd 100644 --- a/lib/android-tools-info.ts +++ b/lib/android-tools-info.ts @@ -29,7 +29,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { @cache() public getToolsInfo(): IAndroidToolsInfoData { if (!this.toolsInfo) { - let infoData: IAndroidToolsInfoData = Object.create(null); + const infoData: IAndroidToolsInfoData = Object.create(null); infoData.androidHomeEnvVar = this.androidHome; infoData.compileSdkVersion = this.getCompileSdkVersion(); infoData.buildToolsVersion = this.getBuildToolsVersion(); @@ -46,8 +46,8 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { public validateInfo(options?: { showWarningsAsErrors: boolean, validateTargetSdk: boolean }): boolean { let detectedErrors = false; this.showWarningsAsErrors = options && options.showWarningsAsErrors; - let toolsInfoData = this.getToolsInfo(); - let isAndroidHomeValid = this.validateAndroidHomeEnvVariable(); + const toolsInfoData = this.getToolsInfo(); + const isAndroidHomeValid = this.validateAndroidHomeEnvVariable(); if (!toolsInfoData.compileSdkVersion) { this.printMessage(`Cannot find a compatible Android SDK for compilation. To be able to build for Android, install Android SDK ${AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET} or later.`, `Run \`\$ ${this.getPathToSdkManagementTool()}\` to manage your Android SDK versions.`); @@ -55,8 +55,8 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { } if (!toolsInfoData.buildToolsVersion) { - let buildToolsRange = this.getBuildToolsRange(); - let versionRangeMatches = buildToolsRange.match(/^.*?([\d\.]+)\s+.*?([\d\.]+)$/); + const buildToolsRange = this.getBuildToolsRange(); + const versionRangeMatches = buildToolsRange.match(/^.*?([\d\.]+)\s+.*?([\d\.]+)$/); let message = `You can install any version in the following range: '${buildToolsRange}'.`; // Improve message in case buildToolsRange is something like: ">=22.0.0 <=22.0.0" - same numbers on both sides @@ -83,11 +83,11 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { } if (options && options.validateTargetSdk) { - let targetSdk = toolsInfoData.targetSdkVersion; - let newTarget = `${AndroidToolsInfo.ANDROID_TARGET_PREFIX}-${targetSdk}`; + const targetSdk = toolsInfoData.targetSdkVersion; + const newTarget = `${AndroidToolsInfo.ANDROID_TARGET_PREFIX}-${targetSdk}`; if (!_.includes(AndroidToolsInfo.SUPPORTED_TARGETS, newTarget)) { - let supportedVersions = AndroidToolsInfo.SUPPORTED_TARGETS.sort(); - let minSupportedVersion = this.parseAndroidSdkString(_.first(supportedVersions)); + const supportedVersions = AndroidToolsInfo.SUPPORTED_TARGETS.sort(); + const minSupportedVersion = this.parseAndroidSdkString(_.first(supportedVersions)); if (targetSdk && (targetSdk < minSupportedVersion)) { this.printMessage(`The selected Android target SDK ${newTarget} is not supported. You must target ${minSupportedVersion} or later.`); @@ -107,10 +107,10 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { this.showWarningsAsErrors = options.showWarningsAsErrors; } - let additionalMessage = "You will not be able to build your projects for Android." + EOL + const additionalMessage = "You will not be able to build your projects for Android." + EOL + "To be able to build for Android, verify that you have installed The Java Development Kit (JDK) and configured it according to system requirements as" + EOL + " described in " + this.$staticConfig.SYS_REQUIREMENTS_LINK; - let matchingVersion = (installedJavaVersion || "").match(AndroidToolsInfo.VERSION_REGEX); + const matchingVersion = (installedJavaVersion || "").match(AndroidToolsInfo.VERSION_REGEX); if (matchingVersion && matchingVersion[1]) { if (semver.lt(matchingVersion[1], AndroidToolsInfo.MIN_JAVA_VERSION)) { hasProblemWithJavaVersion = true; @@ -126,7 +126,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { public async getPathToAdbFromAndroidHome(): Promise { if (this.androidHome) { - let pathToAdb = path.join(this.androidHome, "platform-tools", "adb"); + const pathToAdb = path.join(this.androidHome, "platform-tools", "adb"); try { await this.$childProcess.execFile(pathToAdb, ["help"]); return pathToAdb; @@ -212,19 +212,19 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { private getCompileSdkVersion(): number { if (!this.selectedCompileSdk) { - let userSpecifiedCompileSdk = this.$options.compileSdk; + const userSpecifiedCompileSdk = this.$options.compileSdk; if (userSpecifiedCompileSdk) { - let installedTargets = this.getInstalledTargets(); - let androidCompileSdk = `${AndroidToolsInfo.ANDROID_TARGET_PREFIX}-${userSpecifiedCompileSdk}`; + const installedTargets = this.getInstalledTargets(); + const androidCompileSdk = `${AndroidToolsInfo.ANDROID_TARGET_PREFIX}-${userSpecifiedCompileSdk}`; if (!_.includes(installedTargets, androidCompileSdk)) { this.$errors.failWithoutHelp(`You have specified '${userSpecifiedCompileSdk}' for compile sdk, but it is not installed on your system.`); } this.selectedCompileSdk = userSpecifiedCompileSdk; } else { - let latestValidAndroidTarget = this.getLatestValidAndroidTarget(); + const latestValidAndroidTarget = this.getLatestValidAndroidTarget(); if (latestValidAndroidTarget) { - let integerVersion = this.parseAndroidSdkString(latestValidAndroidTarget); + const integerVersion = this.parseAndroidSdkString(latestValidAndroidTarget); if (integerVersion && integerVersion >= AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET) { this.selectedCompileSdk = integerVersion; @@ -237,7 +237,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { } private getTargetSdk(): number { - let targetSdk = this.$options.sdk ? parseInt(this.$options.sdk) : this.getCompileSdkVersion(); + const targetSdk = this.$options.sdk ? parseInt(this.$options.sdk) : this.getCompileSdkVersion(); this.$logger.trace(`Selected targetSdk is: ${targetSdk}`); return targetSdk; } @@ -245,12 +245,12 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { private getMatchingDir(pathToDir: string, versionRange: string): string { let selectedVersion: string; if (this.$fs.exists(pathToDir)) { - let subDirs = this.$fs.readDirectory(pathToDir); + const subDirs = this.$fs.readDirectory(pathToDir); this.$logger.trace(`Directories found in ${pathToDir} are ${subDirs.join(", ")}`); - let subDirsVersions = subDirs + const subDirsVersions = subDirs .map(dirName => { - let dirNameGroups = dirName.match(AndroidToolsInfo.VERSION_REGEX); + const dirNameGroups = dirName.match(AndroidToolsInfo.VERSION_REGEX); if (dirNameGroups) { return dirNameGroups[1]; } @@ -259,7 +259,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { }) .filter(dirName => !!dirName); this.$logger.trace(`Versions found in ${pathToDir} are ${subDirsVersions.join(", ")}`); - let version = semver.maxSatisfying(subDirsVersions, versionRange); + const version = semver.maxSatisfying(subDirsVersions, versionRange); if (version) { selectedVersion = _.find(subDirs, dir => dir.indexOf(version) !== -1); } @@ -275,8 +275,8 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { private getBuildToolsVersion(): string { let buildToolsVersion: string; if (this.androidHome) { - let pathToBuildTools = path.join(this.androidHome, "build-tools"); - let buildToolsRange = this.getBuildToolsRange(); + const pathToBuildTools = path.join(this.androidHome, "build-tools"); + const buildToolsRange = this.getBuildToolsRange(); buildToolsVersion = this.getMatchingDir(pathToBuildTools, buildToolsRange); } @@ -284,7 +284,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { } private getAppCompatRange(): string { - let compileSdkVersion = this.getCompileSdkVersion(); + const compileSdkVersion = this.getCompileSdkVersion(); let requiredAppCompatRange: string; if (compileSdkVersion) { requiredAppCompatRange = `>=${compileSdkVersion} <${compileSdkVersion + 1}`; @@ -295,9 +295,9 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { private getAndroidSupportRepositoryVersion(): string { let selectedAppCompatVersion: string; - let requiredAppCompatRange = this.getAppCompatRange(); + const requiredAppCompatRange = this.getAppCompatRange(); if (this.androidHome && requiredAppCompatRange) { - let pathToAppCompat = path.join(this.androidHome, "extras", "android", "m2repository", "com", "android", "support", "appcompat-v7"); + const pathToAppCompat = path.join(this.androidHome, "extras", "android", "m2repository", "com", "android", "support", "appcompat-v7"); selectedAppCompatVersion = this.getMatchingDir(pathToAppCompat, requiredAppCompatRange); } @@ -306,7 +306,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { } private getLatestValidAndroidTarget(): string { - let installedTargets = this.getInstalledTargets(); + const installedTargets = this.getInstalledTargets(); return _.findLast(AndroidToolsInfo.SUPPORTED_TARGETS.sort(), supportedTarget => _.includes(installedTargets, supportedTarget)); } diff --git a/lib/commands/add-platform.ts b/lib/commands/add-platform.ts index ddb6156c9f..5a597c5d6f 100644 --- a/lib/commands/add-platform.ts +++ b/lib/commands/add-platform.ts @@ -18,7 +18,7 @@ export class AddPlatformCommand implements ICommand { this.$errors.fail("No platform specified. Please specify a platform to add"); } - for (let arg of args) { + for (const arg of args) { this.$platformService.validatePlatform(arg, this.$projectData); const platformData = this.$platformsData.getPlatformData(arg, this.$projectData); const platformProjectService = platformData.platformProjectService; diff --git a/lib/commands/appstore-list.ts b/lib/commands/appstore-list.ts index 379508f538..6313a2f64b 100644 --- a/lib/commands/appstore-list.ts +++ b/lib/commands/appstore-list.ts @@ -20,8 +20,8 @@ export class ListiOSApps implements ICommand { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } - let username = args[0], - password = args[1]; + let username = args[0]; + let password = args[1]; if (!username) { username = await this.$prompter.getString("Apple ID", { allowEmpty: false }); @@ -31,12 +31,12 @@ export class ListiOSApps implements ICommand { password = await this.$prompter.getPassword("Apple ID password"); } - let iOSApplications = await this.$itmsTransporterService.getiOSApplications({ username, password }); + const iOSApplications = await this.$itmsTransporterService.getiOSApplications({ username, password }); if (!iOSApplications || !iOSApplications.length) { this.$logger.out("Seems you don't have any applications yet."); } else { - let table: any = createTable(["Application Name", "Bundle Identifier", "Version"], iOSApplications.map(element => { + const table: any = createTable(["Application Name", "Bundle Identifier", "Version"], iOSApplications.map(element => { return [element.name, element.bundleId, element.version]; })); diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index 48671fea98..9968d0faf3 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -28,12 +28,12 @@ export class PublishIOS implements ICommand { } public async execute(args: string[]): Promise { - let username = args[0], - password = args[1], - mobileProvisionIdentifier = args[2], - codeSignIdentity = args[3], - teamID = this.$options.teamId, - ipaFilePath = this.$options.ipa ? path.resolve(this.$options.ipa) : null; + let username = args[0]; + let password = args[1]; + const mobileProvisionIdentifier = args[2]; + const codeSignIdentity = args[3]; + const teamID = this.$options.teamId; + let ipaFilePath = this.$options.ipa ? path.resolve(this.$options.ipa) : null; if (!username) { username = await this.$prompter.getString("Apple ID", { allowEmpty: false }); @@ -54,11 +54,11 @@ export class PublishIOS implements ICommand { this.$options.release = true; if (!ipaFilePath) { - let platform = this.$devicePlatformsConstants.iOS; + const platform = this.$devicePlatformsConstants.iOS; // No .ipa path provided, build .ipa on out own. const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; if (mobileProvisionIdentifier || codeSignIdentity) { - let iOSBuildConfig: IBuildConfig = { + const iOSBuildConfig: IBuildConfig = { projectDir: this.$options.path, release: this.$options.release, device: this.$options.device, @@ -77,13 +77,13 @@ export class PublishIOS implements ICommand { this.$logger.info("No .ipa, mobile provision or certificate set. Perfect! Now we'll build .xcarchive and let Xcode pick the distribution certificate and provisioning profile for you when exporting .ipa for AppStore submission."); await this.$platformService.preparePlatform(platform, appFilesUpdaterOptions, this.$options.platformTemplate, this.$projectData, this.$options); - let platformData = this.$platformsData.getPlatformData(platform, this.$projectData); - let iOSProjectService = platformData.platformProjectService; + const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + const iOSProjectService = platformData.platformProjectService; - let archivePath = await iOSProjectService.archive(this.$projectData); + const archivePath = await iOSProjectService.archive(this.$projectData); this.$logger.info("Archive at: " + archivePath); - let exportPath = await iOSProjectService.exportArchive(this.$projectData, { archivePath, teamID, provision: mobileProvisionIdentifier || this.$options.provision }); + const exportPath = await iOSProjectService.exportArchive(this.$projectData, { archivePath, teamID, provision: mobileProvisionIdentifier || this.$options.provision }); this.$logger.info("Export at: " + exportPath); ipaFilePath = exportPath; diff --git a/lib/commands/build.ts b/lib/commands/build.ts index 04b86c12e4..f03710a2b6 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -9,7 +9,7 @@ export class BuildCommandBase { } public async executeCore(args: string[]): Promise { - let platform = args[0].toLowerCase(); + const platform = args[0].toLowerCase(); const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; await this.$platformService.preparePlatform(platform, appFilesUpdaterOptions, this.$options.platformTemplate, this.$projectData, this.$options); this.$options.clean = true; diff --git a/lib/commands/clean-app.ts b/lib/commands/clean-app.ts index f948b4bfe8..b32718804d 100644 --- a/lib/commands/clean-app.ts +++ b/lib/commands/clean-app.ts @@ -23,8 +23,8 @@ export class CleanAppCommandBase implements ICommand { this.$errors.fail(`Applications for platform ${this.platform} can not be built on this OS`); } - let platformData = this.$platformsData.getPlatformData(this.platform, this.$projectData); - let platformProjectService = platformData.platformProjectService; + const platformData = this.$platformsData.getPlatformData(this.platform, this.$projectData); + const platformProjectService = platformData.platformProjectService; await platformProjectService.validate(this.$projectData); return true; } diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 6ac49ad201..3b5d4ef9d8 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -1,4 +1,4 @@ -import { CONNECTED_STATUS } from "../common/constants"; +import { CONNECTED_STATUS } from "../common/constants"; import { isInteractive } from "../common/helpers"; import { cache } from "../common/decorators"; import { DebugCommandErrors } from "../constants"; @@ -24,7 +24,7 @@ export class DebugPlatformCommand implements ICommand { public async execute(args: string[]): Promise { const debugOptions = _.cloneDeep(this.$options.argv); - let debugData = this.$debugDataService.createDebugData(this.$projectData, this.$options); + const debugData = this.$debugDataService.createDebugData(this.$projectData, this.$options); await this.$platformService.trackProjectType(this.$projectData); const selectedDeviceForDebug = await this.getDeviceForDebug(); diff --git a/lib/commands/install.ts b/lib/commands/install.ts index 798eed654b..69be170ea7 100644 --- a/lib/commands/install.ts +++ b/lib/commands/install.ts @@ -26,8 +26,8 @@ export class InstallCommand implements ICommand { await this.$pluginsService.ensureAllDependenciesAreInstalled(this.$projectData); - for (let platform of this.$platformsData.platformsNames) { - let platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + for (const platform of this.$platformsData.platformsNames) { + const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); const frameworkPackageData = this.$projectDataService.getNSValue(this.$projectData.projectDir, platformData.frameworkPackageName); if (frameworkPackageData && frameworkPackageData.version) { try { @@ -47,9 +47,9 @@ export class InstallCommand implements ICommand { } private async installModule(moduleName: string): Promise { - let projectDir = this.$projectData.projectDir; + const projectDir = this.$projectData.projectDir; - let devPrefix = 'nativescript-dev-'; + const devPrefix = 'nativescript-dev-'; if (!this.$fs.exists(moduleName) && moduleName.indexOf(devPrefix) !== 0) { moduleName = devPrefix + moduleName; } diff --git a/lib/commands/list-platforms.ts b/lib/commands/list-platforms.ts index a90326b717..7210609be8 100644 --- a/lib/commands/list-platforms.ts +++ b/lib/commands/list-platforms.ts @@ -10,10 +10,10 @@ export class ListPlatformsCommand implements ICommand { } public async execute(args: string[]): Promise { - let installedPlatforms = this.$platformService.getInstalledPlatforms(this.$projectData); + const installedPlatforms = this.$platformService.getInstalledPlatforms(this.$projectData); if (installedPlatforms.length > 0) { - let preparedPlatforms = this.$platformService.getPreparedPlatforms(this.$projectData); + const preparedPlatforms = this.$platformService.getPreparedPlatforms(this.$projectData); if (preparedPlatforms.length > 0) { this.$logger.out("The project is prepared for: ", helpers.formatListOfNames(preparedPlatforms, "and")); } else { @@ -22,7 +22,7 @@ export class ListPlatformsCommand implements ICommand { this.$logger.out("Installed platforms: ", helpers.formatListOfNames(installedPlatforms, "and")); } else { - let formattedPlatformsList = helpers.formatListOfNames(this.$platformService.getAvailablePlatforms(this.$projectData), "and"); + const formattedPlatformsList = helpers.formatListOfNames(this.$platformService.getAvailablePlatforms(this.$projectData), "and"); this.$logger.out("Available platforms for this OS: ", formattedPlatformsList); this.$logger.out("No installed platforms found. Use $ tns platform add"); } diff --git a/lib/commands/platform-clean.ts b/lib/commands/platform-clean.ts index 046ece328b..5542372de9 100644 --- a/lib/commands/platform-clean.ts +++ b/lib/commands/platform-clean.ts @@ -18,7 +18,7 @@ export class CleanCommand implements ICommand { this.$errors.fail("No platform specified. Please specify a platform to clean"); } - for (let platform of args) { + for (const platform of args) { this.$platformService.validatePlatformInstalled(platform, this.$projectData); const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); diff --git a/lib/commands/plugin/add-plugin.ts b/lib/commands/plugin/add-plugin.ts index ed9fd44d3d..a2830b007d 100644 --- a/lib/commands/plugin/add-plugin.ts +++ b/lib/commands/plugin/add-plugin.ts @@ -16,8 +16,8 @@ export class AddPluginCommand implements ICommand { this.$errors.fail("You must specify plugin name."); } - let installedPlugins = await this.$pluginsService.getAllInstalledPlugins(this.$projectData); - let pluginName = args[0].toLowerCase(); + const installedPlugins = await this.$pluginsService.getAllInstalledPlugins(this.$projectData); + const pluginName = args[0].toLowerCase(); if (_.some(installedPlugins, (plugin: IPluginData) => plugin.name.toLowerCase() === pluginName)) { this.$errors.failWithoutHelp(`Plugin "${pluginName}" is already installed.`); } diff --git a/lib/commands/plugin/list-plugins.ts b/lib/commands/plugin/list-plugins.ts index cc5d52e480..f9afa9c316 100644 --- a/lib/commands/plugin/list-plugins.ts +++ b/lib/commands/plugin/list-plugins.ts @@ -10,19 +10,19 @@ export class ListPluginsCommand implements ICommand { } public async execute(args: string[]): Promise { - let installedPlugins: IPackageJsonDepedenciesResult = this.$pluginsService.getDependenciesFromPackageJson(this.$projectData.projectDir); + const installedPlugins: IPackageJsonDepedenciesResult = this.$pluginsService.getDependenciesFromPackageJson(this.$projectData.projectDir); - let headers: string[] = ["Plugin", "Version"]; - let dependenciesData: string[][] = this.createTableCells(installedPlugins.dependencies); + const headers: string[] = ["Plugin", "Version"]; + const dependenciesData: string[][] = this.createTableCells(installedPlugins.dependencies); - let dependenciesTable: any = createTable(headers, dependenciesData); + const dependenciesTable: any = createTable(headers, dependenciesData); this.$logger.out("Dependencies:"); this.$logger.out(dependenciesTable.toString()); if (installedPlugins.devDependencies && installedPlugins.devDependencies.length) { - let devDependenciesData: string[][] = this.createTableCells(installedPlugins.devDependencies); + const devDependenciesData: string[][] = this.createTableCells(installedPlugins.devDependencies); - let devDependenciesTable: any = createTable(headers, devDependenciesData); + const devDependenciesTable: any = createTable(headers, devDependenciesData); this.$logger.out("Dev Dependencies:"); this.$logger.out(devDependenciesTable.toString()); @@ -30,8 +30,8 @@ export class ListPluginsCommand implements ICommand { this.$logger.out("There are no dev dependencies."); } - let viewDependenciesCommand: string = "npm view grep dependencies".cyan.toString(); - let viewDevDependenciesCommand: string = "npm view grep devDependencies".cyan.toString(); + const viewDependenciesCommand: string = "npm view grep dependencies".cyan.toString(); + const viewDevDependenciesCommand: string = "npm view grep devDependencies".cyan.toString(); this.$logger.warn("NOTE:"); this.$logger.warn(`If you want to check the dependencies of installed plugin use ${viewDependenciesCommand}`); diff --git a/lib/commands/plugin/remove-plugin.ts b/lib/commands/plugin/remove-plugin.ts index 9bd07e9a28..9edd83df22 100644 --- a/lib/commands/plugin/remove-plugin.ts +++ b/lib/commands/plugin/remove-plugin.ts @@ -20,14 +20,14 @@ export class RemovePluginCommand implements ICommand { let pluginNames: string[] = []; try { // try installing the plugins, so we can get information from node_modules about their native code, libs, etc. - let installedPlugins = await this.$pluginsService.getAllInstalledPlugins(this.$projectData); + const installedPlugins = await this.$pluginsService.getAllInstalledPlugins(this.$projectData); pluginNames = installedPlugins.map(pl => pl.name); } catch (err) { this.$logger.trace("Error while installing plugins. Error is:", err); pluginNames = _.keys(this.$projectData.dependencies); } - let pluginName = args[0].toLowerCase(); + const pluginName = args[0].toLowerCase(); if (!_.some(pluginNames, name => name.toLowerCase() === pluginName)) { this.$errors.failWithoutHelp(`Plugin "${pluginName}" is not installed.`); } diff --git a/lib/commands/plugin/update-plugin.ts b/lib/commands/plugin/update-plugin.ts index b0d03d1941..d0b9db4f83 100644 --- a/lib/commands/plugin/update-plugin.ts +++ b/lib/commands/plugin/update-plugin.ts @@ -9,11 +9,11 @@ export class UpdatePluginCommand implements ICommand { let pluginNames = args; if (!pluginNames || args.length === 0) { - let installedPlugins = await this.$pluginsService.getAllInstalledPlugins(this.$projectData); + const installedPlugins = await this.$pluginsService.getAllInstalledPlugins(this.$projectData); pluginNames = installedPlugins.map(p => p.name); } - for (let pluginName of pluginNames) { + for (const pluginName of pluginNames) { await this.$pluginsService.remove(pluginName, this.$projectData); await this.$pluginsService.add(pluginName, this.$projectData); } @@ -24,10 +24,10 @@ export class UpdatePluginCommand implements ICommand { return true; } - let installedPlugins = await this.$pluginsService.getAllInstalledPlugins(this.$projectData); - let installedPluginNames: string[] = installedPlugins.map(pl => pl.name); + const installedPlugins = await this.$pluginsService.getAllInstalledPlugins(this.$projectData); + const installedPluginNames: string[] = installedPlugins.map(pl => pl.name); - let pluginName = args[0].toLowerCase(); + const pluginName = args[0].toLowerCase(); if (!_.some(installedPluginNames, name => name.toLowerCase() === pluginName)) { this.$errors.failWithoutHelp(`Plugin "${pluginName}" is not installed.`); } diff --git a/lib/commands/remove-platform.ts b/lib/commands/remove-platform.ts index 38b7ca016d..e6132e14bf 100644 --- a/lib/commands/remove-platform.ts +++ b/lib/commands/remove-platform.ts @@ -17,7 +17,7 @@ export class RemovePlatformCommand implements ICommand { this.$errors.fail("No platform specified. Please specify a platform to remove"); } - for (let platform of args) { + for (const platform of args) { this.$platformService.validatePlatformInstalled(platform, this.$projectData); const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); const platformProjectService = platformData.platformProjectService; diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 28b76c92de..da4da5148b 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -34,7 +34,7 @@ export class RunCommandBase implements ICommand { } const availablePlatforms = this.$liveSyncCommandHelper.getPlatformsForOperation(this.platform); - for (let platform of availablePlatforms) { + for (const platform of availablePlatforms) { const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); const platformProjectService = platformData.platformProjectService; await platformProjectService.validate(this.$projectData); diff --git a/lib/commands/test-init.ts b/lib/commands/test-init.ts index b04a261e0f..2cf77223a5 100644 --- a/lib/commands/test-init.ts +++ b/lib/commands/test-init.ts @@ -21,18 +21,18 @@ class TestInitCommand implements ICommand { } public async execute(args: string[]): Promise { - let projectDir = this.$projectData.projectDir; + const projectDir = this.$projectData.projectDir; - let frameworkToInstall = this.$options.framework || + const frameworkToInstall = this.$options.framework || await this.$prompter.promptForChoice('Select testing framework:', TESTING_FRAMEWORKS); if (TESTING_FRAMEWORKS.indexOf(frameworkToInstall) === -1) { this.$errors.fail(`Unknown or unsupported unit testing framework: ${frameworkToInstall}`); } - let dependencies = this.frameworkDependencies[frameworkToInstall] || []; - let modulesToInstall = ['karma', 'karma-' + frameworkToInstall, 'karma-nativescript-launcher'].concat(dependencies.map(f => 'karma-' + f)); + const dependencies = this.frameworkDependencies[frameworkToInstall] || []; + const modulesToInstall = ['karma', 'karma-' + frameworkToInstall, 'karma-nativescript-launcher'].concat(dependencies.map(f => 'karma-' + f)); - for (let mod of modulesToInstall) { + for (const mod of modulesToInstall) { await this.$npm.install(mod, projectDir, { 'save-dev': true, optional: false, @@ -47,8 +47,8 @@ class TestInitCommand implements ICommand { const modulePackageJsonContent = this.$fs.readJson(modulePackageJsonPath); const modulePeerDependencies = modulePackageJsonContent.peerDependencies || {}; - for (let peerDependency in modulePeerDependencies) { - let dependencyVersion = modulePeerDependencies[peerDependency] || "*"; + for (const peerDependency in modulePeerDependencies) { + const dependencyVersion = modulePeerDependencies[peerDependency] || "*"; // catch errors when a peerDependency is already installed // e.g karma is installed; karma-jasmine depends on karma and will try to install it again @@ -68,7 +68,7 @@ class TestInitCommand implements ICommand { await this.$pluginsService.add('nativescript-unit-test-runner', this.$projectData); - let testsDir = path.join(projectDir, 'app/tests'); + const testsDir = path.join(projectDir, 'app/tests'); let shouldCreateSampleTests = true; if (this.$fs.exists(testsDir)) { this.$logger.info('app/tests/ directory already exists, will not create an example test project.'); @@ -77,8 +77,8 @@ class TestInitCommand implements ICommand { this.$fs.ensureDirectoryExists(testsDir); - let karmaConfTemplate = this.$resources.readText('test/karma.conf.js'); - let karmaConf = _.template(karmaConfTemplate)({ + const karmaConfTemplate = this.$resources.readText('test/karma.conf.js'); + const karmaConf = _.template(karmaConfTemplate)({ frameworks: [frameworkToInstall].concat(dependencies) .map(fw => `'${fw}'`) .join(', ') @@ -86,7 +86,7 @@ class TestInitCommand implements ICommand { this.$fs.writeFile(path.join(projectDir, 'karma.conf.js'), karmaConf); - let exampleFilePath = this.$resources.resolvePath(`test/example.${frameworkToInstall}.js`); + const exampleFilePath = this.$resources.resolvePath(`test/example.${frameworkToInstall}.js`); if (shouldCreateSampleTests && this.$fs.exists(exampleFilePath)) { this.$fs.copyFile(exampleFilePath, path.join(testsDir, 'example.js')); diff --git a/lib/commands/update-platform.ts b/lib/commands/update-platform.ts index 78975d546e..7dc8c1dae3 100644 --- a/lib/commands/update-platform.ts +++ b/lib/commands/update-platform.ts @@ -18,7 +18,7 @@ export class UpdatePlatformCommand implements ICommand { this.$errors.fail("No platform specified. Please specify platforms to update."); } - for (let arg of args) { + for (const arg of args) { const platform = arg.split("@")[0]; this.$platformService.validatePlatform(platform, this.$projectData); const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); diff --git a/lib/commands/update.ts b/lib/commands/update.ts index ca5ba7e888..2669740cca 100644 --- a/lib/commands/update.ts +++ b/lib/commands/update.ts @@ -16,15 +16,15 @@ export class UpdateCommand implements ICommand { } public async execute(args: string[]): Promise { - let folders = ["lib", "hooks", "platforms", "node_modules"]; - let tmpDir = path.join(this.$projectData.projectDir, ".tmp_backup"); + const folders = ["lib", "hooks", "platforms", "node_modules"]; + const tmpDir = path.join(this.$projectData.projectDir, ".tmp_backup"); try { shelljs.rm("-fr", tmpDir); shelljs.mkdir(tmpDir); shelljs.cp(path.join(this.$projectData.projectDir, "package.json"), tmpDir); - for (let folder of folders) { - let folderToCopy = path.join(this.$projectData.projectDir, folder); + for (const folder of folders) { + const folderToCopy = path.join(this.$projectData.projectDir, folder); if (this.$fs.exists(folderToCopy)) { shelljs.cp("-rf", folderToCopy, tmpDir); } @@ -38,10 +38,10 @@ export class UpdateCommand implements ICommand { await this.executeCore(args, folders); } catch (error) { shelljs.cp("-f", path.join(tmpDir, "package.json"), this.$projectData.projectDir); - for (let folder of folders) { + for (const folder of folders) { shelljs.rm("-rf", path.join(this.$projectData.projectDir, folder)); - let folderToCopy = path.join(tmpDir, folder); + const folderToCopy = path.join(tmpDir, folder); if (this.$fs.exists(folderToCopy)) { shelljs.cp("-fr", folderToCopy, this.$projectData.projectDir); @@ -55,7 +55,7 @@ export class UpdateCommand implements ICommand { } public async canExecute(args: string[]): Promise { - for (let arg of args) { + for (const arg of args) { const platform = arg.split("@")[0]; this.$platformService.validatePlatformInstalled(platform, this.$projectData); const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); @@ -68,11 +68,11 @@ export class UpdateCommand implements ICommand { private async executeCore(args: string[], folders: string[]): Promise { let platforms = this.$platformService.getInstalledPlatforms(this.$projectData); - let availablePlatforms = this.$platformService.getAvailablePlatforms(this.$projectData); - let packagePlatforms: string[] = []; + const availablePlatforms = this.$platformService.getAvailablePlatforms(this.$projectData); + const packagePlatforms: string[] = []; - for (let platform of availablePlatforms) { - let platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + for (const platform of availablePlatforms) { + const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); const platformVersion = this.$projectDataService.getNSValue(this.$projectData.projectDir, platformData.frameworkPackageName); if (platformVersion) { packagePlatforms.push(platform); @@ -84,13 +84,13 @@ export class UpdateCommand implements ICommand { await this.$pluginsService.remove("tns-core-modules", this.$projectData); await this.$pluginsService.remove("tns-core-modules-widgets", this.$projectData); - for (let folder of folders) { + for (const folder of folders) { shelljs.rm("-fr", folder); } platforms = platforms.concat(packagePlatforms); if (args.length === 1) { - for (let platform of platforms) { + for (const platform of platforms) { await this.$platformService.addPlatforms([platform + "@" + args[0]], this.$options.platformTemplate, this.$projectData, this.$options, this.$options.frameworkPath); } diff --git a/lib/common b/lib/common index ef87c15950..5b4c6da25c 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit ef87c15950c43c8ebf6c217f3ec192cf9e18af94 +Subproject commit 5b4c6da25ca015f52756431ddc63b5a7fbd8a995 diff --git a/lib/config.ts b/lib/config.ts index 52265023cb..247c721ede 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -78,7 +78,7 @@ export class StaticConfig extends StaticConfigBase implements IStaticConfig { public async getAdbFilePath(): Promise { if (!this._adbFilePath) { - let androidToolsInfo: IAndroidToolsInfo = this.$injector.resolve("androidToolsInfo"); + const androidToolsInfo: IAndroidToolsInfo = this.$injector.resolve("androidToolsInfo"); this._adbFilePath = await androidToolsInfo.getPathToAdbFromAndroidHome() || await super.getAdbFilePath(); } diff --git a/lib/device-path-provider.ts b/lib/device-path-provider.ts index ee360590ec..dba30ee452 100644 --- a/lib/device-path-provider.ts +++ b/lib/device-path-provider.ts @@ -13,7 +13,7 @@ export class DevicePathProvider implements IDevicePathProvider { let projectRoot = ""; if (this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform)) { if (device.isEmulator) { - let applicationPath = this.$iOSSimResolver.iOSSim.getApplicationPath(device.deviceInfo.identifier, options.appIdentifier); + const applicationPath = this.$iOSSimResolver.iOSSim.getApplicationPath(device.deviceInfo.identifier, options.appIdentifier); projectRoot = path.join(applicationPath); } else { projectRoot = LiveSyncPaths.IOS_DEVICE_PROJECT_ROOT_PATH; diff --git a/lib/device-sockets/ios/packet-stream.ts b/lib/device-sockets/ios/packet-stream.ts index accbbfb9ca..7a4fbbe2f6 100644 --- a/lib/device-sockets/ios/packet-stream.ts +++ b/lib/device-sockets/ios/packet-stream.ts @@ -12,14 +12,14 @@ export class PacketStream extends stream.Transform { while (packet.length > 0) { if (!this.buffer) { // read length - let length = packet.readInt32BE(0); + const length = packet.readInt32BE(0); this.buffer = new Buffer(length); this.offset = 0; packet = packet.slice(4); } packet.copy(this.buffer, this.offset); - let copied = Math.min(this.buffer.length - this.offset, packet.length); + const copied = Math.min(this.buffer.length - this.offset, packet.length); this.offset += copied; packet = packet.slice(copied); diff --git a/lib/device-sockets/ios/socket-proxy-factory.ts b/lib/device-sockets/ios/socket-proxy-factory.ts index 7cace33215..33a75b7b03 100644 --- a/lib/device-sockets/ios/socket-proxy-factory.ts +++ b/lib/device-sockets/ios/socket-proxy-factory.ts @@ -19,7 +19,7 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact this.$logger.info("\nSetting up proxy...\nPress Ctrl + C to terminate, or disconnect.\n"); - let server = net.createServer({ + const server = net.createServer({ allowHalfOpen: true }); @@ -63,7 +63,7 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact }); }); - let socketFileLocation = temp.path({ suffix: ".sock" }); + const socketFileLocation = temp.path({ suffix: ".sock" }); server.listen(socketFileLocation); if (!this.$options.client) { this.$logger.info("socket-file-location: " + socketFileLocation); @@ -83,7 +83,7 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact // We store the socket that connects us to the device in the upgrade request object itself and later on retrieve it // in the connection callback. - let server = new ws.Server({ + const server = new ws.Server({ port: localPort, verifyClient: async (info: any, callback: Function) => { this.$logger.info("Frontend client connected."); @@ -102,10 +102,10 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact } }); server.on("connection", (webSocket) => { - let encoding = "utf16le"; + const encoding = "utf16le"; - let deviceSocket: net.Socket = (webSocket.upgradeReq)["__deviceSocket"]; - let packets = new PacketStream(); + const deviceSocket: net.Socket = (webSocket.upgradeReq)["__deviceSocket"]; + const packets = new PacketStream(); deviceSocket.pipe(packets); packets.on("data", (buffer: Buffer) => { @@ -121,8 +121,8 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact }); webSocket.on("message", (message, flags) => { - let length = Buffer.byteLength(message, encoding); - let payload = new Buffer(length + 4); + const length = Buffer.byteLength(message, encoding); + const payload = new Buffer(length + 4); payload.writeInt32BE(length, 0); payload.write(message, 4, length, encoding); deviceSocket.write(payload); diff --git a/lib/dynamic-help-provider.ts b/lib/dynamic-help-provider.ts index 84aa255f27..ab8c43d3b0 100644 --- a/lib/dynamic-help-provider.ts +++ b/lib/dynamic-help-provider.ts @@ -6,7 +6,7 @@ export class DynamicHelpProvider implements IDynamicHelpProvider { } public getLocalVariables(options: { isHtml: boolean }): IDictionary { - let localVariables: IDictionary = { + const localVariables: IDictionary = { constants: constants }; return localVariables; diff --git a/lib/node-package-manager.ts b/lib/node-package-manager.ts index f3b5a39ba4..60ca9c9d54 100644 --- a/lib/node-package-manager.ts +++ b/lib/node-package-manager.ts @@ -21,10 +21,10 @@ export class NodePackageManager implements INodePackageManager { config["ignore-scripts"] = true; } - let packageJsonPath = path.join(pathToSave, "package.json"); - let jsonContentBefore = this.$fs.readJson(packageJsonPath); + const packageJsonPath = path.join(pathToSave, "package.json"); + const jsonContentBefore = this.$fs.readJson(packageJsonPath); - let flags = this.getFlagsString(config, true); + const flags = this.getFlagsString(config, true); let params = ["install"]; const isInstallingAllDependencies = packageName === pathToSave; if (!isInstallingAllDependencies) { @@ -32,7 +32,7 @@ export class NodePackageManager implements INodePackageManager { } params = params.concat(flags); - let cwd = pathToSave; + const cwd = pathToSave; // Npm creates `etc` directory in installation dir when --prefix is passed // https://github.com/npm/npm/issues/11486 // we should delete it if it was created because of us @@ -51,7 +51,7 @@ export class NodePackageManager implements INodePackageManager { } try { - let spawnResult: ISpawnResult = await this.getNpmInstallResult(params, cwd); + const spawnResult: ISpawnResult = await this.getNpmInstallResult(params, cwd); // Whenever calling npm install without any arguments (hence installing all dependencies) no output is emitted on stdout // Luckily, whenever you call npm install to install all dependencies chances are you won't need the name/version of the package you're installing because there is none. @@ -64,7 +64,7 @@ export class NodePackageManager implements INodePackageManager { // We cannot use the actual install with --json to get the information because of post-install scripts which may print on stdout // dry-run install is quite fast when the dependencies are already installed even for many dependencies (e.g. angular) so we can live with this approach // We need the --prefix here because without it no output is emitted on stdout because all the dependencies are already installed. - let spawnNpmDryRunResult = await this.$childProcess.spawnFromEvent(this.getNpmExecutableName(), params, "close"); + const spawnNpmDryRunResult = await this.$childProcess.spawnFromEvent(this.getNpmExecutableName(), params, "close"); return this.parseNpmInstallResult(spawnNpmDryRunResult.stdout, spawnResult.stdout, packageName); } catch (err) { if (err.message && err.message.indexOf("EPEERINVALID") !== -1) { @@ -100,7 +100,7 @@ export class NodePackageManager implements INodePackageManager { public async view(packageName: string, config: Object): Promise { const wrappedConfig = _.extend({}, config, { json: true }); // always require view response as JSON - let flags = this.getFlagsString(wrappedConfig, false); + const flags = this.getFlagsString(wrappedConfig, false); let viewResult: any; try { viewResult = await this.$childProcess.exec(`npm view ${packageName} ${flags}`); @@ -121,8 +121,8 @@ export class NodePackageManager implements INodePackageManager { } private getFlagsString(config: any, asArray: boolean): any { - let array: Array = []; - for (let flag in config) { + const array: Array = []; + for (const flag in config) { if (flag === "global") { array.push(`--${flag}`); array.push(`${config[flag]}`); @@ -176,7 +176,7 @@ export class NodePackageManager implements INodePackageManager { this.$logger.trace(`Unable to parse result of npm --dry-run operation. Output is: ${npmDryRunInstallOutput}.`); this.$logger.trace("Now we'll try to parse the real output of npm install command."); - let npmOutputMatchRegExp = /^.--\s+(?!UNMET)(.*)@((?:\d+\.){2}\d+)/m; + const npmOutputMatchRegExp = /^.--\s+(?!UNMET)(.*)@((?:\d+\.){2}\d+)/m; const match = npmInstallOutput.match(npmOutputMatchRegExp); if (match) { return { @@ -192,8 +192,8 @@ export class NodePackageManager implements INodePackageManager { private getDependencyInformation(dependency: string): INpmInstallResultInfo { const scopeDependencyMatch = dependency.match(NodePackageManager.SCOPED_DEPENDENCY_REGEXP); - let name: string = null, - version: string = null; + let name: string = null; + let version: string = null; if (scopeDependencyMatch) { name = scopeDependencyMatch[1]; diff --git a/lib/npm-installation-manager.ts b/lib/npm-installation-manager.ts index 7a4728912c..0911fcecd5 100644 --- a/lib/npm-installation-manager.ts +++ b/lib/npm-installation-manager.ts @@ -41,10 +41,10 @@ export class NpmInstallationManager implements INpmInstallationManager { public async install(packageName: string, projectDir: string, opts?: INpmInstallOptions): Promise { try { - let packageToInstall = this.$options.frameworkPath || packageName; - let pathToSave = projectDir; - let version = (opts && opts.version) || null; - let dependencyType = (opts && opts.dependencyType) || null; + const packageToInstall = this.$options.frameworkPath || packageName; + const pathToSave = projectDir; + const version = (opts && opts.version) || null; + const dependencyType = (opts && opts.dependencyType) || null; return await this.installCore(packageToInstall, pathToSave, version, dependencyType); } catch (error) { @@ -55,7 +55,7 @@ export class NpmInstallationManager implements INpmInstallationManager { } public async getInspectorFromCache(inspectorNpmPackageName: string, projectDir: string): Promise { - let inspectorPath = path.join(projectDir, constants.NODE_MODULES_FOLDER_NAME, inspectorNpmPackageName); + const inspectorPath = path.join(projectDir, constants.NODE_MODULES_FOLDER_NAME, inspectorNpmPackageName); // local installation takes precedence over cache if (!this.inspectorAlreadyInstalled(inspectorPath)) { @@ -107,10 +107,10 @@ export class NpmInstallationManager implements INpmInstallationManager { version = version || await this.getLatestCompatibleVersion(packageName); } - let installResultInfo = await this.npmInstall(packageName, pathToSave, version, dependencyType); - let installedPackageName = installResultInfo.name; + const installResultInfo = await this.npmInstall(packageName, pathToSave, version, dependencyType); + const installedPackageName = installResultInfo.name; - let pathToInstalledPackage = path.join(pathToSave, "node_modules", installedPackageName); + const pathToInstalledPackage = path.join(pathToSave, "node_modules", installedPackageName); return pathToInstalledPackage; } @@ -119,8 +119,8 @@ export class NpmInstallationManager implements INpmInstallationManager { } private isURL(str: string): boolean { - let urlRegex = '^(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$'; - let url = new RegExp(urlRegex, 'i'); + const urlRegex = '^(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$'; + const url = new RegExp(urlRegex, 'i'); return str.length < 2083 && url.test(str); } @@ -129,7 +129,7 @@ export class NpmInstallationManager implements INpmInstallationManager { packageName = packageName + (version ? `@${version}` : ""); - let npmOptions: any = { silent: true, "save-exact": true }; + const npmOptions: any = { silent: true, "save-exact": true }; if (dependencyType) { npmOptions[dependencyType] = true; @@ -143,7 +143,7 @@ export class NpmInstallationManager implements INpmInstallationManager { * because npm view doens't work with those */ private async getVersion(packageName: string, version: string): Promise { - let data: any = await this.$npm.view(packageName, { "dist-tags": true }); + const data: any = await this.$npm.view(packageName, { "dist-tags": true }); this.$logger.trace("Using version %s. ", data[version]); return data[version]; diff --git a/lib/options.ts b/lib/options.ts index a5c960007a..4d73db3e4c 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -2,7 +2,7 @@ import * as commonOptionsLibPath from "./common/options"; import * as osenv from "osenv"; import * as path from "path"; -let OptionType = commonOptionsLibPath.OptionType; +const OptionType = commonOptionsLibPath.OptionType; export class Options extends commonOptionsLibPath.OptionsBase { constructor($errors: IErrors, @@ -48,7 +48,7 @@ export class Options extends commonOptionsLibPath.OptionsBase { // I guess we can remove this code after some grace period, say after 1.7 is out if ($hostInfo.isWindows) { try { - let shelljs = require("shelljs"), + const shelljs = require("shelljs"), oldSettings = path.join(process.env.LocalAppData, ".nativescript-cli", "user-settings.json"), newSettings = path.join(process.env.AppData, ".nativescript-cli", "user-settings.json"); if (shelljs.test("-e", oldSettings) && !shelljs.test("-e", newSettings)) { @@ -60,7 +60,7 @@ export class Options extends commonOptionsLibPath.OptionsBase { } } - let that = (this); + const that = (this); // if justlaunch is set, it takes precedence over the --watch flag and the default true value if (that.justlaunch) { that.watch = false; diff --git a/lib/providers/project-files-provider.ts b/lib/providers/project-files-provider.ts index 6a70fb2592..89917e1da0 100644 --- a/lib/providers/project-files-provider.ts +++ b/lib/providers/project-files-provider.ts @@ -13,24 +13,24 @@ export class ProjectFilesProvider extends ProjectFilesProviderBase { private static INTERNAL_NONPROJECT_FILES = [ "**/*.ts" ]; public mapFilePath(filePath: string, platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): string { - let platformData = this.$platformsData.getPlatformData(platform.toLowerCase(), projectData); - let parsedFilePath = this.getPreparedFilePath(filePath, projectFilesConfig); + const platformData = this.$platformsData.getPlatformData(platform.toLowerCase(), projectData); + const parsedFilePath = this.getPreparedFilePath(filePath, projectFilesConfig); let mappedFilePath = ""; if (parsedFilePath.indexOf(constants.NODE_MODULES_FOLDER_NAME) > -1) { - let relativePath = path.relative(path.join(projectData.projectDir, constants.NODE_MODULES_FOLDER_NAME), parsedFilePath); + const relativePath = path.relative(path.join(projectData.projectDir, constants.NODE_MODULES_FOLDER_NAME), parsedFilePath); mappedFilePath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, constants.TNS_MODULES_FOLDER_NAME, relativePath); } else { mappedFilePath = path.join(platformData.appDestinationDirectoryPath, path.relative(projectData.projectDir, parsedFilePath)); } - let appResourcesDirectoryPath = path.join(constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME); - let platformSpecificAppResourcesDirectoryPath = path.join(appResourcesDirectoryPath, platformData.normalizedPlatformName); + const appResourcesDirectoryPath = path.join(constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME); + const platformSpecificAppResourcesDirectoryPath = path.join(appResourcesDirectoryPath, platformData.normalizedPlatformName); if (parsedFilePath.indexOf(appResourcesDirectoryPath) > -1 && parsedFilePath.indexOf(platformSpecificAppResourcesDirectoryPath) === -1) { return null; } if (parsedFilePath.indexOf(platformSpecificAppResourcesDirectoryPath) > -1) { - let appResourcesRelativePath = path.relative(path.join(projectData.projectDir, constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME, + const appResourcesRelativePath = path.relative(path.join(projectData.projectDir, constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME, platformData.normalizedPlatformName), parsedFilePath); mappedFilePath = path.join(platformData.platformProjectService.getAppResourcesDestinationDirectoryPath(projectData), appResourcesRelativePath); } diff --git a/lib/services/analytics-settings-service.ts b/lib/services/analytics-settings-service.ts index ef21d53a36..2147bbd273 100644 --- a/lib/services/analytics-settings-service.ts +++ b/lib/services/analytics-settings-service.ts @@ -33,7 +33,7 @@ class AnalyticsSettingsService implements IAnalyticsSettingsService { } public async getUserSessionsCount(projectName: string): Promise { - let oldSessionCount = await this.$userSettingsService.getSettingValue(AnalyticsSettingsService.SESSIONS_STARTED_OBSOLETE_KEY); + const oldSessionCount = await this.$userSettingsService.getSettingValue(AnalyticsSettingsService.SESSIONS_STARTED_OBSOLETE_KEY); if (oldSessionCount) { // remove the old property for sessions count diff --git a/lib/services/android-debug-service.ts b/lib/services/android-debug-service.ts index 65b3cd25d7..a161179e93 100644 --- a/lib/services/android-debug-service.ts +++ b/lib/services/android-debug-service.ts @@ -58,13 +58,13 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe private async getForwardedLocalDebugPortForPackageName(deviceId: string, packageName: string): Promise { let port = -1; - let forwardsResult = await this.device.adb.executeCommand(["forward", "--list"]); + const forwardsResult = await this.device.adb.executeCommand(["forward", "--list"]); - let unixSocketName = `${packageName}-inspectorServer`; + const unixSocketName = `${packageName}-inspectorServer`; //matches 123a188909e6czzc tcp:40001 localabstract:org.nativescript.testUnixSockets-debug - let regexp = new RegExp(`(?:${deviceId} tcp:)([\\d]+)(?= localabstract:${unixSocketName})`, "g"); - let match = regexp.exec(forwardsResult); + const regexp = new RegExp(`(?:${deviceId} tcp:)([\\d]+)(?= localabstract:${unixSocketName})`, "g"); + const match = regexp.exec(forwardsResult); if (match) { port = parseInt(match[1]); @@ -112,7 +112,7 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe } private async printDebugPort(deviceId: string, packageName: string): Promise { - let port = await this.getForwardedLocalDebugPortForPackageName(deviceId, packageName); + const port = await this.getForwardedLocalDebugPortForPackageName(deviceId, packageName); this.$logger.info("device: " + deviceId + " debug port: " + port + "\n"); } @@ -145,11 +145,11 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe } private async waitForDebugger(packageName: String): Promise { - let waitText: string = `0 /data/local/tmp/${packageName}-debugger-started`; + const waitText: string = `0 /data/local/tmp/${packageName}-debugger-started`; let maxWait = 12; let debuggerStarted: boolean = false; while (maxWait > 0 && !debuggerStarted) { - let forwardsResult = await this.device.adb.executeShellCommand(["ls", "-s", `/data/local/tmp/${packageName}-debugger-started`]); + const forwardsResult = await this.device.adb.executeShellCommand(["ls", "-s", `/data/local/tmp/${packageName}-debugger-started`]); maxWait--; diff --git a/lib/services/android-project-properties-manager.ts b/lib/services/android-project-properties-manager.ts index 2d1b250779..71a1338b61 100644 --- a/lib/services/android-project-properties-manager.ts +++ b/lib/services/android-project-properties-manager.ts @@ -14,8 +14,8 @@ export class AndroidProjectPropertiesManager implements IAndroidProjectPropertie public async getProjectReferences(): Promise { if (!this.projectReferences || this.dirty) { - let allProjectProperties = await this.getAllProjectProperties(); - let allProjectPropertiesKeys = _.keys(allProjectProperties); + const allProjectProperties = await this.getAllProjectProperties(); + const allProjectPropertiesKeys = _.keys(allProjectProperties); this.projectReferences = _(allProjectPropertiesKeys) .filter(key => _.startsWith(key, "android.library.reference.")) .map(key => this.createLibraryReference(key, allProjectProperties[key])) @@ -26,16 +26,16 @@ export class AndroidProjectPropertiesManager implements IAndroidProjectPropertie } public async addProjectReference(referencePath: string): Promise { - let references = await this.getProjectReferences(); - let libRefExists = _.some(references, r => path.normalize(r.path) === path.normalize(referencePath)); + const references = await this.getProjectReferences(); + const libRefExists = _.some(references, r => path.normalize(r.path) === path.normalize(referencePath)); if (!libRefExists) { await this.addToPropertyList("android.library.reference", referencePath); } } public async removeProjectReference(referencePath: string): Promise { - let references = await this.getProjectReferences(); - let libRefExists = _.some(references, r => path.normalize(r.path) === path.normalize(referencePath)); + const references = await this.getProjectReferences(); + const libRefExists = _.some(references, r => path.normalize(r.path) === path.normalize(referencePath)); if (libRefExists) { await this.removeFromPropertyList("android.library.reference", referencePath); } else { @@ -65,7 +65,7 @@ export class AndroidProjectPropertiesManager implements IAndroidProjectPropertie } private async addToPropertyList(key: string, value: string): Promise { - let editor = await this.createEditor(); + const editor = await this.createEditor(); let i = 1; while (editor.get(this.buildKeyName(key, i))) { i++; @@ -77,8 +77,8 @@ export class AndroidProjectPropertiesManager implements IAndroidProjectPropertie } private async removeFromPropertyList(key: string, value: string): Promise { - let editor = await this.createEditor(); - let valueLowerCase = value.toLowerCase(); + const editor = await this.createEditor(); + const valueLowerCase = value.toLowerCase(); let i = 1; let currentValue: any; while (currentValue = editor.get(this.buildKeyName(key, i))) { diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 4406bf8064..f8ef8c412a 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -49,8 +49,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject if (projectData && projectData.platformsDir && this._platformsDirCache !== projectData.platformsDir) { this._platformsDirCache = projectData.platformsDir; - let projectRoot = path.join(projectData.platformsDir, "android"); - let packageName = this.getProjectNameFromId(projectData); + const projectRoot = path.join(projectData.platformsDir, "android"); + const packageName = this.getProjectNameFromId(projectData); this._platformData = { frameworkPackageName: "tns-android", normalizedPlatformName: "Android", @@ -97,7 +97,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject this.$androidToolsInfo.validateAndroidHomeEnvVariable({ showWarningsAsErrors: true }); - let javaCompilerVersion = await this.$sysInfo.getJavaCompilerVersion(); + const javaCompilerVersion = await this.$sysInfo.getJavaCompilerVersion(); await this.$androidToolsInfo.validateJavacVersion(javaCompilerVersion, { showWarningsAsErrors: true }); @@ -114,13 +114,13 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } this.$fs.ensureDirectoryExists(this.getPlatformData(projectData).projectRoot); - let androidToolsInfo = this.$androidToolsInfo.getToolsInfo(); - let targetSdkVersion = androidToolsInfo && androidToolsInfo.targetSdkVersion; + const androidToolsInfo = this.$androidToolsInfo.getToolsInfo(); + const targetSdkVersion = androidToolsInfo && androidToolsInfo.targetSdkVersion; this.$logger.trace(`Using Android SDK '${targetSdkVersion}'.`); this.copy(this.getPlatformData(projectData).projectRoot, frameworkDir, "libs", "-R"); if (config.pathToTemplate) { - let mainPath = path.join(this.getPlatformData(projectData).projectRoot, "src", "main"); + const mainPath = path.join(this.getPlatformData(projectData).projectRoot, "src", "main"); this.$fs.createDirectory(mainPath); shell.cp("-R", path.join(path.resolve(config.pathToTemplate), "*"), mainPath); } else { @@ -139,7 +139,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject this.cleanResValues(targetSdkVersion, projectData, frameworkVersion); - let npmConfig: INodePackageManagerInstallOptions = { + const npmConfig: INodePackageManagerInstallOptions = { save: true, "save-dev": true, "save-exact": true, @@ -149,20 +149,20 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject ignoreScripts: config.ignoreScripts }; - let projectPackageJson: any = this.$fs.readJson(projectData.projectFilePath); + const projectPackageJson: any = this.$fs.readJson(projectData.projectFilePath); - for (let dependency of AndroidProjectService.REQUIRED_DEV_DEPENDENCIES) { + for (const dependency of AndroidProjectService.REQUIRED_DEV_DEPENDENCIES) { let dependencyVersionInProject = (projectPackageJson.dependencies && projectPackageJson.dependencies[dependency.name]) || (projectPackageJson.devDependencies && projectPackageJson.devDependencies[dependency.name]); if (!dependencyVersionInProject) { await this.$npm.install(`${dependency.name}@${dependency.version}`, projectData.projectDir, npmConfig); } else { - let cleanedVerson = semver.clean(dependencyVersionInProject); + const cleanedVerson = semver.clean(dependencyVersionInProject); // The plugin version is not valid. Check node_modules for the valid version. if (!cleanedVerson) { - let pathToPluginPackageJson = path.join(projectData.projectDir, constants.NODE_MODULES_FOLDER_NAME, dependency.name, constants.PACKAGE_JSON_FILE_NAME); + const pathToPluginPackageJson = path.join(projectData.projectDir, constants.NODE_MODULES_FOLDER_NAME, dependency.name, constants.PACKAGE_JSON_FILE_NAME); dependencyVersionInProject = this.$fs.exists(pathToPluginPackageJson) && this.$fs.readJson(pathToPluginPackageJson).version; } @@ -174,9 +174,9 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } private cleanResValues(targetSdkVersion: number, projectData: IProjectData, frameworkVersion: string): void { - let resDestinationDir = this.getAppResourcesDestinationDirectoryPath(projectData, frameworkVersion); - let directoriesInResFolder = this.$fs.readDirectory(resDestinationDir); - let directoriesToClean = directoriesInResFolder + const resDestinationDir = this.getAppResourcesDestinationDirectoryPath(projectData, frameworkVersion); + const directoriesInResFolder = this.$fs.readDirectory(resDestinationDir); + const directoriesToClean = directoriesInResFolder .map(dir => { return { dirName: dir, @@ -199,15 +199,15 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject // Interpolate the apilevel and package this.interpolateConfigurationFile(projectData, platformSpecificData); - let stringsFilePath = path.join(this.getAppResourcesDestinationDirectoryPath(projectData), 'values', 'strings.xml'); + const stringsFilePath = path.join(this.getAppResourcesDestinationDirectoryPath(projectData), 'values', 'strings.xml'); shell.sed('-i', /__NAME__/, projectData.projectName, stringsFilePath); shell.sed('-i', /__TITLE_ACTIVITY__/, projectData.projectName, stringsFilePath); - let gradleSettingsFilePath = path.join(this.getPlatformData(projectData).projectRoot, "settings.gradle"); + const gradleSettingsFilePath = path.join(this.getPlatformData(projectData).projectRoot, "settings.gradle"); shell.sed('-i', /__PROJECT_NAME__/, this.getProjectNameFromId(projectData), gradleSettingsFilePath); // will replace applicationId in app/App_Resources/Android/app.gradle if it has not been edited by the user - let userAppGradleFilePath = path.join(projectData.appResourcesDirectoryPath, this.$devicePlatformsConstants.Android, "app.gradle"); + const userAppGradleFilePath = path.join(projectData.appResourcesDirectoryPath, this.$devicePlatformsConstants.Android, "app.gradle"); try { shell.sed('-i', /__PACKAGE__/, projectData.projectId, userAppGradleFilePath); @@ -217,7 +217,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } public interpolateConfigurationFile(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): void { - let manifestPath = this.getPlatformData(projectData).configurationFilePath; + const manifestPath = this.getPlatformData(projectData).configurationFilePath; shell.sed('-i', /__PACKAGE__/, projectData.projectId, manifestPath); if (this.$androidToolsInfo.getToolsInfo().androidHomeEnvVar) { const sdk = (platformSpecificData && platformSpecificData.sdk) || (this.$androidToolsInfo.getToolsInfo().compileSdkVersion || "").toString(); @@ -228,7 +228,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject private getProjectNameFromId(projectData: IProjectData): string { let id: string; if (projectData && projectData.projectId) { - let idParts = projectData.projectId.split("."); + const idParts = projectData.projectId.split("."); id = idParts[idParts.length - 1]; } @@ -245,7 +245,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject public async updatePlatform(currentVersion: string, newVersion: string, canUpdate: boolean, projectData: IProjectData, addPlatform?: Function, removePlatforms?: (platforms: string[]) => Promise): Promise { if (semver.eq(newVersion, AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE)) { - let platformLowercase = this.getPlatformData(projectData).normalizedPlatformName.toLowerCase(); + const platformLowercase = this.getPlatformData(projectData).normalizedPlatformName.toLowerCase(); await removePlatforms([platformLowercase.split("@")[0]]); await addPlatform(platformLowercase); return false; @@ -256,7 +256,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject public async buildProject(projectRoot: string, projectData: IProjectData, buildConfig: IBuildConfig): Promise { if (this.canUseGradle(projectData)) { - let buildOptions = this.getBuildOptions(buildConfig, projectData); + const buildOptions = this.getBuildOptions(buildConfig, projectData); if (this.$logger.getLevel() === "TRACE") { buildOptions.unshift("--stacktrace"); buildOptions.unshift("--debug"); @@ -284,13 +284,13 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject private getBuildOptions(settings: IAndroidBuildOptionsSettings, projectData: IProjectData): Array { this.$androidToolsInfo.validateInfo({ showWarningsAsErrors: true, validateTargetSdk: true }); - let androidToolsInfo = this.$androidToolsInfo.getToolsInfo(); - let compileSdk = androidToolsInfo.compileSdkVersion; - let targetSdk = this.getTargetFromAndroidManifest(projectData) || compileSdk; - let buildToolsVersion = androidToolsInfo.buildToolsVersion; - let appCompatVersion = androidToolsInfo.supportRepositoryVersion; - let generateTypings = androidToolsInfo.generateTypings; - let buildOptions = [ + const androidToolsInfo = this.$androidToolsInfo.getToolsInfo(); + const compileSdk = androidToolsInfo.compileSdkVersion; + const targetSdk = this.getTargetFromAndroidManifest(projectData) || compileSdk; + const buildToolsVersion = androidToolsInfo.buildToolsVersion; + const appCompatVersion = androidToolsInfo.supportRepositoryVersion; + const generateTypings = androidToolsInfo.generateTypings; + const buildOptions = [ `-PcompileSdk=android-${compileSdk}`, `-PtargetSdk=${targetSdk}`, `-PbuildToolsVersion=${buildToolsVersion}`, @@ -326,9 +326,9 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } public ensureConfigurationFileInAppResources(projectData: IProjectData): void { - let originalAndroidManifestFilePath = path.join(projectData.appResourcesDirectoryPath, this.$devicePlatformsConstants.Android, this.getPlatformData(projectData).configurationFileName); + const originalAndroidManifestFilePath = path.join(projectData.appResourcesDirectoryPath, this.$devicePlatformsConstants.Android, this.getPlatformData(projectData).configurationFileName); - let manifestExists = this.$fs.exists(originalAndroidManifestFilePath); + const manifestExists = this.$fs.exists(originalAndroidManifestFilePath); if (!manifestExists) { this.$logger.warn('No manifest found in ' + originalAndroidManifestFilePath); @@ -339,16 +339,16 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } public prepareAppResources(appResourcesDirectoryPath: string, projectData: IProjectData): void { - let resourcesDirPath = path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName); - let valuesDirRegExp = /^values/; - let resourcesDirs = this.$fs.readDirectory(resourcesDirPath).filter(resDir => !resDir.match(valuesDirRegExp)); + const resourcesDirPath = path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName); + const valuesDirRegExp = /^values/; + const resourcesDirs = this.$fs.readDirectory(resourcesDirPath).filter(resDir => !resDir.match(valuesDirRegExp)); _.each(resourcesDirs, resourceDir => { this.$fs.deleteDirectory(path.join(this.getAppResourcesDestinationDirectoryPath(projectData), resourceDir)); }); } public async preparePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise { - let pluginPlatformsFolderPath = this.getPluginPlatformsFolderPath(pluginData, AndroidProjectService.ANDROID_PLATFORM_NAME); + const pluginPlatformsFolderPath = this.getPluginPlatformsFolderPath(pluginData, AndroidProjectService.ANDROID_PLATFORM_NAME); await this.processResourcesFromPlugin(pluginData, pluginPlatformsFolderPath, projectData); } @@ -357,30 +357,30 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } private async processResourcesFromPlugin(pluginData: IPluginData, pluginPlatformsFolderPath: string, projectData: IProjectData): Promise { - let configurationsDirectoryPath = path.join(this.getPlatformData(projectData).projectRoot, "configurations"); + const configurationsDirectoryPath = path.join(this.getPlatformData(projectData).projectRoot, "configurations"); this.$fs.ensureDirectoryExists(configurationsDirectoryPath); - let pluginConfigurationDirectoryPath = path.join(configurationsDirectoryPath, pluginData.name); + const pluginConfigurationDirectoryPath = path.join(configurationsDirectoryPath, pluginData.name); if (this.$fs.exists(pluginPlatformsFolderPath)) { this.$fs.ensureDirectoryExists(pluginConfigurationDirectoryPath); - let isScoped = pluginData.name.indexOf("@") === 0; - let flattenedDependencyName = isScoped ? pluginData.name.replace("/", "_") : pluginData.name; + const isScoped = pluginData.name.indexOf("@") === 0; + const flattenedDependencyName = isScoped ? pluginData.name.replace("/", "_") : pluginData.name; // Copy all resources from plugin - let resourcesDestinationDirectoryPath = path.join(this.getPlatformData(projectData).projectRoot, "src", flattenedDependencyName); + const resourcesDestinationDirectoryPath = path.join(this.getPlatformData(projectData).projectRoot, "src", flattenedDependencyName); this.$fs.ensureDirectoryExists(resourcesDestinationDirectoryPath); shell.cp("-Rf", path.join(pluginPlatformsFolderPath, "*"), resourcesDestinationDirectoryPath); const filesForInterpolation = this.$fs.enumerateFilesInDirectorySync(resourcesDestinationDirectoryPath, file => this.$fs.getFsStats(file).isDirectory() || path.extname(file) === constants.XML_FILE_EXTENSION) || []; - for (let file of filesForInterpolation) { + for (const file of filesForInterpolation) { this.$logger.trace(`Interpolate data for plugin file: ${file}`); await this.$pluginVariablesService.interpolate(pluginData, file, projectData); } } // Copy include.gradle file - let includeGradleFilePath = path.join(pluginPlatformsFolderPath, "include.gradle"); + const includeGradleFilePath = path.join(pluginPlatformsFolderPath, "include.gradle"); if (this.$fs.exists(includeGradleFilePath)) { shell.cp("-f", includeGradleFilePath, pluginConfigurationDirectoryPath); } @@ -389,7 +389,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject public async removePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise { try { // check whether the dependency that's being removed has native code - let pluginConfigDir = path.join(this.getPlatformData(projectData).projectRoot, "configurations", pluginData.name); + const pluginConfigDir = path.join(this.getPlatformData(projectData).projectRoot, "configurations", pluginData.name); if (this.$fs.exists(pluginConfigDir)) { await this.cleanProject(this.getPlatformData(projectData).projectRoot, projectData); } @@ -408,11 +408,11 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject public async beforePrepareAllPlugins(projectData: IProjectData, dependencies?: IDependencyData[]): Promise { if (dependencies) { - let platformDir = path.join(projectData.platformsDir, "android"); - let buildDir = path.join(platformDir, "build-tools"); - let checkV8dependants = path.join(buildDir, "check-v8-dependants.js"); + const platformDir = path.join(projectData.platformsDir, "android"); + const buildDir = path.join(platformDir, "build-tools"); + const checkV8dependants = path.join(buildDir, "check-v8-dependants.js"); if (this.$fs.exists(checkV8dependants)) { - let stringifiedDependencies = JSON.stringify(dependencies); + const stringifiedDependencies = JSON.stringify(dependencies); try { await this.spawn('node', [checkV8dependants, stringifiedDependencies, projectData.platformsDir], { stdio: "inherit" }); } catch (e) { @@ -421,7 +421,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } } - let projectRoot = this.getPlatformData(projectData).projectRoot; + const projectRoot = this.getPlatformData(projectData).projectRoot; await this.cleanProject(projectRoot, projectData); } @@ -439,8 +439,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } public async cleanDeviceTempFolder(deviceIdentifier: string, projectData: IProjectData): Promise { - let adb = this.$injector.resolve(DeviceAndroidDebugBridge, { identifier: deviceIdentifier }); - let deviceRootPath = `/data/local/tmp/${projectData.projectId}`; + const adb = this.$injector.resolve(DeviceAndroidDebugBridge, { identifier: deviceIdentifier }); + const deviceRootPath = `/data/local/tmp/${projectData.projectId}`; await adb.executeShellCommand(["rm", "-rf", deviceRootPath]); } @@ -463,7 +463,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } private copy(projectRoot: string, frameworkDir: string, files: string, cpArg: string): void { - let paths = files.split(' ').map(p => path.join(frameworkDir, p)); + const paths = files.split(' ').map(p => path.join(frameworkDir, p)); shell.cp(cpArg, paths, projectRoot); } @@ -498,9 +498,9 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject private getTargetFromAndroidManifest(projectData: IProjectData): string { let versionInManifest: string; if (this.$fs.exists(this.getPlatformData(projectData).configurationFilePath)) { - let targetFromAndroidManifest: string = this.$fs.readText(this.getPlatformData(projectData).configurationFilePath); + const targetFromAndroidManifest: string = this.$fs.readText(this.getPlatformData(projectData).configurationFilePath); if (targetFromAndroidManifest) { - let match = targetFromAndroidManifest.match(/.*?android:targetSdkVersion=\"(.*?)\"/); + const match = targetFromAndroidManifest.match(/.*?android:targetSdkVersion=\"(.*?)\"/); if (match && match[1]) { versionInManifest = match[1]; } diff --git a/lib/services/app-files-updater.ts b/lib/services/app-files-updater.ts index dde5831ce4..f7de291a29 100644 --- a/lib/services/app-files-updater.ts +++ b/lib/services/app-files-updater.ts @@ -50,7 +50,7 @@ export class AppFilesUpdater { } protected readSourceDir(): string[] { - let tnsDir = path.join(this.appSourceDirectoryPath, constants.TNS_MODULES_FOLDER_NAME); + const tnsDir = path.join(this.appSourceDirectoryPath, constants.TNS_MODULES_FOLDER_NAME); return this.fs.enumerateFilesInDirectorySync(this.appSourceDirectoryPath, null, { includeEmptyDirectories: true }).filter(dirName => dirName !== tnsDir); } @@ -59,7 +59,7 @@ export class AppFilesUpdater { let sourceFiles = this.readSourceDir(); if (this.options.release) { - let testsFolderPath = path.join(this.appSourceDirectoryPath, 'tests'); + const testsFolderPath = path.join(this.appSourceDirectoryPath, 'tests'); sourceFiles = sourceFiles.filter(source => source.indexOf(testsFolderPath) === -1); } @@ -76,7 +76,7 @@ export class AppFilesUpdater { protected copyAppSourceFiles(sourceFiles: string[]): void { sourceFiles.map(source => { - let destinationPath = path.join(this.appDestinationDirectoryPath, path.relative(this.appSourceDirectoryPath, source)); + const destinationPath = path.join(this.appDestinationDirectoryPath, path.relative(this.appSourceDirectoryPath, source)); let exists = fs.lstatSync(source); if (exists.isSymbolicLink()) { diff --git a/lib/services/cocoapods-service.ts b/lib/services/cocoapods-service.ts index c07d49bf1a..e9f8dfc117 100644 --- a/lib/services/cocoapods-service.ts +++ b/lib/services/cocoapods-service.ts @@ -21,18 +21,18 @@ export class CocoaPodsService implements ICocoaPodsService { throw new Error(`The Podfile ${pathToPodfile} does not exist.`); } - let podfileContent = this.$fs.readText(pathToPodfile); - let hookStart = `${hookName} do`; + const podfileContent = this.$fs.readText(pathToPodfile); + const hookStart = `${hookName} do`; - let hookDefinitionRegExp = new RegExp(`${hookStart} *(\\|(\\w+)\\|)?`, "g"); + const hookDefinitionRegExp = new RegExp(`${hookStart} *(\\|(\\w+)\\|)?`, "g"); let newFunctionNameIndex = 1; - let newFunctions: IRubyFunction[] = []; + const newFunctions: IRubyFunction[] = []; - let replacedContent = podfileContent.replace(hookDefinitionRegExp, (substring: string, firstGroup: string, secondGroup: string, index: number): string => { - let newFunctionName = `${hookName}${newFunctionNameIndex++}`; + const replacedContent = podfileContent.replace(hookDefinitionRegExp, (substring: string, firstGroup: string, secondGroup: string, index: number): string => { + const newFunctionName = `${hookName}${newFunctionNameIndex++}`; let newDefinition = `def ${newFunctionName}`; - let rubyFunction: IRubyFunction = { functionName: newFunctionName }; + const rubyFunction: IRubyFunction = { functionName: newFunctionName }; // firstGroup is the block parameter, secondGroup is the block parameter name. if (firstGroup && secondGroup) { newDefinition = `${newDefinition} (${secondGroup})`; @@ -45,7 +45,7 @@ export class CocoaPodsService implements ICocoaPodsService { if (newFunctions.length > 1) { // Execute all methods in the hook and pass the parameter to them. - let blokParameterName = "installer"; + const blokParameterName = "installer"; let mergedHookContent = `${hookStart} |${blokParameterName}|${EOL}`; _.each(newFunctions, (rubyFunction: IRubyFunction) => { @@ -59,7 +59,7 @@ export class CocoaPodsService implements ICocoaPodsService { mergedHookContent = `${mergedHookContent}end`; - let newPodfileContent = `${replacedContent}${EOL}${mergedHookContent}`; + const newPodfileContent = `${replacedContent}${EOL}${mergedHookContent}`; this.$fs.writeFile(pathToPodfile, newPodfileContent); } } diff --git a/lib/services/doctor-service.ts b/lib/services/doctor-service.ts index 5b8df741a6..b2ceb4eba6 100644 --- a/lib/services/doctor-service.ts +++ b/lib/services/doctor-service.ts @@ -2,7 +2,7 @@ import { EOL } from "os"; import * as semver from "semver"; import * as path from "path"; import * as helpers from "../common/helpers"; -let clui = require("clui"); +const clui = require("clui"); class DoctorService implements IDoctorService { private static PROJECT_NAME_PLACEHOLDER = "__PROJECT_NAME__"; @@ -33,7 +33,7 @@ class DoctorService implements IDoctorService { public async printWarnings(configOptions?: { trackResult: boolean }): Promise { let result = false; - let sysInfo = await this.$sysInfo.getSysInfo(this.$staticConfig.pathToPackageJson); + const sysInfo = await this.$sysInfo.getSysInfo(this.$staticConfig.pathToPackageJson); if (!sysInfo.adbVer) { this.$logger.warn("WARNING: adb from the Android SDK is not installed or is not configured properly."); @@ -80,7 +80,7 @@ class DoctorService implements IDoctorService { } if (sysInfo.xcodeVer && sysInfo.cocoapodVer) { - let problemWithCocoaPods = await this.verifyCocoaPods(); + const problemWithCocoaPods = await this.verifyCocoaPods(); if (problemWithCocoaPods) { this.$logger.warn("WARNING: There was a problem with CocoaPods"); this.$logger.out("Verify that CocoaPods are configured properly."); @@ -103,10 +103,10 @@ class DoctorService implements IDoctorService { this.$logger.out("To be able to work with iOS devices and projects, you need Mac OS X Mavericks or later." + EOL); } - let androidToolsIssues = this.$androidToolsInfo.validateInfo(); - let javaVersionIssue = await this.$androidToolsInfo.validateJavacVersion(sysInfo.javacVersion); - let pythonIssues = await this.validatePythonPackages(); - let doctorResult = result || androidToolsIssues || javaVersionIssue || pythonIssues; + const androidToolsIssues = this.$androidToolsInfo.validateInfo(); + const javaVersionIssue = await this.$androidToolsInfo.validateJavacVersion(sysInfo.javacVersion); + const pythonIssues = await this.validatePythonPackages(); + const doctorResult = result || androidToolsIssues || javaVersionIssue || pythonIssues; if (!configOptions || configOptions.trackResult) { await this.$analyticsService.track("DoctorEnvironmentSetup", doctorResult ? "incorrect" : "correct"); @@ -157,16 +157,16 @@ class DoctorService implements IDoctorService { private async verifyCocoaPods(): Promise { this.$logger.out("Verifying CocoaPods. This may take more than a minute, please be patient."); - let temp = require("temp"); + const temp = require("temp"); temp.track(); - let projDir = temp.mkdirSync("nativescript-check-cocoapods"); - let packageJsonData = { + const projDir = temp.mkdirSync("nativescript-check-cocoapods"); + const packageJsonData = { "name": "nativescript-check-cocoapods", "version": "0.0.1" }; this.$fs.writeJson(path.join(projDir, "package.json"), packageJsonData); - let spinner = new clui.Spinner("Installing iOS runtime."); + const spinner = new clui.Spinner("Installing iOS runtime."); try { spinner.start(); await this.$npm.install("tns-ios", projDir, { @@ -178,7 +178,7 @@ class DoctorService implements IDoctorService { ignoreScripts: true }); spinner.stop(); - let iosDir = path.join(projDir, "node_modules", "tns-ios", "framework"); + const iosDir = path.join(projDir, "node_modules", "tns-ios", "framework"); this.$fs.writeFile( path.join(iosDir, "Podfile"), `${this.$cocoapodsService.getPodfileHeader(DoctorService.PROJECT_NAME_PLACEHOLDER)}pod 'AFNetworking', '~> 1.0'${this.$cocoapodsService.getPodfileFooter()}` @@ -186,7 +186,7 @@ class DoctorService implements IDoctorService { spinner.message("Verifying CocoaPods. This may take some time, please be patient."); spinner.start(); - let future = this.$childProcess.spawnFromEvent( + const future = this.$childProcess.spawnFromEvent( this.$config.USE_POD_SANDBOX ? "sandbox-pod" : "pod", ["install"], "exit", @@ -194,7 +194,7 @@ class DoctorService implements IDoctorService { { throwError: false } ); - let result = await this.$progressIndicator.showProgressIndicator(future, 5000); + const result = await this.$progressIndicator.showProgressIndicator(future, 5000); if (result.exitCode) { this.$logger.out(result.stdout, result.stderr); return true; diff --git a/lib/services/emulator-platform-service.ts b/lib/services/emulator-platform-service.ts index ead601fa6d..b2a9af9e83 100644 --- a/lib/services/emulator-platform-service.ts +++ b/lib/services/emulator-platform-service.ts @@ -16,9 +16,9 @@ export class EmulatorPlatformService implements IEmulatorPlatformService { if (this.$mobileHelper.isAndroidPlatform(info.platform)) { this.$options.avd = this.$options.device; this.$options.device = null; - let platformsData: IPlatformsData = $injector.resolve("platformsData"); - let platformData = platformsData.getPlatformData(info.platform, projectData); - let emulatorServices = platformData.emulatorServices; + const platformsData: IPlatformsData = $injector.resolve("platformsData"); + const platformData = platformsData.getPlatformData(info.platform, projectData); + const emulatorServices = platformData.emulatorServices; emulatorServices.checkAvailability(); await emulatorServices.checkDependencies(); await emulatorServices.startEmulator(); @@ -28,13 +28,13 @@ export class EmulatorPlatformService implements IEmulatorPlatformService { if (this.$mobileHelper.isiOSPlatform(info.platform)) { await this.stopEmulator(info.platform); - let deferred = deferPromise(); + const deferred = deferPromise(); await this.$childProcess.exec(`open -a Simulator --args -CurrentDeviceUDID ${info.id}`); - let timeoutFunc = async () => { + const timeoutFunc = async () => { info = await this.getEmulatorInfo("ios", info.id); if (info.isRunning) { await this.$devicesService.initialize({ platform: info.platform, deviceId: info.id }); - let device = this.$devicesService.getDeviceByIdentifier(info.id); + const device = this.$devicesService.getDeviceByIdentifier(info.id); await device.applicationManager.checkForApplicationUpdates(); deferred.resolve(); return; @@ -56,15 +56,15 @@ export class EmulatorPlatformService implements IEmulatorPlatformService { public async getEmulatorInfo(platform: string, idOrName: string): Promise { if (this.$mobileHelper.isAndroidPlatform(platform)) { - let androidEmulators = this.getAndroidEmulators(); - let found = androidEmulators.filter((info: IEmulatorInfo) => info.id === idOrName); + const androidEmulators = this.getAndroidEmulators(); + const found = androidEmulators.filter((info: IEmulatorInfo) => info.id === idOrName); if (found.length > 0) { return found[0]; } await this.$devicesService.initialize({ platform: platform, deviceId: null, skipInferPlatform: true }); let info: IEmulatorInfo = null; - let action = async (device: Mobile.IDevice) => { + const action = async (device: Mobile.IDevice) => { if (device.deviceInfo.identifier === idOrName) { info = { id: device.deviceInfo.identifier, @@ -81,15 +81,15 @@ export class EmulatorPlatformService implements IEmulatorPlatformService { } if (this.$mobileHelper.isiOSPlatform(platform)) { - let emulators = await this.getiOSEmulators(); + const emulators = await this.getiOSEmulators(); let sdk: string = null; - let versionStart = idOrName.indexOf("("); + const versionStart = idOrName.indexOf("("); if (versionStart > 0) { sdk = idOrName.substring(versionStart + 1, idOrName.indexOf(")", versionStart)).trim(); idOrName = idOrName.substring(0, versionStart - 1).trim(); } - let found = emulators.filter((info: IEmulatorInfo) => { - let sdkMatch = sdk ? info.version === sdk : true; + const found = emulators.filter((info: IEmulatorInfo) => { + const sdkMatch = sdk ? info.version === sdk : true; return sdkMatch && info.id === idOrName || info.name === idOrName; }); return found.length > 0 ? found[0] : null; @@ -102,14 +102,14 @@ export class EmulatorPlatformService implements IEmulatorPlatformService { public async listAvailableEmulators(platform: string): Promise { let emulators: IEmulatorInfo[] = []; if (!platform || this.$mobileHelper.isiOSPlatform(platform)) { - let iosEmulators = await this.getiOSEmulators(); + const iosEmulators = await this.getiOSEmulators(); if (iosEmulators) { emulators = emulators.concat(iosEmulators); } } if (!platform || this.$mobileHelper.isAndroidPlatform(platform)) { - let androidEmulators = this.getAndroidEmulators(); + const androidEmulators = this.getAndroidEmulators(); if (androidEmulators) { emulators = emulators.concat(androidEmulators); } @@ -119,20 +119,20 @@ export class EmulatorPlatformService implements IEmulatorPlatformService { } public async getiOSEmulators(): Promise { - let output = await this.$childProcess.exec("xcrun simctl list --json"); - let list = JSON.parse(output); - let emulators: IEmulatorInfo[] = []; - for (let osName in list["devices"]) { + const output = await this.$childProcess.exec("xcrun simctl list --json"); + const list = JSON.parse(output); + const emulators: IEmulatorInfo[] = []; + for (const osName in list["devices"]) { if (osName.indexOf("iOS") === -1) { continue; } - let os = list["devices"][osName]; - let version = this.parseiOSVersion(osName); - for (let device of os) { + const os = list["devices"][osName]; + const version = this.parseiOSVersion(osName); + for (const device of os) { if (device["availability"] !== "(available)") { continue; } - let emulatorInfo: IEmulatorInfo = { + const emulatorInfo: IEmulatorInfo = { id: device["udid"], name: device["name"], isRunning: device["state"] === "Booted", @@ -167,8 +167,8 @@ export class EmulatorPlatformService implements IEmulatorPlatformService { private outputEmulators(title: string, emulators: IEmulatorInfo[]) { this.$logger.out(title); - let table: any = createTable(["Device Name", "Platform", "Version", "Device Identifier"], []); - for (let info of emulators) { + const table: any = createTable(["Device Name", "Platform", "Version", "Device Identifier"], []); + for (const info of emulators) { table.push([info.name, info.platform, info.version, info.id]); } diff --git a/lib/services/emulator-settings-service.ts b/lib/services/emulator-settings-service.ts index 806d0139bb..d9bd1b6f26 100644 --- a/lib/services/emulator-settings-service.ts +++ b/lib/services/emulator-settings-service.ts @@ -4,9 +4,9 @@ export class EmulatorSettingsService implements Mobile.IEmulatorSettingsService constructor(private $injector: IInjector) { } public canStart(platform: string): boolean { - let platformService = this.$injector.resolve("platformService"); // this should be resolved here due to cyclic dependency + const platformService = this.$injector.resolve("platformService"); // this should be resolved here due to cyclic dependency - let installedPlatforms = platformService.getInstalledPlatforms(); + const installedPlatforms = platformService.getInstalledPlatforms(); return _.includes(installedPlatforms, platform.toLowerCase()); } diff --git a/lib/services/info-service.ts b/lib/services/info-service.ts index 40eb96a1b3..e7cb63c8ac 100644 --- a/lib/services/info-service.ts +++ b/lib/services/info-service.ts @@ -3,9 +3,9 @@ export class InfoService implements IInfoService { private $logger: ILogger) { } public async printComponentsInfo(): Promise { - let allComponentsInfo = await this.$versionsService.getAllComponentsVersions(); + const allComponentsInfo = await this.$versionsService.getAllComponentsVersions(); - let table: any = this.$versionsService.createTableWithVersionsInformation(allComponentsInfo); + const table: any = this.$versionsService.createTableWithVersionsInformation(allComponentsInfo); this.$logger.out("All NativeScript components versions information"); this.$logger.out(table.toString()); diff --git a/lib/services/init-service.ts b/lib/services/init-service.ts index 84358a67e9..4017d29863 100644 --- a/lib/services/init-service.ts +++ b/lib/services/init-service.ts @@ -31,7 +31,7 @@ export class InitService implements IInitService { projectData = this.$fs.readJson(this.projectFilePath); } - let projectDataBackup = _.extend({}, projectData); + const projectDataBackup = _.extend({}, projectData); if (!projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE]) { projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE] = {}; @@ -42,30 +42,30 @@ export class InitService implements IInitService { projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE]["id"] = await this.getProjectId(); if (this.$options.frameworkName && this.$options.frameworkVersion) { - let currentPlatformData = projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][this.$options.frameworkName] || {}; + const currentPlatformData = projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][this.$options.frameworkName] || {}; projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][this.$options.frameworkName] = _.extend(currentPlatformData, this.buildVersionData(this.$options.frameworkVersion)); } else { - let $platformsData = this.$injector.resolve("platformsData"); - let $projectData = this.$injector.resolve("projectData"); + const $platformsData = this.$injector.resolve("platformsData"); + const $projectData = this.$injector.resolve("projectData"); $projectData.initializeProjectData(path.dirname(this.projectFilePath)); - for (let platform of $platformsData.platformsNames) { - let platformData: IPlatformData = $platformsData.getPlatformData(platform, $projectData); + for (const platform of $platformsData.platformsNames) { + const platformData: IPlatformData = $platformsData.getPlatformData(platform, $projectData); if (!platformData.targetedOS || (platformData.targetedOS && _.includes(platformData.targetedOS, process.platform))) { - let currentPlatformData = projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][platformData.frameworkPackageName] || {}; + const currentPlatformData = projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][platformData.frameworkPackageName] || {}; projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][platformData.frameworkPackageName] = _.extend(currentPlatformData, await this.getVersionData(platformData.frameworkPackageName)); } } } - let dependencies = projectData.dependencies; + const dependencies = projectData.dependencies; if (!dependencies) { projectData.dependencies = Object.create(null); } // In case console is interactive and --force is not specified, do not read the version from package.json, show all available versions to the user. - let tnsCoreModulesVersionInPackageJson = this.useDefaultValue ? projectData.dependencies[constants.TNS_CORE_MODULES_NAME] : null; + const tnsCoreModulesVersionInPackageJson = this.useDefaultValue ? projectData.dependencies[constants.TNS_CORE_MODULES_NAME] : null; projectData.dependencies[constants.TNS_CORE_MODULES_NAME] = tnsCoreModulesVersionInPackageJson || (await this.getVersionData(constants.TNS_CORE_MODULES_NAME))["version"]; this.$fs.writeJson(this.projectFilePath, projectData); @@ -79,7 +79,7 @@ export class InitService implements IInitService { private get projectFilePath(): string { if (!this._projectFilePath) { - let projectDir = path.resolve(this.$options.path || "."); + const projectDir = path.resolve(this.$options.path || "."); this._projectFilePath = path.join(projectDir, constants.PACKAGE_JSON_FILE_NAME); } @@ -91,7 +91,7 @@ export class InitService implements IInitService { return this.$options.appid; } - let defaultAppId = this.$projectHelper.generateDefaultAppId(path.basename(path.dirname(this.projectFilePath)), constants.DEFAULT_APP_IDENTIFIER_PREFIX); + const defaultAppId = this.$projectHelper.generateDefaultAppId(path.basename(path.dirname(this.projectFilePath)), constants.DEFAULT_APP_IDENTIFIER_PREFIX); if (this.useDefaultValue) { return defaultAppId; } @@ -100,26 +100,26 @@ export class InitService implements IInitService { } private async getVersionData(packageName: string): Promise { - let latestVersion = await this.$npmInstallationManager.getLatestCompatibleVersion(packageName); + const latestVersion = await this.$npmInstallationManager.getLatestCompatibleVersion(packageName); if (this.useDefaultValue) { return this.buildVersionData(latestVersion); } - let allVersions: any = await this.$npm.view(packageName, { "versions": true }); - let versions = _.filter(allVersions, (version: string) => semver.gte(version, InitService.MIN_SUPPORTED_FRAMEWORK_VERSIONS[packageName])); + const allVersions: any = await this.$npm.view(packageName, { "versions": true }); + const versions = _.filter(allVersions, (version: string) => semver.gte(version, InitService.MIN_SUPPORTED_FRAMEWORK_VERSIONS[packageName])); if (versions.length === 1) { this.$logger.info(`Only ${versions[0]} version is available for ${packageName}.`); return this.buildVersionData(versions[0]); } - let sortedVersions = versions.sort(helpers.versionCompare).reverse(); + const sortedVersions = versions.sort(helpers.versionCompare).reverse(); //TODO: plamen5kov: don't offer versions from next (they are not available) - let version = await this.$prompter.promptForChoice(`${packageName} version:`, sortedVersions); + const version = await this.$prompter.promptForChoice(`${packageName} version:`, sortedVersions); return this.buildVersionData(version); } private buildVersionData(version: string): IStringDictionary { - let result: IStringDictionary = {}; + const result: IStringDictionary = {}; result[InitService.VERSION_KEY_NAME] = version; diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index 03fa2f651a..a7adaf468e 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -113,8 +113,8 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } private async emulatorDebugBrk(debugData: IDebugData, debugOptions: IDebugOptions): Promise { - let args = debugOptions.debugBrk ? "--nativescript-debug-brk" : "--nativescript-debug-start"; - let child_process = await this.$iOSEmulatorServices.runApplicationOnEmulator(debugData.pathToAppPackage, { + const args = debugOptions.debugBrk ? "--nativescript-debug-brk" : "--nativescript-debug-start"; + const child_process = await this.$iOSEmulatorServices.runApplicationOnEmulator(debugData.pathToAppPackage, { waitForDebugger: true, captureStdin: true, args: args, @@ -122,11 +122,11 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS skipInstall: true }); - let lineStream = byline(child_process.stdout); + const lineStream = byline(child_process.stdout); this._childProcess = child_process; lineStream.on('data', (line: NodeBuffer) => { - let lineText = line.toString(); + const lineText = line.toString(); if (lineText && _.startsWith(lineText, debugData.applicationIdentifier)) { const pid = getPidFromiOSSimulatorLogs(debugData.applicationIdentifier, lineText); if (!pid) { @@ -151,9 +151,9 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS private async emulatorStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise { const result = await this.wireDebuggerClient(debugData, debugOptions); - let attachRequestMessage = this.$iOSNotification.getAttachRequest(debugData.applicationIdentifier); + const attachRequestMessage = this.$iOSNotification.getAttachRequest(debugData.applicationIdentifier); - let iOSEmulator = this.$iOSEmulatorServices; + const iOSEmulator = this.$iOSEmulatorServices; await iOSEmulator.postDarwinNotification(attachRequestMessage); return result; } @@ -171,7 +171,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS justlaunch: debugOptions.justlaunch }; // we intentionally do not wait on this here, because if we did, we'd miss the AppLaunching notification - let startApplicationAction = this.$platformService.startApplication(this.platform, runOptions, debugData.applicationIdentifier); + const startApplicationAction = this.$platformService.startApplication(this.platform, runOptions, debugData.applicationIdentifier); const result = await this.debugBrkCore(device, debugData, debugOptions); @@ -215,12 +215,12 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS private async openAppInspector(fileDescriptor: string, debugData: IDebugData, debugOptions: IDebugOptions): Promise { if (debugOptions.client) { - let inspectorPath = await this.$npmInstallationManager.getInspectorFromCache(inspectorNpmPackageName, debugData.projectDir); + const inspectorPath = await this.$npmInstallationManager.getInspectorFromCache(inspectorNpmPackageName, debugData.projectDir); - let inspectorSourceLocation = path.join(inspectorPath, inspectorUiDir, "Main.html"); - let inspectorApplicationPath = path.join(inspectorPath, inspectorAppName); + const inspectorSourceLocation = path.join(inspectorPath, inspectorUiDir, "Main.html"); + const inspectorApplicationPath = path.join(inspectorPath, inspectorAppName); - let cmd = `open -a '${inspectorApplicationPath}' --args '${inspectorSourceLocation}' '${debugData.projectName}' '${fileDescriptor}'`; + const cmd = `open -a '${inspectorApplicationPath}' --args '${inspectorSourceLocation}' '${debugData.projectName}' '${fileDescriptor}'`; await this.$childProcess.exec(cmd); } else { this.$logger.info("Suppressing debugging client."); diff --git a/lib/services/ios-entitlements-service.ts b/lib/services/ios-entitlements-service.ts index 74662bea78..0f749e31cf 100644 --- a/lib/services/ios-entitlements-service.ts +++ b/lib/services/ios-entitlements-service.ts @@ -30,10 +30,10 @@ export class IOSEntitlementsService { } public async merge(projectData: IProjectData): Promise { - let session = new PlistSession({ log: (txt: string) => this.$logger.trace("App.entitlements: " + txt) }); + const session = new PlistSession({ log: (txt: string) => this.$logger.trace("App.entitlements: " + txt) }); - let projectDir = projectData.projectDir; - let makePatch = (plistPath: string) => { + const projectDir = projectData.projectDir; + const makePatch = (plistPath: string) => { if (!this.$fs.exists(plistPath)) { this.$logger.trace("No plist found at: " + plistPath); return; @@ -46,19 +46,19 @@ export class IOSEntitlementsService { }); }; - let allPlugins = await this.getAllInstalledPlugins(projectData); - for (let plugin of allPlugins) { - let pluginInfoPlistPath = path.join(plugin.pluginPlatformsFolderPath(this.$devicePlatformsConstants.iOS), + const allPlugins = await this.getAllInstalledPlugins(projectData); + for (const plugin of allPlugins) { + const pluginInfoPlistPath = path.join(plugin.pluginPlatformsFolderPath(this.$devicePlatformsConstants.iOS), IOSEntitlementsService.DefaultEntitlementsName); makePatch(pluginInfoPlistPath); } - let appEntitlementsPath = this.getDefaultAppEntitlementsPath(projectData); + const appEntitlementsPath = this.getDefaultAppEntitlementsPath(projectData); if (this.$fs.exists(appEntitlementsPath)) { makePatch(appEntitlementsPath); } - let plistContent = session.build(); + const plistContent = session.build(); this.$logger.trace("App.entitlements: Write to: " + this.getPlatformsEntitlementsPath(projectData)); this.$fs.writeFile(this.getPlatformsEntitlementsPath(projectData), plistContent); return; diff --git a/lib/services/ios-log-filter.ts b/lib/services/ios-log-filter.ts index 713122e1ea..fde1997d3a 100644 --- a/lib/services/ios-log-filter.ts +++ b/lib/services/ios-log-filter.ts @@ -1,4 +1,4 @@ -let sourcemap = require("source-map"); +const sourcemap = require("source-map"); import * as path from "path"; import { cache } from "../common/decorators"; import * as iOSLogFilterBase from "../common/mobile/ios/ios-log-filter"; @@ -21,8 +21,8 @@ export class IOSLogFilter extends iOSLogFilterBase.IOSLogFilter implements Mobil } if (data) { - let skipLastLine = data[data.length - 1] !== "\n"; - let lines = data.split("\n"); + const skipLastLine = data[data.length - 1] !== "\n"; + const lines = data.split("\n"); let result = ""; for (let i = 0; i < lines.length; i++) { let line = lines[i]; @@ -41,8 +41,8 @@ export class IOSLogFilter extends iOSLogFilterBase.IOSLogFilter implements Mobil // This code removes unnecessary information from log messages. The output looks like: // CONSOLE LOG file:///location:row:column: if (pid) { - let searchString = "[" + pid + "]: "; - let pidIndex = line.indexOf(searchString); + const searchString = "[" + pid + "]: "; + const pidIndex = line.indexOf(searchString); if (pidIndex > 0) { line = line.substring(pidIndex + searchString.length, line.length); this.getOriginalFileLocation(line); @@ -68,17 +68,17 @@ export class IOSLogFilter extends iOSLogFilterBase.IOSLogFilter implements Mobil const projectDir = this.getProjectDir(); if (fileIndex >= 0 && projectDir) { - let parts = data.substring(fileIndex + fileString.length).split(":"); + const parts = data.substring(fileIndex + fileString.length).split(":"); if (parts.length >= 4) { - let file = parts[0]; - let sourceMapFile = path.join(projectDir, file + ".map"); - let row = parseInt(parts[1]); - let column = parseInt(parts[2]); + const file = parts[0]; + const sourceMapFile = path.join(projectDir, file + ".map"); + const row = parseInt(parts[1]); + const column = parseInt(parts[2]); if (this.$fs.exists(sourceMapFile)) { - let sourceMap = this.$fs.readText(sourceMapFile); - let smc = new sourcemap.SourceMapConsumer(sourceMap); - let originalPosition = smc.originalPositionFor({ line: row, column: column }); - let sourceFile = smc.sources.length > 0 ? file.replace(smc.file, smc.sources[0]) : file; + const sourceMap = this.$fs.readText(sourceMapFile); + const smc = new sourcemap.SourceMapConsumer(sourceMap); + const originalPosition = smc.originalPositionFor({ line: row, column: column }); + const sourceFile = smc.sources.length > 0 ? file.replace(smc.file, smc.sources[0]) : file; data = data.substring(0, fileIndex + fileString.length) + sourceFile + ":" + originalPosition.line + ":" diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 65c2fe47d3..3ecfcd4162 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -61,7 +61,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } if (projectData && projectData.platformsDir && this._platformsDirCache !== projectData.platformsDir) { - let projectRoot = path.join(projectData.platformsDir, this.$devicePlatformsConstants.iOS.toLowerCase()); + const projectRoot = path.join(projectData.platformsDir, this.$devicePlatformsConstants.iOS.toLowerCase()); this._platformData = { frameworkPackageName: "tns-ios", @@ -114,7 +114,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } public getAppResourcesDestinationDirectoryPath(projectData: IProjectData): string { - let frameworkVersion = this.getFrameworkVersion(this.getPlatformData(projectData).frameworkPackageName, projectData.projectDir); + const frameworkVersion = this.getFrameworkVersion(this.getPlatformData(projectData).frameworkPackageName, projectData.projectDir); if (semver.lt(frameworkVersion, "1.3.0")) { return path.join(this.getPlatformData(projectData).projectRoot, projectData.projectName, "Resources", "icons"); @@ -134,7 +134,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ this.$errors.fail("Xcode is not installed. Make sure you have Xcode installed and added to your PATH"); } - let xcodeBuildVersion = await this.getXcodeVersion(); + const xcodeBuildVersion = await this.getXcodeVersion(); if (helpers.versionCompare(xcodeBuildVersion, IOSProjectService.XCODEBUILD_MIN_VERSION) < 0) { this.$errors.fail("NativeScript can only run in Xcode version %s or greater", IOSProjectService.XCODEBUILD_MIN_VERSION); } @@ -157,7 +157,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ //TODO: plamen5kov: revisit this method, might have unnecessary/obsolete logic public async interpolateData(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise { - let projectRootFilePath = path.join(this.getPlatformData(projectData).projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER); + const projectRootFilePath = path.join(this.getPlatformData(projectData).projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER); // Starting with NativeScript for iOS 1.6.0, the project Info.plist file resides not in the platform project, // but in the hello-world app template as a platform specific resource. if (this.$fs.exists(path.join(projectRootFilePath, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + "-Info.plist"))) { @@ -165,8 +165,8 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } this.replaceFileName("-Prefix.pch", projectRootFilePath, projectData); - let xcschemeDirPath = path.join(this.getPlatformData(projectData).projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + IOSProjectService.XCODE_PROJECT_EXT_NAME, "xcshareddata/xcschemes"); - let xcschemeFilePath = path.join(xcschemeDirPath, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + IOSProjectService.XCODE_SCHEME_EXT_NAME); + const xcschemeDirPath = path.join(this.getPlatformData(projectData).projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + IOSProjectService.XCODE_PROJECT_EXT_NAME, "xcshareddata/xcschemes"); + const xcschemeFilePath = path.join(xcschemeDirPath, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + IOSProjectService.XCODE_SCHEME_EXT_NAME); if (this.$fs.exists(xcschemeFilePath)) { this.$logger.debug("Found shared scheme at xcschemeFilePath, renaming to match project name."); @@ -181,7 +181,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ this.replaceFileName(IOSProjectService.XCODE_PROJECT_EXT_NAME, this.getPlatformData(projectData).projectRoot, projectData); - let pbxprojFilePath = this.getPbxProjPath(projectData); + const pbxprojFilePath = this.getPbxProjPath(projectData); this.replaceFileContent(pbxprojFilePath, projectData); } @@ -199,9 +199,9 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ * Returns the path to the .xcarchive. */ public async archive(projectData: IProjectData, buildConfig?: IBuildConfig, options?: { archivePath?: string }): Promise { - let projectRoot = this.getPlatformData(projectData).projectRoot; - let archivePath = options && options.archivePath ? path.resolve(options.archivePath) : path.join(projectRoot, "/build/archive/", projectData.projectName + ".xcarchive"); - let args = ["archive", "-archivePath", archivePath, "-configuration", + const projectRoot = this.getPlatformData(projectData).projectRoot; + const archivePath = options && options.archivePath ? path.resolve(options.archivePath) : path.join(projectRoot, "/build/archive/", projectData.projectName + ".xcarchive"); + const args = ["archive", "-archivePath", archivePath, "-configuration", (!buildConfig || buildConfig.release) ? "Release" : "Debug"] .concat(this.xcbuildProjectArgs(projectRoot, projectData, "scheme")); await this.xcodebuild(args, projectRoot, buildConfig && buildConfig.buildOutputStdio); @@ -265,11 +265,11 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ * Exports .xcarchive for a development device. */ private async exportDevelopmentArchive(projectData: IProjectData, buildConfig: IBuildConfig, options: { archivePath: string, exportDir?: string, teamID?: string, provision?: string }): Promise { - let platformData = this.getPlatformData(projectData); - let projectRoot = platformData.projectRoot; - let archivePath = options.archivePath; - let buildOutputPath = path.join(projectRoot, "build", "device"); - let exportOptionsMethod = await this.getExportOptionsMethod(projectData); + const platformData = this.getPlatformData(projectData); + const projectRoot = platformData.projectRoot; + const archivePath = options.archivePath; + const buildOutputPath = path.join(projectRoot, "build", "device"); + const exportOptionsMethod = await this.getExportOptionsMethod(projectData); let plistTemplate = ` @@ -291,12 +291,12 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ // Save the options... temp.track(); - let exportOptionsPlist = temp.path({ prefix: "export-", suffix: ".plist" }); + const exportOptionsPlist = temp.path({ prefix: "export-", suffix: ".plist" }); this.$fs.writeFile(exportOptionsPlist, plistTemplate); // The xcodebuild exportPath expects directory and writes the .ipa at that directory. - let exportPath = path.resolve(options.exportDir || buildOutputPath); - let exportFile = path.join(exportPath, projectData.projectName + ".ipa"); + const exportPath = path.resolve(options.exportDir || buildOutputPath); + const exportFile = path.join(exportPath, projectData.projectName + ".ipa"); await this.xcodebuild( [ @@ -310,11 +310,11 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } private xcbuildProjectArgs(projectRoot: string, projectData: IProjectData, product?: "scheme" | "target"): string[] { - let xcworkspacePath = path.join(projectRoot, projectData.projectName + ".xcworkspace"); + const xcworkspacePath = path.join(projectRoot, projectData.projectName + ".xcworkspace"); if (this.$fs.exists(xcworkspacePath)) { return ["-workspace", xcworkspacePath, product ? "-" + product : "-scheme", projectData.projectName]; } else { - let xcodeprojPath = path.join(projectRoot, projectData.projectName + ".xcodeproj"); + const xcodeprojPath = path.join(projectRoot, projectData.projectName + ".xcodeproj"); return ["-project", xcodeprojPath, product ? "-" + product : "-target", projectData.projectName]; } } @@ -328,7 +328,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ basicArgs = basicArgs.concat(this.xcbuildProjectArgs(projectRoot, projectData)); // Starting from tns-ios 1.4 the xcconfig file is referenced in the project template - let frameworkVersion = this.getFrameworkVersion(this.getPlatformData(projectData).frameworkPackageName, projectData.projectDir); + const frameworkVersion = this.getFrameworkVersion(this.getPlatformData(projectData).frameworkPackageName, projectData.projectDir); if (semver.lt(frameworkVersion, "1.4.0")) { basicArgs.push("-xcconfig", path.join(projectRoot, projectData.projectName, "build.xcconfig")); } @@ -360,10 +360,10 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } public async validatePlugins(projectData: IProjectData): Promise { - let installedPlugins = await (this.$injector.resolve("pluginsService")).getAllInstalledPlugins(projectData); - for (let pluginData of installedPlugins) { - let pluginsFolderExists = this.$fs.exists(path.join(pluginData.pluginPlatformsFolderPath(this.$devicePlatformsConstants.iOS.toLowerCase()), "Podfile")); - let cocoaPodVersion = await this.$sysInfo.getCocoapodVersion(); + const installedPlugins = await (this.$injector.resolve("pluginsService")).getAllInstalledPlugins(projectData); + for (const pluginData of installedPlugins) { + const pluginsFolderExists = this.$fs.exists(path.join(pluginData.pluginPlatformsFolderPath(this.$devicePlatformsConstants.iOS.toLowerCase()), "Podfile")); + const cocoaPodVersion = await this.$sysInfo.getCocoapodVersion(); if (pluginsFolderExists && !cocoaPodVersion) { this.$errors.failWithoutHelp(`${pluginData.name} has Podfile and you don't have Cocoapods installed or it is not configured correctly. Please verify Cocoapods can work on your machine.`); } @@ -372,7 +372,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } private async buildForDevice(projectRoot: string, args: string[], buildConfig: IBuildConfig, projectData: IProjectData): Promise { - let defaultArchitectures = [ + const defaultArchitectures = [ 'ARCHS=armv7 arm64', 'VALID_ARCHS=armv7 arm64' ]; @@ -383,14 +383,14 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ platform: this.$devicePlatformsConstants.iOS.toLowerCase(), deviceId: buildConfig.device, skipEmulatorStart: true }); - let instances = this.$devicesService.getDeviceInstances(); - let devicesArchitectures = _(instances) + const instances = this.$devicesService.getDeviceInstances(); + const devicesArchitectures = _(instances) .filter(d => this.$mobileHelper.isiOSPlatform(d.deviceInfo.platform) && d.deviceInfo.activeArchitecture) .map(d => d.deviceInfo.activeArchitecture) .uniq() .value(); if (devicesArchitectures.length > 0) { - let architectures = [ + const architectures = [ `ARCHS=${devicesArchitectures.join(" ")}`, `VALID_ARCHS=${devicesArchitectures.join(" ")}` ]; @@ -408,7 +408,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ "CONFIGURATION_BUILD_DIR=" + path.join(projectRoot, "build", "device") ]); - let xcodeBuildVersion = await this.getXcodeVersion(); + const xcodeBuildVersion = await this.getXcodeVersion(); if (helpers.versionCompare(xcodeBuildVersion, "8.0") >= 0) { await this.setupSigningForDevice(projectRoot, buildConfig, projectData); } @@ -478,8 +478,8 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ let shouldUpdateXcode = false; if (signing && signing.style === "Manual") { - for (let config in signing.configurations) { - let options = signing.configurations[config]; + for (const config in signing.configurations) { + const options = signing.configurations[config]; if (options.name !== provision && options.uuid !== provision) { shouldUpdateXcode = true; break; @@ -565,17 +565,17 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ if (!this.$hostInfo.isWindows) { this.validateFramework(frameworkPath); - let project = this.createPbxProj(projectData); - let frameworkName = path.basename(frameworkPath, path.extname(frameworkPath)); - let frameworkBinaryPath = path.join(frameworkPath, frameworkName); - let isDynamic = _.includes((await this.$childProcess.spawnFromEvent("file", [frameworkBinaryPath], "close")).stdout, "dynamically linked"); - let frameworkAddOptions: IXcode.Options = { customFramework: true }; + const project = this.createPbxProj(projectData); + const frameworkName = path.basename(frameworkPath, path.extname(frameworkPath)); + const frameworkBinaryPath = path.join(frameworkPath, frameworkName); + const isDynamic = _.includes((await this.$childProcess.spawnFromEvent("file", [frameworkBinaryPath], "close")).stdout, "dynamically linked"); + const frameworkAddOptions: IXcode.Options = { customFramework: true }; if (isDynamic) { frameworkAddOptions["embed"] = true; } - let frameworkRelativePath = '$(SRCROOT)/' + this.getLibSubpathRelativeToProjectPath(frameworkPath, projectData); + const frameworkRelativePath = '$(SRCROOT)/' + this.getLibSubpathRelativeToProjectPath(frameworkPath, projectData); project.addFramework(frameworkRelativePath, frameworkAddOptions); this.savePbxProj(project, projectData); } @@ -584,15 +584,15 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private async addStaticLibrary(staticLibPath: string, projectData: IProjectData): Promise { await this.validateStaticLibrary(staticLibPath); // Copy files to lib folder. - let libraryName = path.basename(staticLibPath, ".a"); - let headersSubpath = path.join(path.dirname(staticLibPath), "include", libraryName); + const libraryName = path.basename(staticLibPath, ".a"); + const headersSubpath = path.join(path.dirname(staticLibPath), "include", libraryName); // Add static library to project file and setup header search paths - let project = this.createPbxProj(projectData); - let relativeStaticLibPath = this.getLibSubpathRelativeToProjectPath(staticLibPath, projectData); + const project = this.createPbxProj(projectData); + const relativeStaticLibPath = this.getLibSubpathRelativeToProjectPath(staticLibPath, projectData); project.addFramework(relativeStaticLibPath); - let relativeHeaderSearchPath = path.join(this.getLibSubpathRelativeToProjectPath(headersSubpath, projectData)); + const relativeHeaderSearchPath = path.join(this.getLibSubpathRelativeToProjectPath(headersSubpath, projectData)); project.addToHeaderSearchPaths({ relativePath: relativeHeaderSearchPath }); this.generateModulemap(headersSubpath, libraryName); @@ -600,14 +600,14 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } public canUpdatePlatform(installedModuleDir: string, projectData: IProjectData): boolean { - let currentXcodeProjectFile = this.buildPathToCurrentXcodeProjectFile(projectData); - let currentXcodeProjectFileContent = this.$fs.readFile(currentXcodeProjectFile); + const currentXcodeProjectFile = this.buildPathToCurrentXcodeProjectFile(projectData); + const currentXcodeProjectFileContent = this.$fs.readFile(currentXcodeProjectFile); - let newXcodeProjectFile = this.buildPathToNewXcodeProjectFile(installedModuleDir); + const newXcodeProjectFile = this.buildPathToNewXcodeProjectFile(installedModuleDir); this.replaceFileContent(newXcodeProjectFile, projectData); - let newXcodeProjectFileContent = this.$fs.readFile(newXcodeProjectFile); + const newXcodeProjectFileContent = this.$fs.readFile(newXcodeProjectFile); - let contentIsTheSame = currentXcodeProjectFileContent.toString() === newXcodeProjectFileContent.toString(); + const contentIsTheSame = currentXcodeProjectFileContent.toString() === newXcodeProjectFileContent.toString(); if (!contentIsTheSame) { this.$logger.warn(`The content of the current project file: ${currentXcodeProjectFile} and the new project file: ${newXcodeProjectFile} is different.`); @@ -628,18 +628,18 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private provideLaunchScreenIfMissing(projectData: IProjectData): void { try { this.$logger.trace("Checking if we need to provide compatability LaunchScreen.xib"); - let platformData = this.getPlatformData(projectData); - let projectPath = path.join(platformData.projectRoot, projectData.projectName); - let projectPlist = this.getInfoPlistPath(projectData); - let plistContent = plist.parse(this.$fs.readText(projectPlist)); - let storyName = plistContent["UILaunchStoryboardName"]; + const platformData = this.getPlatformData(projectData); + const projectPath = path.join(platformData.projectRoot, projectData.projectName); + const projectPlist = this.getInfoPlistPath(projectData); + const plistContent = plist.parse(this.$fs.readText(projectPlist)); + const storyName = plistContent["UILaunchStoryboardName"]; this.$logger.trace(`Examining ${projectPlist} UILaunchStoryboardName: "${storyName}".`); if (storyName !== "LaunchScreen") { this.$logger.trace("The project has its UILaunchStoryboardName set to " + storyName + " which is not the pre v2.1.0 default LaunchScreen, probably the project is migrated so we are good to go."); return; } - let expectedStoryPath = path.join(projectPath, "Resources", "LaunchScreen.storyboard"); + const expectedStoryPath = path.join(projectPath, "Resources", "LaunchScreen.storyboard"); if (this.$fs.exists(expectedStoryPath)) { // Found a LaunchScreen on expected path this.$logger.trace("LaunchScreen.storyboard was found. Project is up to date."); @@ -647,28 +647,28 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } this.$logger.trace("LaunchScreen file not found at: " + expectedStoryPath); - let expectedXibPath = path.join(projectPath, "en.lproj", "LaunchScreen.xib"); + const expectedXibPath = path.join(projectPath, "en.lproj", "LaunchScreen.xib"); if (this.$fs.exists(expectedXibPath)) { this.$logger.trace("Obsolete LaunchScreen.xib was found. It'k OK, we are probably running with iOS runtime from pre v2.1.0."); return; } this.$logger.trace("LaunchScreen file not found at: " + expectedXibPath); - let isTheLaunchScreenFile = (fileName: string) => fileName === "LaunchScreen.xib" || fileName === "LaunchScreen.storyboard"; - let matches = this.$fs.enumerateFilesInDirectorySync(projectPath, isTheLaunchScreenFile, { enumerateDirectories: false }); + const isTheLaunchScreenFile = (fileName: string) => fileName === "LaunchScreen.xib" || fileName === "LaunchScreen.storyboard"; + const matches = this.$fs.enumerateFilesInDirectorySync(projectPath, isTheLaunchScreenFile, { enumerateDirectories: false }); if (matches.length > 0) { this.$logger.trace("Found LaunchScreen by slowly traversing all files here: " + matches + "\nConsider moving the LaunchScreen so it could be found at: " + expectedStoryPath); return; } - let compatabilityXibPath = path.join(projectPath, "Resources", "LaunchScreen.xib"); + const compatabilityXibPath = path.join(projectPath, "Resources", "LaunchScreen.xib"); this.$logger.warn(`Failed to find LaunchScreen.storyboard but it was specified in the Info.plist. Consider updating the resources in app/App_Resources/iOS/. A good starting point would be to create a new project and diff the changes with your current one. Also the following repo may be helpful: https://github.com/NativeScript/template-hello-world/tree/master/App_Resources/iOS We will now place an empty obsolete compatability white screen LauncScreen.xib for you in ${path.relative(projectData.projectDir, compatabilityXibPath)} so your app may appear as it did in pre v2.1.0 versions of the ios runtime.`); - let content = ` + const content = ` @@ -709,28 +709,28 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f await this.setupSigningFromTeam(projectRoot, projectData, teamId); } - let project = this.createPbxProj(projectData); + const project = this.createPbxProj(projectData); this.provideLaunchScreenIfMissing(projectData); - let resources = project.pbxGroupByName("Resources"); + const resources = project.pbxGroupByName("Resources"); if (resources) { - let references = project.pbxFileReferenceSection(); + const references = project.pbxFileReferenceSection(); - let xcodeProjectImages = _.map(resources.children, resource => this.replace(references[resource.value].name)); + const xcodeProjectImages = _.map(resources.children, resource => this.replace(references[resource.value].name)); this.$logger.trace("Images from Xcode project"); this.$logger.trace(xcodeProjectImages); - let appResourcesImages = this.$fs.readDirectory(this.getAppResourcesDestinationDirectoryPath(projectData)); + const appResourcesImages = this.$fs.readDirectory(this.getAppResourcesDestinationDirectoryPath(projectData)); this.$logger.trace("Current images from App_Resources"); this.$logger.trace(appResourcesImages); - let imagesToAdd = _.difference(appResourcesImages, xcodeProjectImages); + const imagesToAdd = _.difference(appResourcesImages, xcodeProjectImages); this.$logger.trace(`New images to add into xcode project: ${imagesToAdd.join(", ")}`); _.each(imagesToAdd, image => project.addResourceFile(path.relative(this.getPlatformData(projectData).projectRoot, path.join(this.getAppResourcesDestinationDirectoryPath(projectData), image)))); - let imagesToRemove = _.difference(xcodeProjectImages, appResourcesImages); + const imagesToRemove = _.difference(xcodeProjectImages, appResourcesImages); this.$logger.trace(`Images to remove from xcode project: ${imagesToRemove.join(", ")}`); _.each(imagesToRemove, image => project.removeResourceFile(path.join(this.getAppResourcesDestinationDirectoryPath(projectData), image))); @@ -739,8 +739,8 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } public prepareAppResources(appResourcesDirectoryPath: string, projectData: IProjectData): void { - let platformFolder = path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName); - let filterFile = (filename: string) => this.$fs.deleteFile(path.join(platformFolder, filename)); + const platformFolder = path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName); + const filterFile = (filename: string) => this.$fs.deleteFile(path.join(platformFolder, filename)); filterFile(this.getPlatformData(projectData).configurationFileName); @@ -751,7 +751,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f await this.mergeInfoPlists({ release }, projectData); await this.$iOSEntitlementsService.merge(projectData); await this.mergeProjectXcconfigFiles(release, projectData); - for (let pluginData of await this.getAllInstalledPlugins(projectData)) { + for (const pluginData of await this.getAllInstalledPlugins(projectData)) { await this.$pluginVariablesService.interpolatePluginVariables(pluginData, this.getPlatformData(projectData).configurationFilePath, projectData); } @@ -781,8 +781,8 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } private async mergeInfoPlists(buildOptions: IRelease, projectData: IProjectData): Promise { - let projectDir = projectData.projectDir; - let infoPlistPath = path.join(projectDir, constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME, this.getPlatformData(projectData).normalizedPlatformName, this.getPlatformData(projectData).configurationFileName); + const projectDir = projectData.projectDir; + const infoPlistPath = path.join(projectDir, constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME, this.getPlatformData(projectData).normalizedPlatformName, this.getPlatformData(projectData).configurationFileName); this.ensureConfigurationFileInAppResources(); if (!this.$fs.exists(infoPlistPath)) { @@ -796,8 +796,8 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f warn: (txt: string) => this.$logger.warn(`${reporterTraceMessage} ${txt}`) }; - let session = new PlistSession(reporter); - let makePatch = (plistPath: string) => { + const session = new PlistSession(reporter); + const makePatch = (plistPath: string) => { if (!this.$fs.exists(plistPath)) { this.$logger.trace("No plist found at: " + plistPath); return; @@ -810,9 +810,9 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f }); }; - let allPlugins = await this.getAllInstalledPlugins(projectData); - for (let plugin of allPlugins) { - let pluginInfoPlistPath = path.join(plugin.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME), this.getPlatformData(projectData).configurationFileName); + const allPlugins = await this.getAllInstalledPlugins(projectData); + for (const plugin of allPlugins) { + const pluginInfoPlistPath = path.join(plugin.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME), this.getPlatformData(projectData).configurationFileName); makePatch(pluginInfoPlistPath); } @@ -857,7 +857,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f }); } - let plistContent = session.build(); + const plistContent = session.build(); this.$logger.trace("Info.plist: Write to: " + this.getPlatformData(projectData).configurationFilePath); this.$fs.writeFile(this.getPlatformData(projectData).configurationFilePath, plistContent); @@ -892,7 +892,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } private getLibSubpathRelativeToProjectPath(targetPath: string, projectData: IProjectData): string { - let frameworkPath = path.relative(this.getPlatformData(projectData).projectRoot, targetPath); + const frameworkPath = path.relative(this.getPlatformData(projectData).projectRoot, targetPath); return frameworkPath; } @@ -901,7 +901,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } private createPbxProj(projectData: IProjectData): any { - let project = new this.$xcode.project(this.getPbxProjPath(projectData)); + const project = new this.$xcode.project(this.getPbxProjPath(projectData)); project.parseSync(); return project; @@ -912,7 +912,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } public async preparePluginNativeCode(pluginData: IPluginData, projectData: IProjectData, opts?: any): Promise { - let pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME); + const pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME); await this.prepareFrameworks(pluginPlatformsFolderPath, pluginData, projectData); await this.prepareStaticLibs(pluginPlatformsFolderPath, pluginData, projectData); @@ -920,7 +920,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } public async removePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise { - let pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME); + const pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME); this.removeFrameworks(pluginPlatformsFolderPath, pluginData, projectData); this.removeStaticLibs(pluginPlatformsFolderPath, pluginData, projectData); @@ -929,23 +929,23 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f public async afterPrepareAllPlugins(projectData: IProjectData): Promise { if (this.$fs.exists(this.getProjectPodFilePath(projectData))) { - let projectPodfileContent = this.$fs.readText(this.getProjectPodFilePath(projectData)); + const projectPodfileContent = this.$fs.readText(this.getProjectPodFilePath(projectData)); this.$logger.trace("Project Podfile content"); this.$logger.trace(projectPodfileContent); - let firstPostInstallIndex = projectPodfileContent.indexOf(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME); + const firstPostInstallIndex = projectPodfileContent.indexOf(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME); if (firstPostInstallIndex !== -1 && firstPostInstallIndex !== projectPodfileContent.lastIndexOf(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME)) { this.$cocoapodsService.mergePodfileHookContent(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME, this.getProjectPodFilePath(projectData)); } - let xcuserDataPath = path.join(this.getXcodeprojPath(projectData), "xcuserdata"); - let sharedDataPath = path.join(this.getXcodeprojPath(projectData), "xcshareddata"); + const xcuserDataPath = path.join(this.getXcodeprojPath(projectData), "xcuserdata"); + const sharedDataPath = path.join(this.getXcodeprojPath(projectData), "xcshareddata"); if (!this.$fs.exists(xcuserDataPath) && !this.$fs.exists(sharedDataPath)) { this.$logger.info("Creating project scheme..."); await this.checkIfXcodeprojIsRequired(); - let createSchemeRubyScript = `ruby -e "require 'xcodeproj'; xcproj = Xcodeproj::Project.open('${projectData.projectName}.xcodeproj'); xcproj.recreate_user_schemes; xcproj.save"`; + const createSchemeRubyScript = `ruby -e "require 'xcodeproj'; xcproj = Xcodeproj::Project.open('${projectData.projectName}.xcodeproj'); xcproj.recreate_user_schemes; xcproj.save"`; await this.$childProcess.exec(createSchemeRubyScript, { cwd: this.getPlatformData(projectData).projectRoot }); } @@ -1005,7 +1005,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } private getAllLibsForPluginWithFileExtension(pluginData: IPluginData, fileExtension: string): string[] { - let filterCallback = (fileName: string, pluginPlatformsFolderPath: string) => path.extname(fileName) === fileExtension; + const filterCallback = (fileName: string, pluginPlatformsFolderPath: string) => path.extname(fileName) === fileExtension; return this.getAllNativeLibrariesForPlugin(pluginData, IOSProjectService.IOS_PLATFORM_NAME, filterCallback); } @@ -1036,8 +1036,8 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.$errors.failWithoutHelp(`The bundle at ${libraryPath} does not contain a valid static library in the '.a' file format.`); } - let expectedArchs = ["armv7", "arm64", "i386"]; - let archsInTheFatFile = await this.$childProcess.exec("lipo -i " + libraryPath); + const expectedArchs = ["armv7", "arm64", "i386"]; + const archsInTheFatFile = await this.$childProcess.exec("lipo -i " + libraryPath); expectedArchs.forEach(expectedArch => { if (archsInTheFatFile.indexOf(expectedArch) < 0) { @@ -1047,14 +1047,14 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } private replaceFileContent(file: string, projectData: IProjectData): void { - let fileContent = this.$fs.readText(file); - let replacedContent = helpers.stringReplaceAll(fileContent, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER, projectData.projectName); + const fileContent = this.$fs.readText(file); + const replacedContent = helpers.stringReplaceAll(fileContent, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER, projectData.projectName); this.$fs.writeFile(file, replacedContent); } private replaceFileName(fileNamePart: string, fileRootLocation: string, projectData: IProjectData): void { - let oldFileName = IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + fileNamePart; - let newFileName = projectData.projectName + fileNamePart; + const oldFileName = IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + fileNamePart; + const newFileName = projectData.projectName + fileNamePart; this.$fs.rename(path.join(fileRootLocation, oldFileName), path.join(fileRootLocation, newFileName)); } @@ -1071,10 +1071,10 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f await this.$xcprojService.verifyXcproj(true); this.$logger.info("Installing pods..."); - let podTool = this.$config.USE_POD_SANDBOX ? "sandbox-pod" : "pod"; - let childProcess = await this.$childProcess.spawnFromEvent(podTool, ["install"], "close", { cwd: this.getPlatformData(projectData).projectRoot, stdio: ['pipe', process.stdout, 'pipe'] }); + const podTool = this.$config.USE_POD_SANDBOX ? "sandbox-pod" : "pod"; + const childProcess = await this.$childProcess.spawnFromEvent(podTool, ["install"], "close", { cwd: this.getPlatformData(projectData).projectRoot, stdio: ['pipe', process.stdout, 'pipe'] }); if (childProcess.stderr) { - let warnings = childProcess.stderr.match(/(\u001b\[(?:\d*;){0,5}\d*m[\s\S]+?\u001b\[(?:\d*;){0,5}\d*m)|(\[!\].*?\n)|(.*?warning.*)/gi); + const warnings = childProcess.stderr.match(/(\u001b\[(?:\d*;){0,5}\d*m[\s\S]+?\u001b\[(?:\d*;){0,5}\d*m)|(\[!\].*?\n)|(.*?warning.*)/gi); _.each(warnings, (warning: string) => { this.$logger.warnWithLabel(warning.replace("\n", "")); }); @@ -1097,26 +1097,26 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } private async prepareFrameworks(pluginPlatformsFolderPath: string, pluginData: IPluginData, projectData: IProjectData): Promise { - for (let fileName of this.getAllLibsForPluginWithFileExtension(pluginData, ".framework")) { + for (const fileName of this.getAllLibsForPluginWithFileExtension(pluginData, ".framework")) { await this.addFramework(path.join(pluginPlatformsFolderPath, fileName), projectData); } } private async prepareStaticLibs(pluginPlatformsFolderPath: string, pluginData: IPluginData, projectData: IProjectData): Promise { - for (let fileName of this.getAllLibsForPluginWithFileExtension(pluginData, ".a")) { + for (const fileName of this.getAllLibsForPluginWithFileExtension(pluginData, ".a")) { await this.addStaticLibrary(path.join(pluginPlatformsFolderPath, fileName), projectData); } } private async prepareCocoapods(pluginPlatformsFolderPath: string, projectData: IProjectData, opts?: any): Promise { - let pluginPodFilePath = path.join(pluginPlatformsFolderPath, "Podfile"); + const pluginPodFilePath = path.join(pluginPlatformsFolderPath, "Podfile"); if (this.$fs.exists(pluginPodFilePath)) { - let pluginPodFileContent = this.$fs.readText(pluginPodFilePath), - pluginPodFilePreparedContent = this.buildPodfileContent(pluginPodFilePath, pluginPodFileContent), - projectPodFileContent = this.$fs.exists(this.getProjectPodFilePath(projectData)) ? this.$fs.readText(this.getProjectPodFilePath(projectData)) : ""; + const pluginPodFileContent = this.$fs.readText(pluginPodFilePath); + const pluginPodFilePreparedContent = this.buildPodfileContent(pluginPodFilePath, pluginPodFileContent); + let projectPodFileContent = this.$fs.exists(this.getProjectPodFilePath(projectData)) ? this.$fs.readText(this.getProjectPodFilePath(projectData)) : ""; if (!~projectPodFileContent.indexOf(pluginPodFilePreparedContent)) { - let podFileHeader = this.$cocoapodsService.getPodfileHeader(projectData.projectName), + const podFileHeader = this.$cocoapodsService.getPodfileHeader(projectData.projectName), podFileFooter = this.$cocoapodsService.getPodfileFooter(); if (_.startsWith(projectPodFileContent, podFileHeader)) { @@ -1127,10 +1127,10 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f projectPodFileContent = projectPodFileContent.substr(0, projectPodFileContent.length - podFileFooter.length); } - let contentToWrite = `${podFileHeader}${projectPodFileContent}${pluginPodFilePreparedContent}${podFileFooter}`; + const contentToWrite = `${podFileHeader}${projectPodFileContent}${pluginPodFilePreparedContent}${podFileFooter}`; this.$fs.writeFile(this.getProjectPodFilePath(projectData), contentToWrite); - let project = this.createPbxProj(projectData); + const project = this.createPbxProj(projectData); this.savePbxProj(project, projectData); } } @@ -1141,9 +1141,9 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } private removeFrameworks(pluginPlatformsFolderPath: string, pluginData: IPluginData, projectData: IProjectData): void { - let project = this.createPbxProj(projectData); + const project = this.createPbxProj(projectData); _.each(this.getAllLibsForPluginWithFileExtension(pluginData, ".framework"), fileName => { - let relativeFrameworkPath = this.getLibSubpathRelativeToProjectPath(fileName, projectData); + const relativeFrameworkPath = this.getLibSubpathRelativeToProjectPath(fileName, projectData); project.removeFramework(relativeFrameworkPath, { customFramework: true, embed: true }); }); @@ -1151,15 +1151,15 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } private removeStaticLibs(pluginPlatformsFolderPath: string, pluginData: IPluginData, projectData: IProjectData): void { - let project = this.createPbxProj(projectData); + const project = this.createPbxProj(projectData); _.each(this.getAllLibsForPluginWithFileExtension(pluginData, ".a"), fileName => { - let staticLibPath = path.join(pluginPlatformsFolderPath, fileName); - let relativeStaticLibPath = this.getLibSubpathRelativeToProjectPath(path.basename(staticLibPath), projectData); + const staticLibPath = path.join(pluginPlatformsFolderPath, fileName); + const relativeStaticLibPath = this.getLibSubpathRelativeToProjectPath(path.basename(staticLibPath), projectData); project.removeFramework(relativeStaticLibPath); - let headersSubpath = path.join("include", path.basename(staticLibPath, ".a")); - let relativeHeaderSearchPath = path.join(this.getLibSubpathRelativeToProjectPath(headersSubpath, projectData)); + const headersSubpath = path.join("include", path.basename(staticLibPath, ".a")); + const relativeHeaderSearchPath = path.join(this.getLibSubpathRelativeToProjectPath(headersSubpath, projectData)); project.removeFromHeaderSearchPaths({ relativePath: relativeHeaderSearchPath }); }); @@ -1167,12 +1167,12 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } private removeCocoapods(pluginPlatformsFolderPath: string, projectData: IProjectData): void { - let pluginPodFilePath = path.join(pluginPlatformsFolderPath, "Podfile"); + const pluginPodFilePath = path.join(pluginPlatformsFolderPath, "Podfile"); if (this.$fs.exists(pluginPodFilePath) && this.$fs.exists(this.getProjectPodFilePath(projectData))) { - let pluginPodFileContent = this.$fs.readText(pluginPodFilePath); + const pluginPodFileContent = this.$fs.readText(pluginPodFilePath); let projectPodFileContent = this.$fs.readText(this.getProjectPodFilePath(projectData)); - let contentToRemove = this.buildPodfileContent(pluginPodFilePath, pluginPodFileContent); + const contentToRemove = this.buildPodfileContent(pluginPodFilePath, pluginPodFileContent); projectPodFileContent = helpers.stringReplaceAll(projectPodFileContent, contentToRemove, ""); if (projectPodFileContent.trim() === `use_frameworks!${os.EOL}${os.EOL}target "${projectData.projectName}" do${os.EOL}${os.EOL}end`) { this.$fs.deleteFile(this.getProjectPodFilePath(projectData)); @@ -1187,8 +1187,8 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } private generateModulemap(headersFolderPath: string, libraryName: string): void { - let headersFilter = (fileName: string, containingFolderPath: string) => (path.extname(fileName) === ".h" && this.$fs.getFsStats(path.join(containingFolderPath, fileName)).isFile()); - let headersFolderContents = this.$fs.readDirectory(headersFolderPath); + const headersFilter = (fileName: string, containingFolderPath: string) => (path.extname(fileName) === ".h" && this.$fs.getFsStats(path.join(containingFolderPath, fileName)).isFile()); + const headersFolderContents = this.$fs.readDirectory(headersFolderPath); let headers = _(headersFolderContents).filter(item => headersFilter(item, headersFolderPath)).value(); if (!headers.length) { @@ -1198,7 +1198,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f headers = _.map(headers, value => `header "${value}"`); - let modulemap = `module ${libraryName} { explicit module ${libraryName} { ${headers.join(" ")} } }`; + const modulemap = `module ${libraryName} { explicit module ${libraryName} { ${headers.join(" ")} } }`; this.$fs.writeFile(path.join(headersFolderPath, "module.modulemap"), modulemap); } @@ -1208,32 +1208,32 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } await this.checkIfXcodeprojIsRequired(); - let escapedProjectFile = projectFile.replace(/'/g, "\\'"), + const escapedProjectFile = projectFile.replace(/'/g, "\\'"), escapedPluginFile = pluginFile.replace(/'/g, "\\'"), mergeScript = `require 'xcodeproj'; Xcodeproj::Config.new('${escapedProjectFile}').merge(Xcodeproj::Config.new('${escapedPluginFile}')).save_as(Pathname.new('${escapedProjectFile}'))`; await this.$childProcess.exec(`ruby -e "${mergeScript}"`); } private async mergeProjectXcconfigFiles(release: boolean, projectData: IProjectData): Promise { - let pluginsXcconfigFilePath = release ? this.getPluginsReleaseXcconfigFilePath(projectData) : this.getPluginsDebugXcconfigFilePath(projectData); + const pluginsXcconfigFilePath = release ? this.getPluginsReleaseXcconfigFilePath(projectData) : this.getPluginsDebugXcconfigFilePath(projectData); this.$fs.deleteFile(pluginsXcconfigFilePath); - let allPlugins: IPluginData[] = await (this.$injector.resolve("pluginsService")).getAllInstalledPlugins(projectData); - for (let plugin of allPlugins) { - let pluginPlatformsFolderPath = plugin.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME); - let pluginXcconfigFilePath = path.join(pluginPlatformsFolderPath, "build.xcconfig"); + const allPlugins: IPluginData[] = await (this.$injector.resolve("pluginsService")).getAllInstalledPlugins(projectData); + for (const plugin of allPlugins) { + const pluginPlatformsFolderPath = plugin.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME); + const pluginXcconfigFilePath = path.join(pluginPlatformsFolderPath, "build.xcconfig"); if (this.$fs.exists(pluginXcconfigFilePath)) { await this.mergeXcconfigFiles(pluginXcconfigFilePath, pluginsXcconfigFilePath); } } - let appResourcesXcconfigPath = path.join(projectData.projectDir, constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME, this.getPlatformData(projectData).normalizedPlatformName, "build.xcconfig"); + const appResourcesXcconfigPath = path.join(projectData.projectDir, constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME, this.getPlatformData(projectData).normalizedPlatformName, "build.xcconfig"); if (this.$fs.exists(appResourcesXcconfigPath)) { await this.mergeXcconfigFiles(appResourcesXcconfigPath, pluginsXcconfigFilePath); } // Set Entitlements Property to point to default file if not set explicitly by the user. - let entitlementsPropertyValue = this.$xCConfigService.readPropertyValue(pluginsXcconfigFilePath, constants.CODE_SIGN_ENTITLEMENTS); + const entitlementsPropertyValue = this.$xCConfigService.readPropertyValue(pluginsXcconfigFilePath, constants.CODE_SIGN_ENTITLEMENTS); if (entitlementsPropertyValue === null) { temp.track(); const tempEntitlementsDir = temp.mkdirSync("entitlements"); @@ -1244,8 +1244,8 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f await this.mergeXcconfigFiles(tempEntitlementsFilePath, pluginsXcconfigFilePath); } - let podFilesRootDirName = path.join("Pods", "Target Support Files", `Pods-${projectData.projectName}`); - let podFolder = path.join(this.getPlatformData(projectData).projectRoot, podFilesRootDirName); + const podFilesRootDirName = path.join("Pods", "Target Support Files", `Pods-${projectData.projectName}`); + const podFolder = path.join(this.getPlatformData(projectData).projectRoot, podFilesRootDirName); if (this.$fs.exists(podFolder)) { if (release) { await this.mergeXcconfigFiles(path.join(this.getPlatformData(projectData).projectRoot, podFilesRootDirName, `Pods-${projectData.projectName}.release.xcconfig`), this.getPluginsReleaseXcconfigFilePath(projectData)); @@ -1256,9 +1256,9 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } private async checkIfXcodeprojIsRequired(): Promise { - let xcprojInfo = await this.$xcprojService.getXcprojInfo(); + const xcprojInfo = await this.$xcprojService.getXcprojInfo(); if (xcprojInfo.shouldUseXcproj && !xcprojInfo.xcprojAvailable) { - let errorMessage = `You are using CocoaPods version ${xcprojInfo.cocoapodVer} which does not support Xcode ${xcprojInfo.xcodeVersion.major}.${xcprojInfo.xcodeVersion.minor} yet.${EOL}${EOL}You can update your cocoapods by running $sudo gem install cocoapods from a terminal.${EOL}${EOL}In order for the NativeScript CLI to be able to work correctly with this setup you need to install xcproj command line tool and add it to your PATH. Xcproj can be installed with homebrew by running $ brew install xcproj from the terminal`; + const errorMessage = `You are using CocoaPods version ${xcprojInfo.cocoapodVer} which does not support Xcode ${xcprojInfo.xcodeVersion.major}.${xcprojInfo.xcodeVersion.minor} yet.${EOL}${EOL}You can update your cocoapods by running $sudo gem install cocoapods from a terminal.${EOL}${EOL}In order for the NativeScript CLI to be able to work correctly with this setup you need to install xcproj command line tool and add it to your PATH. Xcproj can be installed with homebrew by running $ brew install xcproj from the terminal`; this.$errors.failWithoutHelp(errorMessage); @@ -1275,14 +1275,14 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.$errors.fail("xcodebuild execution failed. Make sure that you have latest Xcode and tools installed."); } - let splitedXcodeBuildVersion = xcodeBuildVersion.split("."); + const splitedXcodeBuildVersion = xcodeBuildVersion.split("."); xcodeBuildVersion = `${splitedXcodeBuildVersion[0] || 0}.${splitedXcodeBuildVersion[1] || 0}`; return xcodeBuildVersion; } private getBuildXCConfigFilePath(projectData: IProjectData): string { - let buildXCConfig = path.join(projectData.appResourcesDirectoryPath, + const buildXCConfig = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, "build.xcconfig"); return buildXCConfig; } @@ -1290,7 +1290,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f private readTeamId(projectData: IProjectData): string { let teamId = this.$xCConfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "DEVELOPMENT_TEAM"); - let fileName = path.join(this.getPlatformData(projectData).projectRoot, "teamid"); + const fileName = path.join(this.getPlatformData(projectData).projectRoot, "teamid"); if (this.$fs.exists(fileName)) { teamId = this.$fs.readText(fileName); } @@ -1318,7 +1318,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f teamId = teamId || this.readTeamId(projectData); if (!teamId) { - let teams = await this.$iOSProvisionService.getDevelopmentTeams(); + const teams = await this.$iOSProvisionService.getDevelopmentTeams(); this.$logger.warn("Xcode 8 requires a team id to be specified when building for device."); this.$logger.warn("You can specify the team id by setting the DEVELOPMENT_TEAM setting in build.xcconfig file located in App_Resources folder of your app, or by using the --teamId option when calling run, debug or livesync commands."); if (teams.length === 1) { @@ -1329,22 +1329,22 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.$errors.failWithoutHelp(`Unable to determine default development team. Available development teams are: ${_.map(teams, team => team.id)}. Specify team in app/App_Resources/iOS/build.xcconfig file in the following way: DEVELOPMENT_TEAM = `); } - let choices: string[] = []; - for (let team of teams) { + const choices: string[] = []; + for (const team of teams) { choices.push(team.name + " (" + team.id + ")"); } - let choice = await this.$prompter.promptForChoice('Found multiple development teams, select one:', choices); + const choice = await this.$prompter.promptForChoice('Found multiple development teams, select one:', choices); teamId = teams[choices.indexOf(choice)].id; - let choicesPersist = [ + const choicesPersist = [ "Yes, set the DEVELOPMENT_TEAM setting in build.xcconfig file.", "Yes, persist the team id in platforms folder.", "No, don't persist this setting." ]; - let choicePersist = await this.$prompter.promptForChoice("Do you want to make teamId: " + teamId + " a persistent choice for your app?", choicesPersist); + const choicePersist = await this.$prompter.promptForChoice("Do you want to make teamId: " + teamId + " a persistent choice for your app?", choicesPersist); switch (choicesPersist.indexOf(choicePersist)) { case 0: - let xcconfigFile = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, "build.xcconfig"); + const xcconfigFile = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, "build.xcconfig"); this.$fs.appendFile(xcconfigFile, "\nDEVELOPMENT_TEAM = " + teamId + "\n"); break; case 1: diff --git a/lib/services/itmstransporter-service.ts b/lib/services/itmstransporter-service.ts index 7405e3f29a..83dbbf3698 100644 --- a/lib/services/itmstransporter-service.ts +++ b/lib/services/itmstransporter-service.ts @@ -30,7 +30,7 @@ export class ITMSTransporterService implements IITMSTransporterService { } temp.track(); - let itmsTransporterPath = await this.getITMSTransporterPath(), + const itmsTransporterPath = await this.getITMSTransporterPath(), ipaFileName = "app.ipa", itmsDirectory = temp.mkdirSync("itms-"), innerDirectory = path.join(itmsDirectory, "mybundle.itmsp"), @@ -43,7 +43,7 @@ export class ITMSTransporterService implements IITMSTransporterService { this.$fs.copyFile(data.ipaFilePath, ipaFileLocation); - let ipaFileHash = await this.$fs.getFileShasum(ipaFileLocation, { algorithm: "md5" }), + const ipaFileHash = await this.$fs.getFileShasum(ipaFileLocation, { algorithm: "md5" }), ipaFileSize = this.$fs.getFileSize(ipaFileLocation), metadata = this.getITMSMetadataXml(iOSApplication.adamId, ipaFileName, ipaFileHash, ipaFileSize); @@ -54,7 +54,7 @@ export class ITMSTransporterService implements IITMSTransporterService { public async getiOSApplications(credentials: ICredentials): Promise { if (!this._itunesConnectApplications) { - let requestBody = this.getContentDeliveryRequestBody(credentials), + const requestBody = this.getContentDeliveryRequestBody(credentials), contentDeliveryResponse = await this.$httpClient.httpRequest({ url: "https://contentdelivery.itunes.apple.com/WebObjects/MZLabelService.woa/json/MZITunesProducerService", method: "POST", @@ -88,12 +88,12 @@ export class ITMSTransporterService implements IITMSTransporterService { * @return {IFuture} The iTunes Connect application. */ private async getiOSApplication(username: string, password: string, bundleId: string): Promise { - let iOSApplications = await this.getiOSApplications({ username, password }); + const iOSApplications = await this.getiOSApplications({ username, password }); if (!iOSApplications || !iOSApplications.length) { this.$errors.failWithoutHelp(`Cannot find any registered applications for Apple ID ${username} in iTunes Connect.`); } - let iOSApplication = _.find(iOSApplications, app => app.bundleId === bundleId); + const iOSApplication = _.find(iOSApplications, app => app.bundleId === bundleId); if (!iOSApplication) { this.$errors.failWithoutHelp(`Cannot find registered applications that match the specified identifier ${bundleId} in iTunes Connect.`); @@ -118,10 +118,10 @@ export class ITMSTransporterService implements IITMSTransporterService { this.$logger.trace("--ipa set - extracting .ipa file to get app's bundle identifier"); temp.track(); - let destinationDir = temp.mkdirSync("ipa-"); + const destinationDir = temp.mkdirSync("ipa-"); await this.$fs.unzip(ipaFileFullPath, destinationDir); - let payloadDir = path.join(destinationDir, "Payload"); + const payloadDir = path.join(destinationDir, "Payload"); let allApps = this.$fs.readDirectory(payloadDir); this.$logger.debug("ITMSTransporter .ipa Payload files:"); @@ -133,10 +133,10 @@ export class ITMSTransporterService implements IITMSTransporterService { } else if (allApps.length <= 0) { this.$errors.failWithoutHelp("In the .ipa the ITMSTransporter is uploading there must be at least one .app file."); } - let appFile = path.join(payloadDir, allApps[0]); + const appFile = path.join(payloadDir, allApps[0]); - let plistObject = await this.$bplistParser.parseFile(path.join(appFile, "Info.plist")); - let bundleId = plistObject && plistObject[0] && plistObject[0].CFBundleIdentifier; + const plistObject = await this.$bplistParser.parseFile(path.join(appFile, "Info.plist")); + const bundleId = plistObject && plistObject[0] && plistObject[0].CFBundleIdentifier; if (!bundleId) { this.$errors.failWithoutHelp(`Unable to determine bundle identifier from ${ipaFileFullPath}.`); } @@ -151,9 +151,9 @@ export class ITMSTransporterService implements IITMSTransporterService { private async getITMSTransporterPath(): Promise { if (!this._itmsTransporterPath) { - let xcodePath = await this.$xcodeSelectService.getContentsDirectoryPath(), - xcodeVersion = await this.$xcodeSelectService.getXcodeVersion(), - result = path.join(xcodePath, "Applications", "Application Loader.app", "Contents"); + const xcodePath = await this.$xcodeSelectService.getContentsDirectoryPath(); + const xcodeVersion = await this.$xcodeSelectService.getXcodeVersion(); + let result = path.join(xcodePath, "Applications", "Application Loader.app", "Contents"); xcodeVersion.patch = xcodeVersion.patch || "0"; // iTMS Transporter's path has been modified in Xcode 6.3 diff --git a/lib/services/karma-execution.ts b/lib/services/karma-execution.ts index 2223bef675..728bffcf71 100644 --- a/lib/services/karma-execution.ts +++ b/lib/services/karma-execution.ts @@ -2,7 +2,7 @@ import * as path from "path"; process.on("message", (data: any) => { if (data.karmaConfig) { - let pathToKarma = path.join(data.karmaConfig.projectDir, 'node_modules/karma'), + const pathToKarma = path.join(data.karmaConfig.projectDir, 'node_modules/karma'), KarmaServer = require(path.join(pathToKarma, 'lib/server')), karma = new KarmaServer(data.karmaConfig, (exitCode: number) => { //Exit with the correct exit code and signal the manager process. diff --git a/lib/services/livesync/android-device-livesync-service.ts b/lib/services/livesync/android-device-livesync-service.ts index 985a5f347e..b19cc0d7ad 100644 --- a/lib/services/livesync/android-device-livesync-service.ts +++ b/lib/services/livesync/android-device-livesync-service.ts @@ -58,8 +58,8 @@ export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase impl } private async restartApplication(deviceAppData: Mobile.IDeviceAppData): Promise { - let devicePathRoot = `/data/data/${deviceAppData.appIdentifier}/files`; - let devicePath = this.$mobileHelper.buildDevicePath(devicePathRoot, "code_cache", "secondary_dexes", "proxyThumb"); + const devicePathRoot = `/data/data/${deviceAppData.appIdentifier}/files`; + const devicePath = this.$mobileHelper.buildDevicePath(devicePathRoot, "code_cache", "secondary_dexes", "proxyThumb"); await this.device.adb.executeShellCommand(["rm", "-rf", devicePath]); await this.device.applicationManager.restartApplication(deviceAppData.appIdentifier); @@ -112,14 +112,14 @@ export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase impl @cache() public getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService { - let adb = this.$injector.resolve(DeviceAndroidDebugBridge, { identifier: this.device.deviceInfo.identifier }); + const adb = this.$injector.resolve(DeviceAndroidDebugBridge, { identifier: this.device.deviceInfo.identifier }); return this.$injector.resolve(AndroidDeviceHashService, { adb, appIdentifier }); } private async awaitRuntimeReloadSuccessMessage(): Promise { return new Promise((resolve, reject) => { let isResolved = false; - let socket = new net.Socket(); + const socket = new net.Socket(); socket.connect(AndroidDeviceLiveSyncService.BACKEND_PORT, '127.0.0.1', () => { socket.write(new Buffer([0, 0, 0, 1, 1])); diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index 976376a39f..a29ed506ff 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -84,7 +84,7 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen private async reloadPage(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { if (localToDevicePaths.length) { - let message = JSON.stringify({ + const message = JSON.stringify({ method: "Page.reload", params: { ignoreCache: false @@ -97,9 +97,9 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen } private async liveEdit(localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { - for (let localToDevicePath of localToDevicePaths) { - let content = this.$fs.readText(localToDevicePath.getLocalPath()); - let message = JSON.stringify({ + for (const localToDevicePath of localToDevicePaths) { + const content = this.$fs.readText(localToDevicePath.getLocalPath()); + const message = JSON.stringify({ method: "Debugger.setScriptSource", params: { scriptUrl: localToDevicePath.getRelativeToProjectBasePath(), @@ -133,8 +133,8 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen try { await new Promise((resolve, reject) => { let isResolved = false; - let length = Buffer.byteLength(message, "utf16le"); - let payload = new Buffer(length + 4); + const length = Buffer.byteLength(message, "utf16le"); + const payload = new Buffer(length + 4); payload.writeInt32BE(length, 0); payload.write(message, 4, length, "utf16le"); diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 7b0d9379d8..d01f372d82 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -146,7 +146,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; await this.$debugService.debugStop(deviceIdentifier); - let applicationId = deviceAppData.appIdentifier; + const applicationId = deviceAppData.appIdentifier; const attachDebuggerOptions: IAttachDebuggerOptions = { platform: liveSyncResultInfo.deviceAppData.device.deviceInfo.platform, isEmulator: liveSyncResultInfo.deviceAppData.device.isEmulator, @@ -194,7 +194,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi } const projectData = this.$projectDataService.getProjectData(settings.projectDir); - let debugData = this.$debugDataService.createDebugData(projectData, { device: settings.deviceIdentifier }); + const debugData = this.$debugDataService.createDebugData(projectData, { device: settings.deviceIdentifier }); // Of the properties below only `buildForDevice` and `release` are currently used. // Leaving the others with placeholder values so that they may not be forgotten in future implementations. @@ -473,14 +473,14 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi } private async startWatcher(projectData: IProjectData, liveSyncData: ILiveSyncInfo): Promise { - let pattern = [APP_FOLDER_NAME]; + const pattern = [APP_FOLDER_NAME]; if (liveSyncData.watchAllFiles) { const productionDependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); pattern.push(PACKAGE_JSON_FILE_NAME); // watch only production node_module/packages same one prepare uses - for (let index in productionDependencies) { + for (const index in productionDependencies) { pattern.push(productionDependencies[index].directory); } } @@ -492,8 +492,8 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi currentWatcherInfo.watcher.close(); } - let filesToSync: string[] = [], - filesToRemove: string[] = []; + let filesToSync: string[] = []; + let filesToRemove: string[] = []; let timeoutTimer: NodeJS.Timer; const startTimeout = () => { @@ -502,10 +502,10 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi await this.addActionToChain(projectData.projectDir, async () => { if (filesToSync.length || filesToRemove.length) { try { - let currentFilesToSync = _.cloneDeep(filesToSync); + const currentFilesToSync = _.cloneDeep(filesToSync); filesToSync = []; - let currentFilesToRemove = _.cloneDeep(filesToRemove); + const currentFilesToRemove = _.cloneDeep(filesToRemove); filesToRemove = []; const allModifiedFiles = [].concat(currentFilesToSync).concat(currentFilesToRemove); @@ -550,7 +550,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi const allErrors = (err).allErrors; if (allErrors && _.isArray(allErrors)) { - for (let deviceError of allErrors) { + for (const deviceError of allErrors) { this.$logger.warn(`Unable to apply changes for device: ${deviceError.deviceIdentifier}. Error is: ${deviceError.message}.`); this.emit(LiveSyncEvents.liveSyncError, { diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index bb25daf456..27500ab6d9 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -58,7 +58,7 @@ export abstract class PlatformLiveSyncServiceBase { const syncInfo = _.merge({ device, watch: true }, liveSyncInfo); const deviceAppData = await this.getAppData(syncInfo); - let modifiedLocalToDevicePaths: Mobile.ILocalToDevicePathData[] = []; + const modifiedLocalToDevicePaths: Mobile.ILocalToDevicePathData[] = []; if (liveSyncInfo.filesToSync.length) { const filesToSync = liveSyncInfo.filesToSync; const mappedFiles = _.map(filesToSync, filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)); diff --git a/lib/services/platform-project-service-base.ts b/lib/services/platform-project-service-base.ts index b2cb7f7805..6672516cb6 100644 --- a/lib/services/platform-project-service-base.ts +++ b/lib/services/platform-project-service-base.ts @@ -11,11 +11,11 @@ export class PlatformProjectServiceBase extends EventEmitter implements IPlatfor } protected getAllNativeLibrariesForPlugin(pluginData: IPluginData, platform: string, filter: (fileName: string, _pluginPlatformsFolderPath: string) => boolean): string[] { - let pluginPlatformsFolderPath = this.getPluginPlatformsFolderPath(pluginData, platform), - nativeLibraries: string[] = []; + const pluginPlatformsFolderPath = this.getPluginPlatformsFolderPath(pluginData, platform); + let nativeLibraries: string[] = []; if (pluginPlatformsFolderPath && this.$fs.exists(pluginPlatformsFolderPath)) { - let platformsContents = this.$fs.readDirectory(pluginPlatformsFolderPath); + const platformsContents = this.$fs.readDirectory(pluginPlatformsFolderPath); nativeLibraries = _(platformsContents) .filter(platformItemName => filter(platformItemName, pluginPlatformsFolderPath)) .value(); diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index e6b546e211..206fbcc2ec 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -8,7 +8,7 @@ import { AppFilesUpdater } from "./app-files-updater"; import { attachAwaitDetach } from "../common/helpers"; import * as temp from "temp"; temp.track(); -let clui = require("clui"); +const clui = require("clui"); const buildInfoFileName = ".nsbuildinfo"; @@ -44,8 +44,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { } public async cleanPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, framworkPath?: string): Promise { - for (let platform of platforms) { - let version: string = this.getCurrentPlatformVersion(platform, projectData); + for (const platform of platforms) { + const version: string = this.getCurrentPlatformVersion(platform, projectData); let platformWithVersion: string = platform; if (version !== undefined) { @@ -61,7 +61,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { const platformsDir = projectData.platformsDir; this.$fs.ensureDirectoryExists(platformsDir); - for (let platform of platforms) { + for (const platform of platforms) { this.validatePlatform(platform, projectData); const platformPath = path.join(projectData.platformsDir, platform); @@ -74,8 +74,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { } private getCurrentPlatformVersion(platform: string, projectData: IProjectData): string { - let platformData = this.$platformsData.getPlatformData(platform, projectData); - let currentPlatformData: any = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const currentPlatformData: any = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); let version: string; if (currentPlatformData && currentPlatformData[constants.VERSION_STRING]) { version = currentPlatformData[constants.VERSION_STRING]; @@ -85,11 +85,11 @@ export class PlatformService extends EventEmitter implements IPlatformService { } private async addPlatform(platformParam: string, platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, frameworkPath?: string, nativePrepare?: INativePrepare): Promise { - let data = platformParam.split("@"), - platform = data[0].toLowerCase(), - version = data[1]; + const data = platformParam.split("@"); + const platform = data[0].toLowerCase(); + let version = data[1]; - let platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsData.getPlatformData(platform, projectData); if (version === undefined) { version = this.getCurrentPlatformVersion(platform, projectData); @@ -104,7 +104,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$logger.out("Copying template files..."); let packageToInstall = ""; - let npmOptions: IStringDictionary = { + const npmOptions: IStringDictionary = { pathToSave: path.join(projectData.platformsDir, platform), dependencyType: "save" }; @@ -120,7 +120,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { try { spinner.start(); - let downloadedPackagePath = await this.$npmInstallationManager.install(packageToInstall, projectDir, npmOptions); + const downloadedPackagePath = await this.$npmInstallationManager.install(packageToInstall, projectDir, npmOptions); let frameworkDir = path.join(downloadedPackagePath, constants.PROJECT_FRAMEWORK_FOLDER_NAME); frameworkDir = path.resolve(frameworkDir); @@ -149,7 +149,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { await this.addPlatformCoreNative(platformData, frameworkDir, installedVersion, projectData, config); } - let frameworkPackageNameData: any = { version: installedVersion }; + const frameworkPackageNameData: any = { version: installedVersion }; if (customTemplateOptions) { frameworkPackageNameData.template = customTemplateOptions.selectedTemplate; } @@ -177,7 +177,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } if (selectedTemplate) { - let tempDir = temp.mkdirSync("platform-template"); + const tempDir = temp.mkdirSync("platform-template"); this.$fs.writeJson(path.join(tempDir, constants.PACKAGE_JSON_FILE_NAME), {}); try { const npmInstallResult = await this.$npm.install(selectedTemplate, tempDir, { @@ -185,7 +185,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { frameworkPath: null, ignoreScripts: false }); - let pathToTemplate = path.join(tempDir, constants.NODE_MODULES_FOLDER_NAME, npmInstallResult.name); + const pathToTemplate = path.join(tempDir, constants.NODE_MODULES_FOLDER_NAME, npmInstallResult.name); return { selectedTemplate, pathToTemplate }; } catch (err) { this.$logger.trace("Error while trying to install specified template: ", err); @@ -201,12 +201,12 @@ export class PlatformService extends EventEmitter implements IPlatformService { return []; } - let subDirs = this.$fs.readDirectory(projectData.platformsDir); + const subDirs = this.$fs.readDirectory(projectData.platformsDir); return _.filter(subDirs, p => this.$platformsData.platformsNames.indexOf(p) > -1); } public getAvailablePlatforms(projectData: IProjectData): string[] { - let installedPlatforms = this.getInstalledPlatforms(projectData); + const installedPlatforms = this.getInstalledPlatforms(projectData); return _.filter(this.$platformsData.platformsNames, p => { return installedPlatforms.indexOf(p) < 0 && this.isPlatformSupportedForOS(p, projectData); // Only those not already installed }); @@ -234,13 +234,13 @@ export class PlatformService extends EventEmitter implements IPlatformService { if (platform) { platform = this.$mobileHelper.normalizePlatformName(platform); this.$logger.trace("Validate options for platform: " + platform); - let platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsData.getPlatformData(platform, projectData); return await platformData.platformProjectService.validateOptions(projectData.projectId, provision, teamId); } else { let valid = true; - for (let availablePlatform in this.$platformsData.availablePlatforms) { + for (const availablePlatform in this.$platformsData.availablePlatforms) { this.$logger.trace("Validate options for platform: " + availablePlatform); - let platformData = this.$platformsData.getPlatformData(availablePlatform, projectData); + const platformData = this.$platformsData.getPlatformData(availablePlatform, projectData); valid = valid && await platformData.platformProjectService.validateOptions(projectData.projectId, provision, teamId); } @@ -295,7 +295,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { private async preparePlatformCore(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, changesInfo?: IProjectChangesInfo, filesToSync?: Array, nativePrepare?: INativePrepare): Promise { this.$logger.out("Preparing project..."); - let platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsData.getPlatformData(platform, projectData); const projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: appFilesUpdaterOptions.release }); await this.preparePlatformCoreJS(platform, platformData, appFilesUpdaterOptions, projectData, platformSpecificData, changesInfo, filesToSync, projectFilesConfig); @@ -303,8 +303,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { await this.preparePlatformCoreNative(platform, platformData, appFilesUpdaterOptions, projectData, platformSpecificData, changesInfo, projectFilesConfig); } - let directoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); - let excludedDirs = [constants.APP_RESOURCES_FOLDER_NAME]; + const directoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); + const excludedDirs = [constants.APP_RESOURCES_FOLDER_NAME]; if (!changesInfo || !changesInfo.modulesChanged) { excludedDirs.push(constants.TNS_MODULES_FOLDER_NAME); } @@ -345,10 +345,10 @@ export class PlatformService extends EventEmitter implements IPlatformService { if (!changesInfo || changesInfo.modulesChanged || appFilesUpdaterOptions.bundle) { await this.$pluginsService.validate(platformData, projectData); - let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); - let lastModifiedTime = this.$fs.exists(appDestinationDirectoryPath) ? this.$fs.getFsStats(appDestinationDirectoryPath).mtime : null; + const appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); + const lastModifiedTime = this.$fs.exists(appDestinationDirectoryPath) ? this.$fs.getFsStats(appDestinationDirectoryPath).mtime : null; - let tnsModulesDestinationPath = path.join(appDestinationDirectoryPath, constants.TNS_MODULES_FOLDER_NAME); + const tnsModulesDestinationPath = path.join(appDestinationDirectoryPath, constants.TNS_MODULES_FOLDER_NAME); // Process node_modules folder await this.$nodeModulesBuilder.prepareNodeModules(tnsModulesDestinationPath, platform, lastModifiedTime, projectData, projectFilesConfig); } @@ -364,11 +364,11 @@ export class PlatformService extends EventEmitter implements IPlatformService { private async copyAppFiles(platformData: IPlatformData, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData): Promise { platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData); - let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); + const appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); // Copy app folder to native project this.$fs.ensureDirectoryExists(appDestinationDirectoryPath); - let appSourceDirectoryPath = path.join(projectData.projectDir, constants.APP_FOLDER_NAME); + const appSourceDirectoryPath = path.join(projectData.projectDir, constants.APP_FOLDER_NAME); const appUpdater = new AppFilesUpdater(appSourceDirectoryPath, appDestinationDirectoryPath, appFilesUpdaterOptions, this.$fs); appUpdater.updateApp(sourceFiles => { @@ -377,11 +377,11 @@ export class PlatformService extends EventEmitter implements IPlatformService { } private copyAppResources(platformData: IPlatformData, projectData: IProjectData): void { - let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); - let appResourcesDirectoryPath = path.join(appDestinationDirectoryPath, constants.APP_RESOURCES_FOLDER_NAME); + const appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); + const appResourcesDirectoryPath = path.join(appDestinationDirectoryPath, constants.APP_RESOURCES_FOLDER_NAME); if (this.$fs.exists(appResourcesDirectoryPath)) { platformData.platformProjectService.prepareAppResources(appResourcesDirectoryPath, projectData); - let appResourcesDestination = platformData.platformProjectService.getAppResourcesDestinationDirectoryPath(projectData); + const appResourcesDestination = platformData.platformProjectService.getAppResourcesDestinationDirectoryPath(projectData); this.$fs.ensureDirectoryExists(appResourcesDestination); shell.cp("-Rf", path.join(appResourcesDirectoryPath, platformData.normalizedPlatformName, "*"), appResourcesDestination); this.$fs.deleteDirectory(appResourcesDirectoryPath); @@ -389,11 +389,11 @@ export class PlatformService extends EventEmitter implements IPlatformService { } private async copyTnsModules(platform: string, platformData: IPlatformData, projectData: IProjectData, projectFilesConfig?: IProjectFilesConfig): Promise { - let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); - let lastModifiedTime = this.$fs.exists(appDestinationDirectoryPath) ? this.$fs.getFsStats(appDestinationDirectoryPath).mtime : null; + const appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); + const lastModifiedTime = this.$fs.exists(appDestinationDirectoryPath) ? this.$fs.getFsStats(appDestinationDirectoryPath).mtime : null; try { - let tnsModulesDestinationPath = path.join(appDestinationDirectoryPath, constants.TNS_MODULES_FOLDER_NAME); + const tnsModulesDestinationPath = path.join(appDestinationDirectoryPath, constants.TNS_MODULES_FOLDER_NAME); // Process node_modules folder await this.$nodeModulesBuilder.prepareJSNodeModules(tnsModulesDestinationPath, platform, lastModifiedTime, projectData, projectFilesConfig); } catch (error) { @@ -408,21 +408,21 @@ export class PlatformService extends EventEmitter implements IPlatformService { return true; } - let platformData = this.$platformsData.getPlatformData(platform, projectData); - let forDevice = !buildConfig || buildConfig.buildForDevice; + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const forDevice = !buildConfig || buildConfig.buildForDevice; outputPath = outputPath || (forDevice ? platformData.deviceBuildOutputPath : platformData.emulatorBuildOutputPath || platformData.deviceBuildOutputPath); if (!this.$fs.exists(outputPath)) { return true; } - let packageNames = platformData.getValidPackageNames({ isForDevice: forDevice }); - let packages = this.getApplicationPackages(outputPath, packageNames); + const packageNames = platformData.getValidPackageNames({ isForDevice: forDevice }); + const packages = this.getApplicationPackages(outputPath, packageNames); if (packages.length === 0) { return true; } - let prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); - let buildInfo = this.getBuildInfo(platform, platformData, buildConfig, outputPath); + const prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); + const buildInfo = this.getBuildInfo(platform, platformData, buildConfig, outputPath); if (!prepareInfo || !buildInfo) { return true; } @@ -470,7 +470,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { const isForDevice = this.$mobileHelper.isAndroidPlatform(platform) ? null : buildConfig && buildConfig.buildForDevice; await this.trackActionForPlatform({ action: "Build", platform, isForDevice }); - let platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsData.getPlatformData(platform, projectData); const handler = (data: any) => { this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data); this.$logger.printInfoMessageOnSameLine(data.data.toString()); @@ -485,10 +485,10 @@ export class PlatformService extends EventEmitter implements IPlatformService { } public saveBuildInfoFile(platform: string, projectDir: string, buildInfoFileDirname: string): void { - let buildInfoFile = path.join(buildInfoFileDirname, buildInfoFileName); + const buildInfoFile = path.join(buildInfoFileDirname, buildInfoFileName); - let prepareInfo = this.$projectChangesService.getPrepareInfo(platform, this.$projectDataService.getProjectData(projectDir)); - let buildInfo = { + const prepareInfo = this.$projectChangesService.getPrepareInfo(platform, this.$projectDataService.getProjectData(projectDir)); + const buildInfo = { prepareTime: prepareInfo.changesRequireBuildTime, buildTime: new Date().toString() }; @@ -497,20 +497,20 @@ export class PlatformService extends EventEmitter implements IPlatformService { } public async shouldInstall(device: Mobile.IDevice, projectData: IProjectData, outputPath?: string): Promise { - let platform = device.deviceInfo.platform; + const platform = device.deviceInfo.platform; if (!(await device.applicationManager.isApplicationInstalled(projectData.projectId))) { return true; } - let platformData = this.$platformsData.getPlatformData(platform, projectData); - let deviceBuildInfo: IBuildInfo = await this.getDeviceBuildInfo(device, projectData); - let localBuildInfo = this.getBuildInfo(platform, platformData, { buildForDevice: !device.isEmulator }, outputPath); + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const deviceBuildInfo: IBuildInfo = await this.getDeviceBuildInfo(device, projectData); + const localBuildInfo = this.getBuildInfo(platform, platformData, { buildForDevice: !device.isEmulator }, outputPath); return !localBuildInfo || !deviceBuildInfo || deviceBuildInfo.buildTime !== localBuildInfo.buildTime; } public async installApplication(device: Mobile.IDevice, buildConfig: IBuildConfig, projectData: IProjectData, packageFile?: string, outputFilePath?: string): Promise { this.$logger.out("Installing..."); - let platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); if (!packageFile) { if (this.$devicesService.isiOSSimulator(device)) { packageFile = this.getLatestApplicationPackageForEmulator(platformData, buildConfig, outputFilePath).packageName; @@ -524,9 +524,9 @@ export class PlatformService extends EventEmitter implements IPlatformService { await device.applicationManager.reinstallApplication(projectData.projectId, packageFile); if (!buildConfig.release) { - let deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); - let buildInfoFilePath = outputFilePath || this.getBuildOutputPath(device.deviceInfo.platform, platformData, { buildForDevice: !device.isEmulator }); - let appIdentifier = projectData.projectId; + const deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); + const buildInfoFilePath = outputFilePath || this.getBuildOutputPath(device.deviceInfo.platform, platformData, { buildForDevice: !device.isEmulator }); + const appIdentifier = projectData.projectId; await device.fileSystem.putFile(path.join(buildInfoFilePath, buildInfoFileName), deviceFilePath, appIdentifier); } @@ -536,12 +536,12 @@ export class PlatformService extends EventEmitter implements IPlatformService { public async deployPlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, deployOptions: IDeployPlatformOptions, projectData: IProjectData, config: IPlatformOptions): Promise { await this.preparePlatform(platform, appFilesUpdaterOptions, deployOptions.platformTemplate, projectData, config); - let options: Mobile.IDevicesServicesInitializationOptions = { + const options: Mobile.IDevicesServicesInitializationOptions = { platform: platform, deviceId: deployOptions.device, emulator: deployOptions.emulator }; await this.$devicesService.initialize(options); - let action = async (device: Mobile.IDevice): Promise => { - let buildConfig: IBuildConfig = { + const action = async (device: Mobile.IDevice): Promise => { + const buildConfig: IBuildConfig = { buildForDevice: !this.$devicesService.isiOSSimulator(device), projectDir: deployOptions.projectDir, release: deployOptions.release, @@ -554,7 +554,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { keyStorePath: deployOptions.keyStorePath, clean: deployOptions.clean }; - let shouldBuild = await this.shouldBuild(platform, projectData, buildConfig); + const shouldBuild = await this.shouldBuild(platform, projectData, buildConfig); if (shouldBuild) { await this.buildPlatform(platform, buildConfig, projectData); } else { @@ -576,7 +576,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { public async startApplication(platform: string, runOptions: IRunPlatformOptions, projectId: string): Promise { this.$logger.out("Starting..."); - let action = async (device: Mobile.IDevice) => { + const action = async (device: Mobile.IDevice) => { await device.applicationManager.startApplication(projectId); this.$logger.out(`Successfully started on device with identifier '${device.deviceInfo.identifier}'.`); }; @@ -602,7 +602,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } private async getDeviceBuildInfo(device: Mobile.IDevice, projectData: IProjectData): Promise { - let deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); + const deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); try { return JSON.parse(await this.readFile(device, deviceFilePath, projectData)); } catch (e) { @@ -612,10 +612,10 @@ export class PlatformService extends EventEmitter implements IPlatformService { private getBuildInfo(platform: string, platformData: IPlatformData, options: IBuildForDevice, buildOutputPath?: string): IBuildInfo { buildOutputPath = buildOutputPath || this.getBuildOutputPath(platform, platformData, options); - let buildInfoFile = path.join(buildOutputPath, buildInfoFileName); + const buildInfoFile = path.join(buildOutputPath, buildInfoFileName); if (this.$fs.exists(buildInfoFile)) { try { - let buildInfoTime = this.$fs.readJson(buildInfoFile); + const buildInfoTime = this.$fs.readJson(buildInfoFile); return buildInfoTime; } catch (e) { return null; @@ -629,15 +629,15 @@ export class PlatformService extends EventEmitter implements IPlatformService { await this.ensurePlatformInstalled(platform, platformTemplate, projectData, config); const appSourceDirectoryPath = path.join(projectData.projectDir, constants.APP_FOLDER_NAME); - let platformData = this.$platformsData.getPlatformData(platform, projectData); - let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); const appUpdater = new AppFilesUpdater(appSourceDirectoryPath, appDestinationDirectoryPath, appFilesUpdaterOptions, this.$fs); appUpdater.cleanDestinationApp(); } public lastOutputPath(platform: string, buildConfig: IBuildConfig, projectData: IProjectData, outputPath?: string): string { let packageFile: string; - let platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsData.getPlatformData(platform, projectData); if (buildConfig.buildForDevice) { packageFile = this.getLatestApplicationPackageForDevice(platformData, buildConfig, outputPath).packageName; } else { @@ -653,12 +653,12 @@ export class PlatformService extends EventEmitter implements IPlatformService { platform = platform.toLowerCase(); targetPath = path.resolve(targetPath); - let packageFile = this.lastOutputPath(platform, buildConfig, projectData); + const packageFile = this.lastOutputPath(platform, buildConfig, projectData); this.$fs.ensureDirectoryExists(path.dirname(targetPath)); if (this.$fs.exists(targetPath) && this.$fs.getFsStats(targetPath).isDirectory()) { - let sourceFileName = path.basename(packageFile); + const sourceFileName = path.basename(packageFile); this.$logger.trace(`Specified target path: '${targetPath}' is directory. Same filename will be used: '${sourceFileName}'.`); targetPath = path.join(targetPath, sourceFileName); } @@ -667,13 +667,13 @@ export class PlatformService extends EventEmitter implements IPlatformService { } public async removePlatforms(platforms: string[], projectData: IProjectData): Promise { - for (let platform of platforms) { + for (const platform of platforms) { this.validatePlatformInstalled(platform, projectData); - let platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsData.getPlatformData(platform, projectData); await platformData.platformProjectService.stopServices(platformData.projectRoot); - let platformDir = path.join(projectData.platformsDir, platform); + const platformDir = path.join(projectData.platformsDir, platform); this.$fs.deleteDirectory(platformDir); this.$projectDataService.removeNSProperty(projectData.projectDir, platformData.frameworkPackageName); @@ -682,8 +682,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { } public async updatePlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IPlatformOptions): Promise { - for (let platformParam of platforms) { - let data = platformParam.split("@"), + for (const platformParam of platforms) { + const data = platformParam.split("@"), platform = data[0], version = data[1]; @@ -696,9 +696,9 @@ export class PlatformService extends EventEmitter implements IPlatformService { } private getCanExecuteAction(platform: string, options: IDeviceEmulator): any { - let canExecute = (currentDevice: Mobile.IDevice): boolean => { + const canExecute = (currentDevice: Mobile.IDevice): boolean => { if (options.device && currentDevice && currentDevice.deviceInfo) { - let device = this.$devicesService.getDeviceByDeviceOption(); + const device = this.$devicesService.getDeviceByDeviceOption(); if (device && device.deviceInfo) { return currentDevice.deviceInfo.identifier === device.deviceInfo.identifier; } @@ -763,20 +763,20 @@ export class PlatformService extends EventEmitter implements IPlatformService { } public isPlatformSupportedForOS(platform: string, projectData: IProjectData): boolean { - let targetedOS = this.$platformsData.getPlatformData(platform, projectData).targetedOS; - let res = !targetedOS || targetedOS.indexOf("*") >= 0 || targetedOS.indexOf(process.platform) >= 0; + const targetedOS = this.$platformsData.getPlatformData(platform, projectData).targetedOS; + const res = !targetedOS || targetedOS.indexOf("*") >= 0 || targetedOS.indexOf(process.platform) >= 0; return res; } private isPlatformPrepared(platform: string, projectData: IProjectData): boolean { - let platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsData.getPlatformData(platform, projectData); return platformData.platformProjectService.isPlatformPrepared(platformData.projectRoot, projectData); } private getApplicationPackages(buildOutputPath: string, validPackageNames: string[]): IApplicationPackage[] { // Get latest package` that is produced from build - let candidates = this.$fs.readDirectory(buildOutputPath); - let packages = _.filter(candidates, candidate => { + const candidates = this.$fs.readDirectory(buildOutputPath); + const packages = _.filter(candidates, candidate => { return _.includes(validPackageNames, candidate); }).map(currentPackage => { currentPackage = path.join(buildOutputPath, currentPackage); @@ -793,7 +793,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { private getLatestApplicationPackage(buildOutputPath: string, validPackageNames: string[]): IApplicationPackage { let packages = this.getApplicationPackages(buildOutputPath, validPackageNames); if (packages.length === 0) { - let packageExtName = path.extname(validPackageNames[0]); + const packageExtName = path.extname(validPackageNames[0]); this.$errors.fail("No %s found in %s directory", packageExtName, buildOutputPath); } @@ -811,19 +811,19 @@ export class PlatformService extends EventEmitter implements IPlatformService { } private async updatePlatform(platform: string, version: string, platformTemplate: string, projectData: IProjectData, config: IPlatformOptions): Promise { - let platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsData.getPlatformData(platform, projectData); - let data = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); - let currentVersion = data && data.version ? data.version : "0.2.0"; + const data = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); + const currentVersion = data && data.version ? data.version : "0.2.0"; let newVersion = version === constants.PackageVersion.NEXT ? await this.$npmInstallationManager.getNextVersion(platformData.frameworkPackageName) : version || await this.$npmInstallationManager.getLatestCompatibleVersion(platformData.frameworkPackageName); - let installedModuleDir = await this.$npmInstallationManager.install(platformData.frameworkPackageName, projectData.projectDir, { version: newVersion, dependencyType: "save" }); - let cachedPackageData = this.$fs.readJson(path.join(installedModuleDir, "package.json")); + const installedModuleDir = await this.$npmInstallationManager.install(platformData.frameworkPackageName, projectData.projectDir, { version: newVersion, dependencyType: "save" }); + const cachedPackageData = this.$fs.readJson(path.join(installedModuleDir, "package.json")); newVersion = (cachedPackageData && cachedPackageData.version) || newVersion; - let canUpdate = platformData.platformProjectService.canUpdatePlatform(installedModuleDir, projectData); + const canUpdate = platformData.platformProjectService.canUpdatePlatform(installedModuleDir, projectData); await this.$npm.uninstall(platformData.frameworkPackageName, { save: true }, projectData.projectDir); if (canUpdate) { if (!semver.valid(newVersion)) { @@ -854,7 +854,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { // TODO: Remove this method from here. It has nothing to do with platform public async readFile(device: Mobile.IDevice, deviceFilePath: string, projectData: IProjectData): Promise { temp.track(); - let uniqueFilePath = temp.path({ suffix: ".tmp" }); + const uniqueFilePath = temp.path({ suffix: ".tmp" }); try { await device.fileSystem.getFile(deviceFilePath, projectData.projectId, uniqueFilePath); } catch (e) { @@ -862,7 +862,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } if (this.$fs.exists(uniqueFilePath)) { - let text = this.$fs.readText(uniqueFilePath); + const text = this.$fs.readText(uniqueFilePath); shell.rm(uniqueFilePath); return text; } diff --git a/lib/services/plugin-variables-service.ts b/lib/services/plugin-variables-service.ts index 2a85e515bc..ccb822fcc5 100644 --- a/lib/services/plugin-variables-service.ts +++ b/lib/services/plugin-variables-service.ts @@ -14,9 +14,9 @@ export class PluginVariablesService implements IPluginVariablesService { } public async savePluginVariablesInProjectFile(pluginData: IPluginData, projectData: IProjectData): Promise { - let values = Object.create(null); + const values = Object.create(null); await this.executeForAllPluginVariables(pluginData, async (pluginVariableData: IPluginVariableData) => { - let pluginVariableValue = await this.getPluginVariableValue(pluginVariableData); + const pluginVariableValue = await this.getPluginVariableValue(pluginVariableData); this.ensurePluginVariableValue(pluginVariableValue, `Unable to find value for ${pluginVariableData.name} plugin variable from ${pluginData.name} plugin. Ensure the --var option is specified or the plugin variable has default value.`); values[pluginVariableData.name] = pluginVariableValue; }, projectData); @@ -41,8 +41,8 @@ export class PluginVariablesService implements IPluginVariablesService { } public interpolateAppIdentifier(pluginConfigurationFilePath: string, projectData: IProjectData): void { - let pluginConfigurationFileContent = this.$fs.readText(pluginConfigurationFilePath); - let newContent = this.interpolateCore("nativescript.id", projectData.projectId, pluginConfigurationFileContent); + const pluginConfigurationFileContent = this.$fs.readText(pluginConfigurationFilePath); + const newContent = this.interpolateCore("nativescript.id", projectData.projectId, pluginConfigurationFileContent); this.$fs.writeFile(pluginConfigurationFilePath, newContent); } @@ -62,20 +62,20 @@ export class PluginVariablesService implements IPluginVariablesService { } private async getPluginVariableValue(pluginVariableData: IPluginVariableData): Promise { - let pluginVariableName = pluginVariableData.name; + const pluginVariableName = pluginVariableData.name; let value = this.$pluginVariablesHelper.getPluginVariableFromVarOption(pluginVariableName); if (value) { value = value[pluginVariableName]; } else { value = pluginVariableData.defaultValue; if (!value && helpers.isInteractive()) { - let promptSchema = { + const promptSchema = { name: pluginVariableName, type: "input", message: `Enter value for ${pluginVariableName} variable:`, validate: (val: string) => !!val ? true : 'Please enter a value!' }; - let promptData = await this.$prompter.get([promptSchema]); + const promptData = await this.$prompter.get([promptSchema]); value = promptData[pluginVariableName]; } } @@ -84,13 +84,13 @@ export class PluginVariablesService implements IPluginVariablesService { } private async executeForAllPluginVariables(pluginData: IPluginData, action: (pluginVariableData: IPluginVariableData) => Promise, projectData: IProjectData): Promise { - let pluginVariables = pluginData.pluginVariables; - let pluginVariablesNames = _.keys(pluginVariables); + const pluginVariables = pluginData.pluginVariables; + const pluginVariablesNames = _.keys(pluginVariables); await Promise.all(_.map(pluginVariablesNames, pluginVariableName => action(this.createPluginVariableData(pluginData, pluginVariableName, projectData)))); } private createPluginVariableData(pluginData: IPluginData, pluginVariableName: string, projectData: IProjectData): IPluginVariableData { - let variableData = pluginData.pluginVariables[pluginVariableName]; + const variableData = pluginData.pluginVariables[pluginVariableName]; variableData.name = pluginVariableName; diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index 0b460da5db..7cd9e5b280 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -45,15 +45,15 @@ export class PluginsService implements IPluginsService { plugin = possiblePackageName; } - let name = (await this.$npm.install(plugin, projectData.projectDir, this.npmInstallOptions)).name; - let pathToRealNpmPackageJson = path.join(projectData.projectDir, "node_modules", name, "package.json"); - let realNpmPackageJson = this.$fs.readJson(pathToRealNpmPackageJson); + const name = (await this.$npm.install(plugin, projectData.projectDir, this.npmInstallOptions)).name; + const pathToRealNpmPackageJson = path.join(projectData.projectDir, "node_modules", name, "package.json"); + const realNpmPackageJson = this.$fs.readJson(pathToRealNpmPackageJson); if (realNpmPackageJson.nativescript) { - let pluginData = this.convertToPluginData(realNpmPackageJson, projectData.projectDir); + const pluginData = this.convertToPluginData(realNpmPackageJson, projectData.projectDir); // Validate - let action = async (pluginDestinationPath: string, platform: string, platformData: IPlatformData): Promise => { + const action = async (pluginDestinationPath: string, platform: string, platformData: IPlatformData): Promise => { this.isPluginDataValidForPlatform(pluginData, platform, projectData); }; @@ -77,8 +77,8 @@ export class PluginsService implements IPluginsService { } public async remove(pluginName: string, projectData: IProjectData): Promise { - let removePluginNativeCodeAction = async (modulesDestinationPath: string, platform: string, platformData: IPlatformData): Promise => { - let pluginData = this.convertToPluginData(this.getNodeModuleData(pluginName, projectData.projectDir), projectData.projectDir); + const removePluginNativeCodeAction = async (modulesDestinationPath: string, platform: string, platformData: IPlatformData): Promise => { + const pluginData = this.convertToPluginData(this.getNodeModuleData(pluginName, projectData.projectDir), projectData.projectDir); await platformData.platformProjectService.removePluginNativeCode(pluginData, projectData); }; @@ -89,7 +89,7 @@ export class PluginsService implements IPluginsService { await this.executeNpmCommand(PluginsService.UNINSTALL_COMMAND_NAME, pluginName, projectData); let showMessage = true; - let action = async (modulesDestinationPath: string, platform: string, platformData: IPlatformData): Promise => { + const action = async (modulesDestinationPath: string, platform: string, platformData: IPlatformData): Promise => { shelljs.rm("-rf", path.join(modulesDestinationPath, pluginName)); this.$logger.out(`Successfully removed plugin ${pluginName} for ${platform}.`); @@ -109,10 +109,10 @@ export class PluginsService implements IPluginsService { public async prepare(dependencyData: IDependencyData, platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise { platform = platform.toLowerCase(); - let platformData = this.$platformsData.getPlatformData(platform, projectData); - let pluginData = this.convertToPluginData(dependencyData, projectData.projectDir); + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const pluginData = this.convertToPluginData(dependencyData, projectData.projectDir); - let appFolderExists = this.$fs.exists(path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME)); + const appFolderExists = this.$fs.exists(path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME)); if (appFolderExists) { this.preparePluginScripts(pluginData, platform, projectData, projectFilesConfig); await this.preparePluginNativeCode(pluginData, platform, projectData); @@ -123,9 +123,9 @@ export class PluginsService implements IPluginsService { } public preparePluginScripts(pluginData: IPluginData, platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): void { - let platformData = this.$platformsData.getPlatformData(platform, projectData); - let pluginScriptsDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, "tns_modules"); - let scriptsDestinationExists = this.$fs.exists(pluginScriptsDestinationPath); + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const pluginScriptsDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, "tns_modules"); + const scriptsDestinationExists = this.$fs.exists(pluginScriptsDestinationPath); if (!scriptsDestinationExists) { //tns_modules/ doesn't exist. Assuming we're running a bundled prepare. return; @@ -140,7 +140,7 @@ export class PluginsService implements IPluginsService { } public async preparePluginNativeCode(pluginData: IPluginData, platform: string, projectData: IProjectData): Promise { - let platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsData.getPlatformData(platform, projectData); pluginData.pluginPlatformsFolderPath = (_platform: string) => path.join(pluginData.fullPath, "platforms", _platform); await platformData.platformProjectService.preparePluginNativeCode(pluginData, projectData); @@ -154,13 +154,13 @@ export class PluginsService implements IPluginsService { _(installedDependencies) .filter(dependencyName => _.startsWith(dependencyName, "@")) .each(scopedDependencyDir => { - let contents = this.$fs.readDirectory(path.join(this.getNodeModulesPath(projectData.projectDir), scopedDependencyDir)); + const contents = this.$fs.readDirectory(path.join(this.getNodeModulesPath(projectData.projectDir), scopedDependencyDir)); installedDependencies = installedDependencies.concat(contents.map(dependencyName => `${scopedDependencyDir}/${dependencyName}`)); }); - let packageJsonContent = this.$fs.readJson(this.getPackageJsonFilePath(projectData.projectDir)); - let allDependencies = _.keys(packageJsonContent.dependencies).concat(_.keys(packageJsonContent.devDependencies)); - let notInstalledDependencies = _.difference(allDependencies, installedDependencies); + const packageJsonContent = this.$fs.readJson(this.getPackageJsonFilePath(projectData.projectDir)); + const allDependencies = _.keys(packageJsonContent.dependencies).concat(_.keys(packageJsonContent.devDependencies)); + const notInstalledDependencies = _.difference(allDependencies, installedDependencies); if (this.$options.force || notInstalledDependencies.length) { this.$logger.trace("Npm install will be called from CLI. Force option is: ", this.$options.force, " Not installed dependencies are: ", notInstalledDependencies); await this.$npm.install(projectData.projectDir, projectData.projectDir, { @@ -173,15 +173,15 @@ export class PluginsService implements IPluginsService { } public async getAllInstalledPlugins(projectData: IProjectData): Promise { - let nodeModules = (await this.getAllInstalledModules(projectData)).map(nodeModuleData => this.convertToPluginData(nodeModuleData, projectData.projectDir)); + const nodeModules = (await this.getAllInstalledModules(projectData)).map(nodeModuleData => this.convertToPluginData(nodeModuleData, projectData.projectDir)); return _.filter(nodeModules, nodeModuleData => nodeModuleData && nodeModuleData.isPlugin); } public getDependenciesFromPackageJson(projectDir: string): IPackageJsonDepedenciesResult { - let packageJson = this.$fs.readJson(this.getPackageJsonFilePath(projectDir)); - let dependencies: IBasePluginData[] = this.getBasicPluginInformation(packageJson.dependencies); + const packageJson = this.$fs.readJson(this.getPackageJsonFilePath(projectDir)); + const dependencies: IBasePluginData[] = this.getBasicPluginInformation(packageJson.dependencies); - let devDependencies: IBasePluginData[] = this.getBasicPluginInformation(packageJson.devDependencies); + const devDependencies: IBasePluginData[] = this.getBasicPluginInformation(packageJson.devDependencies); return { dependencies, @@ -209,7 +209,7 @@ export class PluginsService implements IPluginsService { } private getDependencies(projectDir: string): string[] { - let packageJsonFilePath = this.getPackageJsonFilePath(projectDir); + const packageJsonFilePath = this.getPackageJsonFilePath(projectDir); return _.keys(require(packageJsonFilePath).dependencies); } @@ -218,7 +218,7 @@ export class PluginsService implements IPluginsService { module = this.getPackageJsonFilePathForModule(module, projectDir); } - let data = this.$fs.readJson(module); + const data = this.$fs.readJson(module); return { name: data.name, version: data.version, @@ -229,13 +229,13 @@ export class PluginsService implements IPluginsService { } public convertToPluginData(cacheData: any, projectDir: string): IPluginData { - let pluginData: any = {}; + const pluginData: any = {}; pluginData.name = cacheData.name; pluginData.version = cacheData.version; pluginData.fullPath = cacheData.directory || path.dirname(this.getPackageJsonFilePathForModule(cacheData.name, projectDir)); pluginData.isPlugin = !!cacheData.nativescript || !!cacheData.moduleInfo; pluginData.pluginPlatformsFolderPath = (platform: string) => path.join(pluginData.fullPath, "platforms", platform); - let data = cacheData.nativescript || cacheData.moduleInfo; + const data = cacheData.nativescript || cacheData.moduleInfo; if (pluginData.isPlugin) { pluginData.platformsData = data.platforms; @@ -253,7 +253,7 @@ export class PluginsService implements IPluginsService { private async getAllInstalledModules(projectData: IProjectData): Promise { await this.ensure(projectData); - let nodeModules = this.getDependencies(projectData.projectDir); + const nodeModules = this.getDependencies(projectData.projectDir); return _.map(nodeModules, nodeModuleName => this.getNodeModuleData(nodeModuleName, projectData.projectDir)); } @@ -272,19 +272,19 @@ export class PluginsService implements IPluginsService { } private async executeForAllInstalledPlatforms(action: (_pluginDestinationPath: string, pl: string, _platformData: IPlatformData) => Promise, projectData: IProjectData): Promise { - let availablePlatforms = _.keys(this.$platformsData.availablePlatforms); - for (let platform of availablePlatforms) { - let isPlatformInstalled = this.$fs.exists(path.join(projectData.platformsDir, platform.toLowerCase())); + const availablePlatforms = _.keys(this.$platformsData.availablePlatforms); + for (const platform of availablePlatforms) { + const isPlatformInstalled = this.$fs.exists(path.join(projectData.platformsDir, platform.toLowerCase())); if (isPlatformInstalled) { - let platformData = this.$platformsData.getPlatformData(platform.toLowerCase(), projectData); - let pluginDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, "tns_modules"); + const platformData = this.$platformsData.getPlatformData(platform.toLowerCase(), projectData); + const pluginDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, "tns_modules"); await action(pluginDestinationPath, platform.toLowerCase(), platformData); } } } private getInstalledFrameworkVersion(platform: string, projectData: IProjectData): string { - let platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsData.getPlatformData(platform, projectData); const frameworkData = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); return frameworkData.version; } @@ -292,10 +292,10 @@ export class PluginsService implements IPluginsService { private isPluginDataValidForPlatform(pluginData: IPluginData, platform: string, projectData: IProjectData): boolean { let isValid = true; - let installedFrameworkVersion = this.getInstalledFrameworkVersion(platform, projectData); - let pluginPlatformsData = pluginData.platformsData; + const installedFrameworkVersion = this.getInstalledFrameworkVersion(platform, projectData); + const pluginPlatformsData = pluginData.platformsData; if (pluginPlatformsData) { - let pluginVersion = (pluginPlatformsData)[platform]; + const pluginVersion = (pluginPlatformsData)[platform]; if (!pluginVersion) { this.$logger.warn(`${pluginData.name} is not supported for ${platform}.`); isValid = false; diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index e9982cae43..1baafc517c 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -55,7 +55,7 @@ export class ProjectChangesService implements IProjectChangesService { } public async checkForChanges(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): Promise { - let platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsData.getPlatformData(platform, projectData); this._changesInfo = new ProjectChangesInfo(); if (!this.ensurePrepareInfo(platform, projectData, projectChangesOptions)) { this._newFiles = 0; @@ -72,7 +72,7 @@ export class ProjectChangesService implements IProjectChangesService { if (this._newFiles > 0) { this._changesInfo.modulesChanged = true; } - let platformResourcesDir = path.join(projectData.appResourcesDirectoryPath, platformData.normalizedPlatformName); + const platformResourcesDir = path.join(projectData.appResourcesDirectoryPath, platformData.normalizedPlatformName); if (platform === this.$devicePlatformsConstants.iOS.toLowerCase()) { this._changesInfo.configChanged = this.filesChanged([path.join(platformResourcesDir, platformData.configurationFileName), path.join(platformResourcesDir, "LaunchScreen.storyboard"), @@ -86,7 +86,7 @@ export class ProjectChangesService implements IProjectChangesService { } } - let projectService = platformData.platformProjectService; + const projectService = platformData.platformProjectService; await projectService.checkForChanges(this._changesInfo, projectChangesOptions, projectData); if (projectChangesOptions.bundle !== this._prepareInfo.bundle || projectChangesOptions.release !== this._prepareInfo.release) { @@ -119,13 +119,13 @@ export class ProjectChangesService implements IProjectChangesService { } public getPrepareInfoFilePath(platform: string, projectData: IProjectData): string { - let platformData = this.$platformsData.getPlatformData(platform, projectData); - let prepareInfoFilePath = path.join(platformData.projectRoot, prepareInfoFileName); + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const prepareInfoFilePath = path.join(platformData.projectRoot, prepareInfoFileName); return prepareInfoFilePath; } public getPrepareInfo(platform: string, projectData: IProjectData): IPrepareInfo { - let prepareInfoFilePath = this.getPrepareInfoFilePath(platform, projectData); + const prepareInfoFilePath = this.getPrepareInfoFilePath(platform, projectData); let prepareInfo: IPrepareInfo = null; if (this.$fs.exists(prepareInfoFilePath)) { try { @@ -138,7 +138,7 @@ export class ProjectChangesService implements IProjectChangesService { } public savePrepareInfo(platform: string, projectData: IProjectData): void { - let prepareInfoFilePath = this.getPrepareInfoFilePath(platform, projectData); + const prepareInfoFilePath = this.getPrepareInfoFilePath(platform, projectData); this.$fs.writeJson(prepareInfoFilePath, this._prepareInfo); } @@ -157,8 +157,8 @@ export class ProjectChangesService implements IProjectChangesService { projectChangesOptions.nativePlatformStatus : this._prepareInfo.nativePlatformStatus || projectChangesOptions.nativePlatformStatus; - let platformData = this.$platformsData.getPlatformData(platform, projectData); - let prepareInfoFile = path.join(platformData.projectRoot, prepareInfoFileName); + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const prepareInfoFile = path.join(platformData.projectRoot, prepareInfoFileName); this._outputProjectMtime = this.$fs.getFsStats(prepareInfoFile).mtime.getTime(); this._outputProjectCTime = this.$fs.getFsStats(prepareInfoFile).ctime.getTime(); return false; @@ -206,9 +206,9 @@ export class ProjectChangesService implements IProjectChangesService { } private filesChanged(files: string[]): boolean { - for (let file of files) { + for (const file of files) { if (this.$fs.exists(file)) { - let fileStats = this.$fs.getFsStats(file); + const fileStats = this.$fs.getFsStats(file); if (fileStats.mtime.getTime() >= this._outputProjectMtime || fileStats.ctime.getTime() >= this._outputProjectCTime) { return true; } @@ -224,20 +224,20 @@ export class ProjectChangesService implements IProjectChangesService { return true; } - let files = this.$fs.readDirectory(dir); - for (let file of files) { - let filePath = path.join(dir, file); + const files = this.$fs.readDirectory(dir); + for (const file of files) { + const filePath = path.join(dir, file); if (filePath === skipDir) { continue; } const fileStats = this.$fs.getFsStats(filePath); - let changed = this.isFileModified(fileStats, filePath); + const changed = this.isFileModified(fileStats, filePath); if (changed) { if (processFunc) { this._newFiles++; - let filePathRelative = path.relative(projectData.projectDir, filePath); + const filePathRelative = path.relative(projectData.projectDir, filePath); if (processFunc.call(this, filePathRelative, projectData)) { return true; } @@ -261,7 +261,7 @@ export class ProjectChangesService implements IProjectChangesService { filePathStat.ctime.getTime() >= this._outputProjectCTime; if (!changed) { - let lFileStats = this.$fs.getLsStats(filePath); + const lFileStats = this.$fs.getLsStats(filePath); changed = lFileStats.mtime.getTime() >= this._outputProjectMtime || lFileStats.ctime.getTime() >= this._outputProjectCTime; } @@ -273,7 +273,7 @@ export class ProjectChangesService implements IProjectChangesService { if (path.basename(file) === "package.json") { return true; } - let projectDir = projectData.projectDir; + const projectDir = projectData.projectDir; if (_.startsWith(path.join(projectDir, file), projectData.appResourcesDirectoryPath)) { return true; } @@ -281,9 +281,9 @@ export class ProjectChangesService implements IProjectChangesService { let filePath = file; while (filePath !== NODE_MODULES_FOLDER_NAME) { filePath = path.dirname(filePath); - let fullFilePath = path.join(projectDir, path.join(filePath, "package.json")); + const fullFilePath = path.join(projectDir, path.join(filePath, "package.json")); if (this.$fs.exists(fullFilePath)) { - let json = this.$fs.readJson(fullFilePath); + const json = this.$fs.readJson(fullFilePath); if (json["nativescript"] && _.startsWith(file, path.join(filePath, "platforms"))) { return true; } diff --git a/lib/services/project-data-service.ts b/lib/services/project-data-service.ts index 29f1f964c7..46703f552d 100644 --- a/lib/services/project-data-service.ts +++ b/lib/services/project-data-service.ts @@ -63,7 +63,7 @@ export class ProjectDataService implements IProjectDataService { const props = dottedPropertyName.split("."); let result = jsonData[props.shift()]; - for (let prop of props) { + for (const prop of props) { result = result[prop]; } @@ -74,7 +74,7 @@ export class ProjectDataService implements IProjectDataService { const projectFileInfo = this.getProjectFileData(projectDir); const props = key.split("."); - let data: any = projectFileInfo.projectData; + const data: any = projectFileInfo.projectData; let currentData = data; _.each(props, (prop, index: number) => { @@ -92,7 +92,7 @@ export class ProjectDataService implements IProjectDataService { private removeProperty(projectDir: string, propertyName: string): void { const projectFileInfo = this.getProjectFileData(projectDir); - let data: any = projectFileInfo.projectData; + const data: any = projectFileInfo.projectData; let currentData = data; const props = propertyName.split("."); const propertyToDelete = props.splice(props.length - 1, 1)[0]; diff --git a/lib/services/project-name-service.ts b/lib/services/project-name-service.ts index 3b685d9249..2346a52036 100644 --- a/lib/services/project-name-service.ts +++ b/lib/services/project-name-service.ts @@ -15,7 +15,7 @@ export class ProjectNameService implements IProjectNameService { return await this.promptForNewName("The project name is invalid.", projectName, validateOptions); } - let userCanInteract = isInteractive(); + const userCanInteract = isInteractive(); if (!this.checkIfNameStartsWithLetter(projectName)) { if (!userCanInteract) { @@ -37,7 +37,7 @@ export class ProjectNameService implements IProjectNameService { } private checkIfNameStartsWithLetter(projectName: string): boolean { - let startsWithLetterExpression = /^[a-zA-Z]/; + const startsWithLetterExpression = /^[a-zA-Z]/; return startsWithLetterExpression.test(projectName); } @@ -46,7 +46,7 @@ export class ProjectNameService implements IProjectNameService { return projectName; } - let newProjectName = await this.$prompter.getString("Enter the new project name:"); + const newProjectName = await this.$prompter.getString("Enter the new project name:"); return await this.ensureValidName(newProjectName, validateOptions); } diff --git a/lib/services/project-service.ts b/lib/services/project-service.ts index f75f5b1d57..fcb50d79ca 100644 --- a/lib/services/project-service.ts +++ b/lib/services/project-service.ts @@ -19,8 +19,8 @@ export class ProjectService implements IProjectService { @exported("projectService") public async createProject(projectOptions: IProjectSettings): Promise { - let projectName = projectOptions.projectName, - selectedTemplate = projectOptions.template; + let projectName = projectOptions.projectName; + let selectedTemplate = projectOptions.template; if (!projectName) { this.$errors.fail("You must specify when creating a new project."); @@ -37,7 +37,7 @@ export class ProjectService implements IProjectService { this.$errors.fail("Path already exists and is not empty %s", projectDir); } - let projectId = projectOptions.appId || this.$projectHelper.generateDefaultAppId(projectName, constants.DEFAULT_APP_IDENTIFIER_PREFIX); + const projectId = projectOptions.appId || this.$projectHelper.generateDefaultAppId(projectName, constants.DEFAULT_APP_IDENTIFIER_PREFIX); this.createPackageJson(projectDir, projectId); this.$logger.trace(`Creating a new NativeScript project with name ${projectName} and id ${projectId} at location ${projectDir}`); @@ -46,12 +46,12 @@ export class ProjectService implements IProjectService { } try { - let templatePath = await this.$projectTemplatesService.prepareTemplate(selectedTemplate, projectDir); + const templatePath = await this.$projectTemplatesService.prepareTemplate(selectedTemplate, projectDir); await this.extractTemplate(projectDir, templatePath); await this.ensureAppResourcesExist(projectDir); - let templatePackageJsonData = this.getDataFromJson(templatePath); + const templatePackageJsonData = this.getDataFromJson(templatePath); if (!(templatePackageJsonData && templatePackageJsonData.dependencies && templatePackageJsonData.dependencies[constants.TNS_CORE_MODULES_NAME])) { await this.$npmInstallationManager.install(constants.TNS_CORE_MODULES_NAME, projectDir, { dependencyType: "save" }); @@ -66,7 +66,7 @@ export class ProjectService implements IProjectService { ignoreScripts: projectOptions.ignoreScripts }); - let templatePackageJson = this.$fs.readJson(path.join(templatePath, "package.json")); + const templatePackageJson = this.$fs.readJson(path.join(templatePath, "package.json")); await this.$npm.uninstall(templatePackageJson.name, { save: true }, projectDir); } catch (err) { this.$fs.deleteDirectory(projectDir); @@ -87,9 +87,9 @@ export class ProjectService implements IProjectService { } private getDataFromJson(templatePath: string): any { - let templatePackageJsonPath = path.join(templatePath, constants.PACKAGE_JSON_FILE_NAME); + const templatePackageJsonPath = path.join(templatePath, constants.PACKAGE_JSON_FILE_NAME); if (this.$fs.exists(templatePackageJsonPath)) { - let templatePackageJsonData = this.$fs.readJson(templatePackageJsonPath); + const templatePackageJsonData = this.$fs.readJson(templatePackageJsonPath); return templatePackageJsonData; } else { this.$logger.trace(`Template ${templatePath} does not have ${constants.PACKAGE_JSON_FILE_NAME} file.`); @@ -101,7 +101,7 @@ export class ProjectService implements IProjectService { private async extractTemplate(projectDir: string, realTemplatePath: string): Promise { this.$fs.ensureDirectoryExists(projectDir); - let appDestinationPath = path.join(projectDir, constants.APP_FOLDER_NAME); + const appDestinationPath = path.join(projectDir, constants.APP_FOLDER_NAME); this.$fs.createDirectory(appDestinationPath); this.$logger.trace(`Copying application from '${realTemplatePath}' into '${appDestinationPath}'.`); @@ -111,14 +111,14 @@ export class ProjectService implements IProjectService { } private async ensureAppResourcesExist(projectDir: string): Promise { - let appPath = path.join(projectDir, constants.APP_FOLDER_NAME), + const appPath = path.join(projectDir, constants.APP_FOLDER_NAME), appResourcesDestinationPath = path.join(appPath, constants.APP_RESOURCES_FOLDER_NAME); if (!this.$fs.exists(appResourcesDestinationPath)) { this.$fs.createDirectory(appResourcesDestinationPath); // the template installed doesn't have App_Resources -> get from a default template - let defaultTemplateName = constants.RESERVED_TEMPLATE_NAMES["default"]; + const defaultTemplateName = constants.RESERVED_TEMPLATE_NAMES["default"]; await this.$npm.install(defaultTemplateName, projectDir, { save: true, disableNpmInstall: false, @@ -126,7 +126,7 @@ export class ProjectService implements IProjectService { ignoreScripts: false }); - let defaultTemplateAppResourcesPath = path.join(projectDir, constants.NODE_MODULES_FOLDER_NAME, + const defaultTemplateAppResourcesPath = path.join(projectDir, constants.NODE_MODULES_FOLDER_NAME, defaultTemplateName, constants.APP_RESOURCES_FOLDER_NAME); if (this.$fs.exists(defaultTemplateAppResourcesPath)) { @@ -138,8 +138,8 @@ export class ProjectService implements IProjectService { } private removeMergedDependencies(projectDir: string, templatePackageJsonData: any): void { - let extractedTemplatePackageJsonPath = path.join(projectDir, constants.APP_FOLDER_NAME, constants.PACKAGE_JSON_FILE_NAME); - for (let key in templatePackageJsonData) { + const extractedTemplatePackageJsonPath = path.join(projectDir, constants.APP_FOLDER_NAME, constants.PACKAGE_JSON_FILE_NAME); + for (const key in templatePackageJsonData) { if (constants.PackageJsonKeysToKeep.indexOf(key) === -1) { delete templatePackageJsonData[key]; } @@ -151,8 +151,8 @@ export class ProjectService implements IProjectService { private mergeProjectAndTemplateProperties(projectDir: string, templatePackageJsonData: any): void { if (templatePackageJsonData) { - let projectPackageJsonPath = path.join(projectDir, constants.PACKAGE_JSON_FILE_NAME); - let projectPackageJsonData = this.$fs.readJson(projectPackageJsonPath); + const projectPackageJsonPath = path.join(projectDir, constants.PACKAGE_JSON_FILE_NAME); + const projectPackageJsonData = this.$fs.readJson(projectPackageJsonPath); this.$logger.trace("Initial project package.json data: ", projectPackageJsonData); if (projectPackageJsonData.dependencies || templatePackageJsonData.dependencies) { projectPackageJsonData.dependencies = this.mergeDependencies(projectPackageJsonData.dependencies, templatePackageJsonData.dependencies); @@ -174,8 +174,8 @@ export class ProjectService implements IProjectService { this.$logger.trace("Merging dependencies, projectDependencies are: ", projectDependencies, " templateDependencies are: ", templateDependencies); projectDependencies = projectDependencies || {}; _.extend(projectDependencies, templateDependencies || {}); - let sortedDeps: IStringDictionary = {}; - let dependenciesNames = _.keys(projectDependencies).sort(); + const sortedDeps: IStringDictionary = {}; + const dependenciesNames = _.keys(projectDependencies).sort(); _.each(dependenciesNames, (key: string) => { sortedDeps[key] = projectDependencies[key]; }); diff --git a/lib/services/project-templates-service.ts b/lib/services/project-templates-service.ts index fb5292384d..d3cb0a0489 100644 --- a/lib/services/project-templates-service.ts +++ b/lib/services/project-templates-service.ts @@ -12,7 +12,7 @@ export class ProjectTemplatesService implements IProjectTemplatesService { public async prepareTemplate(originalTemplateName: string, projectDir: string): Promise { // support @ syntax - let data = originalTemplateName.split("@"), + const data = originalTemplateName.split("@"), name = data[0], version = data[1]; diff --git a/lib/services/subscription-service.ts b/lib/services/subscription-service.ts index 8a9e18a6cf..73778df593 100644 --- a/lib/services/subscription-service.ts +++ b/lib/services/subscription-service.ts @@ -12,7 +12,7 @@ export class SubscriptionService implements ISubscriptionService { public async subscribeForNewsletter(): Promise { if (await this.shouldAskForEmail()) { this.$logger.out("Leave your e-mail address here to subscribe for NativeScript newsletter and product updates, tips and tricks:"); - let email = await this.getEmail("(press Enter for blank)"); + const email = await this.getEmail("(press Enter for blank)"); await this.$userSettingsService.saveSetting("EMAIL_REGISTERED", true); await this.sendEmail(email); } @@ -28,7 +28,7 @@ export class SubscriptionService implements ISubscriptionService { } private async getEmail(prompt: string, options?: IPrompterOptions): Promise { - let schema: IPromptSchema = { + const schema: IPromptSchema = { message: prompt, type: "input", name: "inputEmail", @@ -41,20 +41,20 @@ export class SubscriptionService implements ISubscriptionService { } }; - let result = await this.$prompter.get([schema]); + const result = await this.$prompter.get([schema]); return result.inputEmail; } private async sendEmail(email: string): Promise { if (email) { - let postData = queryString.stringify({ + const postData = queryString.stringify({ 'elqFormName': "dev_uins_cli", 'elqSiteID': '1325', 'emailAddress': email, 'elqCookieWrite': '0' }); - let options = { + const options = { url: 'https://s1325.t.eloqua.com/e/f2', method: 'POST', headers: { diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index eaee3ee9cd..b3d4221d26 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -37,24 +37,24 @@ class TestExecutionService implements ITestExecutionService { await new Promise((resolve, reject) => { process.on('message', async (launcherConfig: any) => { try { - let platformData = this.$platformsData.getPlatformData(platform.toLowerCase(), projectData); - let projectDir = projectData.projectDir; + const platformData = this.$platformsData.getPlatformData(platform.toLowerCase(), projectData); + const projectDir = projectData.projectDir; await this.$devicesService.initialize({ platform: platform, deviceId: this.$options.device, emulator: this.$options.emulator }); await this.$devicesService.detectCurrentlyAttachedDevices(); - let projectFilesPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); + const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); - let configOptions: IKarmaConfigOptions = JSON.parse(launcherConfig); + const configOptions: IKarmaConfigOptions = JSON.parse(launcherConfig); this.$options.debugBrk = configOptions.debugBrk; this.$options.debugTransport = configOptions.debugTransport; - let configJs = this.generateConfig(this.$options.port.toString(), configOptions); + const configJs = this.generateConfig(this.$options.port.toString(), configOptions); this.$fs.writeFile(path.join(projectDir, TestExecutionService.CONFIG_FILE_NAME), configJs); - let socketIoJsUrl = `http://localhost:${this.$options.port}/socket.io/socket.io.js`; - let socketIoJs = (await this.$httpClient.httpRequest(socketIoJsUrl)).body; + const socketIoJsUrl = `http://localhost:${this.$options.port}/socket.io/socket.io.js`; + const socketIoJs = (await this.$httpClient.httpRequest(socketIoJsUrl)).body; this.$fs.writeFile(path.join(projectDir, TestExecutionService.SOCKETIO_JS_FILE_NAME), socketIoJs); const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release }; @@ -121,7 +121,7 @@ class TestExecutionService implements ITestExecutionService { if (this.$options.debugBrk) { this.$logger.info('Starting debugger...'); - let debugService: IPlatformDebugService = this.$injector.resolve(`${platform}DebugService`); + const debugService: IPlatformDebugService = this.$injector.resolve(`${platform}DebugService`); const debugData = this.getDebugData(platform, projectData, deployOptions); await debugService.debugStart(debugData, this.$options); } @@ -147,28 +147,28 @@ class TestExecutionService implements ITestExecutionService { // We need the dependencies installed here, so we can start the Karma server. await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); - let projectDir = projectData.projectDir; + const projectDir = projectData.projectDir; await this.$devicesService.initialize({ platform: platform, deviceId: this.$options.device, emulator: this.$options.emulator }); - let karmaConfig = this.getKarmaConfiguration(platform, projectData), + const karmaConfig = this.getKarmaConfiguration(platform, projectData), karmaRunner = this.$childProcess.fork(path.join(__dirname, "karma-execution.js")), launchKarmaTests = async (karmaData: any) => { this.$logger.trace("## Unit-testing: Parent process received message", karmaData); let port: string; if (karmaData.url) { port = karmaData.url.port; - let socketIoJsUrl = `http://${karmaData.url.host}/socket.io/socket.io.js`; - let socketIoJs = (await this.$httpClient.httpRequest(socketIoJsUrl)).body; + const socketIoJsUrl = `http://${karmaData.url.host}/socket.io/socket.io.js`; + const socketIoJs = (await this.$httpClient.httpRequest(socketIoJsUrl)).body; this.$fs.writeFile(path.join(projectDir, TestExecutionService.SOCKETIO_JS_FILE_NAME), socketIoJs); } if (karmaData.launcherConfig) { - let configOptions: IKarmaConfigOptions = JSON.parse(karmaData.launcherConfig); - let configJs = this.generateConfig(port, configOptions); + const configOptions: IKarmaConfigOptions = JSON.parse(karmaData.launcherConfig); + const configJs = this.generateConfig(port, configOptions); this.$fs.writeFile(path.join(projectDir, TestExecutionService.CONFIG_FILE_NAME), configJs); } @@ -263,20 +263,20 @@ class TestExecutionService implements ITestExecutionService { allowedParameters: ICommandParameter[] = []; private detourEntryPoint(projectFilesPath: string): void { - let packageJsonPath = path.join(projectFilesPath, 'package.json'); - let packageJson = this.$fs.readJson(packageJsonPath); + const packageJsonPath = path.join(projectFilesPath, 'package.json'); + const packageJson = this.$fs.readJson(packageJsonPath); packageJson.main = TestExecutionService.MAIN_APP_NAME; this.$fs.writeJson(packageJsonPath, packageJson); } private generateConfig(port: string, options: any): string { - let nics = os.networkInterfaces(); - let ips = Object.keys(nics) + const nics = os.networkInterfaces(); + const ips = Object.keys(nics) .map(nicName => nics[nicName].filter((binding: any) => binding.family === 'IPv4')[0]) .filter(binding => binding) .map(binding => binding.address); - let config = { + const config = { port, ips, options, @@ -286,7 +286,7 @@ class TestExecutionService implements ITestExecutionService { } private getKarmaConfiguration(platform: string, projectData: IProjectData): any { - let karmaConfig: any = { + const karmaConfig: any = { browsers: [platform], configFile: path.join(projectData.projectDir, 'karma.conf.js'), _NS: { @@ -323,7 +323,7 @@ class TestExecutionService implements ITestExecutionService { private getDebugData(platform: string, projectData: IProjectData, deployOptions: IDeployPlatformOptions): IDebugData { const buildConfig: IBuildConfig = _.merge({ buildForDevice: this.$options.forDevice }, deployOptions); - let debugData = this.$debugDataService.createDebugData(projectData, this.$options); + const debugData = this.$debugDataService.createDebugData(projectData, this.$options); debugData.pathToAppPackage = this.$platformService.lastOutputPath(platform, buildConfig, projectData); return debugData; diff --git a/lib/services/user-settings-service.ts b/lib/services/user-settings-service.ts index f42bf2cc63..07201c799f 100644 --- a/lib/services/user-settings-service.ts +++ b/lib/services/user-settings-service.ts @@ -4,7 +4,7 @@ import * as userSettingsServiceBaseLib from "../common/services/user-settings-se class UserSettingsService extends userSettingsServiceBaseLib.UserSettingsServiceBase { constructor($fs: IFileSystem, $options: IOptions) { - let userSettingsFilePath = path.join($options.profileDir, "user-settings.json"); + const userSettingsFilePath = path.join($options.profileDir, "user-settings.json"); super(userSettingsFilePath, $fs); } } diff --git a/lib/services/versions-service.ts b/lib/services/versions-service.ts index 03e39414a4..908a3eb289 100644 --- a/lib/services/versions-service.ts +++ b/lib/services/versions-service.ts @@ -21,8 +21,8 @@ class VersionsService implements IVersionsService { } public async getNativescriptCliVersion(): Promise { - let currentCliVersion = this.$staticConfig.version; - let latestCliVersion = await this.$npmInstallationManager.getLatestVersion(constants.NATIVESCRIPT_KEY_NAME); + const currentCliVersion = this.$staticConfig.version; + const latestCliVersion = await this.$npmInstallationManager.getLatestVersion(constants.NATIVESCRIPT_KEY_NAME); return { componentName: constants.NATIVESCRIPT_KEY_NAME, @@ -32,21 +32,21 @@ class VersionsService implements IVersionsService { } public async getTnsCoreModulesVersion(): Promise { - let latestTnsCoreModulesVersion = await this.$npmInstallationManager.getLatestVersion(constants.TNS_CORE_MODULES_NAME); - let nativescriptCoreModulesInfo: IVersionInformation = { + const latestTnsCoreModulesVersion = await this.$npmInstallationManager.getLatestVersion(constants.TNS_CORE_MODULES_NAME); + const nativescriptCoreModulesInfo: IVersionInformation = { componentName: constants.TNS_CORE_MODULES_NAME, latestVersion: latestTnsCoreModulesVersion }; if (this.projectData) { - let nodeModulesPath = path.join(this.projectData.projectDir, constants.NODE_MODULES_FOLDER_NAME); - let tnsCoreModulesPath = path.join(nodeModulesPath, constants.TNS_CORE_MODULES_NAME); + const nodeModulesPath = path.join(this.projectData.projectDir, constants.NODE_MODULES_FOLDER_NAME); + const tnsCoreModulesPath = path.join(nodeModulesPath, constants.TNS_CORE_MODULES_NAME); if (!this.$fs.exists(nodeModulesPath) || !this.$fs.exists(tnsCoreModulesPath)) { await this.$pluginsService.ensureAllDependenciesAreInstalled(this.projectData); } - let currentTnsCoreModulesVersion = this.$fs.readJson(path.join(tnsCoreModulesPath, constants.PACKAGE_JSON_FILE_NAME)).version; + const currentTnsCoreModulesVersion = this.$fs.readJson(path.join(tnsCoreModulesPath, constants.PACKAGE_JSON_FILE_NAME)).version; nativescriptCoreModulesInfo.currentVersion = currentTnsCoreModulesVersion; } @@ -54,7 +54,7 @@ class VersionsService implements IVersionsService { } public async getRuntimesVersions(): Promise { - let runtimes: string[] = [ + const runtimes: string[] = [ constants.TNS_ANDROID_RUNTIME_NAME, constants.TNS_IOS_RUNTIME_NAME ]; @@ -65,17 +65,17 @@ class VersionsService implements IVersionsService { projectConfig = this.$fs.readJson(this.projectData.projectFilePath); } - let runtimesVersions: IVersionInformation[] = await Promise.all(runtimes.map(async (runtime: string) => { - let latestRuntimeVersion = await this.$npmInstallationManager.getLatestVersion(runtime); - let runtimeInformation: IVersionInformation = { + const runtimesVersions: IVersionInformation[] = await Promise.all(runtimes.map(async (runtime: string) => { + const latestRuntimeVersion = await this.$npmInstallationManager.getLatestVersion(runtime); + const runtimeInformation: IVersionInformation = { componentName: runtime, latestVersion: latestRuntimeVersion }; if (projectConfig) { - let projectRuntimeInformation = projectConfig.nativescript && projectConfig.nativescript[runtime]; + const projectRuntimeInformation = projectConfig.nativescript && projectConfig.nativescript[runtime]; if (projectRuntimeInformation) { - let runtimeVersionInProject = projectRuntimeInformation.version; + const runtimeVersionInProject = projectRuntimeInformation.version; runtimeInformation.currentVersion = runtimeVersionInProject; } } @@ -87,8 +87,8 @@ class VersionsService implements IVersionsService { } public async checkComponentsForUpdate(): Promise { - let allComponents: IVersionInformation[] = await this.getAllComponentsVersions(); - let componentsForUpdate: IVersionInformation[] = []; + const allComponents: IVersionInformation[] = await this.getAllComponentsVersions(); + const componentsForUpdate: IVersionInformation[] = []; _.forEach(allComponents, (component: IVersionInformation) => { if (component.currentVersion && this.hasUpdate(component)) { @@ -101,7 +101,7 @@ class VersionsService implements IVersionsService { private printVersionsInformation(versionsInformation: IVersionInformation[], allComponents: IVersionInformation[]): void { if (versionsInformation && versionsInformation.length) { - let table: any = this.createTableWithVersionsInformation(versionsInformation); + const table: any = this.createTableWithVersionsInformation(versionsInformation); this.$logger.warn("Updates available"); this.$logger.out(table.toString() + EOL); @@ -113,17 +113,17 @@ class VersionsService implements IVersionsService { public async getAllComponentsVersions(): Promise { let allComponents: IVersionInformation[] = []; - let nativescriptCliInformation: IVersionInformation = await this.getNativescriptCliVersion(); + const nativescriptCliInformation: IVersionInformation = await this.getNativescriptCliVersion(); if (nativescriptCliInformation) { allComponents.push(nativescriptCliInformation); } - let nativescriptCoreModulesInformation: IVersionInformation = await this.getTnsCoreModulesVersion(); + const nativescriptCoreModulesInformation: IVersionInformation = await this.getTnsCoreModulesVersion(); if (nativescriptCoreModulesInformation) { allComponents.push(nativescriptCoreModulesInformation); } - let runtimesVersions: IVersionInformation[] = await this.getRuntimesVersions(); + const runtimesVersions: IVersionInformation[] = await this.getRuntimesVersions(); allComponents = allComponents.concat(runtimesVersions); @@ -131,11 +131,11 @@ class VersionsService implements IVersionsService { } public createTableWithVersionsInformation(versionsInformation: IVersionInformation[]): any { - let headers = ["Component", "Current version", "Latest version", "Information"]; - let data: string[][] = []; + const headers = ["Component", "Current version", "Latest version", "Information"]; + const data: string[][] = []; _.forEach(versionsInformation, (componentInformation: IVersionInformation) => { - let row: string[] = [ + const row: string[] = [ componentInformation.componentName, componentInformation.currentVersion, componentInformation.latestVersion @@ -155,7 +155,7 @@ class VersionsService implements IVersionsService { private getProjectData(): IProjectData { try { - let projectData: IProjectData = this.$injector.resolve("projectData"); + const projectData: IProjectData = this.$injector.resolve("projectData"); projectData.initializeProjectData(); return projectData; } catch (error) { diff --git a/lib/services/xcconfig-service.ts b/lib/services/xcconfig-service.ts index d6fcb658ea..f595d5332b 100644 --- a/lib/services/xcconfig-service.ts +++ b/lib/services/xcconfig-service.ts @@ -9,14 +9,14 @@ export class XCConfigService { */ public readPropertyValue(xcconfigFilePath: string, propertyName: string): string { if (this.$fs.exists(xcconfigFilePath)) { - let text = this.$fs.readText(xcconfigFilePath); + const text = this.$fs.readText(xcconfigFilePath); let property: string; let isPropertyParsed: boolean = false; text.split(/\r?\n/).forEach((line) => { line = line.replace(/\/(\/)[^\n]*$/, ""); if (line.indexOf(propertyName) >= 0) { - let parts = line.split("="); + const parts = line.split("="); if (parts.length > 1 && parts[1]) { property = parts[1].trim(); isPropertyParsed = true; diff --git a/lib/services/xcproj-service.ts b/lib/services/xcproj-service.ts index 0746d9fb65..a3894d8dbe 100644 --- a/lib/services/xcproj-service.ts +++ b/lib/services/xcproj-service.ts @@ -13,9 +13,9 @@ class XcprojService implements IXcprojService { } public async verifyXcproj(shouldFail: boolean): Promise { - let xcprojInfo = await this.getXcprojInfo(); + const xcprojInfo = await this.getXcprojInfo(); if (xcprojInfo.shouldUseXcproj && !xcprojInfo.xcprojAvailable) { - let errorMessage = `You are using CocoaPods version ${xcprojInfo.cocoapodVer} which does not support Xcode ${xcprojInfo.xcodeVersion.major}.${xcprojInfo.xcodeVersion.minor} yet.${EOL}${EOL}You can update your cocoapods by running $sudo gem install cocoapods from a terminal.${EOL}${EOL}In order for the NativeScript CLI to be able to work correctly with this setup you need to install xcproj command line tool and add it to your PATH. Xcproj can be installed with homebrew by running $ brew install xcproj from the terminal`; + const errorMessage = `You are using CocoaPods version ${xcprojInfo.cocoapodVer} which does not support Xcode ${xcprojInfo.xcodeVersion.major}.${xcprojInfo.xcodeVersion.minor} yet.${EOL}${EOL}You can update your cocoapods by running $sudo gem install cocoapods from a terminal.${EOL}${EOL}In order for the NativeScript CLI to be able to work correctly with this setup you need to install xcproj command line tool and add it to your PATH. Xcproj can be installed with homebrew by running $ brew install xcproj from the terminal`; if (shouldFail) { this.$errors.failWithoutHelp(errorMessage); } else { @@ -30,8 +30,8 @@ class XcprojService implements IXcprojService { public async getXcprojInfo(): Promise { if (!this.xcprojInfoCache) { - let cocoapodVer = await this.$sysInfo.getCocoapodVersion(), - xcodeVersion = await this.$xcodeSelectService.getXcodeVersion(); + let cocoapodVer = await this.$sysInfo.getCocoapodVersion(); + const xcodeVersion = await this.$xcodeSelectService.getXcodeVersion(); if (cocoapodVer && !semver.valid(cocoapodVer)) { // Cocoapods betas have names like 1.0.0.beta.8 @@ -44,8 +44,8 @@ class XcprojService implements IXcprojService { // CocoaPods with version lower than 1.0.0 don't support Xcode 7.3 yet // https://github.com/CocoaPods/CocoaPods/issues/2530#issuecomment-210470123 // as a result of this all .pbxprojects touched by CocoaPods get converted to XML plist format - let shouldUseXcproj = cocoapodVer && !!(semver.lt(cocoapodVer, "1.0.0") && ~helpers.versionCompare(xcodeVersion, "7.3.0")), - xcprojAvailable: boolean; + const shouldUseXcproj = cocoapodVer && !!(semver.lt(cocoapodVer, "1.0.0") && ~helpers.versionCompare(xcodeVersion, "7.3.0")); + let xcprojAvailable: boolean; if (shouldUseXcproj) { // if that's the case we can use xcproj gem to convert them back to ASCII plist format diff --git a/lib/sys-info.ts b/lib/sys-info.ts index 6f7fdd904a..d2ac752a47 100644 --- a/lib/sys-info.ts +++ b/lib/sys-info.ts @@ -13,7 +13,7 @@ export class SysInfo extends SysInfoBase { } public async getSysInfo(pathToPackageJson: string, androidToolsInfo?: { pathToAdb: string }): Promise { - let defaultAndroidToolsInfo = { + const defaultAndroidToolsInfo = { pathToAdb: await this.$androidToolsInfo.getPathToAdbFromAndroidHome() }; diff --git a/lib/tools/node-modules/node-modules-dependencies-builder.ts b/lib/tools/node-modules/node-modules-dependencies-builder.ts index 396eb7dc34..f2482819b5 100644 --- a/lib/tools/node-modules/node-modules-dependencies-builder.ts +++ b/lib/tools/node-modules/node-modules-dependencies-builder.ts @@ -16,9 +16,9 @@ export class NodeModulesDependenciesBuilder implements INodeModulesDependenciesB const packageJsonContent = this.$fs.readJson(projectPackageJsonPath); const dependencies = packageJsonContent && packageJsonContent.dependencies; - let resolvedDependencies: IDependencyData[] = []; + const resolvedDependencies: IDependencyData[] = []; - let queue: IDependencyDescription[] = _.keys(dependencies) + const queue: IDependencyDescription[] = _.keys(dependencies) .map(dependencyName => ({ parentDir: projectPath, name: dependencyName, @@ -83,7 +83,7 @@ export class NodeModulesDependenciesBuilder implements INodeModulesDependenciesB const packageJsonExists = this.$fs.getLsStats(packageJsonPath).isFile(); if (packageJsonExists) { - let packageJsonContents = this.$fs.readJson(packageJsonPath); + const packageJsonContents = this.$fs.readJson(packageJsonPath); if (!!packageJsonContents.nativescript) { // add `nativescript` property, necessary for resolving plugins diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts index f3f534c373..37d6a03903 100644 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ b/lib/tools/node-modules/node-modules-dest-copy.ts @@ -16,17 +16,17 @@ export class TnsModulesCopy { } public copyModules(dependencies: IDependencyData[], platform: string): void { - for (let entry in dependencies) { - let dependency = dependencies[entry]; + for (const entry in dependencies) { + const dependency = dependencies[entry]; this.copyDependencyDir(dependency); if (dependency.name === constants.TNS_CORE_MODULES_NAME) { - let tnsCoreModulesResourcePath = path.join(this.outputRoot, constants.TNS_CORE_MODULES_NAME); + const tnsCoreModulesResourcePath = path.join(this.outputRoot, constants.TNS_CORE_MODULES_NAME); // Remove .ts files - let allFiles = this.$fs.enumerateFilesInDirectorySync(tnsCoreModulesResourcePath); - let matchPattern = this.$options.release ? "**/*.ts" : "**/*.d.ts"; + const allFiles = this.$fs.enumerateFilesInDirectorySync(tnsCoreModulesResourcePath); + const matchPattern = this.$options.release ? "**/*.ts" : "**/*.d.ts"; allFiles.filter(file => minimatch(file, matchPattern, { nocase: true })).map(file => this.$fs.deleteFile(file)); shelljs.rm("-rf", path.join(tnsCoreModulesResourcePath, constants.NODE_MODULES_FOLDER_NAME)); @@ -98,7 +98,7 @@ export class NpmPluginPrepare { } private writePreparedDependencyInfo(dependencies: IDependencyData[], platform: string, projectData: IProjectData): void { - let prepareData: IDictionary = {}; + const prepareData: IDictionary = {}; _.each(dependencies, d => { prepareData[d.name] = true; }); @@ -145,11 +145,11 @@ export class NpmPluginPrepare { } await this.beforePrepare(dependencies, platform, projectData); - for (let dependencyKey in dependencies) { + for (const dependencyKey in dependencies) { const dependency = dependencies[dependencyKey]; - let isPlugin = !!dependency.nativescript; + const isPlugin = !!dependency.nativescript; if (isPlugin) { - let pluginData = this.$pluginsService.convertToPluginData(dependency, projectData.projectDir); + const pluginData = this.$pluginsService.convertToPluginData(dependency, projectData.projectDir); await this.$pluginsService.preparePluginNativeCode(pluginData, platform, projectData); } } @@ -162,14 +162,14 @@ export class NpmPluginPrepare { return; } - for (let dependencyKey in dependencies) { + for (const dependencyKey in dependencies) { const dependency = dependencies[dependencyKey]; - let isPlugin = !!dependency.nativescript; + const isPlugin = !!dependency.nativescript; if (isPlugin) { platform = platform.toLowerCase(); - let pluginData = this.$pluginsService.convertToPluginData(dependency, projectData.projectDir); - let platformData = this.$platformsData.getPlatformData(platform, projectData); - let appFolderExists = this.$fs.exists(path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME)); + const pluginData = this.$pluginsService.convertToPluginData(dependency, projectData.projectDir); + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const appFolderExists = this.$fs.exists(path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME)); if (appFolderExists) { this.$pluginsService.preparePluginScripts(pluginData, platform, projectData, projectFilesConfig); // Show message diff --git a/lib/xml-validator.ts b/lib/xml-validator.ts index 25d31dcf89..6d33e1ca28 100644 --- a/lib/xml-validator.ts +++ b/lib/xml-validator.ts @@ -10,8 +10,8 @@ export class XmlValidator implements IXmlValidator { sourceFiles .filter(file => _.endsWith(file, constants.XML_FILE_EXTENSION)) .forEach(file => { - let errorOutput = this.getXmlFileErrors(file); - let hasErrors = !!errorOutput; + const errorOutput = this.getXmlFileErrors(file); + const hasErrors = !!errorOutput; xmlHasErrors = xmlHasErrors || hasErrors; if (hasErrors) { this.$logger.info(`${file} has syntax errors.`.red.bold); @@ -23,8 +23,8 @@ export class XmlValidator implements IXmlValidator { public getXmlFileErrors(sourceFile: string): string { let errorOutput = ""; - let fileContents = this.$fs.readText(sourceFile); - let domErrorHandler = (level: any, msg: string) => { + const fileContents = this.$fs.readText(sourceFile); + const domErrorHandler = (level: any, msg: string) => { errorOutput += level + EOL + msg + EOL; }; this.getDomParser(domErrorHandler).parseFromString(fileContents, "text/xml"); @@ -33,8 +33,8 @@ export class XmlValidator implements IXmlValidator { } private getDomParser(errorHandler: (level: any, msg: string) => void): any { - let DomParser = require("xmldom").DOMParser; - let parser = new DomParser({ + const DomParser = require("xmldom").DOMParser; + const parser = new DomParser({ locator: {}, errorHandler: errorHandler }); diff --git a/package.json b/package.json index 254c0143d9..7b8addf80b 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "mocha": "node test-scripts/mocha.js", "tsc": "tsc", "tslint": "tslint -p tsconfig.json --type-check", - "test-watch": "node ./dev/tsc-to-mocha-watch.js" + "test-watch": "node ./dev/tsc-to-mocha-watch.js", + "tslint-fix": "tslint -p tsconfig.json --type-check --fix" }, "repository": { "type": "git", diff --git a/test/android-project-properties-manager.ts b/test/android-project-properties-manager.ts index 8b3a316e76..a92ab97f40 100644 --- a/test/android-project-properties-manager.ts +++ b/test/android-project-properties-manager.ts @@ -14,7 +14,7 @@ temp.track(); import { assert } from "chai"; function createTestInjector(): IInjector { - let testInjector = new yok.Yok(); + const testInjector = new yok.Yok(); testInjector.register("propertiesParser", ProjectPropertiesParserLib.PropertiesParser); testInjector.register("fs", FsLib.FileSystem); testInjector.register("hostInfo", HostInfoLib.HostInfo); @@ -29,113 +29,113 @@ function createTestInjector(): IInjector { describe("Android project properties parser tests", () => { it("adds project reference", async () => { - let testInjector = createTestInjector(); - let fs = testInjector.resolve("fs"); + const testInjector = createTestInjector(); + const fs = testInjector.resolve("fs"); - let projectPropertiesFileContent = 'target=android-21'; - let tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); + const projectPropertiesFileContent = 'target=android-21'; + const tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); fs.writeFile(path.join(tempFolder, "project.properties"), projectPropertiesFileContent); - let projectPropertiesManager: IAndroidProjectPropertiesManager = testInjector.resolve( + const projectPropertiesManager: IAndroidProjectPropertiesManager = testInjector.resolve( ProjectPropertiesManagerLib.AndroidProjectPropertiesManager, { directoryPath: tempFolder }); await projectPropertiesManager.addProjectReference("testValue"); - let expectedContent = 'target=android-21' + '\n' + + const expectedContent = 'target=android-21' + '\n' + 'android.library.reference.1=testValue'; - let actualContent = fs.readText(path.join(tempFolder, "project.properties")); + const actualContent = fs.readText(path.join(tempFolder, "project.properties")); assert.equal(expectedContent, actualContent); assert.equal(1, _.keys(await projectPropertiesManager.getProjectReferences()).length); }); it("adds project reference if another referencence already exists in project.properties file", async () => { - let testInjector = createTestInjector(); - let fs = testInjector.resolve("fs"); + const testInjector = createTestInjector(); + const fs = testInjector.resolve("fs"); - let projectPropertiesFileContent = 'target=android-21' + '\n' + + const projectPropertiesFileContent = 'target=android-21' + '\n' + 'android.library.reference.1=someValue'; - let tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); + const tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); fs.writeFile(path.join(tempFolder, "project.properties"), projectPropertiesFileContent); - let projectPropertiesManager = testInjector.resolve( + const projectPropertiesManager = testInjector.resolve( ProjectPropertiesManagerLib.AndroidProjectPropertiesManager, { directoryPath: tempFolder }); await projectPropertiesManager.addProjectReference("testValue"); - let expectedContent = ['target=android-21', + const expectedContent = ['target=android-21', 'android.library.reference.1=someValue', 'android.library.reference.2=testValue'].join('\n'); - let actualContent = fs.readText(path.join(tempFolder, "project.properties")); + const actualContent = fs.readText(path.join(tempFolder, "project.properties")); assert.equal(expectedContent, actualContent); assert.equal(2, _.keys(await projectPropertiesManager.getProjectReferences()).length); }); it("adds project reference if more than one references exist in project.properties file", async () => { - let testInjector = createTestInjector(); - let fs = testInjector.resolve("fs"); + const testInjector = createTestInjector(); + const fs = testInjector.resolve("fs"); - let projectPropertiesFileContent = ['target=android-21', + const projectPropertiesFileContent = ['target=android-21', 'android.library.reference.1=value1', 'android.library.reference.2=value2', 'android.library.reference.3=value3', 'android.library.reference.4=value4', 'android.library.reference.5=value5'].join('\n'); - let tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); + const tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); fs.writeFile(path.join(tempFolder, "project.properties"), projectPropertiesFileContent); - let projectPropertiesManager: IAndroidProjectPropertiesManager = testInjector.resolve( + const projectPropertiesManager: IAndroidProjectPropertiesManager = testInjector.resolve( ProjectPropertiesManagerLib.AndroidProjectPropertiesManager, { directoryPath: tempFolder }); await projectPropertiesManager.addProjectReference("testValue"); - let expectedContent = projectPropertiesFileContent + '\n' + + const expectedContent = projectPropertiesFileContent + '\n' + 'android.library.reference.6=testValue'; - let actualContent = fs.readText(path.join(tempFolder, "project.properties")); + const actualContent = fs.readText(path.join(tempFolder, "project.properties")); assert.equal(expectedContent, actualContent); assert.equal(6, _.keys(await projectPropertiesManager.getProjectReferences()).length); }); it("removes project reference if only one reference exists", async () => { - let testInjector = createTestInjector(); - let fs = testInjector.resolve("fs"); + const testInjector = createTestInjector(); + const fs = testInjector.resolve("fs"); - let projectPropertiesFileContent = 'android.library.reference.1=value1' + '\n' + + const projectPropertiesFileContent = 'android.library.reference.1=value1' + '\n' + 'target=android-21'; - let tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); + const tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); fs.writeFile(path.join(tempFolder, "project.properties"), projectPropertiesFileContent); - let projectPropertiesManager: IAndroidProjectPropertiesManager = testInjector.resolve( + const projectPropertiesManager: IAndroidProjectPropertiesManager = testInjector.resolve( ProjectPropertiesManagerLib.AndroidProjectPropertiesManager, { directoryPath: tempFolder }); await projectPropertiesManager.removeProjectReference("value1"); - let expectedContent = 'target=android-21'; - let actualContent = fs.readText(path.join(tempFolder, "project.properties")); + const expectedContent = 'target=android-21'; + const actualContent = fs.readText(path.join(tempFolder, "project.properties")); assert.equal(expectedContent, actualContent); assert.equal(0, _.keys(await projectPropertiesManager.getProjectReferences()).length); }); it("removes project reference when another references exist before and after the specified reference", async () => { - let testInjector = createTestInjector(); - let fs = testInjector.resolve("fs"); + const testInjector = createTestInjector(); + const fs = testInjector.resolve("fs"); - let projectPropertiesFileContent = ['target=android-17', + const projectPropertiesFileContent = ['target=android-17', 'android.library.reference.1=value1', 'android.library.reference.2=value2', 'android.library.reference.3=value3', 'android.library.reference.4=value4', 'android.library.reference.5=value5'].join('\n'); - let tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); + const tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); fs.writeFile(path.join(tempFolder, "project.properties"), projectPropertiesFileContent); - let projectPropertiesManager: IAndroidProjectPropertiesManager = testInjector.resolve( + const projectPropertiesManager: IAndroidProjectPropertiesManager = testInjector.resolve( ProjectPropertiesManagerLib.AndroidProjectPropertiesManager, { directoryPath: tempFolder }); await projectPropertiesManager.removeProjectReference("value3"); - let expectedContent = ['target=android-17', + const expectedContent = ['target=android-17', 'android.library.reference.1=value1', 'android.library.reference.2=value2', 'android.library.reference.3=value4', 'android.library.reference.4=value5'].join('\n') + '\n'; - let actualContent = fs.readText(path.join(tempFolder, "project.properties")); + const actualContent = fs.readText(path.join(tempFolder, "project.properties")); assert.equal(expectedContent, actualContent); assert.equal(4, _.keys(await projectPropertiesManager.getProjectReferences()).length); diff --git a/test/cocoapods-service.ts b/test/cocoapods-service.ts index f8e11c5958..f63770aeba 100644 --- a/test/cocoapods-service.ts +++ b/test/cocoapods-service.ts @@ -10,7 +10,7 @@ interface IMergePodfileHooksTestCase { } function createTestInjector(): IInjector { - let testInjector: IInjector = new yok.Yok(); + const testInjector: IInjector = new yok.Yok(); testInjector.register("fs", {}); testInjector.register("cocoapodsService", CocoaPodsService); @@ -31,8 +31,8 @@ describe("Cocoapods service", () => { let cocoapodsService: ICocoaPodsService; let newPodfileContent: string; - let mockFileSystem = (injector: IInjector, podfileContent: string): void => { - let fs: IFileSystem = injector.resolve("fs"); + const mockFileSystem = (injector: IInjector, podfileContent: string): void => { + const fs: IFileSystem = injector.resolve("fs"); fs.exists = () => true; fs.readText = () => podfileContent; @@ -41,7 +41,7 @@ describe("Cocoapods service", () => { }; }; - let testCaces: IMergePodfileHooksTestCase[] = [ + const testCaces: IMergePodfileHooksTestCase[] = [ { input: ` target 'MyApp' do diff --git a/test/debug.ts b/test/debug.ts index f004e806ff..efd107b116 100644 --- a/test/debug.ts +++ b/test/debug.ts @@ -15,7 +15,7 @@ const helpers = require("../lib/common/helpers"); const originalIsInteracive = helpers.isInteractive; function createTestInjector(): IInjector { - let testInjector: IInjector = new yok.Yok(); + const testInjector: IInjector = new yok.Yok(); testInjector.register("debug|android", DebugAndroidCommand); testInjector.register("config", Configuration); @@ -342,10 +342,10 @@ describe("debug command tests", () => { }); it("Ensures that beforePrepareAllPlugins will call gradle with clean option when *NOT* livesyncing", async () => { - let childProcess: stubs.ChildProcessStub = testInjector.resolve("childProcess"); - let androidProjectService: IPlatformProjectService = testInjector.resolve("androidProjectService"); - let projectData: IProjectData = testInjector.resolve("projectData"); - let spawnFromEventCount = childProcess.spawnFromEventCount; + const childProcess: stubs.ChildProcessStub = testInjector.resolve("childProcess"); + const androidProjectService: IPlatformProjectService = testInjector.resolve("androidProjectService"); + const projectData: IProjectData = testInjector.resolve("projectData"); + const spawnFromEventCount = childProcess.spawnFromEventCount; await androidProjectService.beforePrepareAllPlugins(projectData); assert.isTrue(childProcess.lastCommand.indexOf("gradle") !== -1); assert.isTrue(childProcess.lastCommandArgs[0] === "clean"); diff --git a/test/ios-entitlements-service.ts b/test/ios-entitlements-service.ts index 0987039ad6..2edfb58ba1 100644 --- a/test/ios-entitlements-service.ts +++ b/test/ios-entitlements-service.ts @@ -35,12 +35,12 @@ describe("IOSEntitlements Service Tests", () => { return testInjector; }; - let injector: IInjector, - platformsData: any, - projectData: IProjectData, - fs: IFileSystem, - iOSEntitlementsService: IOSEntitlementsService, - destinationFilePath: string; + let injector: IInjector; + let platformsData: any; + let projectData: IProjectData; + let fs: IFileSystem; + let iOSEntitlementsService: IOSEntitlementsService; + let destinationFilePath: string; beforeEach(() => { injector = createTestInjector(); @@ -61,7 +61,7 @@ describe("IOSEntitlements Service Tests", () => { describe("Ensure paths constructed are correct", () => { it("Ensure destination entitlements relative path is calculated correctly.", () => { const expected = path.join("testApp", "testApp.entitlements"); - let actual = iOSEntitlementsService.getPlatformsEntitlementsRelativePath(projectData); + const actual = iOSEntitlementsService.getPlatformsEntitlementsRelativePath(projectData); assert.equal(actual, expected); }); @@ -114,7 +114,7 @@ describe("IOSEntitlements Service Tests", () => { `; function assertContent(actual: string, expected: string) { - let strip = (x: string) => { + const strip = (x: string) => { return x.replace(EOL, '').trim(); }; assert.equal(strip(actual), strip(expected)); @@ -125,62 +125,62 @@ describe("IOSEntitlements Service Tests", () => { await iOSEntitlementsService.merge(projectData); // assert - let actual = fs.readText(destinationFilePath); + const actual = fs.readText(destinationFilePath); assertContent(actual, defaultPlistContent); }); it("Merge uses the entitlements from App_Resources folder", async () => { - let appResourcesEntitlement = (iOSEntitlementsService).getDefaultAppEntitlementsPath(projectData); + const appResourcesEntitlement = (iOSEntitlementsService).getDefaultAppEntitlementsPath(projectData); fs.writeFile(appResourcesEntitlement, defaultAppResourcesEntitlementsContent); // act await iOSEntitlementsService.merge(projectData); // assert - let actual = fs.readText(destinationFilePath); + const actual = fs.readText(destinationFilePath); assertContent(actual, defaultAppResourcesEntitlementsContent); }); it("Merge uses the entitlements file from a Plugin", async () => { - let pluginsService = injector.resolve("pluginsService"); - let testPluginFolderPath = temp.mkdirSync("testPlugin"); + const pluginsService = injector.resolve("pluginsService"); + const testPluginFolderPath = temp.mkdirSync("testPlugin"); pluginsService.getAllInstalledPlugins = async () => [{ pluginPlatformsFolderPath: (platform: string) => { return testPluginFolderPath; } }]; - let pluginAppEntitlementsPath = path.join(testPluginFolderPath, IOSEntitlementsService.DefaultEntitlementsName); + const pluginAppEntitlementsPath = path.join(testPluginFolderPath, IOSEntitlementsService.DefaultEntitlementsName); fs.writeFile(pluginAppEntitlementsPath, defaultPluginEntitlementsContent); // act await iOSEntitlementsService.merge(projectData); // assert - let actual = fs.readText(destinationFilePath); + const actual = fs.readText(destinationFilePath); assertContent(actual, defaultPluginEntitlementsContent); }); it("Merge uses App_Resources and Plugins and merges all keys", async () => { // setup app resoruces - let appResourcesEntitlement = (iOSEntitlementsService).getDefaultAppEntitlementsPath(projectData); + const appResourcesEntitlement = (iOSEntitlementsService).getDefaultAppEntitlementsPath(projectData); fs.writeFile(appResourcesEntitlement, namedAppResourcesEntitlementsContent); // setup plugin entitlements - let pluginsService = injector.resolve("pluginsService"); - let testPluginFolderPath = temp.mkdirSync("testPlugin"); + const pluginsService = injector.resolve("pluginsService"); + const testPluginFolderPath = temp.mkdirSync("testPlugin"); pluginsService.getAllInstalledPlugins = async () => [{ pluginPlatformsFolderPath: (platform: string) => { return testPluginFolderPath; } }]; - let pluginAppEntitlementsPath = path.join(testPluginFolderPath, IOSEntitlementsService.DefaultEntitlementsName); + const pluginAppEntitlementsPath = path.join(testPluginFolderPath, IOSEntitlementsService.DefaultEntitlementsName); fs.writeFile(pluginAppEntitlementsPath, defaultPluginEntitlementsContent); // act await iOSEntitlementsService.merge(projectData); // assert - let actual = fs.readText(destinationFilePath); + const actual = fs.readText(destinationFilePath); assertContent(actual, mergedEntitlementsContent); }); }); diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index aa9f32a494..30b7e58629 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -48,7 +48,7 @@ class IOSSimulatorDiscoveryMock extends DeviceDiscovery { } function createTestInjector(projectPath: string, projectName: string): IInjector { - let testInjector = new yok.Yok(); + const testInjector = new yok.Yok(); testInjector.register("childProcess", ChildProcessLib.ChildProcess); testInjector.register("config", ConfigLib.Configuration); testInjector.register("errors", ErrorsLib.Errors); @@ -118,7 +118,7 @@ function createTestInjector(projectPath: string, projectName: string): IInjector } function createPackageJson(testInjector: IInjector, projectPath: string, projectName: string) { - let packageJsonData = { + const packageJsonData = { "name": projectName, "version": "0.1.0", "nativescript": { @@ -134,14 +134,14 @@ function createPackageJson(testInjector: IInjector, projectPath: string, project } function expectOption(args: string[], option: string, value: string, message?: string): void { - let index = args.indexOf(option); + const index = args.indexOf(option); assert.ok(index >= 0, "Expected " + option + " to be set."); assert.ok(args.length > index + 1, "Expected " + option + " to have value"); assert.equal(args[index + 1], value, message); } function readOption(args: string[], option: string): string { - let index = args.indexOf(option); + const index = args.indexOf(option); assert.ok(index >= 0, "Expected " + option + " to be set."); assert.ok(args.length > index + 1, "Expected " + option + " to have value"); return args[index + 1]; @@ -150,16 +150,16 @@ function readOption(args: string[], option: string): string { describe("iOSProjectService", () => { describe("archive", () => { async function setupArchive(options?: { archivePath?: string }): Promise<{ run: () => Promise, assert: () => void }> { - let hasCustomArchivePath = options && options.archivePath; + const hasCustomArchivePath = options && options.archivePath; - let projectName = "projectDirectory"; - let projectPath = temp.mkdirSync(projectName); + const projectName = "projectDirectory"; + const projectPath = temp.mkdirSync(projectName); - let testInjector = createTestInjector(projectPath, projectName); - let iOSProjectService = testInjector.resolve("iOSProjectService"); - let projectData: IProjectData = testInjector.resolve("projectData"); + const testInjector = createTestInjector(projectPath, projectName); + const iOSProjectService = testInjector.resolve("iOSProjectService"); + const projectData: IProjectData = testInjector.resolve("projectData"); - let childProcess = testInjector.resolve("childProcess"); + const childProcess = testInjector.resolve("childProcess"); let xcodebuildExeced = false; let archivePath: string; @@ -204,12 +204,12 @@ describe("iOSProjectService", () => { console.log("Skipping iOS archive tests. They can work only on macOS"); } else { it("by default exports xcodearchive to platforms/ios/build/archive/.xcarchive", async () => { - let setup = await setupArchive(); + const setup = await setupArchive(); await setup.run(); setup.assert(); }); it("can pass archivePath to xcodebuild -archivePath", async () => { - let setup = await setupArchive({ archivePath: "myarchive.xcarchive" }); + const setup = await setupArchive({ archivePath: "myarchive.xcarchive" }); await setup.run(); setup.assert(); }); @@ -217,7 +217,7 @@ describe("iOSProjectService", () => { }); describe("exportArchive", () => { - let noTeamPlist = ` + const noTeamPlist = ` @@ -230,7 +230,7 @@ describe("iOSProjectService", () => { `; - let myTeamPlist = ` + const myTeamPlist = ` @@ -246,17 +246,17 @@ describe("iOSProjectService", () => { `; async function testExportArchive(options: { teamID?: string }, expectedPlistContent: string): Promise { - let projectName = "projectDirectory"; - let projectPath = temp.mkdirSync(projectName); + const projectName = "projectDirectory"; + const projectPath = temp.mkdirSync(projectName); - let testInjector = createTestInjector(projectPath, projectName); - let iOSProjectService = testInjector.resolve("iOSProjectService"); - let projectData: IProjectData = testInjector.resolve("projectData"); + const testInjector = createTestInjector(projectPath, projectName); + const iOSProjectService = testInjector.resolve("iOSProjectService"); + const projectData: IProjectData = testInjector.resolve("projectData"); - let archivePath = path.join(projectPath, "platforms", "ios", "build", "archive", projectName + ".xcarchive"); + const archivePath = path.join(projectPath, "platforms", "ios", "build", "archive", projectName + ".xcarchive"); - let childProcess = testInjector.resolve("childProcess"); - let fs = testInjector.resolve("fs"); + const childProcess = testInjector.resolve("childProcess"); + const fs = testInjector.resolve("fs"); let xcodebuildExeced = false; @@ -268,19 +268,19 @@ describe("iOSProjectService", () => { expectOption(args, "-archivePath", archivePath, "Expected the -archivePath to be passed to xcodebuild."); expectOption(args, "-exportPath", path.join(projectPath, "platforms", "ios", "build", "archive"), "Expected the -archivePath to be passed to xcodebuild."); - let plist = readOption(args, "-exportOptionsPlist"); + const plist = readOption(args, "-exportOptionsPlist"); assert.ok(plist); - let plistContent = fs.readText(plist); + const plistContent = fs.readText(plist); // There may be better way to equal property lists assert.equal(plistContent, expectedPlistContent, "Mismatch in exportOptionsPlist content"); return Promise.resolve(); }; - let resultIpa = await iOSProjectService.exportArchive(projectData, { archivePath, teamID: options.teamID }); - let expectedIpa = path.join(projectPath, "platforms", "ios", "build", "archive", projectName + ".ipa"); + const resultIpa = await iOSProjectService.exportArchive(projectData, { archivePath, teamID: options.teamID }); + const expectedIpa = path.join(projectPath, "platforms", "ios", "build", "archive", projectName + ".ipa"); assert.equal(resultIpa, expectedIpa, "Expected IPA at the specified location"); @@ -306,13 +306,13 @@ describe("Cocoapods support", () => { console.log("Skipping Cocoapods tests. They cannot work on windows"); } else { it("adds plugin with Podfile", async () => { - let projectName = "projectDirectory"; - let projectPath = temp.mkdirSync(projectName); + const projectName = "projectDirectory"; + const projectPath = temp.mkdirSync(projectName); - let testInjector = createTestInjector(projectPath, projectName); - let fs: IFileSystem = testInjector.resolve("fs"); + const testInjector = createTestInjector(projectPath, projectName); + const fs: IFileSystem = testInjector.resolve("fs"); - let packageJsonData = { + const packageJsonData = { "name": "myProject", "version": "0.1.0", "nativescript": { @@ -324,10 +324,10 @@ describe("Cocoapods support", () => { }; fs.writeJson(path.join(projectPath, "package.json"), packageJsonData); - let platformsFolderPath = path.join(projectPath, "platforms", "ios"); + const platformsFolderPath = path.join(projectPath, "platforms", "ios"); fs.createDirectory(platformsFolderPath); - let iOSProjectService = testInjector.resolve("iOSProjectService"); + const iOSProjectService = testInjector.resolve("iOSProjectService"); iOSProjectService.prepareFrameworks = (pluginPlatformsFolderPath: string, pluginData: IPluginData): Promise => { return Promise.resolve(); }; @@ -342,26 +342,26 @@ describe("Cocoapods support", () => { }; iOSProjectService.savePbxProj = (): Promise => Promise.resolve(); - let pluginPath = temp.mkdirSync("pluginDirectory"); - let pluginPlatformsFolderPath = path.join(pluginPath, "platforms", "ios"); - let pluginPodfilePath = path.join(pluginPlatformsFolderPath, "Podfile"); - let pluginPodfileContent = ["source 'https://github.com/CocoaPods/Specs.git'", "platform :ios, '8.1'", "pod 'GoogleMaps'"].join("\n"); + const pluginPath = temp.mkdirSync("pluginDirectory"); + const pluginPlatformsFolderPath = path.join(pluginPath, "platforms", "ios"); + const pluginPodfilePath = path.join(pluginPlatformsFolderPath, "Podfile"); + const pluginPodfileContent = ["source 'https://github.com/CocoaPods/Specs.git'", "platform :ios, '8.1'", "pod 'GoogleMaps'"].join("\n"); fs.writeFile(pluginPodfilePath, pluginPodfileContent); - let pluginData = { + const pluginData = { pluginPlatformsFolderPath(platform: string): string { return pluginPlatformsFolderPath; } }; - let projectData: IProjectData = testInjector.resolve("projectData"); + const projectData: IProjectData = testInjector.resolve("projectData"); await iOSProjectService.preparePluginNativeCode(pluginData, projectData); - let projectPodfilePath = path.join(platformsFolderPath, "Podfile"); + const projectPodfilePath = path.join(platformsFolderPath, "Podfile"); assert.isTrue(fs.exists(projectPodfilePath)); - let actualProjectPodfileContent = fs.readText(projectPodfilePath); - let expectedProjectPodfileContent = ["use_frameworks!\n", + const actualProjectPodfileContent = fs.readText(projectPodfilePath); + const expectedProjectPodfileContent = ["use_frameworks!\n", `target "${projectName}" do`, `# Begin Podfile - ${pluginPodfilePath} `, ` ${pluginPodfileContent} `, @@ -371,13 +371,13 @@ describe("Cocoapods support", () => { assert.equal(actualProjectPodfileContent, expectedProjectPodfileContent); }); it("adds and removes plugin with Podfile", async () => { - let projectName = "projectDirectory2"; - let projectPath = temp.mkdirSync(projectName); + const projectName = "projectDirectory2"; + const projectPath = temp.mkdirSync(projectName); - let testInjector = createTestInjector(projectPath, projectName); - let fs: IFileSystem = testInjector.resolve("fs"); + const testInjector = createTestInjector(projectPath, projectName); + const fs: IFileSystem = testInjector.resolve("fs"); - let packageJsonData = { + const packageJsonData = { "name": "myProject2", "version": "0.1.0", "nativescript": { @@ -389,10 +389,10 @@ describe("Cocoapods support", () => { }; fs.writeJson(path.join(projectPath, "package.json"), packageJsonData); - let platformsFolderPath = path.join(projectPath, "platforms", "ios"); + const platformsFolderPath = path.join(projectPath, "platforms", "ios"); fs.createDirectory(platformsFolderPath); - let iOSProjectService = testInjector.resolve("iOSProjectService"); + const iOSProjectService = testInjector.resolve("iOSProjectService"); iOSProjectService.prepareFrameworks = (pluginPlatformsFolderPath: string, pluginData: IPluginData): Promise => { return Promise.resolve(); }; @@ -413,26 +413,26 @@ describe("Cocoapods support", () => { }; iOSProjectService.savePbxProj = (): Promise => Promise.resolve(); - let pluginPath = temp.mkdirSync("pluginDirectory"); - let pluginPlatformsFolderPath = path.join(pluginPath, "platforms", "ios"); - let pluginPodfilePath = path.join(pluginPlatformsFolderPath, "Podfile"); - let pluginPodfileContent = ["source 'https://github.com/CocoaPods/Specs.git'", "platform :ios, '8.1'", "pod 'GoogleMaps'"].join("\n"); + const pluginPath = temp.mkdirSync("pluginDirectory"); + const pluginPlatformsFolderPath = path.join(pluginPath, "platforms", "ios"); + const pluginPodfilePath = path.join(pluginPlatformsFolderPath, "Podfile"); + const pluginPodfileContent = ["source 'https://github.com/CocoaPods/Specs.git'", "platform :ios, '8.1'", "pod 'GoogleMaps'"].join("\n"); fs.writeFile(pluginPodfilePath, pluginPodfileContent); - let pluginData = { + const pluginData = { pluginPlatformsFolderPath(platform: string): string { return pluginPlatformsFolderPath; } }; - let projectData: IProjectData = testInjector.resolve("projectData"); + const projectData: IProjectData = testInjector.resolve("projectData"); await iOSProjectService.preparePluginNativeCode(pluginData, projectData); - let projectPodfilePath = path.join(platformsFolderPath, "Podfile"); + const projectPodfilePath = path.join(platformsFolderPath, "Podfile"); assert.isTrue(fs.exists(projectPodfilePath)); - let actualProjectPodfileContent = fs.readText(projectPodfilePath); - let expectedProjectPodfileContent = ["use_frameworks!\n", + const actualProjectPodfileContent = fs.readText(projectPodfilePath); + const expectedProjectPodfileContent = ["use_frameworks!\n", `target "${projectName}" do`, `# Begin Podfile - ${pluginPodfilePath} `, ` ${pluginPodfileContent} `, @@ -454,17 +454,17 @@ describe("Static libraries support", () => { return; } - let projectName = "TNSApp"; - let projectPath = temp.mkdirSync(projectName); - let libraryName = "testLibrary1"; - let headers = ["TestHeader1.h", "TestHeader2.h"]; - let testInjector = createTestInjector(projectPath, projectName); - let fs: IFileSystem = testInjector.resolve("fs"); - let staticLibraryPath = path.join(path.join(temp.mkdirSync("pluginDirectory"), "platforms", "ios")); - let staticLibraryHeadersPath = path.join(staticLibraryPath, "include", libraryName); + const projectName = "TNSApp"; + const projectPath = temp.mkdirSync(projectName); + const libraryName = "testLibrary1"; + const headers = ["TestHeader1.h", "TestHeader2.h"]; + const testInjector = createTestInjector(projectPath, projectName); + const fs: IFileSystem = testInjector.resolve("fs"); + const staticLibraryPath = path.join(path.join(temp.mkdirSync("pluginDirectory"), "platforms", "ios")); + const staticLibraryHeadersPath = path.join(staticLibraryPath, "include", libraryName); it("checks validation of header files", async () => { - let iOSProjectService = testInjector.resolve("iOSProjectService"); + const iOSProjectService = testInjector.resolve("iOSProjectService"); fs.ensureDirectoryExists(staticLibraryHeadersPath); _.each(headers, header => { fs.writeFile(path.join(staticLibraryHeadersPath, header), ""); }); @@ -482,15 +482,15 @@ describe("Static libraries support", () => { }); it("checks generation of modulemaps", () => { - let iOSProjectService = testInjector.resolve("iOSProjectService"); + const iOSProjectService = testInjector.resolve("iOSProjectService"); fs.ensureDirectoryExists(staticLibraryHeadersPath); _.each(headers, header => { fs.writeFile(path.join(staticLibraryHeadersPath, header), ""); }); iOSProjectService.generateModulemap(staticLibraryHeadersPath, libraryName); // Read the generated modulemap and verify it. let modulemap = fs.readFile(path.join(staticLibraryHeadersPath, "module.modulemap")); - let headerCommands = _.map(headers, value => `header "${value}"`); - let modulemapExpectation = `module ${libraryName} { explicit module ${libraryName} { ${headerCommands.join(" ")} } }`; + const headerCommands = _.map(headers, value => `header "${value}"`); + const modulemapExpectation = `module ${libraryName} { explicit module ${libraryName} { ${headerCommands.join(" ")} } }`; assert.equal(modulemap, modulemapExpectation); @@ -511,16 +511,16 @@ describe("Static libraries support", () => { describe("Relative paths", () => { it("checks for correct calculation of relative paths", () => { - let projectName = "projectDirectory"; - let projectPath = temp.mkdirSync(projectName); - let subpath = path.join(projectPath, "sub", "path"); + const projectName = "projectDirectory"; + const projectPath = temp.mkdirSync(projectName); + const subpath = path.join(projectPath, "sub", "path"); - let testInjector = createTestInjector(projectPath, projectName); + const testInjector = createTestInjector(projectPath, projectName); createPackageJson(testInjector, projectPath, projectName); - let iOSProjectService = testInjector.resolve("iOSProjectService"); - let projectData: IProjectData = testInjector.resolve("projectData"); + const iOSProjectService = testInjector.resolve("iOSProjectService"); + const projectData: IProjectData = testInjector.resolve("projectData"); - let result = iOSProjectService.getLibSubpathRelativeToProjectPath(subpath, projectData); + const result = iOSProjectService.getLibSubpathRelativeToProjectPath(subpath, projectData); assert.equal(result, path.join("..", "..", "sub", "path")); }); }); @@ -621,7 +621,7 @@ describe("iOS Project Service Signing", () => { describe("Check for Changes", () => { it("sets signingChanged if no Xcode project exists", async () => { - let changes = {}; + const changes = {}; await iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev", teamId: undefined }, projectData); assert.isTrue(!!changes.signingChanged); }); @@ -635,7 +635,7 @@ describe("iOS Project Service Signing", () => { } }; }; - let changes = {}; + const changes = {}; await iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev", teamId: undefined }, projectData); assert.isTrue(!!changes.signingChanged); }); @@ -654,7 +654,7 @@ describe("iOS Project Service Signing", () => { } }; }; - let changes = {}; + const changes = {}; await iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev", teamId: undefined }, projectData); assert.isTrue(!!changes.signingChanged); }); @@ -673,7 +673,7 @@ describe("iOS Project Service Signing", () => { } }; }; - let changes = {}; + const changes = {}; await iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev", teamId: undefined }, projectData); assert.isFalse(!!changes.signingChanged); }); @@ -699,7 +699,7 @@ describe("iOS Project Service Signing", () => { } }); it("succeeds if the provision name is provided for development cert", async () => { - let stack: any = []; + const stack: any = []; pbxprojDomXcode.Xcode.open = function (path: string) { assert.equal(path, pbxproj); return { @@ -718,7 +718,7 @@ describe("iOS Project Service Signing", () => { assert.deepEqual(stack, [{ targetName: projectDirName, manualSigning: { team: "TKID101", uuid: "12345", name: "NativeScriptDev", identity: "iPhone Developer" } }, "save()"]); }); it("succeds if the provision name is provided for distribution cert", async () => { - let stack: any = []; + const stack: any = []; pbxprojDomXcode.Xcode.open = function (path: string) { assert.equal(path, pbxproj); return { @@ -737,7 +737,7 @@ describe("iOS Project Service Signing", () => { assert.deepEqual(stack, [{ targetName: projectDirName, manualSigning: { team: "TKID202", uuid: "6789", name: "NativeScriptDist", identity: "iPhone Distribution" } }, "save()"]); }); it("succeds if the provision name is provided for adhoc cert", async () => { - let stack: any = []; + const stack: any = []; pbxprojDomXcode.Xcode.open = function (path: string) { assert.equal(path, pbxproj); return { @@ -765,22 +765,22 @@ describe("Merge Project XCConfig files", () => { return; } const assertPropertyValues = (expected: any, xcconfigPath: string, injector: IInjector) => { - let service = injector.resolve('xCConfigService'); + const service = injector.resolve('xCConfigService'); _.forOwn(expected, (value, key) => { - let actual = service.readPropertyValue(xcconfigPath, key); + const actual = service.readPropertyValue(xcconfigPath, key); assert.equal(actual, value); }); }; - let projectName: string, - projectPath: string, - testInjector: IInjector, - iOSProjectService: IOSProjectService, - projectData: IProjectData, - fs: IFileSystem, - appResourcesXcconfigPath: string, - appResourceXCConfigContent: string, - iOSEntitlementsService: IOSEntitlementsService; + let projectName: string; + let projectPath: string; + let testInjector: IInjector; + let iOSProjectService: IOSProjectService; + let projectData: IProjectData; + let fs: IFileSystem; + let appResourcesXcconfigPath: string; + let appResourceXCConfigContent: string; + let iOSEntitlementsService: IOSEntitlementsService; beforeEach(() => { projectName = "projectDirectory"; @@ -801,7 +801,7 @@ describe("Merge Project XCConfig files", () => { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; `; - let testPackageJson = { + const testPackageJson = { "name": "test-project", "version": "0.0.1" }; @@ -814,14 +814,14 @@ describe("Merge Project XCConfig files", () => { fs.writeFile(appResourcesXcconfigPath, appResourceXCConfigContent); // run merge for all release: debug|release - for (let release in [true, false]) { + for (const release in [true, false]) { await (iOSProjectService).mergeProjectXcconfigFiles(release, projectData); - let destinationFilePath = release ? (iOSProjectService).getPluginsReleaseXcconfigFilePath(projectData) + const destinationFilePath = release ? (iOSProjectService).getPluginsReleaseXcconfigFilePath(projectData) : (iOSProjectService).getPluginsDebugXcconfigFilePath(projectData); assert.isTrue(fs.exists(destinationFilePath), 'Target build xcconfig is missing for release: ' + release); - let expected = { + const expected = { 'ASSETCATALOG_COMPILER_APPICON_NAME': 'AppIcon', 'ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME': 'LaunchImage', 'CODE_SIGN_IDENTITY': 'iPhone Distribution' @@ -831,14 +831,14 @@ describe("Merge Project XCConfig files", () => { }); it("Adds the entitlements property if not set by the user", async () => { - for (let release in [true, false]) { + for (const release in [true, false]) { await (iOSProjectService).mergeProjectXcconfigFiles(release, projectData); - let destinationFilePath = release ? (iOSProjectService).getPluginsReleaseXcconfigFilePath(projectData) + const destinationFilePath = release ? (iOSProjectService).getPluginsReleaseXcconfigFilePath(projectData) : (iOSProjectService).getPluginsDebugXcconfigFilePath(projectData); assert.isTrue(fs.exists(destinationFilePath), 'Target build xcconfig is missing for release: ' + release); - let expected = { + const expected = { 'CODE_SIGN_ENTITLEMENTS': iOSEntitlementsService.getPlatformsEntitlementsRelativePath(projectData) }; assertPropertyValues(expected, destinationFilePath, testInjector); @@ -848,18 +848,18 @@ describe("Merge Project XCConfig files", () => { it("The user specified entitlements property takes precedence", async () => { // setup app_resource build.xcconfig const expectedEntitlementsFile = 'user.entitlements'; - let xcconfigEntitlements = appResourceXCConfigContent + `${EOL}CODE_SIGN_ENTITLEMENTS = ${expectedEntitlementsFile}`; + const xcconfigEntitlements = appResourceXCConfigContent + `${EOL}CODE_SIGN_ENTITLEMENTS = ${expectedEntitlementsFile}`; fs.writeFile(appResourcesXcconfigPath, xcconfigEntitlements); // run merge for all release: debug|release - for (let release in [true, false]) { + for (const release in [true, false]) { await (iOSProjectService).mergeProjectXcconfigFiles(release, projectData); - let destinationFilePath = release ? (iOSProjectService).getPluginsReleaseXcconfigFilePath(projectData) + const destinationFilePath = release ? (iOSProjectService).getPluginsReleaseXcconfigFilePath(projectData) : (iOSProjectService).getPluginsDebugXcconfigFilePath(projectData); assert.isTrue(fs.exists(destinationFilePath), 'Target build xcconfig is missing for release: ' + release); - let expected = { + const expected = { 'ASSETCATALOG_COMPILER_APPICON_NAME': 'AppIcon', 'ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME': 'LaunchImage', 'CODE_SIGN_IDENTITY': 'iPhone Distribution', diff --git a/test/npm-installation-manager.ts b/test/npm-installation-manager.ts index ef11d4098d..f36ff54e90 100644 --- a/test/npm-installation-manager.ts +++ b/test/npm-installation-manager.ts @@ -11,7 +11,7 @@ import * as yok from "../lib/common/yok"; import ChildProcessLib = require("../lib/common/child-process"); function createTestInjector(): IInjector { - let testInjector = new yok.Yok(); + const testInjector = new yok.Yok(); testInjector.register("config", ConfigLib.Configuration); testInjector.register("logger", LoggerLib.Logger); @@ -63,7 +63,7 @@ interface ITestData { } describe("Npm installation manager tests", () => { - let testData: IDictionary = { + const testData: IDictionary = { "when there's only one available version and it matches CLI's version": { versions: ["1.4.0"], packageLatestVersion: "1.4.0", @@ -157,19 +157,19 @@ describe("Npm installation manager tests", () => { _.each(testData, (currentTestData: ITestData, testName: string) => { it(`returns correct latest compatible version, ${testName}`, async () => { - let testInjector = createTestInjector(); + const testInjector = createTestInjector(); mockNpm(testInjector, currentTestData.versions, currentTestData.packageLatestVersion); // Mock staticConfig.version - let staticConfig = testInjector.resolve("staticConfig"); + const staticConfig = testInjector.resolve("staticConfig"); staticConfig.version = currentTestData.cliVersion; // Mock npmInstallationManager.getLatestVersion - let npmInstallationManager = testInjector.resolve("npmInstallationManager"); + const npmInstallationManager = testInjector.resolve("npmInstallationManager"); npmInstallationManager.getLatestVersion = (packageName: string) => Promise.resolve(currentTestData.packageLatestVersion); - let actualLatestCompatibleVersion = await npmInstallationManager.getLatestCompatibleVersion(""); + const actualLatestCompatibleVersion = await npmInstallationManager.getLatestCompatibleVersion(""); assert.equal(actualLatestCompatibleVersion, currentTestData.expectedResult); }); }); diff --git a/test/npm-support.ts b/test/npm-support.ts index 3e72a558bb..1b9bb23c3c 100644 --- a/test/npm-support.ts +++ b/test/npm-support.ts @@ -32,12 +32,12 @@ import path = require("path"); import temp = require("temp"); temp.track(); -let assert = require("chai").assert; -let nodeModulesFolderName = "node_modules"; -let packageJsonName = "package.json"; +const assert = require("chai").assert; +const nodeModulesFolderName = "node_modules"; +const packageJsonName = "package.json"; function createTestInjector(): IInjector { - let testInjector = new yok.Yok(); + const testInjector = new yok.Yok(); testInjector.register("fs", FsLib.FileSystem); testInjector.register("adb", {}); testInjector.register("options", OptionsLib.Options); @@ -91,15 +91,15 @@ function createTestInjector(): IInjector { } function createProject(testInjector: IInjector, dependencies?: any): string { - let tempFolder = temp.mkdirSync("npmSupportTests"); - let options = testInjector.resolve("options"); + const tempFolder = temp.mkdirSync("npmSupportTests"); + const options = testInjector.resolve("options"); options.path = tempFolder; dependencies = dependencies || { "lodash": "3.9.3" }; - let packageJsonData: any = { + const packageJsonData: any = { "name": "testModuleName", "version": "0.1.0", "nativescript": { @@ -121,27 +121,27 @@ function createProject(testInjector: IInjector, dependencies?: any): string { } async function setupProject(dependencies?: any): Promise { - let testInjector = createTestInjector(); - let projectFolder = createProject(testInjector, dependencies); + const testInjector = createTestInjector(); + const projectFolder = createProject(testInjector, dependencies); - let fs = testInjector.resolve("fs"); + const fs = testInjector.resolve("fs"); // Creates app folder - let appFolderPath = path.join(projectFolder, "app"); + const appFolderPath = path.join(projectFolder, "app"); fs.createDirectory(appFolderPath); - let appResourcesFolderPath = path.join(appFolderPath, "App_Resources"); + const appResourcesFolderPath = path.join(appFolderPath, "App_Resources"); fs.createDirectory(appResourcesFolderPath); fs.createDirectory(path.join(appResourcesFolderPath, "Android")); fs.createDirectory(path.join(appResourcesFolderPath, "Android", "mockdir")); fs.createDirectory(path.join(appFolderPath, "tns_modules")); // Creates platforms/android folder - let androidFolderPath = path.join(projectFolder, "platforms", "android"); + const androidFolderPath = path.join(projectFolder, "platforms", "android"); fs.ensureDirectoryExists(androidFolderPath); // Mock platform data - let appDestinationFolderPath = path.join(androidFolderPath, "assets"); - let platformsData = testInjector.resolve("platformsData"); + const appDestinationFolderPath = path.join(androidFolderPath, "assets"); + const platformsData = testInjector.resolve("platformsData"); platformsData.getPlatformData = (platform: string) => { return { @@ -176,15 +176,15 @@ async function setupProject(dependencies?: any): Promise { } async function addDependencies(testInjector: IInjector, projectFolder: string, dependencies: any, devDependencies?: any): Promise { - let fs = testInjector.resolve("fs"); - let packageJsonPath = path.join(projectFolder, "package.json"); - let packageJsonData = fs.readJson(packageJsonPath); + const fs = testInjector.resolve("fs"); + const packageJsonPath = path.join(projectFolder, "package.json"); + const packageJsonData = fs.readJson(packageJsonPath); - let currentDependencies = packageJsonData.dependencies; + const currentDependencies = packageJsonData.dependencies; _.extend(currentDependencies, dependencies); if (devDependencies) { - let currentDevDependencies = packageJsonData.devDependencies; + const currentDevDependencies = packageJsonData.devDependencies; _.extend(currentDevDependencies, devDependencies); } fs.writeJson(packageJsonPath, packageJsonData); @@ -202,13 +202,13 @@ async function preparePlatform(testInjector: IInjector): Promise { describe("Npm support tests", () => { let testInjector: IInjector, projectFolder: string, appDestinationFolderPath: string; beforeEach(async () => { - let projectSetup = await setupProject(); + const projectSetup = await setupProject(); testInjector = projectSetup.testInjector; projectFolder = projectSetup.projectFolder; appDestinationFolderPath = projectSetup.appDestinationFolderPath; }); it("Ensures that the installed dependencies are prepared correctly", async () => { - let fs: IFileSystem = testInjector.resolve("fs"); + const fs: IFileSystem = testInjector.resolve("fs"); // Setup await addDependencies(testInjector, projectFolder, { "bplist": "0.0.4" }); @@ -216,9 +216,9 @@ describe("Npm support tests", () => { await preparePlatform(testInjector); // Assert - let tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules"); + const tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules"); - let results = fs.enumerateFilesInDirectorySync(tnsModulesFolderPath, (file, stat) => { + const results = fs.enumerateFilesInDirectorySync(tnsModulesFolderPath, (file, stat) => { return true; }, { enumerateDirectories: true }); @@ -229,38 +229,38 @@ describe("Npm support tests", () => { }); it("Ensures that scoped dependencies are prepared correctly", async () => { // Setup - let fs = testInjector.resolve("fs"); - let scopedName = "@reactivex/rxjs"; - let dependencies: any = {}; + const fs = testInjector.resolve("fs"); + const scopedName = "@reactivex/rxjs"; + const 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. await addDependencies(testInjector, projectFolder, dependencies); // Act await preparePlatform(testInjector); // Assert - let tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules"); - let scopedDependencyPath = path.join(tnsModulesFolderPath, "@reactivex", "rxjs"); + const tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules"); + const scopedDependencyPath = path.join(tnsModulesFolderPath, "@reactivex", "rxjs"); assert.isTrue(fs.exists(scopedDependencyPath)); }); it("Ensures that scoped dependencies are prepared correctly when are not in root level", async () => { // Setup - let customPluginName = "plugin-with-scoped-dependency"; - let customPluginDirectory = temp.mkdirSync("custom-plugin-directory"); + const customPluginName = "plugin-with-scoped-dependency"; + const customPluginDirectory = temp.mkdirSync("custom-plugin-directory"); - let fs: IFileSystem = testInjector.resolve("fs"); + const fs: IFileSystem = testInjector.resolve("fs"); await fs.unzip(path.join("resources", "test", `${customPluginName}.zip`), customPluginDirectory); await addDependencies(testInjector, projectFolder, { "plugin-with-scoped-dependency": `file:${path.join(customPluginDirectory, customPluginName)}` }); // Act await preparePlatform(testInjector); // Assert - let tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules"); - let results = fs.enumerateFilesInDirectorySync(tnsModulesFolderPath, (file, stat) => { + const tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules"); + const results = fs.enumerateFilesInDirectorySync(tnsModulesFolderPath, (file, stat) => { return true; }, { enumerateDirectories: true }); - let filteredResults = results.filter((val) => { + const filteredResults = results.filter((val) => { return _.endsWith(val, path.join("@scoped-plugin", "inner-plugin")); }); @@ -268,9 +268,9 @@ describe("Npm support tests", () => { }); it("Ensures that tns_modules absent when bundling", async () => { - let fs = testInjector.resolve("fs"); - let options = testInjector.resolve("options"); - let tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules"); + const fs = testInjector.resolve("fs"); + const options = testInjector.resolve("options"); + const tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules"); try { options.bundle = false; @@ -292,12 +292,12 @@ describe("Npm support tests", () => { describe("Flatten npm modules tests", () => { it("Doesn't handle the dependencies of devDependencies", async () => { - let projectSetup = await setupProject({}); - let testInjector = projectSetup.testInjector; - let projectFolder = projectSetup.projectFolder; - let appDestinationFolderPath = projectSetup.appDestinationFolderPath; + const projectSetup = await setupProject({}); + const testInjector = projectSetup.testInjector; + const projectFolder = projectSetup.projectFolder; + const appDestinationFolderPath = projectSetup.appDestinationFolderPath; - let devDependencies = { + const devDependencies = { "gulp": "3.9.0", "gulp-jscs": "1.6.0", "gulp-jshint": "1.11.0" @@ -308,32 +308,32 @@ describe("Flatten npm modules tests", () => { await preparePlatform(testInjector); // Assert - let fs = testInjector.resolve("fs"); - let tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules"); + const fs = testInjector.resolve("fs"); + const tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules"); - let gulpFolderPath = path.join(tnsModulesFolderPath, "gulp"); + const gulpFolderPath = path.join(tnsModulesFolderPath, "gulp"); assert.isFalse(fs.exists(gulpFolderPath)); - let gulpJscsFolderPath = path.join(tnsModulesFolderPath, "gulp-jscs"); + const gulpJscsFolderPath = path.join(tnsModulesFolderPath, "gulp-jscs"); assert.isFalse(fs.exists(gulpJscsFolderPath)); - let gulpJshint = path.join(tnsModulesFolderPath, "gulp-jshint"); + const gulpJshint = path.join(tnsModulesFolderPath, "gulp-jshint"); assert.isFalse(fs.exists(gulpJshint)); // Get all gulp dependencies - let gulpJsonContent = fs.readJson(path.join(projectFolder, nodeModulesFolderName, "gulp", packageJsonName)); + const gulpJsonContent = fs.readJson(path.join(projectFolder, nodeModulesFolderName, "gulp", packageJsonName)); _.each(_.keys(gulpJsonContent.dependencies), dependency => { assert.isFalse(fs.exists(path.join(tnsModulesFolderPath, dependency))); }); // Get all gulp-jscs dependencies - let gulpJscsJsonContent = fs.readJson(path.join(projectFolder, nodeModulesFolderName, "gulp-jscs", packageJsonName)); + const gulpJscsJsonContent = fs.readJson(path.join(projectFolder, nodeModulesFolderName, "gulp-jscs", packageJsonName)); _.each(_.keys(gulpJscsJsonContent.dependencies), dependency => { assert.isFalse(fs.exists(path.join(tnsModulesFolderPath, dependency))); }); // Get all gulp-jshint dependencies - let gulpJshintJsonContent = fs.readJson(path.join(projectFolder, nodeModulesFolderName, "gulp-jshint", packageJsonName)); + const gulpJshintJsonContent = fs.readJson(path.join(projectFolder, nodeModulesFolderName, "gulp-jshint", packageJsonName)); _.each(_.keys(gulpJshintJsonContent.dependencies), dependency => { assert.isFalse(fs.exists(path.join(tnsModulesFolderPath, dependency))); }); diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 52a5695698..51dc5acb70 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -91,7 +91,7 @@ class PlatformsData implements IPlatformsData { } function createTestInjector() { - let testInjector = new yok.Yok(); + const testInjector = new yok.Yok(); testInjector.register("injector", testInjector); testInjector.register("hooksService", stubs.HooksServiceStub); @@ -438,8 +438,8 @@ describe('Platform Service Tests', () => { }); it("will call removePlatform and addPlatform on the platformService passing the provided platforms", async () => { - let platformActions: { action: string, platforms: string[] }[] = []; - let cleanCommand = testInjector.resolveCommand("platform|clean"); + const platformActions: { action: string, platforms: string[] }[] = []; + const cleanCommand = testInjector.resolveCommand("platform|clean"); platformService.removePlatforms = async (platforms: string[]) => { platformActions.push({ action: "removePlatforms", platforms }); @@ -453,7 +453,7 @@ describe('Platform Service Tests', () => { await cleanCommand.execute(["ios"]); - let expectedPlatformActions = [ + const expectedPlatformActions = [ { action: "removePlatforms", platforms: ["ios"] }, { action: "addPlatforms", platforms: ["ios"] }, ]; diff --git a/test/platform-service.ts b/test/platform-service.ts index 9333bb297e..8a4faa965a 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -21,11 +21,11 @@ import ProjectChangesLib = require("../lib/services/project-changes-service"); import { Messages } from "../lib/common/messages/messages"; require("should"); -let temp = require("temp"); +const temp = require("temp"); temp.track(); function createTestInjector() { - let testInjector = new yok.Yok(); + const testInjector = new yok.Yok(); testInjector.register('platformService', PlatformServiceLib.PlatformService); testInjector.register('errors', stubs.ErrorsStub); @@ -164,9 +164,9 @@ describe('Platform Service Tests', () => { describe("add platform unit tests", () => { describe("#add platform()", () => { it("should not fail if platform is not normalized", async () => { - let fs = testInjector.resolve("fs"); + const fs = testInjector.resolve("fs"); fs.exists = () => false; - let projectData: IProjectData = testInjector.resolve("projectData"); + const projectData: IProjectData = testInjector.resolve("projectData"); await platformService.addPlatforms(["Android"], "", projectData, config); await platformService.addPlatforms(["ANDROID"], "", projectData, config); await platformService.addPlatforms(["AnDrOiD"], "", projectData, config); @@ -178,19 +178,19 @@ describe('Platform Service Tests', () => { await platformService.addPlatforms(["iOs"], "", projectData, config); }); it("should fail if platform is already installed", async () => { - let projectData: IProjectData = testInjector.resolve("projectData"); + const projectData: IProjectData = testInjector.resolve("projectData"); // By default fs.exists returns true, so the platforms directory should exists await assert.isRejected(platformService.addPlatforms(["android"], "", projectData, config)); await assert.isRejected(platformService.addPlatforms(["ios"], "", projectData, config)); }); it("should fail if npm is unavalible", async () => { - let fs = testInjector.resolve("fs"); + const fs = testInjector.resolve("fs"); fs.exists = () => false; - let errorMessage = "Npm is unavalible"; - let npmInstallationManager = testInjector.resolve("npmInstallationManager"); + const errorMessage = "Npm is unavalible"; + const npmInstallationManager = testInjector.resolve("npmInstallationManager"); npmInstallationManager.install = () => { throw new Error(errorMessage); }; - let projectData: IProjectData = testInjector.resolve("projectData"); + const projectData: IProjectData = testInjector.resolve("projectData"); try { await platformService.addPlatforms(["android"], "", projectData, config); @@ -200,39 +200,39 @@ describe('Platform Service Tests', () => { }); it("should respect platform version in package.json's nativescript key", async () => { const versionString = "2.5.0"; - let fs = testInjector.resolve("fs"); + const fs = testInjector.resolve("fs"); fs.exists = () => false; - let nsValueObject: any = {}; + const nsValueObject: any = {}; nsValueObject[VERSION_STRING] = versionString; - let projectDataService = testInjector.resolve("projectDataService"); + const projectDataService = testInjector.resolve("projectDataService"); projectDataService.getNSValue = () => nsValueObject; - let npmInstallationManager = testInjector.resolve("npmInstallationManager"); + const npmInstallationManager = testInjector.resolve("npmInstallationManager"); npmInstallationManager.install = (packageName: string, packageDir: string, options: INpmInstallOptions) => { assert.deepEqual(options.version, versionString); return ""; }; - let projectData: IProjectData = testInjector.resolve("projectData"); + const projectData: IProjectData = testInjector.resolve("projectData"); await platformService.addPlatforms(["android"], "", projectData, config); await platformService.addPlatforms(["ios"], "", projectData, config); }); it("should install latest platform if no information found in package.json's nativescript key", async () => { - let fs = testInjector.resolve("fs"); + const fs = testInjector.resolve("fs"); fs.exists = () => false; - let projectDataService = testInjector.resolve("projectDataService"); + const projectDataService = testInjector.resolve("projectDataService"); projectDataService.getNSValue = (): any => null; - let npmInstallationManager = testInjector.resolve("npmInstallationManager"); + const npmInstallationManager = testInjector.resolve("npmInstallationManager"); npmInstallationManager.install = (packageName: string, packageDir: string, options: INpmInstallOptions) => { assert.deepEqual(options.version, undefined); return ""; }; - let projectData: IProjectData = testInjector.resolve("projectData"); + const projectData: IProjectData = testInjector.resolve("projectData"); await platformService.addPlatforms(["android"], "", projectData, config); await platformService.addPlatforms(["ios"], "", projectData, config); @@ -240,13 +240,13 @@ describe('Platform Service Tests', () => { }); describe("#add platform(ios)", () => { it("should call validate method", async () => { - let fs = testInjector.resolve("fs"); + const fs = testInjector.resolve("fs"); fs.exists = () => false; - let errorMessage = "Xcode is not installed or Xcode version is smaller that 5.0"; - let platformsData = testInjector.resolve("platformsData"); - let platformProjectService = platformsData.getPlatformData().platformProjectService; - let projectData: IProjectData = testInjector.resolve("projectData"); + const errorMessage = "Xcode is not installed or Xcode version is smaller that 5.0"; + const platformsData = testInjector.resolve("platformsData"); + const platformProjectService = platformsData.getPlatformData().platformProjectService; + const projectData: IProjectData = testInjector.resolve("projectData"); platformProjectService.validate = () => { throw new Error(errorMessage); }; @@ -260,16 +260,16 @@ describe('Platform Service Tests', () => { }); describe("#add platform(android)", () => { it("should fail if java, ant or android are not installed", async () => { - let fs = testInjector.resolve("fs"); + const fs = testInjector.resolve("fs"); fs.exists = () => false; - let errorMessage = "Java, ant or android are not installed"; - let platformsData = testInjector.resolve("platformsData"); - let platformProjectService = platformsData.getPlatformData().platformProjectService; + const errorMessage = "Java, ant or android are not installed"; + const platformsData = testInjector.resolve("platformsData"); + const platformProjectService = platformsData.getPlatformData().platformProjectService; platformProjectService.validate = () => { throw new Error(errorMessage); }; - let projectData: IProjectData = testInjector.resolve("projectData"); + const projectData: IProjectData = testInjector.resolve("projectData"); try { await platformService.addPlatforms(["android"], "", projectData, config); @@ -284,7 +284,7 @@ describe('Platform Service Tests', () => { it("should fail when platforms are not added", async () => { const ExpectedErrorsCaught = 2; let errorsCaught = 0; - let projectData: IProjectData = testInjector.resolve("projectData"); + const projectData: IProjectData = testInjector.resolve("projectData"); testInjector.resolve("fs").exists = () => false; try { @@ -302,7 +302,7 @@ describe('Platform Service Tests', () => { assert.isTrue(errorsCaught === ExpectedErrorsCaught); }); it("shouldn't fail when platforms are added", async () => { - let projectData: IProjectData = testInjector.resolve("projectData"); + const projectData: IProjectData = testInjector.resolve("projectData"); testInjector.resolve("fs").exists = () => false; await platformService.addPlatforms(["android"], "", projectData, config); @@ -314,21 +314,21 @@ describe('Platform Service Tests', () => { describe("clean platform unit tests", () => { it("should preserve the specified in the project nativescript version", async () => { const versionString = "2.4.1"; - let fs = testInjector.resolve("fs"); + const fs = testInjector.resolve("fs"); fs.exists = () => false; - let nsValueObject: any = {}; + const nsValueObject: any = {}; nsValueObject[VERSION_STRING] = versionString; - let projectDataService = testInjector.resolve("projectDataService"); + const projectDataService = testInjector.resolve("projectDataService"); projectDataService.getNSValue = () => nsValueObject; - let npmInstallationManager = testInjector.resolve("npmInstallationManager"); + const npmInstallationManager = testInjector.resolve("npmInstallationManager"); npmInstallationManager.install = (packageName: string, packageDir: string, options: INpmInstallOptions) => { assert.deepEqual(options.version, versionString); return ""; }; - let projectData: IProjectData = testInjector.resolve("projectData"); + const projectData: IProjectData = testInjector.resolve("projectData"); platformService.removePlatforms = (platforms: string[], prjctData: IProjectData): Promise => { nsValueObject[VERSION_STRING] = undefined; return Promise.resolve(); @@ -351,9 +351,9 @@ describe('Platform Service Tests', () => { describe("update Platform", () => { describe("#updatePlatform(platform)", () => { it("should fail when the versions are the same", async () => { - let npmInstallationManager: INpmInstallationManager = testInjector.resolve("npmInstallationManager"); + const npmInstallationManager: INpmInstallationManager = testInjector.resolve("npmInstallationManager"); npmInstallationManager.getLatestVersion = async () => "0.2.0"; - let projectData: IProjectData = testInjector.resolve("projectData"); + const projectData: IProjectData = testInjector.resolve("projectData"); await assert.isRejected(platformService.updatePlatforms(["android"], "", projectData, null)); }); @@ -370,22 +370,22 @@ describe('Platform Service Tests', () => { }); function prepareDirStructure() { - let tempFolder = temp.mkdirSync("prepare_platform"); + const tempFolder = temp.mkdirSync("prepare_platform"); - let appFolderPath = path.join(tempFolder, "app"); + const appFolderPath = path.join(tempFolder, "app"); fs.createDirectory(appFolderPath); - let nodeModulesPath = path.join(tempFolder, "node_modules"); + const nodeModulesPath = path.join(tempFolder, "node_modules"); fs.createDirectory(nodeModulesPath); - let testsFolderPath = path.join(appFolderPath, "tests"); + const testsFolderPath = path.join(appFolderPath, "tests"); fs.createDirectory(testsFolderPath); - let app1FolderPath = path.join(tempFolder, "app1"); + const app1FolderPath = path.join(tempFolder, "app1"); fs.createDirectory(app1FolderPath); - let appDestFolderPath = path.join(tempFolder, "appDest"); - let appResourcesFolderPath = path.join(appDestFolderPath, "App_Resources"); + const appDestFolderPath = path.join(tempFolder, "appDest"); + const appResourcesFolderPath = path.join(appDestFolderPath, "App_Resources"); fs.writeJson(path.join(tempFolder, "package.json"), { name: "testname", nativescript: { @@ -398,7 +398,7 @@ describe('Platform Service Tests', () => { async function execPreparePlatform(platformToTest: string, testDirData: any, release?: boolean) { - let platformsData = testInjector.resolve("platformsData"); + const platformsData = testInjector.resolve("platformsData"); platformsData.platformsNames = ["ios", "android"]; platformsData.getPlatformData = (platform: string) => { return { @@ -415,11 +415,11 @@ describe('Platform Service Tests', () => { afterCreateProject: (projectRoot: string): any => null, getAppResourcesDestinationDirectoryPath: (projectData: IProjectData, frameworkVersion?: string): string => { if (platform.toLowerCase() === "ios") { - let dirPath = path.join(testDirData.appDestFolderPath, "Resources"); + const dirPath = path.join(testDirData.appDestFolderPath, "Resources"); fs.ensureDirectoryExists(dirPath); return dirPath; } else { - let dirPath = path.join(testDirData.appDestFolderPath, "src", "main", "res"); + const dirPath = path.join(testDirData.appDestFolderPath, "src", "main", "res"); fs.ensureDirectoryExists(dirPath); return dirPath; } @@ -434,7 +434,7 @@ describe('Platform Service Tests', () => { }; }; - let projectData = testInjector.resolve("projectData"); + const projectData = testInjector.resolve("projectData"); projectData.projectDir = testDirData.tempFolder; projectData.appDirectoryPath = testDirData.appFolderPath; projectData.appResourcesDirectoryPath = path.join(testDirData.appFolderPath, "App_Resources"); @@ -446,21 +446,21 @@ describe('Platform Service Tests', () => { } async function testPreparePlatform(platformToTest: string, release?: boolean): Promise { - let testDirData = prepareDirStructure(); - let created: CreatedTestData = new CreatedTestData(); + const testDirData = prepareDirStructure(); + const created: CreatedTestData = new CreatedTestData(); created.testDirData = testDirData; // Add platform specific files to app and app1 folders - let platformSpecificFiles = [ + const platformSpecificFiles = [ "test1.ios.js", "test1-ios-js", "test2.android.js", "test2-android-js", "main.js" ]; - let destinationDirectories = [testDirData.appFolderPath, testDirData.app1FolderPath]; + const destinationDirectories = [testDirData.appFolderPath, testDirData.app1FolderPath]; _.each(destinationDirectories, directoryPath => { _.each(platformSpecificFiles, filePath => { - let fileFullPath = path.join(directoryPath, filePath); + const fileFullPath = path.join(directoryPath, filePath); fs.writeFile(fileFullPath, "testData"); created.files.push(fileFullPath); @@ -469,19 +469,19 @@ describe('Platform Service Tests', () => { // Add App_Resources file to app and app1 folders _.each(destinationDirectories, directoryPath => { - let iosIconFullPath = path.join(directoryPath, "App_Resources/iOS/icon.png"); + const iosIconFullPath = path.join(directoryPath, "App_Resources/iOS/icon.png"); fs.writeFile(iosIconFullPath, "test-image"); created.resources.ios.push(iosIconFullPath); - let androidFullPath = path.join(directoryPath, "App_Resources/Android/icon.png"); + const androidFullPath = path.join(directoryPath, "App_Resources/Android/icon.png"); fs.writeFile(androidFullPath, "test-image"); created.resources.android.push(androidFullPath); }); await execPreparePlatform(platformToTest, testDirData, release); - let test1FileName = platformToTest.toLowerCase() === "ios" ? "test1.js" : "test2.js"; - let test2FileName = platformToTest.toLowerCase() === "ios" ? "test2.js" : "test1.js"; + const test1FileName = platformToTest.toLowerCase() === "ios" ? "test1.js" : "test2.js"; + const test2FileName = platformToTest.toLowerCase() === "ios" ? "test2.js" : "test1.js"; // Asserts that the files in app folder are process as platform specific assert.isTrue(fs.exists(path.join(testDirData.appDestFolderPath, "app", test1FileName))); @@ -502,7 +502,7 @@ describe('Platform Service Tests', () => { } function updateFile(files: string[], fileName: string, content: string) { - let fileToUpdate = _.find(files, (f) => f.indexOf(fileName) !== -1); + const fileToUpdate = _.find(files, (f) => f.indexOf(fileName) !== -1); fs.writeFile(fileToUpdate, content); } @@ -523,7 +523,7 @@ describe('Platform Service Tests', () => { }); function getDefaultFolderVerificationData(platform: string, appDestFolderPath: string) { - let data: any = {}; + const data: any = {}; if (platform.toLowerCase() === "ios") { data[path.join(appDestFolderPath, "app")] = { missingFiles: ["test1.ios.js", "test2.android.js", "test2.js", "App_Resources"], @@ -559,13 +559,13 @@ describe('Platform Service Tests', () => { function mergeModifications(def: any, mod: any) { // custom merge to reflect changes - let merged: any = _.cloneDeep(def); + const merged: any = _.cloneDeep(def); _.forOwn(mod, (modFolder, folderRoot) => { // whole folder not present in Default if (!def.hasOwnProperty(folderRoot)) { merged[folderRoot] = _.cloneDeep(modFolder[folderRoot]); } else { - let defFolder = def[folderRoot]; + const defFolder = def[folderRoot]; merged[folderRoot].filesWithContent = _.merge(defFolder.filesWithContent || [], modFolder.filesWithContent || []); merged[folderRoot].missingFiles = (defFolder.missingFiles || []).concat(modFolder.missingFiles || []); merged[folderRoot].presentFiles = (defFolder.presentFiles || []).concat(modFolder.presentFiles || []); @@ -592,27 +592,27 @@ describe('Platform Service Tests', () => { // 4. Gets the Default Destination App Structure and merges it with the Modifications // 5. Asserts the Destination App matches our expectations async function testChangesApplied(platform: string, applyChangesFn: (createdTestData: CreatedTestData) => any) { - let createdTestData = await testPreparePlatform(platform); + const createdTestData = await testPreparePlatform(platform); - let modifications = applyChangesFn(createdTestData); + const modifications = applyChangesFn(createdTestData); await execPreparePlatform(platform, createdTestData.testDirData); - let defaultStructure = getDefaultFolderVerificationData(platform, createdTestData.testDirData.appDestFolderPath); + const defaultStructure = getDefaultFolderVerificationData(platform, createdTestData.testDirData.appDestFolderPath); - let merged = mergeModifications(defaultStructure, modifications); + const merged = mergeModifications(defaultStructure, modifications); DestinationFolderVerifier.verify(merged, fs); } it("should sync only changed files, without special folders (iOS)", async () => { - let applyChangesFn = (createdTestData: CreatedTestData) => { + const applyChangesFn = (createdTestData: CreatedTestData) => { // apply changes const expectedFileContent = "updated-content-ios"; updateFile(createdTestData.files, "test1.ios.js", expectedFileContent); // construct the folder modifications data - let modifications: any = {}; + const modifications: any = {}; modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { filesWithContent: [ { @@ -627,13 +627,13 @@ describe('Platform Service Tests', () => { }); it("should sync only changed files, without special folders (Android) #2697", async () => { - let applyChangesFn = (createdTestData: CreatedTestData) => { + const applyChangesFn = (createdTestData: CreatedTestData) => { // apply changes const expectedFileContent = "updated-content-android"; updateFile(createdTestData.files, "test2.android.js", expectedFileContent); // construct the folder modifications data - let modifications: any = {}; + const modifications: any = {}; modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { filesWithContent: [ { @@ -648,14 +648,14 @@ describe('Platform Service Tests', () => { }); it("Ensure App_Resources get reloaded after change in the app folder (iOS) #2560", async () => { - let applyChangesFn = (createdTestData: CreatedTestData) => { + const applyChangesFn = (createdTestData: CreatedTestData) => { // apply changes const expectedFileContent = "updated-icon-content"; - let iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/iOS/icon.png"); + const iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/iOS/icon.png"); fs.writeFile(iconPngPath, expectedFileContent); // construct the folder modifications data - let modifications: any = {}; + const modifications: any = {}; modifications[createdTestData.testDirData.appDestFolderPath] = { filesWithContent: [ { @@ -671,14 +671,14 @@ describe('Platform Service Tests', () => { }); it("Ensure App_Resources get reloaded after change in the app folder (Android) #2560", async () => { - let applyChangesFn = (createdTestData: CreatedTestData) => { + const applyChangesFn = (createdTestData: CreatedTestData) => { // apply changes const expectedFileContent = "updated-icon-content"; - let iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/Android/icon.png"); + const iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/Android/icon.png"); fs.writeFile(iconPngPath, expectedFileContent); // construct the folder modifications data - let modifications: any = {}; + const modifications: any = {}; modifications[createdTestData.testDirData.appDestFolderPath] = { filesWithContent: [ { @@ -694,14 +694,14 @@ describe('Platform Service Tests', () => { }); it("Ensure App_Resources get reloaded after a new file appears in the app folder (iOS) #2560", async () => { - let applyChangesFn = (createdTestData: CreatedTestData) => { + const applyChangesFn = (createdTestData: CreatedTestData) => { // apply changes const expectedFileContent = "new-file-content"; - let iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/iOS/new-file.png"); + const iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/iOS/new-file.png"); fs.writeFile(iconPngPath, expectedFileContent); // construct the folder modifications data - let modifications: any = {}; + const modifications: any = {}; modifications[createdTestData.testDirData.appDestFolderPath] = { filesWithContent: [ { @@ -717,14 +717,14 @@ describe('Platform Service Tests', () => { }); it("Ensure App_Resources get reloaded after a new file appears in the app folder (Android) #2560", async () => { - let applyChangesFn = (createdTestData: CreatedTestData) => { + const applyChangesFn = (createdTestData: CreatedTestData) => { // apply changes const expectedFileContent = "new-file-content"; - let iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/Android/new-file.png"); + const iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/Android/new-file.png"); fs.writeFile(iconPngPath, expectedFileContent); // construct the folder modifications data - let modifications: any = {}; + const modifications: any = {}; modifications[createdTestData.testDirData.appDestFolderPath] = { filesWithContent: [ { @@ -740,13 +740,13 @@ describe('Platform Service Tests', () => { }); it("should sync new platform specific files (iOS)", async () => { - let applyChangesFn = (createdTestData: CreatedTestData) => { + const applyChangesFn = (createdTestData: CreatedTestData) => { // apply changes const expectedFileContent = "new-content-ios"; fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.ios.js"), expectedFileContent); // construct the folder modifications data - let modifications: any = {}; + const modifications: any = {}; modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { filesWithContent: [ { @@ -762,13 +762,13 @@ describe('Platform Service Tests', () => { }); it("should sync new platform specific files (Android)", async () => { - let applyChangesFn = (createdTestData: CreatedTestData) => { + const applyChangesFn = (createdTestData: CreatedTestData) => { // apply changes const expectedFileContent = "new-content-android"; fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.android.js"), expectedFileContent); // construct the folder modifications data - let modifications: any = {}; + const modifications: any = {}; modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { filesWithContent: [ { @@ -784,13 +784,13 @@ describe('Platform Service Tests', () => { }); it("should sync new common files (iOS)", async () => { - let applyChangesFn = (createdTestData: CreatedTestData) => { + const applyChangesFn = (createdTestData: CreatedTestData) => { // apply changes const expectedFileContent = "new-content-ios"; fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.js"), expectedFileContent); // construct the folder modifications data - let modifications: any = {}; + const modifications: any = {}; modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { filesWithContent: [ { @@ -806,13 +806,13 @@ describe('Platform Service Tests', () => { }); it("should sync new common file (Android)", async () => { - let applyChangesFn = (createdTestData: CreatedTestData) => { + const applyChangesFn = (createdTestData: CreatedTestData) => { // apply changes const expectedFileContent = "new-content-android"; fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.js"), expectedFileContent); // construct the folder modifications data - let modifications: any = {}; + const modifications: any = {}; modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { filesWithContent: [ { @@ -829,13 +829,13 @@ describe('Platform Service Tests', () => { it("invalid xml is caught", async () => { require("colors"); - let testDirData = prepareDirStructure(); + const testDirData = prepareDirStructure(); // generate invalid xml - let fileFullPath = path.join(testDirData.appFolderPath, "file.xml"); + const fileFullPath = path.join(testDirData.appFolderPath, "file.xml"); fs.writeFile(fileFullPath, ""); - let platformsData = testInjector.resolve("platformsData"); + const platformsData = testInjector.resolve("platformsData"); platformsData.platformsNames = ["android"]; platformsData.getPlatformData = (platform: string) => { return { @@ -860,11 +860,11 @@ describe('Platform Service Tests', () => { }; }; - let projectData = testInjector.resolve("projectData"); + const projectData = testInjector.resolve("projectData"); projectData.projectDir = testDirData.tempFolder; platformService = testInjector.resolve("platformService"); - let oldLoggerWarner = testInjector.resolve("$logger").warn; + const oldLoggerWarner = testInjector.resolve("$logger").warn; let warnings: string = ""; try { testInjector.resolve("$logger").warn = (text: string) => warnings += text; diff --git a/test/plugin-variables-service.ts b/test/plugin-variables-service.ts index 3576c4d284..2beb1737e0 100644 --- a/test/plugin-variables-service.ts +++ b/test/plugin-variables-service.ts @@ -17,7 +17,7 @@ import * as temp from "temp"; temp.track(); function createTestInjector(): IInjector { - let testInjector = new Yok(); + const testInjector = new Yok(); testInjector.register("messagesService", MessagesService); testInjector.register("errors", Errors); @@ -32,7 +32,7 @@ function createTestInjector(): IInjector { testInjector.register("projectHelper", ProjectHelper); testInjector.register("prompter", { get: () => { - let errors: IErrors = testInjector.resolve("errors"); + const errors: IErrors = testInjector.resolve("errors"); errors.fail("$prompter.get function shouldn't be called!"); } }); @@ -42,12 +42,12 @@ function createTestInjector(): IInjector { } async function createProjectFile(testInjector: IInjector): Promise { - let tempFolder = temp.mkdirSync("pluginVariablesService"); + const tempFolder = temp.mkdirSync("pluginVariablesService"); - let options = testInjector.resolve("options"); + const options = testInjector.resolve("options"); options.path = tempFolder; - let projectData = { + const projectData = { "name": "myProject", "nativescript": {} }; @@ -57,7 +57,7 @@ async function createProjectFile(testInjector: IInjector): Promise { } function createPluginData(pluginVariables: any): IPluginData { - let pluginData = { + const pluginData = { name: "myTestPlugin", version: "", fullPath: "", @@ -82,19 +82,19 @@ describe("Plugin Variables service", () => { describe("plugin add when the console is non interactive", () => { beforeEach(() => { - let helpers = require("./../lib/common/helpers"); + const helpers = require("./../lib/common/helpers"); helpers.isInteractive = () => false; }); it("fails when no --var option and no default value are specified", async () => { await createProjectFile(testInjector); - let pluginVariables = { "MY_TEST_PLUGIN_VARIABLE": {} }; - let pluginData = createPluginData(pluginVariables); - let pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - let projectData: IProjectData = testInjector.resolve("projectData"); + const pluginVariables = { "MY_TEST_PLUGIN_VARIABLE": {} }; + const pluginData = createPluginData(pluginVariables); + const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); + const projectData: IProjectData = testInjector.resolve("projectData"); projectData.initializeProjectData(); - let expectedError = `Unable to find value for MY_TEST_PLUGIN_VARIABLE plugin variable from ${pluginData.name} plugin. Ensure the --var option is specified or the plugin variable has default value.`; + const expectedError = `Unable to find value for MY_TEST_PLUGIN_VARIABLE plugin variable from ${pluginData.name} plugin. Ensure the --var option is specified or the plugin variable has default value.`; let actualError: string = null; try { @@ -108,126 +108,126 @@ describe("Plugin Variables service", () => { it("does not fail when --var option is specified", async () => { await createProjectFile(testInjector); - let pluginVariableValue = "myAppId"; + const pluginVariableValue = "myAppId"; testInjector.resolve("options").var = { "MY_APP_ID": pluginVariableValue }; - let pluginVariables = { "MY_APP_ID": {} }; - let pluginData = createPluginData(pluginVariables); - let pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - let projectData: IProjectData = testInjector.resolve("projectData"); + const pluginVariables = { "MY_APP_ID": {} }; + const pluginData = createPluginData(pluginVariables); + const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); + const projectData: IProjectData = testInjector.resolve("projectData"); projectData.initializeProjectData(); await pluginVariablesService.savePluginVariablesInProjectFile(pluginData, projectData); - let fs = testInjector.resolve("fs"); - let staticConfig: IStaticConfig = testInjector.resolve("staticConfig"); + const fs = testInjector.resolve("fs"); + const staticConfig: IStaticConfig = testInjector.resolve("staticConfig"); - let projectFileContent = fs.readJson(path.join(projectData.projectDir, "package.json")); + const projectFileContent = fs.readJson(path.join(projectData.projectDir, "package.json")); assert.equal(pluginVariableValue, projectFileContent[staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][`${pluginData.name}-variables`]["MY_APP_ID"]); }); it("does not fail when default value is specified", async () => { await createProjectFile(testInjector); - let defaultPluginValue = "myDefaultValue"; - let pluginVariables = { "MY_TEST_PLUGIN_VARIABLE": { defaultValue: defaultPluginValue } }; - let pluginData = createPluginData(pluginVariables); - let pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - let projectData = testInjector.resolve("projectData"); + const defaultPluginValue = "myDefaultValue"; + const pluginVariables = { "MY_TEST_PLUGIN_VARIABLE": { defaultValue: defaultPluginValue } }; + const pluginData = createPluginData(pluginVariables); + const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); + const projectData = testInjector.resolve("projectData"); projectData.initializeProjectData(); await pluginVariablesService.savePluginVariablesInProjectFile(pluginData, projectData); - let fs = testInjector.resolve("fs"); - let staticConfig: IStaticConfig = testInjector.resolve("staticConfig"); + const fs = testInjector.resolve("fs"); + const staticConfig: IStaticConfig = testInjector.resolve("staticConfig"); - let projectFileContent = fs.readJson(path.join(projectData.projectDir, "package.json")); + const projectFileContent = fs.readJson(path.join(projectData.projectDir, "package.json")); assert.equal(defaultPluginValue, projectFileContent[staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][`${pluginData.name}-variables`]["MY_TEST_PLUGIN_VARIABLE"]); }); }); describe("plugin add when the console is interactive", () => { beforeEach(() => { - let helpers = require("./../lib/common/helpers"); + const helpers = require("./../lib/common/helpers"); helpers.isInteractive = () => true; }); it("prompt for plugin variable value when no --var option and no default value are specified", async () => { await createProjectFile(testInjector); - let pluginVariableValue = "testAppURL"; - let prompter = testInjector.resolve("prompter"); + const pluginVariableValue = "testAppURL"; + const prompter = testInjector.resolve("prompter"); prompter.get = async () => ({ "APP_URL": pluginVariableValue }); - let pluginVariables = { "APP_URL": {} }; - let pluginData = createPluginData(pluginVariables); - let pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - let projectData = testInjector.resolve("projectData"); + const pluginVariables = { "APP_URL": {} }; + const pluginData = createPluginData(pluginVariables); + const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); + const projectData = testInjector.resolve("projectData"); projectData.initializeProjectData(); await pluginVariablesService.savePluginVariablesInProjectFile(pluginData, projectData); - let fs = testInjector.resolve("fs"); - let staticConfig: IStaticConfig = testInjector.resolve("staticConfig"); + const fs = testInjector.resolve("fs"); + const staticConfig: IStaticConfig = testInjector.resolve("staticConfig"); - let projectFileContent = fs.readJson(path.join(projectData.projectDir, "package.json")); + const projectFileContent = fs.readJson(path.join(projectData.projectDir, "package.json")); assert.equal(pluginVariableValue, projectFileContent[staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][`${pluginData.name}-variables`]["APP_URL"]); }); it("does not prompt for plugin variable value when default value is specified", async () => { await createProjectFile(testInjector); - let defaultPluginValue = "myAppNAme"; - let pluginVariables = { "APP_NAME": { defaultValue: defaultPluginValue } }; - let pluginData = createPluginData(pluginVariables); - let pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - let projectData = testInjector.resolve("projectData"); + const defaultPluginValue = "myAppNAme"; + const pluginVariables = { "APP_NAME": { defaultValue: defaultPluginValue } }; + const pluginData = createPluginData(pluginVariables); + const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); + const projectData = testInjector.resolve("projectData"); projectData.initializeProjectData(); await pluginVariablesService.savePluginVariablesInProjectFile(pluginData, projectData); - let fs = testInjector.resolve("fs"); - let staticConfig: IStaticConfig = testInjector.resolve("staticConfig"); + const fs = testInjector.resolve("fs"); + const staticConfig: IStaticConfig = testInjector.resolve("staticConfig"); - let projectFileContent = fs.readJson(path.join(projectData.projectDir, "package.json")); + const projectFileContent = fs.readJson(path.join(projectData.projectDir, "package.json")); assert.equal(defaultPluginValue, projectFileContent[staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][`${pluginData.name}-variables`]["APP_NAME"]); }); it("does not prompt for plugin variable value when --var option is specified", async () => { await createProjectFile(testInjector); - let pluginVariableValue = "pencho.goshko"; + const pluginVariableValue = "pencho.goshko"; testInjector.resolve("options").var = { "USERNAME": pluginVariableValue }; - let pluginVariables = { "USERNAME": {} }; - let pluginData = createPluginData(pluginVariables); - let pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - let projectData = testInjector.resolve("projectData"); + const pluginVariables = { "USERNAME": {} }; + const pluginData = createPluginData(pluginVariables); + const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); + const projectData = testInjector.resolve("projectData"); projectData.initializeProjectData(); await pluginVariablesService.savePluginVariablesInProjectFile(pluginData, projectData); - let fs = testInjector.resolve("fs"); - let staticConfig: IStaticConfig = testInjector.resolve("staticConfig"); + const fs = testInjector.resolve("fs"); + const staticConfig: IStaticConfig = testInjector.resolve("staticConfig"); - let projectFileContent = fs.readJson(path.join(projectData.projectDir, "package.json")); + const projectFileContent = fs.readJson(path.join(projectData.projectDir, "package.json")); assert.equal(pluginVariableValue, projectFileContent[staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][`${pluginData.name}-variables`]["USERNAME"]); }); }); describe("plugin interpolation", () => { it("fails when the plugin value is undefined", async () => { - let tempFolder = await createProjectFile(testInjector); + const tempFolder = await createProjectFile(testInjector); - let pluginVariables = { "MY_VAR": {} }; - let pluginData = createPluginData(pluginVariables); + const pluginVariables = { "MY_VAR": {} }; + const pluginData = createPluginData(pluginVariables); - let fs: IFileSystem = testInjector.resolve("fs"); - let filePath = path.join(tempFolder, "myfile"); + const fs: IFileSystem = testInjector.resolve("fs"); + const filePath = path.join(tempFolder, "myfile"); fs.writeFile(filePath, ""); - let pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - let projectData = testInjector.resolve("projectData"); + const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); + const projectData = testInjector.resolve("projectData"); projectData.initializeProjectData(); - let expectedError = "Unable to find the value for MY_VAR plugin variable into project package.json file. Verify that your package.json file is correct and try again."; + const expectedError = "Unable to find the value for MY_VAR plugin variable into project package.json file. Verify that your package.json file is correct and try again."; let error: string = null; try { await pluginVariablesService.interpolatePluginVariables(pluginData, filePath, projectData); @@ -239,36 +239,36 @@ describe("Plugin Variables service", () => { }); it("interpolates correctly plugin variable value", async () => { - let tempFolder = await createProjectFile(testInjector); + const tempFolder = await createProjectFile(testInjector); - let projectData: IProjectData = testInjector.resolve("projectData"); + const projectData: IProjectData = testInjector.resolve("projectData"); projectData.initializeProjectData(); - let fs: IFileSystem = testInjector.resolve("fs"); + const fs: IFileSystem = testInjector.resolve("fs"); // Write plugin variables values to package.json file - let packageJsonFilePath = path.join(projectData.projectDir, "package.json"); - let data = fs.readJson(packageJsonFilePath); + const packageJsonFilePath = path.join(projectData.projectDir, "package.json"); + const data = fs.readJson(packageJsonFilePath); data["nativescript"]["myTestPlugin-variables"] = { "FB_APP_NAME": "myFacebookAppName" }; fs.writeJson(packageJsonFilePath, data); - let pluginVariables = { "FB_APP_NAME": {} }; - let pluginData = createPluginData(pluginVariables); - let pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - let pluginConfigurationFileContent = '' + + const pluginVariables = { "FB_APP_NAME": {} }; + const pluginData = createPluginData(pluginVariables); + const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); + const pluginConfigurationFileContent = '' + '' + '' + '' + '' + ''; - let filePath = path.join(tempFolder, "myfile"); + const filePath = path.join(tempFolder, "myfile"); fs.writeFile(filePath, pluginConfigurationFileContent); await pluginVariablesService.interpolatePluginVariables(pluginData, filePath, projectData); - let result = fs.readText(filePath); - let expectedResult = '' + + const result = fs.readText(filePath); + const expectedResult = '' + '' + '' + '' + @@ -279,36 +279,36 @@ describe("Plugin Variables service", () => { }); it("interpolates correctly case sensive plugin variable value", async () => { - let tempFolder = await createProjectFile(testInjector); + const tempFolder = await createProjectFile(testInjector); - let projectData: IProjectData = testInjector.resolve("projectData"); + const projectData: IProjectData = testInjector.resolve("projectData"); projectData.initializeProjectData(); - let fs: IFileSystem = testInjector.resolve("fs"); + const fs: IFileSystem = testInjector.resolve("fs"); // Write plugin variables values to package.json file - let packageJsonFilePath = path.join(projectData.projectDir, "package.json"); - let data = fs.readJson(packageJsonFilePath); + const packageJsonFilePath = path.join(projectData.projectDir, "package.json"); + const data = fs.readJson(packageJsonFilePath); data["nativescript"]["myTestPlugin-variables"] = { "FB_APP_NAME": "myFacebookAppName" }; fs.writeJson(packageJsonFilePath, data); - let pluginVariables = { "FB_APP_NAME": {} }; - let pluginData = createPluginData(pluginVariables); - let pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - let pluginConfigurationFileContent = '' + + const pluginVariables = { "FB_APP_NAME": {} }; + const pluginData = createPluginData(pluginVariables); + const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); + const pluginConfigurationFileContent = '' + '' + '' + '' + '' + ''; - let filePath = path.join(tempFolder, "myfile"); + const filePath = path.join(tempFolder, "myfile"); fs.writeFile(filePath, pluginConfigurationFileContent); await pluginVariablesService.interpolatePluginVariables(pluginData, filePath, projectData); - let result = fs.readText(filePath); - let expectedResult = '' + + const result = fs.readText(filePath); + const expectedResult = '' + '' + '' + '' + @@ -319,37 +319,37 @@ describe("Plugin Variables service", () => { }); it("interpolates correctly more than one plugin variables values", async () => { - let tempFolder = await createProjectFile(testInjector); + const tempFolder = await createProjectFile(testInjector); - let projectData: IProjectData = testInjector.resolve("projectData"); + const projectData: IProjectData = testInjector.resolve("projectData"); projectData.initializeProjectData(); - let fs: IFileSystem = testInjector.resolve("fs"); + const fs: IFileSystem = testInjector.resolve("fs"); - let packageJsonFilePath = path.join(projectData.projectDir, "package.json"); - let data = fs.readJson(packageJsonFilePath); + const packageJsonFilePath = path.join(projectData.projectDir, "package.json"); + const data = fs.readJson(packageJsonFilePath); data["nativescript"]["myTestPlugin-variables"] = { "FB_APP_NAME": "myFacebookAppName", "FB_APP_URL": "myFacebookAppURl" }; fs.writeJson(packageJsonFilePath, data); - let pluginVariables = { "FB_APP_NAME": {}, "FB_APP_URL": {} }; - let pluginData = createPluginData(pluginVariables); - let pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - let pluginConfigurationFileContent = '' + + const pluginVariables = { "FB_APP_NAME": {}, "FB_APP_URL": {} }; + const pluginData = createPluginData(pluginVariables); + const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); + const pluginConfigurationFileContent = '' + '' + '' + '' + '' + '' + ''; - let filePath = path.join(tempFolder, "myfile"); + const filePath = path.join(tempFolder, "myfile"); fs.writeFile(filePath, pluginConfigurationFileContent); await pluginVariablesService.interpolatePluginVariables(pluginData, filePath, projectData); - let result = fs.readText(filePath); - let expectedResult = '' + + const result = fs.readText(filePath); + const expectedResult = '' + '' + '' + '' + diff --git a/test/plugins-service.ts b/test/plugins-service.ts index 2744d5f055..57533401b3 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -38,7 +38,7 @@ temp.track(); let isErrorThrown = false; function createTestInjector() { - let testInjector = new Yok(); + const testInjector = new Yok(); testInjector.register("messagesService", MessagesService); testInjector.register("npm", NodePackageManager); testInjector.register("fs", FileSystem); @@ -101,11 +101,11 @@ function createTestInjector() { } function createProjectFile(testInjector: IInjector): string { - let tempFolder = temp.mkdirSync("pluginsService"); - let options = testInjector.resolve("options"); + const tempFolder = temp.mkdirSync("pluginsService"); + const options = testInjector.resolve("options"); options.path = tempFolder; - let packageJsonData = { + const packageJsonData = { "name": "testModuleName", "version": "0.1.0", "nativescript": { @@ -121,7 +121,7 @@ function createProjectFile(testInjector: IInjector): string { } function mockBeginCommand(testInjector: IInjector, expectedErrorMessage: string) { - let errors = testInjector.resolve("errors"); + const errors = testInjector.resolve("errors"); errors.beginCommand = async (action: () => Promise): Promise => { try { return await action(); @@ -135,7 +135,7 @@ function mockBeginCommand(testInjector: IInjector, expectedErrorMessage: string) async function addPluginWhenExpectingToFail(testInjector: IInjector, plugin: string, expectedErrorMessage: string, command?: string) { createProjectFile(testInjector); - let pluginsService: IPluginsService = testInjector.resolve("pluginsService"); + const pluginsService: IPluginsService = testInjector.resolve("pluginsService"); pluginsService.getAllInstalledPlugins = async (projectData: IProjectData) => { return [{ name: "" }]; }; @@ -146,14 +146,14 @@ async function addPluginWhenExpectingToFail(testInjector: IInjector, plugin: str mockBeginCommand(testInjector, "Exception: " + expectedErrorMessage); isErrorThrown = false; - let commandsService = testInjector.resolve(CommandsService); + const commandsService = testInjector.resolve(CommandsService); await commandsService.tryExecuteCommand(`plugin|${command}`, [plugin]); assert.isTrue(isErrorThrown); } function createAndroidManifestFile(projectFolder: string, fs: IFileSystem): void { - let manifest = ` + const manifest = ` @@ -179,7 +179,7 @@ function createAndroidManifestFile(projectFolder: string, fs: IFileSystem): void describe("Plugins service", () => { let testInjector: IInjector; - let commands = ["add", "install"]; + const commands = ["add", "install"]; beforeEach(() => { testInjector = createTestInjector(); testInjector.registerCommand("plugin|add", AddPluginCommand); @@ -195,18 +195,18 @@ describe("Plugins service", () => { await addPluginWhenExpectingToFail(testInjector, "lodash", "lodash is not a valid NativeScript plugin. Verify that the plugin package.json file contains a nativescript key and try again.", command); }); it("fails when the plugin is already installed", async () => { - let pluginName = "plugin1"; - let projectFolder = createProjectFile(testInjector); - let fs = testInjector.resolve("fs"); + const pluginName = "plugin1"; + const projectFolder = createProjectFile(testInjector); + const fs = testInjector.resolve("fs"); // Add plugin - let projectFilePath = path.join(projectFolder, "package.json"); - let projectData = require(projectFilePath); + const projectFilePath = path.join(projectFolder, "package.json"); + const projectData = require(projectFilePath); projectData.dependencies = {}; projectData.dependencies[pluginName] = "^1.0.0"; fs.writeJson(projectFilePath, projectData); - let pluginsService: IPluginsService = testInjector.resolve("pluginsService"); + const pluginsService: IPluginsService = testInjector.resolve("pluginsService"); pluginsService.getAllInstalledPlugins = async (projData: IProjectData) => { return [{ name: "plugin1" }]; }; @@ -214,20 +214,20 @@ describe("Plugins service", () => { mockBeginCommand(testInjector, "Exception: " + 'Plugin "plugin1" is already installed.'); isErrorThrown = false; - let commandsService = testInjector.resolve(CommandsService); + const commandsService = testInjector.resolve(CommandsService); await commandsService.tryExecuteCommand(`plugin|${command}`, [pluginName]); assert.isTrue(isErrorThrown); }); it("fails when the plugin does not support the installed framework", async () => { let isWarningMessageShown = false; - let expectedWarningMessage = "mySamplePlugin 1.5.0 for android is not compatible with the currently installed framework version 1.4.0."; + const 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"; - let projectFolder = createProjectFile(testInjector); - let pluginFolderPath = path.join(projectFolder, pluginName); - let pluginJsonData = { + const pluginName = "mySamplePlugin"; + const projectFolder = createProjectFile(testInjector); + const pluginFolderPath = path.join(projectFolder, pluginName); + const pluginJsonData = { "name": pluginName, "version": "0.0.1", "nativescript": { @@ -236,7 +236,7 @@ describe("Plugins service", () => { } }, }; - let fs = testInjector.resolve("fs"); + const fs = testInjector.resolve("fs"); fs.writeJson(path.join(pluginFolderPath, "package.json"), pluginJsonData); // Adds android platform @@ -245,22 +245,22 @@ describe("Plugins service", () => { fs.createDirectory(path.join(projectFolder, "platforms", "android", "app")); // Mock logger.warn - let logger = testInjector.resolve("logger"); + const logger = testInjector.resolve("logger"); logger.warn = (message: string) => { assert.equal(message, expectedWarningMessage); isWarningMessageShown = true; }; // Mock pluginsService - let pluginsService: IPluginsService = testInjector.resolve("pluginsService"); - let projectData: IProjectData = testInjector.resolve("projectData"); + const pluginsService: IPluginsService = testInjector.resolve("pluginsService"); + const projectData: IProjectData = testInjector.resolve("projectData"); projectData.initializeProjectData(); pluginsService.getAllInstalledPlugins = async (projData: IProjectData) => { return [{ name: "" }]; }; // Mock platformsData - let platformsData = testInjector.resolve("platformsData"); + const platformsData = testInjector.resolve("platformsData"); platformsData.getPlatformData = (platform: string) => { return { appDestinationDirectoryPath: path.join(projectFolder, "platforms", "android"), @@ -274,77 +274,77 @@ describe("Plugins service", () => { assert.isTrue(isWarningMessageShown); }); it("adds plugin by name", async () => { - let pluginName = "plugin1"; - let projectFolder = createProjectFile(testInjector); + const pluginName = "plugin1"; + const projectFolder = createProjectFile(testInjector); - let pluginsService: IPluginsService = testInjector.resolve("pluginsService"); + const pluginsService: IPluginsService = testInjector.resolve("pluginsService"); pluginsService.getAllInstalledPlugins = async (projectData: IProjectData) => { return [{ name: "" }]; }; - let commandsService = testInjector.resolve(CommandsService); + const commandsService = testInjector.resolve(CommandsService); await commandsService.tryExecuteCommand(`plugin|${command}`, [pluginName]); - let fs = testInjector.resolve("fs"); + const fs = testInjector.resolve("fs"); // Asserts that the all plugin's content is successfully added to node_modules folder - let nodeModulesFolderPath = path.join(projectFolder, "node_modules"); + const nodeModulesFolderPath = path.join(projectFolder, "node_modules"); assert.isTrue(fs.exists(nodeModulesFolderPath)); - let pluginFolderPath = path.join(nodeModulesFolderPath, pluginName); + const pluginFolderPath = path.join(nodeModulesFolderPath, pluginName); assert.isTrue(fs.exists(pluginFolderPath)); - let pluginFiles = ["injex.js", "main.js", "package.json"]; + const pluginFiles = ["injex.js", "main.js", "package.json"]; _.each(pluginFiles, pluginFile => { assert.isTrue(fs.exists(path.join(pluginFolderPath, pluginFile))); }); // Asserts that the plugin is added in package.json file - let packageJsonContent = fs.readJson(path.join(projectFolder, "package.json")); - let actualDependencies = packageJsonContent.dependencies; - let expectedDependencies = { "plugin1": "^1.0.3" }; - let expectedDependenciesExact = { "plugin1": "1.0.3" }; + const packageJsonContent = fs.readJson(path.join(projectFolder, "package.json")); + const actualDependencies = packageJsonContent.dependencies; + const expectedDependencies = { "plugin1": "^1.0.3" }; + const expectedDependenciesExact = { "plugin1": "1.0.3" }; assert.isTrue(_.isEqual(actualDependencies, expectedDependencies) || _.isEqual(actualDependencies, expectedDependenciesExact)); }); it("adds plugin by name and version", async () => { - let pluginName = "plugin1"; - let projectFolder = createProjectFile(testInjector); + const pluginName = "plugin1"; + const projectFolder = createProjectFile(testInjector); - let pluginsService: IPluginsService = testInjector.resolve("pluginsService"); + const pluginsService: IPluginsService = testInjector.resolve("pluginsService"); pluginsService.getAllInstalledPlugins = async (projectData: IProjectData) => { return [{ name: "" }]; }; - let commandsService = testInjector.resolve(CommandsService); + const commandsService = testInjector.resolve(CommandsService); await commandsService.tryExecuteCommand(`plugin|${command}`, [pluginName + "@1.0.0"]); - let fs = testInjector.resolve("fs"); + const fs = testInjector.resolve("fs"); // Assert that the all plugin's content is successfully added to node_modules folder - let nodeModulesFolderPath = path.join(projectFolder, "node_modules"); + const nodeModulesFolderPath = path.join(projectFolder, "node_modules"); assert.isTrue(fs.exists(nodeModulesFolderPath)); - let pluginFolderPath = path.join(nodeModulesFolderPath, pluginName); + const pluginFolderPath = path.join(nodeModulesFolderPath, pluginName); assert.isTrue(fs.exists(pluginFolderPath)); - let pluginFiles = ["injex.js", "main.js", "package.json"]; + const pluginFiles = ["injex.js", "main.js", "package.json"]; _.each(pluginFiles, pluginFile => { assert.isTrue(fs.exists(path.join(pluginFolderPath, pluginFile))); }); // Assert that the plugin is added in package.json file - let packageJsonContent = fs.readJson(path.join(projectFolder, "package.json")); - let actualDependencies = packageJsonContent.dependencies; - let expectedDependencies = { "plugin1": "^1.0.0" }; - let expectedDependenciesExact = { "plugin1": "1.0.0" }; + const packageJsonContent = fs.readJson(path.join(projectFolder, "package.json")); + const actualDependencies = packageJsonContent.dependencies; + const expectedDependencies = { "plugin1": "^1.0.0" }; + const expectedDependenciesExact = { "plugin1": "1.0.0" }; assert.isTrue(_.isEqual(actualDependencies, expectedDependencies) || _.isEqual(actualDependencies, expectedDependenciesExact)); }); it("adds plugin by local path", async () => { // Creates a plugin in tempFolder - let pluginName = "mySamplePlugin"; - let projectFolder = createProjectFile(testInjector); - let pluginFolderPath = path.join(projectFolder, pluginName); - let pluginJsonData = { + const pluginName = "mySamplePlugin"; + const projectFolder = createProjectFile(testInjector); + const pluginFolderPath = path.join(projectFolder, pluginName); + const pluginJsonData = { "name": pluginName, "version": "0.0.1", "nativescript": { @@ -353,23 +353,23 @@ describe("Plugins service", () => { } }, }; - let fs = testInjector.resolve("fs"); + const fs = testInjector.resolve("fs"); fs.writeJson(path.join(pluginFolderPath, "package.json"), pluginJsonData); - let pluginsService: IPluginsService = testInjector.resolve("pluginsService"); + const pluginsService: IPluginsService = testInjector.resolve("pluginsService"); pluginsService.getAllInstalledPlugins = async (projectData: IProjectData) => { return [{ name: "" }]; }; - let commandsService = testInjector.resolve(CommandsService); + const commandsService = testInjector.resolve(CommandsService); await commandsService.tryExecuteCommand(`plugin|${command}`, [pluginFolderPath]); // Assert that the all plugin's content is successfully added to node_modules folder - let nodeModulesFolderPath = path.join(projectFolder, "node_modules"); + const nodeModulesFolderPath = path.join(projectFolder, "node_modules"); assert.isTrue(fs.exists(nodeModulesFolderPath)); assert.isTrue(fs.exists(path.join(nodeModulesFolderPath, pluginName))); - let pluginFiles = ["package.json"]; + const pluginFiles = ["package.json"]; _.each(pluginFiles, pluginFile => { assert.isTrue(fs.exists(path.join(nodeModulesFolderPath, pluginName, pluginFile))); }); @@ -379,10 +379,10 @@ describe("Plugins service", () => { }); it("doesn't install dev dependencies when --production option is specified", async () => { // Creates a plugin in tempFolder - let pluginName = "mySamplePlugin"; - let projectFolder = createProjectFile(testInjector); - let pluginFolderPath = path.join(projectFolder, pluginName); - let pluginJsonData = { + const pluginName = "mySamplePlugin"; + const projectFolder = createProjectFile(testInjector); + const pluginFolderPath = path.join(projectFolder, pluginName); + const pluginJsonData = { "name": pluginName, "version": "0.0.1", "nativescript": { @@ -394,30 +394,30 @@ describe("Plugins service", () => { "grunt": "0.4.2" } }; - let fs = testInjector.resolve("fs"); + const fs = testInjector.resolve("fs"); fs.writeJson(path.join(pluginFolderPath, "package.json"), pluginJsonData); - let pluginsService: IPluginsService = testInjector.resolve("pluginsService"); + const pluginsService: IPluginsService = testInjector.resolve("pluginsService"); pluginsService.getAllInstalledPlugins = async (projectData: IProjectData) => { return [{ name: "" }]; }; // Mock options - let options = testInjector.resolve("options"); + const options = testInjector.resolve("options"); options.production = true; - let commandsService = testInjector.resolve(CommandsService); + const commandsService = testInjector.resolve(CommandsService); await commandsService.tryExecuteCommand(`plugin|${command}`, [pluginFolderPath]); - let nodeModulesFolderPath = path.join(projectFolder, "node_modules"); + const nodeModulesFolderPath = path.join(projectFolder, "node_modules"); assert.isFalse(fs.exists(path.join(nodeModulesFolderPath, pluginName, "node_modules", "grunt"))); }); it("install dev dependencies when --production option is not specified", async () => { // Creates a plugin in tempFolder - let pluginName = "mySamplePlugin"; - let projectFolder = createProjectFile(testInjector); - let pluginFolderPath = path.join(projectFolder, pluginName); - let pluginJsonData = { + const pluginName = "mySamplePlugin"; + const projectFolder = createProjectFile(testInjector); + const pluginFolderPath = path.join(projectFolder, pluginName); + const pluginJsonData = { "name": pluginName, "version": "0.0.1", "nativescript": { @@ -432,19 +432,19 @@ describe("Plugins service", () => { "grunt": "0.4.2" } }; - let fs = testInjector.resolve("fs"); + const fs = testInjector.resolve("fs"); fs.writeJson(path.join(pluginFolderPath, "package.json"), pluginJsonData); - let pluginsService: IPluginsService = testInjector.resolve("pluginsService"); + const pluginsService: IPluginsService = testInjector.resolve("pluginsService"); pluginsService.getAllInstalledPlugins = async (projectData: IProjectData) => { return [{ name: "" }]; }; // Mock options - let options = testInjector.resolve("options"); + const options = testInjector.resolve("options"); options.production = false; - let commandsService = testInjector.resolve(CommandsService); + const commandsService = testInjector.resolve(CommandsService); await commandsService.tryExecuteCommand(`plugin|${command}`, [pluginFolderPath]); }); }); @@ -456,10 +456,10 @@ describe("Plugins service", () => { testInjector.registerCommand("plugin|add", AddPluginCommand); }); it("fails if the plugin contains incorrect xml", async () => { - let pluginName = "mySamplePlugin"; - let projectFolder = createProjectFile(testInjector); - let pluginFolderPath = path.join(projectFolder, pluginName); - let pluginJsonData: IDependencyData = { + const pluginName = "mySamplePlugin"; + const projectFolder = createProjectFile(testInjector); + const pluginFolderPath = path.join(projectFolder, pluginName); + const pluginJsonData: IDependencyData = { name: pluginName, nativescript: { platforms: { @@ -469,22 +469,22 @@ describe("Plugins service", () => { depth: 0, directory: "some dir" }; - let fs = testInjector.resolve("fs"); + const fs = testInjector.resolve("fs"); fs.writeJson(path.join(pluginFolderPath, "package.json"), pluginJsonData); // Adds AndroidManifest.xml file in platforms/android folder createAndroidManifestFile(projectFolder, fs); // Mock plugins service - let pluginsService: IPluginsService = testInjector.resolve("pluginsService"); + const pluginsService: IPluginsService = testInjector.resolve("pluginsService"); pluginsService.getAllInstalledPlugins = async (projectData: IProjectData) => { return [{ name: "" }]; }; - let appDestinationDirectoryPath = path.join(projectFolder, "platforms", "android"); + const appDestinationDirectoryPath = path.join(projectFolder, "platforms", "android"); // Mock platformsData - let platformsData = testInjector.resolve("platformsData"); + const platformsData = testInjector.resolve("platformsData"); platformsData.getPlatformData = (platform: string) => { return { appDestinationDirectoryPath: appDestinationDirectoryPath, @@ -498,20 +498,20 @@ describe("Plugins service", () => { }; // Ensure the pluginDestinationPath folder exists - let pluginPlatformsDirPath = path.join(projectFolder, "node_modules", pluginName, "platforms", "android"); - let projectData: IProjectData = testInjector.resolve("projectData"); + const pluginPlatformsDirPath = path.join(projectFolder, "node_modules", pluginName, "platforms", "android"); + const projectData: IProjectData = testInjector.resolve("projectData"); projectData.initializeProjectData(); fs.ensureDirectoryExists(pluginPlatformsDirPath); // Creates invalid plugin's AndroidManifest.xml file - let xml = '' + + const xml = '' + '' + ''; - let pluginConfigurationFilePath = path.join(pluginPlatformsDirPath, "AndroidManifest.xml"); + const pluginConfigurationFilePath = path.join(pluginPlatformsDirPath, "AndroidManifest.xml"); fs.writeFile(pluginConfigurationFilePath, xml); // Expected error message. The assertion happens in mockBeginCommand - let expectedErrorMessage = `Exception: Invalid xml file ${pluginConfigurationFilePath}. Additional technical information: element parse error: Exception: Invalid xml file ` + + const expectedErrorMessage = `Exception: Invalid xml file ${pluginConfigurationFilePath}. Additional technical information: element parse error: Exception: Invalid xml file ` + `${pluginConfigurationFilePath}. Additional technical information: unclosed xml attribute` + `\n@#[line:1,col:39].` + `\n@#[line:1,col:39].`; diff --git a/test/post-install.ts b/test/post-install.ts index 2df08117b8..7f2f98bd7a 100644 --- a/test/post-install.ts +++ b/test/post-install.ts @@ -1,7 +1,7 @@ import { assert } from "chai"; // Use require instead of import in order to replace the `spawn` method of child_process -let childProcess = require("child_process"); +const childProcess = require("child_process"); import { SpawnOptions, ChildProcess } from "child_process"; import * as path from "path"; diff --git a/test/project-changes-service.ts b/test/project-changes-service.ts index f4b091bb73..47e8451bdd 100644 --- a/test/project-changes-service.ts +++ b/test/project-changes-service.ts @@ -91,8 +91,8 @@ describe("Project Changes Service Tests", () => { describe("Get Prepare Info File Path", () => { it("Gets the correct Prepare Info path for ios/android", () => { - for (let platform of ["ios", "android"]) { - let actualPrepareInfoPath = serviceTest.projectChangesService + for (const platform of ["ios", "android"]) { + const actualPrepareInfoPath = serviceTest.projectChangesService .getPrepareInfoFilePath(platform, this.projectData); const expectedPrepareInfoPath = path.join(serviceTest.projectDir, @@ -106,8 +106,8 @@ describe("Project Changes Service Tests", () => { describe("Get Prepare Info", () => { it("Returns empty if file path doesn't exists", () => { - for (let platform of ["ios", "android"]) { - let projectInfo = serviceTest.projectChangesService.getPrepareInfo(platform, this.projectData); + for (const platform of ["ios", "android"]) { + const projectInfo = serviceTest.projectChangesService.getPrepareInfo(platform, this.projectData); assert.isNull(projectInfo); } @@ -115,7 +115,7 @@ describe("Project Changes Service Tests", () => { it("Reads the Prepare Info correctly", () => { const fs: FileSystem = serviceTest.resolve("fs"); - for (let platform of ["ios", "android"]) { + for (const platform of ["ios", "android"]) { // arrange const prepareInfoPath = path.join(serviceTest.projectDir, Constants.PLATFORMS_DIR_NAME, platform, ".nsprepareinfo"); @@ -132,7 +132,7 @@ describe("Project Changes Service Tests", () => { fs.writeJson(prepareInfoPath, expectedPrepareInfo); // act - let actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo(platform, this.projectData); + const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo(platform, this.projectData); // assert assert.deepEqual(actualPrepareInfo, expectedPrepareInfo); @@ -142,9 +142,9 @@ describe("Project Changes Service Tests", () => { describe("Accumulates Changes From Project Services", () => { it("accumulates changes from the project service", async () => { - let iOSChanges = await serviceTest.projectChangesService.checkForChanges("ios", serviceTest.projectData, { bundle: false, release: false, provision: undefined, teamId: undefined }); + const iOSChanges = await serviceTest.projectChangesService.checkForChanges("ios", serviceTest.projectData, { bundle: false, release: false, provision: undefined, teamId: undefined }); assert.isTrue(!!iOSChanges.signingChanged, "iOS signingChanged expected to be true"); - let androidChanges = await serviceTest.projectChangesService.checkForChanges("android", serviceTest.projectData, { bundle: false, release: false, provision: undefined, teamId: undefined }); + const androidChanges = await serviceTest.projectChangesService.checkForChanges("android", serviceTest.projectData, { bundle: false, release: false, provision: undefined, teamId: undefined }); assert.isFalse(!!androidChanges.signingChanged, "Android signingChanged expected to be false"); }); }); diff --git a/test/project-commands.ts b/test/project-commands.ts index 3b201324a1..d5ddccaa41 100644 --- a/test/project-commands.ts +++ b/test/project-commands.ts @@ -7,7 +7,7 @@ import { assert } from "chai"; let selectedTemplateName: string; let isProjectCreated: boolean; -let dummyArgs = ["dummyArgsString"]; +const dummyArgs = ["dummyArgsString"]; class ProjectServiceMock implements IProjectService { async createProject(projectOptions: IProjectSettings): Promise { @@ -27,7 +27,7 @@ class ProjectNameValidatorMock implements IProjectNameValidator { } function createTestInjector() { - let testInjector = new Yok(); + const testInjector = new Yok(); testInjector.register("injector", testInjector); testInjector.register("staticConfig", {}); diff --git a/test/project-files-provider.ts b/test/project-files-provider.ts index 2c910275e7..0edd3f5566 100644 --- a/test/project-files-provider.ts +++ b/test/project-files-provider.ts @@ -3,13 +3,13 @@ import { ProjectFilesProvider } from "../lib/providers/project-files-provider"; import { assert } from "chai"; import * as path from "path"; -let projectDir = "projectDir", +const projectDir = "projectDir", appDestinationDirectoryPath = "appDestinationDirectoryPath", appResourcesDestinationDirectoryPath = "appResourcesDestinationDirectoryPath", appSourceDir = path.join(projectDir, "app"); function createTestInjector(): IInjector { - let testInjector = new Yok(); + const testInjector = new Yok(); testInjector.register("mobileHelper", { platformNames: ["Android", "iOS"] }); @@ -36,8 +36,8 @@ function createTestInjector(): IInjector { } describe("project-files-provider", () => { - let testInjector: IInjector, - projectFilesProvider: IProjectFilesProvider; + let testInjector: IInjector; + let projectFilesProvider: IProjectFilesProvider; beforeEach(() => { testInjector = createTestInjector(); @@ -56,38 +56,38 @@ describe("project-files-provider", () => { describe("mapFilePath", () => { it("returns file path from prepared project when path from app dir is passed", () => { - let projectData: IProjectData = testInjector.resolve("projectData"); - let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "test.js"), "android", projectData, {}); + const projectData: IProjectData = testInjector.resolve("projectData"); + const mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "test.js"), "android", projectData, {}); assert.deepEqual(mappedFilePath, path.join(appDestinationDirectoryPath, "app", "test.js")); }); it("returns file path from prepared project when path from app/App_Resources/platform dir is passed", () => { - let projectData: IProjectData = testInjector.resolve("projectData"); - let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "App_Resources", "android", "test.js"), "android", projectData, {}); + const projectData: IProjectData = testInjector.resolve("projectData"); + const mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "App_Resources", "android", "test.js"), "android", projectData, {}); assert.deepEqual(mappedFilePath, path.join(appResourcesDestinationDirectoryPath, "test.js")); }); it("returns null when path from app/App_Resources/android dir is passed and iOS platform is specified", () => { - let projectData: IProjectData = testInjector.resolve("projectData"); - let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "App_Resources", "android", "test.js"), "iOS", projectData, {}); + const projectData: IProjectData = testInjector.resolve("projectData"); + const mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "App_Resources", "android", "test.js"), "iOS", projectData, {}); assert.deepEqual(mappedFilePath, null); }); it("returns null when path from app/App_Resources/ dir (not platform specific) is passed", () => { - let projectData: IProjectData = testInjector.resolve("projectData"); - let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "App_Resources", "test.js"), "android", projectData, {}); + const projectData: IProjectData = testInjector.resolve("projectData"); + const mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "App_Resources", "test.js"), "android", projectData, {}); assert.deepEqual(mappedFilePath, null); }); it("returns file path from prepared project when path from app dir is passed and it contains platform in its name", () => { - let projectData: IProjectData = testInjector.resolve("projectData"); - let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "test.android.js"), "android", projectData, {}); + const projectData: IProjectData = testInjector.resolve("projectData"); + const mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "test.android.js"), "android", projectData, {}); assert.deepEqual(mappedFilePath, path.join(appDestinationDirectoryPath, "app", "test.js")); }); it("returns file path from prepared project when path from app dir is passed and it contains configuration in its name", () => { - let projectData: IProjectData = testInjector.resolve("projectData"); - let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "test.debug.js"), "android", projectData, {}); + const projectData: IProjectData = testInjector.resolve("projectData"); + const mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "test.debug.js"), "android", projectData, {}); assert.deepEqual(mappedFilePath, path.join(appDestinationDirectoryPath, "app", "test.js")); }); }); diff --git a/test/project-name-service.ts b/test/project-name-service.ts index 2fa9bb3989..d354a295af 100644 --- a/test/project-name-service.ts +++ b/test/project-name-service.ts @@ -3,11 +3,11 @@ import { ProjectNameService } from "../lib/services/project-name-service"; import { assert } from "chai"; import { ErrorsStub, LoggerStub } from "./stubs"; -let mockProjectNameValidator = { +const mockProjectNameValidator = { validate: () => true }; -let dummyString: string = "dummyString"; +const dummyString: string = "dummyString"; function createTestInjector(): IInjector { let testInjector: IInjector; @@ -28,8 +28,8 @@ function createTestInjector(): IInjector { describe("Project Name Service Tests", () => { let testInjector: IInjector; let projectNameService: IProjectNameService; - let validProjectName = "valid"; - let invalidProjectNames = ["1invalid", "app"]; + const validProjectName = "valid"; + const invalidProjectNames = ["1invalid", "app"]; beforeEach(() => { testInjector = createTestInjector(); @@ -37,17 +37,17 @@ describe("Project Name Service Tests", () => { }); it("returns correct name when valid name is entered", async () => { - let actualProjectName = await projectNameService.ensureValidName(validProjectName); + const actualProjectName = await projectNameService.ensureValidName(validProjectName); assert.deepEqual(actualProjectName, validProjectName); }); _.each(invalidProjectNames, invalidProjectName => { it(`returns correct name when "${invalidProjectName}" is entered several times and then valid name is entered`, async () => { - let prompter = testInjector.resolve("prompter"); + const prompter = testInjector.resolve("prompter"); prompter.confirm = (message: string): Promise => Promise.resolve(false); - let incorrectInputsLimit = 5; + const incorrectInputsLimit = 5; let incorrectInputsCount = 0; prompter.getString = async (message: string): Promise => { @@ -60,13 +60,13 @@ describe("Project Name Service Tests", () => { } }; - let actualProjectName = await projectNameService.ensureValidName(invalidProjectName); + const actualProjectName = await projectNameService.ensureValidName(invalidProjectName); assert.deepEqual(actualProjectName, validProjectName); }); it(`returns the invalid name when "${invalidProjectName}" is entered and --force flag is present`, async () => { - let actualProjectName = await projectNameService.ensureValidName(validProjectName, { force: true }); + const actualProjectName = await projectNameService.ensureValidName(validProjectName, { force: true }); assert.deepEqual(actualProjectName, validProjectName); }); diff --git a/test/project-service.ts b/test/project-service.ts index 8fd3e9d574..4cb3367dce 100644 --- a/test/project-service.ts +++ b/test/project-service.ts @@ -19,13 +19,13 @@ import { Options } from "../lib/options"; import { HostInfo } from "../lib/common/host-info"; import { ProjectTemplatesService } from "../lib/services/project-templates-service"; -let mockProjectNameValidator = { +const mockProjectNameValidator = { validate: () => true }; -let dummyString: string = "dummyString"; +const dummyString: string = "dummyString"; let hasPromptedForString = false; -let originalIsInteractive = helpers.isInteractive; +const originalIsInteractive = helpers.isInteractive; temp.track(); @@ -33,8 +33,8 @@ async function prepareTestingPath(testInjector: IInjector, packageToInstall: str options = options || { dependencyType: "save" }; const fs = testInjector.resolve("fs"); - let npmInstallationManager = testInjector.resolve("npmInstallationManager"); - let defaultTemplateDir = temp.mkdirSync("project-service"); + const npmInstallationManager = testInjector.resolve("npmInstallationManager"); + const defaultTemplateDir = temp.mkdirSync("project-service"); fs.writeJson(path.join(defaultTemplateDir, constants.PACKAGE_JSON_FILE_NAME), { "name": "defaultTemplate", "version": "1.0.0", @@ -60,7 +60,7 @@ class ProjectIntegrationTest { } public async createProject(projectOptions: IProjectSettings): Promise { - let projectService: IProjectService = this.testInjector.resolve("projectService"); + const projectService: IProjectService = this.testInjector.resolve("projectService"); if (!projectOptions.template) { projectOptions.template = constants.RESERVED_TEMPLATE_NAMES["default"]; } @@ -68,13 +68,13 @@ class ProjectIntegrationTest { } public async assertProject(tempFolder: string, projectName: string, appId: string, projectSourceDirectory: string): Promise { - let fs: IFileSystem = this.testInjector.resolve("fs"); - let projectDir = path.join(tempFolder, projectName); - let appDirectoryPath = path.join(projectDir, "app"); - let platformsDirectoryPath = path.join(projectDir, "platforms"); - let tnsProjectFilePath = path.join(projectDir, "package.json"); - let tnsModulesPath = path.join(projectDir, constants.NODE_MODULES_FOLDER_NAME, constants.TNS_CORE_MODULES_NAME); - let packageJsonContent = fs.readJson(tnsProjectFilePath); + const fs: IFileSystem = this.testInjector.resolve("fs"); + const projectDir = path.join(tempFolder, projectName); + const appDirectoryPath = path.join(projectDir, "app"); + const platformsDirectoryPath = path.join(projectDir, "platforms"); + const tnsProjectFilePath = path.join(projectDir, "package.json"); + const tnsModulesPath = path.join(projectDir, constants.NODE_MODULES_FOLDER_NAME, constants.TNS_CORE_MODULES_NAME); + const packageJsonContent = fs.readJson(tnsProjectFilePath); assert.isTrue(fs.exists(appDirectoryPath)); assert.isTrue(fs.exists(platformsDirectoryPath)); @@ -84,37 +84,37 @@ class ProjectIntegrationTest { assert.isFalse(fs.isEmptyDir(appDirectoryPath)); assert.isTrue(fs.isEmptyDir(platformsDirectoryPath)); - let actualAppId = packageJsonContent["nativescript"].id; - let expectedAppId = appId; + const actualAppId = packageJsonContent["nativescript"].id; + const expectedAppId = appId; assert.equal(actualAppId, expectedAppId); - let tnsCoreModulesRecord = packageJsonContent["dependencies"][constants.TNS_CORE_MODULES_NAME]; + const tnsCoreModulesRecord = packageJsonContent["dependencies"][constants.TNS_CORE_MODULES_NAME]; assert.isTrue(tnsCoreModulesRecord !== null); - let sourceDir = projectSourceDirectory; + const sourceDir = projectSourceDirectory; // Hidden files (starting with dots ".") are not copied. - let expectedFiles = fs.enumerateFilesInDirectorySync(sourceDir, (file, stat) => stat.isDirectory() || !_.startsWith(path.basename(file), ".")); - let actualFiles = fs.enumerateFilesInDirectorySync(appDirectoryPath); + const expectedFiles = fs.enumerateFilesInDirectorySync(sourceDir, (file, stat) => stat.isDirectory() || !_.startsWith(path.basename(file), ".")); + const actualFiles = fs.enumerateFilesInDirectorySync(appDirectoryPath); assert.isTrue(actualFiles.length >= expectedFiles.length, "Files in created project must be at least as files in app dir."); _.each(expectedFiles, file => { - let relativeToProjectDir = helpers.getRelativeToRootPath(sourceDir, file); - let filePathInApp = path.join(appDirectoryPath, relativeToProjectDir); + const relativeToProjectDir = helpers.getRelativeToRootPath(sourceDir, file); + const filePathInApp = path.join(appDirectoryPath, relativeToProjectDir); assert.isTrue(fs.exists(filePathInApp), `File ${filePathInApp} does not exist.`); }); // assert dependencies and devDependencies are copied from template to real project - let sourcePackageJsonContent = fs.readJson(path.join(sourceDir, "package.json")); - let missingDeps = _.difference(_.keys(sourcePackageJsonContent.dependencies), _.keys(packageJsonContent.dependencies)); - let missingDevDeps = _.difference(_.keys(sourcePackageJsonContent.devDependencies), _.keys(packageJsonContent.devDependencies)); + const sourcePackageJsonContent = fs.readJson(path.join(sourceDir, "package.json")); + const missingDeps = _.difference(_.keys(sourcePackageJsonContent.dependencies), _.keys(packageJsonContent.dependencies)); + const missingDevDeps = _.difference(_.keys(sourcePackageJsonContent.devDependencies), _.keys(packageJsonContent.devDependencies)); assert.deepEqual(missingDeps, [], `All dependencies from template must be copied to project's package.json. Missing ones are: ${missingDeps.join(", ")}.`); assert.deepEqual(missingDevDeps, [], `All devDependencies from template must be copied to project's package.json. Missing ones are: ${missingDevDeps.join(", ")}.`); // assert App_Resources are prepared correctly - let appResourcesDir = path.join(appDirectoryPath, "App_Resources"); - let appResourcesContents = fs.readDirectory(appResourcesDir); + const appResourcesDir = path.join(appDirectoryPath, "App_Resources"); + const appResourcesContents = fs.readDirectory(appResourcesDir); assert.deepEqual(appResourcesContents, ["Android", "iOS"], "Project's app/App_Resources must contain Android and iOS directories."); } @@ -168,7 +168,7 @@ describe("Project Service Tests", () => { const noAppResourcesTemplateName = "tns-template-hello-world-ts"; before(async () => { - let projectIntegrationTest = new ProjectIntegrationTest(); + const projectIntegrationTest = new ProjectIntegrationTest(); defaultTemplatePath = await prepareTestingPath(projectIntegrationTest.testInjector, constants.RESERVED_TEMPLATE_NAMES["default"], constants.RESERVED_TEMPLATE_NAMES["default"]); defaultSpecificVersionTemplatePath = await prepareTestingPath(projectIntegrationTest.testInjector, constants.RESERVED_TEMPLATE_NAMES["default"], constants.RESERVED_TEMPLATE_NAMES["default"], { version: "1.4.0", dependencyType: "save" }); @@ -178,45 +178,45 @@ describe("Project Service Tests", () => { }); it("creates valid project from default template", async () => { - let projectIntegrationTest = new ProjectIntegrationTest(); - let tempFolder = temp.mkdirSync("project"); - let projectName = "myapp"; + const projectIntegrationTest = new ProjectIntegrationTest(); + const tempFolder = temp.mkdirSync("project"); + const projectName = "myapp"; await projectIntegrationTest.createProject({ projectName: projectName, pathToProject: tempFolder }); await projectIntegrationTest.assertProject(tempFolder, projectName, "org.nativescript.myapp", defaultTemplatePath); }); it("creates valid project from default template when --template default is specified", async () => { - let projectIntegrationTest = new ProjectIntegrationTest(); - let tempFolder = temp.mkdirSync("project"); - let projectName = "myapp"; + const projectIntegrationTest = new ProjectIntegrationTest(); + const tempFolder = temp.mkdirSync("project"); + const projectName = "myapp"; await projectIntegrationTest.createProject({ projectName: projectName, template: "default", pathToProject: tempFolder }); await projectIntegrationTest.assertProject(tempFolder, projectName, "org.nativescript.myapp", defaultTemplatePath); }); it("creates valid project from default template when --template default@version is specified", async () => { - let projectIntegrationTest = new ProjectIntegrationTest(); - let tempFolder = temp.mkdirSync("project"); - let projectName = "myapp"; + const projectIntegrationTest = new ProjectIntegrationTest(); + const tempFolder = temp.mkdirSync("project"); + const projectName = "myapp"; await projectIntegrationTest.createProject({ projectName: projectName, template: "default@1.4.0", pathToProject: tempFolder }); await projectIntegrationTest.assertProject(tempFolder, projectName, "org.nativescript.myapp", defaultSpecificVersionTemplatePath); }); it("creates valid project from a template without App_Resources", async () => { - let projectIntegrationTest = new ProjectIntegrationTest(); - let tempFolder = temp.mkdirSync("project"); - let projectName = "myapp"; + const projectIntegrationTest = new ProjectIntegrationTest(); + const tempFolder = temp.mkdirSync("project"); + const projectName = "myapp"; await projectIntegrationTest.createProject({ projectName: projectName, template: noAppResourcesTemplateName + "@2.0.0", pathToProject: tempFolder }); await projectIntegrationTest.assertProject(tempFolder, projectName, "org.nativescript.myapp", noAppResourcesTemplatePath); }); it("creates valid project from typescript template", async () => { - let projectIntegrationTest = new ProjectIntegrationTest(); - let tempFolder = temp.mkdirSync("projectTypescript"); - let projectName = "myapp"; + const projectIntegrationTest = new ProjectIntegrationTest(); + const tempFolder = temp.mkdirSync("projectTypescript"); + const projectName = "myapp"; await projectIntegrationTest.createProject({ projectName: projectName, template: "typescript", pathToProject: tempFolder }); @@ -224,18 +224,18 @@ describe("Project Service Tests", () => { }); it("creates valid project from tsc template", async () => { - let projectIntegrationTest = new ProjectIntegrationTest(); - let tempFolder = temp.mkdirSync("projectTsc"); - let projectName = "myapp"; + const projectIntegrationTest = new ProjectIntegrationTest(); + const tempFolder = temp.mkdirSync("projectTsc"); + const projectName = "myapp"; await projectIntegrationTest.createProject({ projectName: projectName, template: "tsc", pathToProject: tempFolder }); await projectIntegrationTest.assertProject(tempFolder, projectName, "org.nativescript.myapp", typescriptTemplatePath); }); it("creates valid project from angular template", async () => { - let projectIntegrationTest = new ProjectIntegrationTest(); - let tempFolder = temp.mkdirSync("projectAngular"); - let projectName = "myapp"; + const projectIntegrationTest = new ProjectIntegrationTest(); + const tempFolder = temp.mkdirSync("projectAngular"); + const projectName = "myapp"; await projectIntegrationTest.createProject({ projectName: projectName, template: "angular", pathToProject: tempFolder }); @@ -243,9 +243,9 @@ describe("Project Service Tests", () => { }); it("creates valid project from ng template", async () => { - let projectIntegrationTest = new ProjectIntegrationTest(); - let tempFolder = temp.mkdirSync("projectNg"); - let projectName = "myapp"; + const projectIntegrationTest = new ProjectIntegrationTest(); + const tempFolder = temp.mkdirSync("projectNg"); + const projectName = "myapp"; await projectIntegrationTest.createProject({ projectName: projectName, template: "ng", pathToProject: tempFolder }); @@ -253,14 +253,14 @@ describe("Project Service Tests", () => { }); it("creates valid project from local directory template", async () => { - let projectIntegrationTest = new ProjectIntegrationTest(); - let tempFolder = temp.mkdirSync("projectLocalDir"); - let projectName = "myapp"; - let options = projectIntegrationTest.testInjector.resolve("options"); + const projectIntegrationTest = new ProjectIntegrationTest(); + const tempFolder = temp.mkdirSync("projectLocalDir"); + const projectName = "myapp"; + const options = projectIntegrationTest.testInjector.resolve("options"); options.path = tempFolder; - let tempDir = temp.mkdirSync("template"); - let fs: IFileSystem = projectIntegrationTest.testInjector.resolve("fs"); + const tempDir = temp.mkdirSync("template"); + const fs: IFileSystem = projectIntegrationTest.testInjector.resolve("fs"); fs.writeJson(path.join(tempDir, "package.json"), { name: "myCustomTemplate", version: "1.0.0", @@ -287,9 +287,9 @@ describe("Project Service Tests", () => { }); it("creates valid project from tarball", async () => { - let projectIntegrationTest = new ProjectIntegrationTest(); - let tempFolder = temp.mkdirSync("projectLocalDir"); - let projectName = "myapp"; + const projectIntegrationTest = new ProjectIntegrationTest(); + const tempFolder = temp.mkdirSync("projectLocalDir"); + const projectName = "myapp"; const template = "https://github.com/NativeScript/template-hello-world/tarball/master"; await projectIntegrationTest.createProject({ @@ -303,9 +303,9 @@ describe("Project Service Tests", () => { }); it("creates valid project from git url", async () => { - let projectIntegrationTest = new ProjectIntegrationTest(); - let tempFolder = temp.mkdirSync("projectLocalDir"); - let projectName = "myapp"; + const projectIntegrationTest = new ProjectIntegrationTest(); + const tempFolder = temp.mkdirSync("projectLocalDir"); + const projectName = "myapp"; const template = "https://github.com/NativeScript/template-hello-world.git"; await projectIntegrationTest.createProject({ @@ -319,17 +319,17 @@ describe("Project Service Tests", () => { }); it("creates valid project with specified id from default template", async () => { - let projectIntegrationTest = new ProjectIntegrationTest(); - let tempFolder = temp.mkdirSync("project1"); - let projectName = "myapp"; + const projectIntegrationTest = new ProjectIntegrationTest(); + const tempFolder = temp.mkdirSync("project1"); + const projectName = "myapp"; await projectIntegrationTest.createProject({ projectName: projectName, pathToProject: tempFolder, appId: "my.special.id" }); await projectIntegrationTest.assertProject(tempFolder, projectName, "my.special.id", defaultTemplatePath); }); describe("project name validation tests", () => { - let validProjectName = "valid"; - let invalidProjectName = "1invalid"; + const validProjectName = "valid"; + const invalidProjectName = "1invalid"; let projectIntegrationTest: ProjectIntegrationTest; let tempFolder: string; let options: IOptions; @@ -349,13 +349,13 @@ describe("Project Service Tests", () => { }); it("creates project when is interactive and incorrect name is specified and the --force option is set", async () => { - let projectName = invalidProjectName; + const projectName = invalidProjectName; await projectIntegrationTest.createProject({ projectName: projectName, pathToProject: tempFolder, force: true }); await projectIntegrationTest.assertProject(tempFolder, projectName, `org.nativescript.${projectName}`, defaultTemplatePath); }); it("creates project when is interactive and incorrect name is specified and the user confirms to use the incorrect name", async () => { - let projectName = invalidProjectName; + const projectName = invalidProjectName; prompter.confirm = (message: string): Promise => Promise.resolve(true); await projectIntegrationTest.createProject({ projectName: projectName, pathToProject: tempFolder }); @@ -363,7 +363,7 @@ describe("Project Service Tests", () => { }); it("prompts for new name when is interactive and incorrect name is specified and the user does not confirm to use the incorrect name", async () => { - let projectName = invalidProjectName; + const projectName = invalidProjectName; prompter.confirm = (message: string): Promise => Promise.resolve(false); @@ -372,11 +372,11 @@ describe("Project Service Tests", () => { }); it("creates project when is interactive and incorrect name s specified and the user does not confirm to use the incorrect name and enters incorrect name again several times and then enters correct name", async () => { - let projectName = invalidProjectName; + const projectName = invalidProjectName; prompter.confirm = (message: string): Promise => Promise.resolve(false); - let incorrectInputsLimit = 5; + const incorrectInputsLimit = 5; let incorrectInputsCount = 0; prompter.getString = async (message: string): Promise => { @@ -396,14 +396,14 @@ describe("Project Service Tests", () => { }); it("does not create project when is not interactive and incorrect name is specified", async () => { - let projectName = invalidProjectName; + const projectName = invalidProjectName; helpers.isInteractive = () => false; await assert.isRejected(projectIntegrationTest.createProject({ projectName: projectName, pathToProject: tempFolder, force: false })); }); it("creates project when is not interactive and incorrect name is specified and the --force option is set", async () => { - let projectName = invalidProjectName; + const projectName = invalidProjectName; helpers.isInteractive = () => false; await projectIntegrationTest.createProject({ projectName: projectName, pathToProject: tempFolder, force: true }); diff --git a/test/project-templates-service.ts b/test/project-templates-service.ts index ac46ac405c..46e2d1c918 100644 --- a/test/project-templates-service.ts +++ b/test/project-templates-service.ts @@ -7,10 +7,10 @@ import temp = require("temp"); import * as constants from "../lib/constants"; let isDeleteDirectoryCalledForNodeModulesDir = false; -let nativeScriptValidatedTemplatePath = "nsValidatedTemplatePath"; +const nativeScriptValidatedTemplatePath = "nsValidatedTemplatePath"; function createTestInjector(configuration?: { shouldNpmInstallThrow: boolean, npmInstallationDirContents: string[], npmInstallationDirNodeModulesContents: string[] }): IInjector { - let injector = new Yok(); + const injector = new Yok(); injector.register("errors", stubs.ErrorsStub); injector.register("logger", stubs.LoggerStub); injector.register("fs", { @@ -67,7 +67,7 @@ describe("project-templates-service", () => { it("when npm install fails", async () => { testInjector = createTestInjector({ shouldNpmInstallThrow: true, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: null }); projectTemplatesService = testInjector.resolve("projectTemplatesService"); - let tempFolder = temp.mkdirSync("preparetemplate"); + const tempFolder = temp.mkdirSync("preparetemplate"); await assert.isRejected(projectTemplatesService.prepareTemplate("invalidName", tempFolder)); }); }); @@ -76,8 +76,8 @@ describe("project-templates-service", () => { it("when reserved template name is used", async () => { testInjector = createTestInjector({ shouldNpmInstallThrow: false, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: [] }); projectTemplatesService = testInjector.resolve("projectTemplatesService"); - let tempFolder = temp.mkdirSync("preparetemplate"); - let actualPathToTemplate = await projectTemplatesService.prepareTemplate("typescript", tempFolder); + const tempFolder = temp.mkdirSync("preparetemplate"); + const actualPathToTemplate = await projectTemplatesService.prepareTemplate("typescript", tempFolder); assert.strictEqual(path.basename(actualPathToTemplate), nativeScriptValidatedTemplatePath); assert.strictEqual(isDeleteDirectoryCalledForNodeModulesDir, true, "When correct path is returned, template's node_modules directory should be deleted."); }); @@ -85,8 +85,8 @@ describe("project-templates-service", () => { it("when reserved template name is used (case-insensitive test)", async () => { testInjector = createTestInjector({ shouldNpmInstallThrow: false, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: [] }); projectTemplatesService = testInjector.resolve("projectTemplatesService"); - let tempFolder = temp.mkdirSync("preparetemplate"); - let actualPathToTemplate = await projectTemplatesService.prepareTemplate("tYpEsCriPT", tempFolder); + const tempFolder = temp.mkdirSync("preparetemplate"); + const actualPathToTemplate = await projectTemplatesService.prepareTemplate("tYpEsCriPT", tempFolder); assert.strictEqual(path.basename(actualPathToTemplate), nativeScriptValidatedTemplatePath); assert.strictEqual(isDeleteDirectoryCalledForNodeModulesDir, true, "When correct path is returned, template's node_modules directory should be deleted."); }); @@ -94,8 +94,8 @@ describe("project-templates-service", () => { it("uses defaultTemplate when undefined is passed as parameter", async () => { testInjector = createTestInjector({ shouldNpmInstallThrow: false, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: [] }); projectTemplatesService = testInjector.resolve("projectTemplatesService"); - let tempFolder = temp.mkdirSync("preparetemplate"); - let actualPathToTemplate = await projectTemplatesService.prepareTemplate(constants.RESERVED_TEMPLATE_NAMES["default"], tempFolder); + const tempFolder = temp.mkdirSync("preparetemplate"); + const actualPathToTemplate = await projectTemplatesService.prepareTemplate(constants.RESERVED_TEMPLATE_NAMES["default"], tempFolder); assert.strictEqual(path.basename(actualPathToTemplate), nativeScriptValidatedTemplatePath); assert.strictEqual(isDeleteDirectoryCalledForNodeModulesDir, true, "When correct path is returned, template's node_modules directory should be deleted."); }); diff --git a/test/services/extensibility-service.ts b/test/services/extensibility-service.ts index a03138676f..3243331703 100644 --- a/test/services/extensibility-service.ts +++ b/test/services/extensibility-service.ts @@ -72,7 +72,7 @@ describe("extensibilityService", () => { fs.readDirectory = (dir: string): string[] => [userSpecifiedValue]; const npm: INodePackageManager = testInjector.resolve("npm"); - let argsPassedToNpmInstall: any = {}; + const argsPassedToNpmInstall: any = {}; npm.install = async (packageName: string, pathToSave: string, config?: any): Promise => { argsPassedToNpmInstall.packageName = packageName; argsPassedToNpmInstall.pathToSave = pathToSave; @@ -468,7 +468,7 @@ describe("extensibilityService", () => { fs.readDirectory = (dir: string): string[] => [userSpecifiedValue]; const npm: INodePackageManager = testInjector.resolve("npm"); - let argsPassedToNpmInstall: any = {}; + const argsPassedToNpmInstall: any = {}; npm.uninstall = async (packageName: string, config?: any, path?: string): Promise => { argsPassedToNpmInstall.packageName = packageName; argsPassedToNpmInstall.pathToSave = path; diff --git a/test/services/project-data-service.ts b/test/services/project-data-service.ts index 71f08eff31..71112f4abb 100644 --- a/test/services/project-data-service.ts +++ b/test/services/project-data-service.ts @@ -61,7 +61,7 @@ const createTestInjector = (readTextData?: string): IInjector => { describe("projectDataService", () => { const generateJsonDataFromTestData = (currentTestData: any, skipNativeScriptKey?: boolean) => { const props = currentTestData.propertyName.split("."); - let data: any = {}; + const data: any = {}; let currentData: any = skipNativeScriptKey ? data : (data[CLIENT_NAME_KEY_IN_PROJECT_FILE] = {}); _.each(props, (prop, index: number) => { @@ -157,7 +157,7 @@ describe("projectDataService", () => { const props = currentTestData.propertyName.split("."); props.splice(props.length - 1, 1); - let data: any = {}; + const data: any = {}; let currentData: any = data[CLIENT_NAME_KEY_IN_PROJECT_FILE] = {}; _.each(props, (prop) => { @@ -214,7 +214,7 @@ describe("projectDataService", () => { describe("removeDependency", () => { it("removes specified dependency from project file", () => { - let currentTestData = { + const currentTestData = { propertyName: "dependencies.myDeps", propertyValue: "1.0.0" }; diff --git a/test/services/subscription-service.ts b/test/services/subscription-service.ts index ac2106ad0c..c9fc7548e6 100644 --- a/test/services/subscription-service.ts +++ b/test/services/subscription-service.ts @@ -3,7 +3,7 @@ import { assert } from "chai"; import { SubscriptionService } from "../../lib/services/subscription-service"; import { LoggerStub } from "../stubs"; import { stringify } from "querystring"; -let helpers = require("../../lib/common/helpers"); +const helpers = require("../../lib/common/helpers"); interface IValidateTestData { name: string; diff --git a/test/stubs.ts b/test/stubs.ts index 8c98f21c2f..04f128f8dc 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -444,13 +444,13 @@ export class PrompterStub implements IPrompter { } async getPassword(prompt: string, options?: IAllowEmpty): Promise { chai.assert.ok(prompt in this.passwords, `PrompterStub didn't expect to give password for: ${prompt}`); - let result = this.passwords[prompt]; + const result = this.passwords[prompt]; delete this.passwords[prompt]; return result; } async getString(prompt: string, options?: IPrompterOptions): Promise { chai.assert.ok(prompt in this.strings, `PrompterStub didn't expect to be asked for: ${prompt}`); - let result = this.strings[prompt]; + const result = this.strings[prompt]; delete this.strings[prompt]; return result; } @@ -465,10 +465,10 @@ export class PrompterStub implements IPrompter { } assert() { - for (let key in this.strings) { + for (const key in this.strings) { throw unexpected(`PrompterStub was instructed to reply with "${this.strings[key]}" to a "${key}" question, but was never asked!`); } - for (let key in this.passwords) { + for (const key in this.passwords) { throw unexpected(`PrompterStub was instructed to reply with "${this.passwords[key]}" to a "${key}" password request, but was never asked!`); } } @@ -479,7 +479,7 @@ function unreachable(): Error { } function unexpected(msg: string): Error { - let err = new chai.AssertionError(msg); + const err = new chai.AssertionError(msg); err.showDiff = false; return err; } @@ -516,7 +516,7 @@ export class LiveSyncServiceStub implements ILiveSyncService { export class AndroidToolsInfoStub implements IAndroidToolsInfo { public getToolsInfo(): IAndroidToolsInfoData { - let infoData: IAndroidToolsInfoData = Object.create(null); + const infoData: IAndroidToolsInfoData = Object.create(null); infoData.androidHomeEnvVar = "ANDROID_HOME"; infoData.compileSdkVersion = 23; infoData.buildToolsVersion = "23"; diff --git a/test/tns-appstore-upload.ts b/test/tns-appstore-upload.ts index bc930731ce..50bb3afc3f 100644 --- a/test/tns-appstore-upload.ts +++ b/test/tns-appstore-upload.ts @@ -72,10 +72,10 @@ class AppStore { initInjector(services?: { commands?: { [service: string]: any }, services?: { [service: string]: any } }) { this.injector = new yok.Yok(); if (services) { - for (let cmd in services.commands) { + for (const cmd in services.commands) { this.injector.registerCommand(cmd, services.commands[cmd]); } - for (let serv in services.services) { + for (const serv in services.services) { this.injector.register(serv, services.services[serv]); } } diff --git a/test/tools/node-modules/node-modules-dependencies-builder.ts b/test/tools/node-modules/node-modules-dependencies-builder.ts index 8b25691fde..0b8d85dcb3 100644 --- a/test/tools/node-modules/node-modules-dependencies-builder.ts +++ b/test/tools/node-modules/node-modules-dependencies-builder.ts @@ -59,7 +59,7 @@ describe("nodeModulesDependenciesBuilder", () => { }; const getNodeModuleInfoForExpecteDependency = (name: string, depth: number, nativescript?: any, dependencies?: string[]): IDependencyData => { - let result: IDependencyData = { + const result: IDependencyData = { name: path.basename(name), directory: getPathToDependencyInNodeModules(name), depth, @@ -83,7 +83,7 @@ describe("nodeModulesDependenciesBuilder", () => { dependencies[innerDependency.name] = innerDependency.version; }); - let result: any = { + const result: any = { dependencies }; @@ -96,7 +96,7 @@ describe("nodeModulesDependenciesBuilder", () => { const getDependenciesObject = (filename: string, deps: IDependencyInfo[], parentDir: string): { dependencies: any } => { let result: { dependencies: any } = null; - for (let dependencyInfo of deps) { + for (const dependencyInfo of deps) { const pathToPackageJson = getPathToPackageJsonOfDependency(dependencyInfo.name, parentDir); if (filename === pathToPackageJson) { return getDependenciesObjectFromDependencyInfo(dependencyInfo.dependencies, dependencyInfo.nativescript); @@ -126,7 +126,7 @@ describe("nodeModulesDependenciesBuilder", () => { const isDirectory = (searchedPath: string, currentRootPath: string, deps: IDependencyInfo[], currentDepthLevel: number): boolean => { let result = false; - for (let dependencyInfo of deps) { + for (const dependencyInfo of deps) { const pathToDependency = path.join(currentRootPath, constants.NODE_MODULES_FOLDER_NAME, dependencyInfo.name); if (pathToDependency === searchedPath && currentDepthLevel === dependencyInfo.depth) { @@ -146,7 +146,7 @@ describe("nodeModulesDependenciesBuilder", () => { const isPackageJsonOfDependency = (searchedPath: string, currentRootPath: string, deps: IDependencyInfo[], currentDepthLevel: number): boolean => { let result = false; - for (let dependencyInfo of deps) { + for (const dependencyInfo of deps) { const pathToDependency = path.join(currentRootPath, constants.NODE_MODULES_FOLDER_NAME, dependencyInfo.name); const pathToPackageJson = path.join(pathToDependency, constants.PACKAGE_JSON_FILE_NAME); diff --git a/test/xcconfig-service.ts b/test/xcconfig-service.ts index ba9e13a330..9a96daecf8 100644 --- a/test/xcconfig-service.ts +++ b/test/xcconfig-service.ts @@ -21,9 +21,9 @@ describe("XCConfig Service Tests", () => { }; const assertPropertyValues = (expected: any, injector: IInjector) => { - let service = getXCConfigService(injector); + const service = getXCConfigService(injector); _.forOwn(expected, (value, key) => { - let actual = service.readPropertyValue("any", key); + const actual = service.readPropertyValue("any", key); assert.equal(actual, value); }); }; @@ -38,22 +38,22 @@ describe("XCConfig Service Tests", () => { describe("Read Property Value", () => { it("Return empty value, for unexistent file", () => { - let injector = createTestInjector(); + const injector = createTestInjector(); injector.register("fs", { exists: (path: string) => { return false; } }); - let service = getXCConfigService(injector); - let actual = service.readPropertyValue("any", "any"); + const service = getXCConfigService(injector); + const actual = service.readPropertyValue("any", "any"); assert.isNull(actual); }); it("Returns correct value for well-formatted document", () => { - let injector = createTestInjector(); - let fs = getFileSystemMock(injector); + const injector = createTestInjector(); + const fs = getFileSystemMock(injector); fs.readText = (filename: string, options?: IReadFileOptions | string): string => { return `// You can add custom settings here // for example you can uncomment the following line to force distribution code signing @@ -64,7 +64,7 @@ describe("XCConfig Service Tests", () => { ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;`; }; - let expected = { + const expected = { 'ASSETCATALOG_COMPILER_APPICON_NAME': 'AppIcon', 'ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME': 'LaunchImage', 'CODE_SIGN_IDENTITY': 'iPhone Distribution' @@ -74,8 +74,8 @@ describe("XCConfig Service Tests", () => { }); it("Returns correct value for values with missing ; at the end of the line.", () => { - let injector = createTestInjector(); - let fs = getFileSystemMock(injector); + const injector = createTestInjector(); + const fs = getFileSystemMock(injector); fs.readText = (filename: string, options?: IReadFileOptions | string): string => { return `// You can add custom settings here // for example you can uncomment the following line to force distribution code signing @@ -86,7 +86,7 @@ describe("XCConfig Service Tests", () => { ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage`; }; - let expected = { + const expected = { 'ASSETCATALOG_COMPILER_APPICON_NAME': 'AppIcon', 'ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME': 'LaunchImage', 'CODE_SIGN_IDENTITY': 'iPhone Distribution' @@ -96,14 +96,14 @@ describe("XCConfig Service Tests", () => { }); it("Doesn't read value from a commented-out line.", () => { - let injector = createTestInjector(); - let fs = getFileSystemMock(injector); + const injector = createTestInjector(); + const fs = getFileSystemMock(injector); fs.readText = (filename: string, options?: IReadFileOptions | string): string => { return `// DEVELOPMENT_TEAM = YOUR_TEAM_ID; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;`; }; - let expected = { + const expected = { 'ASSETCATALOG_COMPILER_APPICON_NAME': 'AppIcon', 'DEVELOPMENT_TEAM': null }; @@ -112,8 +112,8 @@ describe("XCConfig Service Tests", () => { }); it("Returns correct empty value.", () => { - let injector = createTestInjector(); - let fs = getFileSystemMock(injector); + const injector = createTestInjector(); + const fs = getFileSystemMock(injector); fs.readText = (filename: string, options?: IReadFileOptions | string): string => { return ` ASSETCATALOG_COMPILER_APPICON_NAME = ; @@ -121,7 +121,7 @@ describe("XCConfig Service Tests", () => { `; }; - let expected = { + const expected = { 'ASSETCATALOG_COMPILER_APPICON_NAME': '', 'ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME': 'LaunchImage' }; @@ -130,13 +130,13 @@ describe("XCConfig Service Tests", () => { }); it("First part only property doesn't break the service.", () => { - let injector = createTestInjector(); - let fs = getFileSystemMock(injector); + const injector = createTestInjector(); + const fs = getFileSystemMock(injector); fs.readText = (filename: string, options?: IReadFileOptions | string): string => { return `ASSETCATALOG_COMPILER_APPICON_NAME`; }; - let expected = { + const expected = { 'ASSETCATALOG_COMPILER_APPICON_NAME': null }; @@ -144,13 +144,13 @@ describe("XCConfig Service Tests", () => { }); it("Invalid config property value with = doesn't break the service.", () => { - let injector = createTestInjector(); - let fs = getFileSystemMock(injector); + const injector = createTestInjector(); + const fs = getFileSystemMock(injector); fs.readText = (filename: string, options?: IReadFileOptions | string): string => { return `ASSETCATALOG_COMPILER_APPICON_NAME=`; }; - let expected = { + const expected = { 'ASSETCATALOG_COMPILER_APPICON_NAME': null }; @@ -158,13 +158,13 @@ describe("XCConfig Service Tests", () => { }); it("Property with space is read correctly.", () => { - let injector = createTestInjector(); - let fs = getFileSystemMock(injector); + const injector = createTestInjector(); + const fs = getFileSystemMock(injector); fs.readText = (filename: string, options?: IReadFileOptions | string): string => { return `ASSETCATALOG_COMPILER_APPICON_NAME= `; }; - let expected = { + const expected = { 'ASSETCATALOG_COMPILER_APPICON_NAME': '' }; @@ -172,13 +172,13 @@ describe("XCConfig Service Tests", () => { }); it("Ensure property can be an empty value.", () => { - let injector = createTestInjector(); - let fs = getFileSystemMock(injector); + const injector = createTestInjector(); + const fs = getFileSystemMock(injector); fs.readText = (filename: string, options?: IReadFileOptions | string): string => { return `ASSETCATALOG_COMPILER_APPICON_NAME= ;`; }; - let expected = { + const expected = { 'ASSETCATALOG_COMPILER_APPICON_NAME': '' }; diff --git a/tslint.json b/tslint.json index e37be6cc07..908fdf9f32 100644 --- a/tslint.json +++ b/tslint.json @@ -13,6 +13,7 @@ false, 140 ], + "prefer-const": true, "no-consecutive-blank-lines": true, "no-construct": true, "no-debugger": true, From 41b10b5f7ee249718d7e2ff7ec220ceb8eccc18e Mon Sep 17 00:00:00 2001 From: pkoleva Date: Tue, 29 Aug 2017 14:03:29 +0300 Subject: [PATCH 200/212] Updated changelog for 3.2.0 RC (#3093) * Updated changelog for 3.2.0 RC * Update CHANGELOG.md --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35a9bee4b6..ee07c39dcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,32 @@ NativeScript CLI Changelog ================ +3.2.0 - RC.1 (2017, August 29) +== + +### Fixed +* [Fixed #3073](https://github.com/NativeScript/nativescript-cli/issues/3073): Saving two platform-specific files during LiveSync causes an exception +* [Fixed #3054](https://github.com/NativeScript/nativescript-cli/issues/3054): `tns prepare ` fails with Unhandled promise rejection +* [Fixed #3048](https://github.com/NativeScript/nativescript-cli/issues/3048): Fixed setup script for Windows +* [Fixed #3046](https://github.com/NativeScript/nativescript-cli/issues/3046): Export fails for Xcode 9 beta 5 +* [Fixed #3026](https://github.com/NativeScript/nativescript-cli/issues/3026): Fixed scripts for local installation of NativeScript on macOS +* [Fixed #3021](https://github.com/NativeScript/nativescript-cli/issues/3021): If multiple devices from the same platform are connected `tns debug --start` should ask you which of them to use +* [Fixed #3020](https://github.com/NativeScript/nativescript-cli/issues/3020): iOS Archive Export unexpected behavior when using AdHoc or AppStore provisioning +* [Fixed #3007](https://github.com/NativeScript/nativescript-cli/issues/3007): Application hangs on iOS during LiveSync +* [Fixed #3006](https://github.com/NativeScript/nativescript-cli/issues/3006): Add help for --provision option +* [Fixed #2952](https://github.com/NativeScript/nativescript-cli/issues/2952): Do not select automatically on which Android device to start debugging +* [Fixed #2946](https://github.com/NativeScript/nativescript-cli/issues/2946): Can't run iOS app on case-sensitive filesystem on macOS +* [Fixed #2934](https://github.com/NativeScript/nativescript-cli/issues/2934): Running `tns build android --release ...` uses *.debug.* files in build output +* [Fixed #2888](https://github.com/NativeScript/nativescript-cli/issues/2888): Build in release mode for iOS doesn't seem to set the production mode for APN +* [Fixed #2825](https://github.com/NativeScript/nativescript-cli/issues/2825): LiveSync won't work if appId doesn't match - no warning/error +* [Fixed #2818](https://github.com/NativeScript/nativescript-cli/issues/2818): Exception and stack trace is not shown in terminal for Android +* [Fixed #2810](https://github.com/NativeScript/nativescript-cli/issues/2810): Cannot read property 'match' of null error when installing nativescript cli +* [Fixed #2728](https://github.com/NativeScript/nativescript-cli/issues/2728): `tns run ios --device fakeID` starts iOS Simulator +* [Fixed #2716](https://github.com/NativeScript/nativescript-cli/issues/2716): Webpack issues when build in release mode +* [Fixed #2657](https://github.com/NativeScript/nativescript-cli/issues/2657): `tns run android/ios` does not remove folders correctly +* [Fixed #2515](https://github.com/NativeScript/nativescript-cli/issues/2515): CLI captures logs from Chrome and Cordova apps +* [Fixed #2501](https://github.com/NativeScript/nativescript-cli/issues/2501): Manual signing with distribution provisioning profile fails with NS 2.5 + 3.1.3 (2017, July 25) == From 557cc893bcaca7a132dbb5f3c63545b053c86168 Mon Sep 17 00:00:00 2001 From: Peter Kanev Date: Tue, 29 Aug 2017 16:25:03 +0300 Subject: [PATCH 201/212] update proxy-set command manual to contain information about setting up npm and gradle proxies (#3004) --- docs/man_pages/general/proxy-set.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/man_pages/general/proxy-set.md b/docs/man_pages/general/proxy-set.md index 3ab89f331b..94b2d2735f 100644 --- a/docs/man_pages/general/proxy-set.md +++ b/docs/man_pages/general/proxy-set.md @@ -20,6 +20,9 @@ Sets proxy settings. ### Command Limitations * You can set credentials only on Windows Systems. +* Proxy settings for npm and (Android) Gradle need to be set separately. + * configuring `npm` proxy - https://docs.npmjs.com/misc/config#https-proxy + * (Android) configuring Gradle proxy - set global configuration in the user directory - _/.gradle/gradle.properties_ - https://docs.gradle.org/3.3/userguide/build_environment.html#sec:accessing_the_web_via_a_proxy ### Related Commands From 9afb25114748119ab5cd33807a003e4dcef64ed9 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Wed, 30 Aug 2017 16:30:07 +0300 Subject: [PATCH 202/212] Set version to 3.3.0 (#3095) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7b8addf80b..114d1bc60d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "3.2.0", + "version": "3.3.0", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { From 608737f28c013b1b02755116aa8820c37837bc10 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Thu, 31 Aug 2017 16:12:33 +0300 Subject: [PATCH 203/212] Debug improvements (#3094) * Debug improvements Includes but is not limited to: * `enableDebugging` - each promise is now resolved with an object * added docs for `debuggerAttached` event * added `debuggerDetached` event and docs for it * PR Fixes --- PublicAPI.md | 18 ++++++++++ lib/commands/debug.ts | 3 +- lib/constants.ts | 1 + lib/declarations.d.ts | 11 ++++-- lib/definitions/debug.d.ts | 14 ++++++-- lib/definitions/livesync.d.ts | 14 ++++---- lib/services/debug-service.ts | 15 ++++++-- lib/services/livesync/livesync-service.ts | 43 ++++++++++++----------- test/services/debug-service.ts | 12 ++++--- 9 files changed, 91 insertions(+), 40 deletions(-) diff --git a/PublicAPI.md b/PublicAPI.md index a2056851cc..fd56bf03e9 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -839,6 +839,24 @@ tns.liveSyncService.on("userInteractionNeeded", data => { }); ``` +* debuggerAttached - raised whenever CLI attaches the backend debugging socket and a frontend debugging client may be attached. The event is raised with an object containing the device's identifier: + +Example: +```JavaScript +tns.liveSyncService.on("debuggerAttached", debugInfo => { + console.log(`Backend client connected, frontend client may be connected at ${debugInfo.url}`); +}); +``` + +* debuggerDetached - raised whenever CLI detaches the backend debugging socket. The event is raised with an object of the `IDebugInformation` type: + +Example: +```JavaScript +tns.liveSyncService.on("debuggerDetached", debugInfo => { + console.log(`Detached debugger for device with id ${debugInfo.deviceIdentifier}`); +}); +``` + ## How to add a new method to Public API CLI is designed as command line tool and when it is used as a library, it does not give you access to all of the methods. This is mainly implementation detail. Most of the CLI's code is created to work in command line, not as a library, so before adding method to public API, most probably it will require some modification. For example the `$options` injected module contains information about all `--` options passed on the terminal. When the CLI is used as a library, the options are not populated. Before adding method to public API, make sure its implementation does not rely on `$options`. diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 3b5d4ef9d8..28be7548f7 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -31,7 +31,8 @@ export class DebugPlatformCommand implements ICommand { debugData.deviceIdentifier = selectedDeviceForDebug.deviceInfo.identifier; if (this.$options.start) { - return this.$liveSyncService.printDebugInformation(await this.$debugService.debug(debugData, debugOptions)); + await this.$liveSyncService.printDebugInformation(await this.$debugService.debug(debugData, debugOptions)); + return; } await this.$devicesService.detectCurrentlyAttachedDevices({ shouldReturnImmediateResult: false, platform: this.platform }); diff --git a/lib/constants.ts b/lib/constants.ts index 2ce856a677..346d475dab 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -87,6 +87,7 @@ export const BUILD_OUTPUT_EVENT_NAME = "buildOutput"; export const CONNECTION_ERROR_EVENT_NAME = "connectionError"; export const USER_INTERACTION_NEEDED_EVENT_NAME = "userInteractionNeeded"; export const DEBUGGER_ATTACHED_EVENT_NAME = "debuggerAttached"; +export const DEBUGGER_DETACHED_EVENT_NAME = "debuggerDetached"; export const VERSION_STRING = "version"; export const INSPECTOR_CACHE_DIRNAME = "ios-inspector"; export const POST_INSTALL_COMMAND_NAME = "post-install-cli"; diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index f26c55ba52..e91c807a72 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -372,7 +372,15 @@ interface ICreateProjectOptions extends INpmInstallConfigurationOptionsBase { pathToTemplate?: string; } -interface IOptions extends ICommonOptions, IBundle, IPlatformTemplate, IEmulator, IClean, IProvision, ITeamIdentifier, IAndroidReleaseOptions, INpmInstallConfigurationOptions { +interface IDebugInformation extends IPort { + url: string; +} + +interface IPort { + port: Number; +} + +interface IOptions extends ICommonOptions, IBundle, IPlatformTemplate, IEmulator, IClean, IProvision, ITeamIdentifier, IAndroidReleaseOptions, INpmInstallConfigurationOptions, IPort { all: boolean; client: boolean; compileSdk: number; @@ -386,7 +394,6 @@ interface IOptions extends ICommonOptions, IBundle, IPlatformTemplate, IEmulator tsc: boolean; ng: boolean; androidTypings: boolean; - port: Number; production: boolean; //npm flag sdk: string; syncAllFiles: boolean; diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 37640c0848..698185efd8 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -105,9 +105,9 @@ interface IDebugServiceBase extends NodeJS.EventEmitter { * Starts debug operation based on the specified debug data. * @param {IDebugData} debugData Describes information for device and application that will be debugged. * @param {IDebugOptions} debugOptions Describe possible options to modify the behaivor of the debug operation, for example stop on the first line. - * @returns {Promise} Array of URLs that can be used for debugging or a string representing a single url that can be used for debugging. + * @returns {Promise} Full url and port where the frontend client can be connected. */ - debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; + debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; } interface IDebugService extends IDebugServiceBase { @@ -122,7 +122,7 @@ interface IDebugService extends IDebugServiceBase { /** * Describes actions required for debugging on specific platform (Android or iOS). */ -interface IPlatformDebugService extends IDebugServiceBase, IPlatform { +interface IPlatformDebugService extends IPlatform, NodeJS.EventEmitter { /** * Starts debug operation. * @param {IDebugData} debugData Describes information for device and application that will be debugged. @@ -136,4 +136,12 @@ interface IPlatformDebugService extends IDebugServiceBase, IPlatform { * @returns {Promise} */ debugStop(): Promise; + + /** + * Starts debug operation based on the specified debug data. + * @param {IDebugData} debugData Describes information for device and application that will be debugged. + * @param {IDebugOptions} debugOptions Describe possible options to modify the behaivor of the debug operation, for example stop on the first line. + * @returns {Promise} Full url where the frontend client may be connected. + */ + debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; } diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 1ba0230268..19b73bcd11 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -206,18 +206,18 @@ interface ILiveSyncService { interface IDebugLiveSyncService extends ILiveSyncService { /** * Prints debug information. - * @param {string[]} information Array of information to be printed. Note that false-like values will be stripped from the array. - * @returns {void} + * @param {IDebugInformation} debugInformation Information to be printed. + * @returns {IDebugInformation} Full url and port where the frontend client can be connected. */ - printDebugInformation(information: string): void; + printDebugInformation(debugInformation: IDebugInformation): IDebugInformation; /** * Enables debugging for the specified devices * @param {IEnableDebuggingDeviceOptions[]} deviceOpts Settings used for enabling debugging for each device. * @param {IDebuggingAdditionalOptions} enableDebuggingOptions Settings used for enabling debugging. - * @returns {Promise[]} Array of promises for each device. + * @returns {Promise[]} Array of promises for each device. */ - enableDebugging(deviceOpts: IEnableDebuggingDeviceOptions[], enableDebuggingOptions: IDebuggingAdditionalOptions): Promise[]; + enableDebugging(deviceOpts: IEnableDebuggingDeviceOptions[], enableDebuggingOptions: IDebuggingAdditionalOptions): Promise[]; /** * Disables debugging for the specified devices @@ -230,9 +230,9 @@ interface IDebugLiveSyncService extends ILiveSyncService { /** * Attaches a debugger to the specified device. * @param {IAttachDebuggerOptions} settings Settings used for controling the attaching process. - * @returns {Promise} + * @returns {Promise} Full url and port where the frontend client can be connected. */ - attachDebugger(settings: IAttachDebuggerOptions): Promise; + attachDebugger(settings: IAttachDebuggerOptions): Promise; } /** diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index ab7151baca..a276e484d4 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -1,4 +1,5 @@ import { platform } from "os"; +import { parse } from "url"; import { EventEmitter } from "events"; import { CONNECTION_ERROR_EVENT_NAME, DebugCommandErrors } from "../constants"; import { CONNECTED_STATUS } from "../common/constants"; @@ -14,7 +15,7 @@ export class DebugService extends EventEmitter implements IDebugService { this._platformDebugServices = {}; } - public async debug(debugData: IDebugData, options: IDebugOptions): Promise { + public async debug(debugData: IDebugData, options: IDebugOptions): Promise { const device = this.$devicesService.getDeviceByIdentifier(debugData.deviceIdentifier); if (!device) { @@ -57,7 +58,7 @@ export class DebugService extends EventEmitter implements IDebugService { result = await debugService.debug(debugData, debugOptions); } - return result; + return this.getDebugInformation(result); } public debugStop(deviceIdentifier: string): Promise { @@ -92,6 +93,16 @@ export class DebugService extends EventEmitter implements IDebugService { connectionErrorHandler = connectionErrorHandler.bind(this); platformDebugService.on(CONNECTION_ERROR_EVENT_NAME, connectionErrorHandler); } + + private getDebugInformation(fullUrl: string): IDebugInformation { + const parseQueryString = true; + const wsQueryParam = parse(fullUrl, parseQueryString).query.ws; + const hostPortSplit = wsQueryParam && wsQueryParam.split(":"); + return { + url: fullUrl, + port: hostPortSplit && +hostPortSplit[1] + }; + } } $injector.register("debugService", DebugService); diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index d01f372d82..e4bb9936c6 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -1,10 +1,9 @@ import * as path from "path"; import * as choki from "chokidar"; -import { parse } from "url"; import { EOL } from "os"; import { EventEmitter } from "events"; import { hook } from "../../common/helpers"; -import { APP_FOLDER_NAME, PACKAGE_JSON_FILE_NAME, LiveSyncTrackActionNames, USER_INTERACTION_NEEDED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME } from "../../constants"; +import { APP_FOLDER_NAME, PACKAGE_JSON_FILE_NAME, LiveSyncTrackActionNames, USER_INTERACTION_NEEDED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME, DEBUGGER_DETACHED_EVENT_NAME } from "../../constants"; import { FileExtensions, DeviceTypes } from "../../common/constants"; const deviceDescriptorPrimaryKey = "identifier"; @@ -101,7 +100,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi return currentDescriptors || []; } - private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string): Promise { + private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string): Promise { const deviceDescriptor = this.getDeviceDescriptor(liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, projectData.projectDir); return deviceDescriptor && deviceDescriptor.debugggingEnabled ? @@ -138,13 +137,14 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); } - private async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOptions: IDebugOptions, outputPath?: string): Promise { + private async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOptions: IDebugOptions, outputPath?: string): Promise { await this.$platformService.trackProjectType(projectData); const deviceAppData = liveSyncResultInfo.deviceAppData; const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; await this.$debugService.debugStop(deviceIdentifier); + this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier }); const applicationId = deviceAppData.appIdentifier; const attachDebuggerOptions: IAttachDebuggerOptions = { @@ -181,7 +181,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, { projectDir: projectData.projectDir }); } - public async attachDebugger(settings: IAttachDebuggerOptions): Promise { + public async attachDebugger(settings: IAttachDebuggerOptions): Promise { // Default values if (settings.debugOptions) { settings.debugOptions.chrome = settings.debugOptions.chrome === undefined ? true : settings.debugOptions.chrome; @@ -208,22 +208,19 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi }; debugData.pathToAppPackage = this.$platformService.lastOutputPath(settings.platform, buildConfig, projectData, settings.outputPath); - this.printDebugInformation(await this.$debugService.debug(debugData, settings.debugOptions)); + return this.printDebugInformation(await this.$debugService.debug(debugData, settings.debugOptions)); } - public printDebugInformation(information: string): void { - if (!!information) { - const wsQueryParam = parse(information).query.ws; - const hostPortSplit = wsQueryParam && wsQueryParam.split(":"); - this.emit(DEBUGGER_ATTACHED_EVENT_NAME, { - url: information, - port: hostPortSplit && hostPortSplit[1] - }); - this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${information}${EOL}`.cyan); + public printDebugInformation(debugInformation: IDebugInformation): IDebugInformation { + if (!!debugInformation.url) { + this.emit(DEBUGGER_ATTACHED_EVENT_NAME, debugInformation); + this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${debugInformation.url}${EOL}`.cyan); } + + return debugInformation; } - public enableDebugging(deviceOpts: IEnableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[] { + public enableDebugging(deviceOpts: IEnableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[] { return _.map(deviceOpts, d => this.enableDebuggingCore(d, debuggingAdditionalOptions)); } @@ -233,7 +230,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi return _.find(deviceDescriptors, d => d.identifier === deviceIdentifier); } - private async enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption: IEnableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { + private async enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption: IEnableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { const currentDeviceDescriptor = this.getDeviceDescriptor(deviceOption.deviceIdentifier, debuggingAdditionalOptions.projectDir); if (!currentDeviceDescriptor) { this.$errors.failWithoutHelp(`Couldn't enable debugging for ${deviceOption.deviceIdentifier}`); @@ -250,16 +247,19 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi debugOptions: deviceOption.debugOptions }; + let debugInformation: IDebugInformation; try { - await this.attachDebugger(attachDebuggerOptions); + debugInformation = await this.attachDebugger(attachDebuggerOptions); } catch (err) { this.$logger.trace("Couldn't attach debugger, will modify options and try again.", err); attachDebuggerOptions.debugOptions.start = false; - await this.attachDebugger(attachDebuggerOptions); + debugInformation = await this.attachDebugger(attachDebuggerOptions); } + + return debugInformation; } - private async enableDebuggingCore(deviceOption: IEnableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { + private async enableDebuggingCore(deviceOption: IEnableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { const liveSyncProcessInfo: ILiveSyncProcessInfo = this.liveSyncProcessesInfo[debuggingAdditionalOptions.projectDir]; if (liveSyncProcessInfo && liveSyncProcessInfo.currentSyncAction) { await liveSyncProcessInfo.currentSyncAction; @@ -290,7 +290,8 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi this.$errors.failWithoutHelp(`Couldn't disable debugging for ${deviceOption.deviceIdentifier}. Could not find device.`); } - return this.$debugService.debugStop(currentDevice.deviceInfo.identifier); + await this.$debugService.debugStop(currentDevice.deviceInfo.identifier); + this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier: currentDeviceDescriptor.identifier }); } @hook("liveSync") diff --git a/test/services/debug-service.ts b/test/services/debug-service.ts index b320178890..2b4da60510 100644 --- a/test/services/debug-service.ts +++ b/test/services/debug-service.ts @@ -6,7 +6,8 @@ import { EventEmitter } from "events"; import * as constants from "../../lib/common/constants"; import { CONNECTION_ERROR_EVENT_NAME, DebugCommandErrors } from "../../lib/constants"; -const fakeChromeDebugUrl = "fakeChromeDebugUrl"; +const fakeChromeDebugPort = 123; +const fakeChromeDebugUrl = `fakeChromeDebugUrl?experiments=true&ws=localhost:${fakeChromeDebugPort}`; class PlatformDebugService extends EventEmitter /* implements IPlatformDebugService */ { public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { return fakeChromeDebugUrl; @@ -202,7 +203,7 @@ describe("debugService", () => { }); }); - describe("returns chrome url returned by platform specific debug service", () => { + describe("returns chrome url along with port returned by platform specific debug service", () => { _.each(["android", "iOS"], platform => { it(`for ${platform} device`, async () => { const testData = getDefaultTestData(); @@ -212,9 +213,12 @@ describe("debugService", () => { const debugService = testInjector.resolve(DebugService); const debugData = getDebugData(); - const url = await debugService.debug(debugData, null); + const debugInfo = await debugService.debug(debugData, null); - assert.deepEqual(url, fakeChromeDebugUrl); + assert.deepEqual(debugInfo, { + url: fakeChromeDebugUrl, + port: fakeChromeDebugPort + }); }); }); }); From 8fe4465edfc85d69bcb0ee5bc69d6912446faab0 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Tue, 5 Sep 2017 12:14:32 +0300 Subject: [PATCH 204/212] Only return debugInfo if url is passed (#3103) --- lib/services/debug-service.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index a276e484d4..a55cfb17c0 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -95,13 +95,22 @@ export class DebugService extends EventEmitter implements IDebugService { } private getDebugInformation(fullUrl: string): IDebugInformation { - const parseQueryString = true; - const wsQueryParam = parse(fullUrl, parseQueryString).query.ws; - const hostPortSplit = wsQueryParam && wsQueryParam.split(":"); - return { + let debugInfo: IDebugInformation = { url: fullUrl, - port: hostPortSplit && +hostPortSplit[1] + port: 0 }; + + if (fullUrl) { + const parseQueryString = true; + const wsQueryParam = parse(fullUrl, parseQueryString).query.ws; + const hostPortSplit = wsQueryParam && wsQueryParam.split(":"); + debugInfo = { + url: fullUrl, + port: hostPortSplit && +hostPortSplit[1] + }; + } + + return debugInfo; } } From f989e8dc2071b7002773ebaf19d4731757de7817 Mon Sep 17 00:00:00 2001 From: Emil Tabakov Date: Tue, 5 Sep 2017 13:29:50 +0300 Subject: [PATCH 205/212] Enable silent mode setup for Windows #3076 (#3082) * Add option to skip the questions during setup * Initialize correctly the answer variable --- setup/native-script.ps1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup/native-script.ps1 b/setup/native-script.ps1 index 605fd226a4..047c754b09 100644 --- a/setup/native-script.ps1 +++ b/setup/native-script.ps1 @@ -5,7 +5,9 @@ # @powershell -NoProfile -ExecutionPolicy Bypass -Command "iex ((new-object net.webclient).DownloadString('https://www.nativescript.org/setup/win'))" # To run it inside a WINDOWS POWERSHELL console against the production branch (only one supported with self-elevation) use # start-process -FilePath PowerShell.exe -Verb Runas -Wait -ArgumentList "-NoProfile -ExecutionPolicy Bypass -Command iex ((new-object net.webclient).DownloadString('https://www.nativescript.org/setup/win'))" - +param( + [switch] $SilentMode +) # Check if latest .NET framework installed is at least 4 $dotNetVersions = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -recurse | Get-ItemProperty -name Version,Release -EA 0 | Where { $_.PSChildName -match '^(?!S)\p{L}'} | Select Version $latestDotNetVersion = $dotNetVersions.GetEnumerator() | Sort-Object Version | Select-Object -Last 1 @@ -27,7 +29,7 @@ if (-not $isElevated) { } # Help with installing other dependencies -$script:answer = "" +$script:answer = if ($SilentMode) {"a"} else {""} function Install($programName, $message, $script, $shouldExit) { if ($script:answer -ne "a") { Write-Host -ForegroundColor Green "Allow the script to install $($programName)?" From e343c0a7d63c95a0a9ef2d6e6880cdc627fe2fef Mon Sep 17 00:00:00 2001 From: Emil Tabakov Date: Tue, 5 Sep 2017 14:12:21 +0300 Subject: [PATCH 206/212] Install platform tools api level 25 (#3083) --- setup/native-script.ps1 | 2 +- setup/native-script.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup/native-script.ps1 b/setup/native-script.ps1 index 047c754b09..d90687ee39 100644 --- a/setup/native-script.ps1 +++ b/setup/native-script.ps1 @@ -103,7 +103,7 @@ $androidExecutable = [io.path]::combine($env:ANDROID_HOME, "tools", "bin", "sdkm echo y | cmd /c "$androidExecutable" "platform-tools" echo y | cmd /c "$androidExecutable" "tools" echo y | cmd /c "$androidExecutable" "build-tools;25.0.2" -echo y | cmd /c "$androidExecutable" "platforms;android-23" +echo y | cmd /c "$androidExecutable" "platforms;android-25" echo y | cmd /c "$androidExecutable" "extras;android;m2repository" echo y | cmd /c "$androidExecutable" "extras;google;m2repository" diff --git a/setup/native-script.rb b/setup/native-script.rb index 8687c50336..f2891799ad 100755 --- a/setup/native-script.rb +++ b/setup/native-script.rb @@ -132,7 +132,7 @@ def install_environment_variable(name, value) execute("echo y | #{sdk_manager} \"platform-tools\"", error_msg) execute("echo y | #{sdk_manager} \"tools\"", error_msg) execute("echo y | #{sdk_manager} \"build-tools;25.0.2\"", error_msg) -execute("echo y | #{sdk_manager} \"platforms;android-23\"", error_msg) +execute("echo y | #{sdk_manager} \"platforms;android-25\"", error_msg) execute("echo y | #{sdk_manager} \"extras;android;m2repository\"", error_msg) execute("echo y | #{sdk_manager} \"extras;google;m2repository\"", error_msg) @@ -143,7 +143,7 @@ def install_environment_variable(name, value) execute("echo y | #{sdk_manager} \"extras;intel;Hardware_Accelerated_Execution_Manager\"", error_msg) haxm_silent_installer = File.join(ENV["ANDROID_HOME"], "extras", "intel", "Hardware_Accelerated_Execution_Manager", "silent_install.sh") execute("sudo #{haxm_silent_installer}", "There seem to be some problems with the Android configuration") - execute("echo y | #{sdk_manager} \"system-images;android-23;default;x86\"", error_msg) + execute("echo y | #{sdk_manager} \"system-images;android-25;default;x86\"", error_msg) else execute("echo y | #{sdk_manager} \"system-images;android-25;google_apis;armeabi-v7a\"", error_msg) end From e0b05f41c55ff515c6617e840e1d97ed0f0e8d01 Mon Sep 17 00:00:00 2001 From: Emil Tabakov Date: Tue, 5 Sep 2017 15:15:17 +0300 Subject: [PATCH 207/212] Install Google Chrome on Mac for debugging purpose (#3102) --- setup/native-script.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup/native-script.rb b/setup/native-script.rb index f2891799ad..fa6752ee68 100755 --- a/setup/native-script.rb +++ b/setup/native-script.rb @@ -92,6 +92,7 @@ def install_environment_variable(name, value) exit end +install("Google Chrome", "Installing Google Chrome, used for debugging", "brew cask install google-chrome", false, false); install("Java SE Development Kit", "Installing the Java SE Development Kit... This might take some time, please, be patient. (You will be prompted for your password)", 'brew cask install java', false, false) install("Android SDK", "Installing Android SDK", 'brew tap caskroom/cask; brew cask install android-sdk', false) @@ -150,4 +151,4 @@ def install_environment_variable(name, value) end puts "The ANDROID_HOME and JAVA_HOME environment variables have been added to your .bash_profile/.zprofile" -puts "Restart the terminal or run `source ~/.bash_profile` to use them." \ No newline at end of file +puts "Restart the terminal or run `source ~/.bash_profile` to use them." From 570ebbbfaa985f1cb463d73e51674e14190c3ab2 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Tue, 5 Sep 2017 20:11:08 +0300 Subject: [PATCH 208/212] Emit deviceIdentifier in debuggerAttached event (#3104) `debuggerAttached` event should emit full information required for debugging. However, the deviceIdentifier is missing, so add it. Update PublicAPI.md according to latest changes. --- PublicAPI.md | 24 +++++++++++++++++++----- lib/declarations.d.ts | 2 +- lib/definitions/debug.d.ts | 2 +- lib/services/debug-service.ts | 14 ++++++-------- test/services/debug-service.ts | 11 ++++++++--- 5 files changed, 35 insertions(+), 18 deletions(-) diff --git a/PublicAPI.md b/PublicAPI.md index fd56bf03e9..a84043d074 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -30,6 +30,10 @@ const tns = require("nativescript"); * [liveSyncService](#livesyncservice) * [liveSync](#livesync) * [stopLiveSync](#stopLiveSync) + * [enableDebugging](#enableDebugging) + * [attachDebugger](#attachDebugger) + * [disableDebugging](#disableDebugging) + * [getLiveSyncDeviceDescriptors](#getLiveSyncDeviceDescriptors) * [events](#events) @@ -454,9 +458,9 @@ The returned Promise will be rejected in case any error occurs. It will also be * Starts debug operation based on the specified debug data. * @param {IDebugData} debugData Describes information for device and application that will be debugged. * @param {IDebugOptions} debugOptions Describe possible options to modify the behaivor of the debug operation, for example stop on the first line. - * @returns {Promise} URL that should be opened in Chrome DevTools. + * @returns {Promise} Device Identifier, full url and port where the frontend client can be connected. */ -debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; +debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; ``` The type of arguments that you can pass are described below: @@ -515,6 +519,16 @@ interface IDebugOptions { * Default value is 02e6bde1bbe34e43b309d4ef774b1168d25fd024 which corresponds to 55.0.2883 Chrome version */ devToolsCommit?: string; + + /** + * Defines if Chrome DevTools should be used for debugging. + */ + chrome?: boolean; + + /** + * Defines if thе application is already started on device. + */ + start?: boolean; } ``` @@ -536,7 +550,7 @@ const debugOptions = { }; tns.debugService.debug(debugData, debugOptions) - .then(url => console.log(`Open the following url in Chrome DevTools: ${url}`)) + .then(debugInfo => console.log(`Open the following url in Chrome DevTools: ${debugInfo.url}, port is: ${debugInfo.port} and deviceIdentifier is: ${debugInfo.deviceIdentifier}`)) .catch(err => console.log(`Unable to start debug operation, reason: ${err.message}.`)); ``` @@ -839,12 +853,12 @@ tns.liveSyncService.on("userInteractionNeeded", data => { }); ``` -* debuggerAttached - raised whenever CLI attaches the backend debugging socket and a frontend debugging client may be attached. The event is raised with an object containing the device's identifier: +* debuggerAttached - raised whenever CLI attaches the backend debugging socket and a frontend debugging client may be attached. The event is raised with an object containing the device's identifier, url for debugging and port Example: ```JavaScript tns.liveSyncService.on("debuggerAttached", debugInfo => { - console.log(`Backend client connected, frontend client may be connected at ${debugInfo.url}`); + console.log(`Backend client connected, frontend client may be connected at ${debugInfo.url} to debug app on device ${debugInfo.deviceIdentifier}. Port is: ${debugInfo.port}`); }); ``` diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index e91c807a72..d6d40af9f1 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -372,7 +372,7 @@ interface ICreateProjectOptions extends INpmInstallConfigurationOptionsBase { pathToTemplate?: string; } -interface IDebugInformation extends IPort { +interface IDebugInformation extends IPort, Mobile.IDeviceIdentifier { url: string; } diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 698185efd8..beed61b239 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -105,7 +105,7 @@ interface IDebugServiceBase extends NodeJS.EventEmitter { * Starts debug operation based on the specified debug data. * @param {IDebugData} debugData Describes information for device and application that will be debugged. * @param {IDebugOptions} debugOptions Describe possible options to modify the behaivor of the debug operation, for example stop on the first line. - * @returns {Promise} Full url and port where the frontend client can be connected. + * @returns {Promise} Device Identifier, full url and port where the frontend client can be connected. */ debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; } diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index a55cfb17c0..a8c314019b 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -58,7 +58,7 @@ export class DebugService extends EventEmitter implements IDebugService { result = await debugService.debug(debugData, debugOptions); } - return this.getDebugInformation(result); + return this.getDebugInformation(result, device.deviceInfo.identifier); } public debugStop(deviceIdentifier: string): Promise { @@ -94,20 +94,18 @@ export class DebugService extends EventEmitter implements IDebugService { platformDebugService.on(CONNECTION_ERROR_EVENT_NAME, connectionErrorHandler); } - private getDebugInformation(fullUrl: string): IDebugInformation { - let debugInfo: IDebugInformation = { + private getDebugInformation(fullUrl: string, deviceIdentifier: string): IDebugInformation { + const debugInfo: IDebugInformation = { url: fullUrl, - port: 0 + port: 0, + deviceIdentifier }; if (fullUrl) { const parseQueryString = true; const wsQueryParam = parse(fullUrl, parseQueryString).query.ws; const hostPortSplit = wsQueryParam && wsQueryParam.split(":"); - debugInfo = { - url: fullUrl, - port: hostPortSplit && +hostPortSplit[1] - }; + debugInfo.port = hostPortSplit && +hostPortSplit[1]; } return debugInfo; diff --git a/test/services/debug-service.ts b/test/services/debug-service.ts index 2b4da60510..8d4887c61f 100644 --- a/test/services/debug-service.ts +++ b/test/services/debug-service.ts @@ -8,6 +8,8 @@ import { CONNECTION_ERROR_EVENT_NAME, DebugCommandErrors } from "../../lib/const const fakeChromeDebugPort = 123; const fakeChromeDebugUrl = `fakeChromeDebugUrl?experiments=true&ws=localhost:${fakeChromeDebugPort}`; +const defaultDeviceIdentifier = "Nexus5"; + class PlatformDebugService extends EventEmitter /* implements IPlatformDebugService */ { public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { return fakeChromeDebugUrl; @@ -18,6 +20,7 @@ interface IDebugTestDeviceInfo { deviceInfo: { status: string; platform: string; + identifier: string; }; isEmulator: boolean; @@ -36,7 +39,8 @@ interface IDebugTestData { const getDefaultDeviceInformation = (platform?: string): IDebugTestDeviceInfo => ({ deviceInfo: { status: constants.CONNECTED_STATUS, - platform: platform || "Android" + platform: platform || "Android", + identifier: defaultDeviceIdentifier }, isEmulator: false @@ -95,7 +99,7 @@ describe("debugService", () => { describe("debug", () => { const getDebugData = (deviceIdentifier?: string): IDebugData => ({ applicationIdentifier: "org.nativescript.app1", - deviceIdentifier: deviceIdentifier || "Nexus5", + deviceIdentifier: deviceIdentifier || defaultDeviceIdentifier, projectDir: "/Users/user/app1", projectName: "app1" }); @@ -217,7 +221,8 @@ describe("debugService", () => { assert.deepEqual(debugInfo, { url: fakeChromeDebugUrl, - port: fakeChromeDebugPort + port: fakeChromeDebugPort, + deviceIdentifier: debugData.deviceIdentifier }); }); }); From 47be8d7b5edec3ede449382c962322b50b0cc005 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Tue, 5 Sep 2017 21:18:45 +0300 Subject: [PATCH 209/212] Improve analytics (#3088) * Improve analytics We have a requirement to track information in several different providers. However, this may slow down CLI's operations as each track (at least for some of the providers) may last around 100-300ms. In order to track in multiple analytics provider without slowing down CLI commands, move the tracking to a new process. The process is detached from CLI, so when CLI finishes its work it will exit, while the child process will remain alive until it sends all required information. The new process is called `analytics-broker-process`, it is detached from CLI, but it there's IPC channel between them, so CLI can send information to be tracked. Just before CLI dies, it will send "finish" message to the `broker` process and the IPC channel will be closed. In CLI implement a new `analytics-service`, so when the methods `track`, `trackFeature` and `trackException` are called, instead of tracking directly in CLI's process (i.e. the old implementation), to send information to the broker process. The only exception is tracking of the answer if user allows us to send anonymous statistics. As this action requires user interaction, it is executed in CLI's process, a new eqatec analytics monitor is started and the answer is sent to it. After that the analytics is stopped. In case the answer is "yes" and there is more information that we want to track in the current process, it will be send to broker and tracked in all providers. In case the answer is "no", no other data will be tracked. The broker process is started from analytics process once per process, i.e. on each CLI process (which have something to track) there's a corresponding `analytics-broker-process`. Once it is initialized, it sends BrokerReadyToReceive message to CLI and CLI knows that it can start sending tracking information. Initialization phase is creating a new instance of AnalyticsBroker and attaching handlers for `message` and `disconnect` events. The AnalyitcsBroker itself has a single purpose - sends tracking data to all available prociders. Currently the only provider is `eqatecAnalyticsProvider`. Once CLI sends "finish" message (or `analytics-broker-process` receives `disconnect` event), the process sends information to all providers that there's nothing more to be tracked. When all providers finish their work, they should disocnnect themselves from the broker process and it will exit gracefully. `EqatecAnalyticsProvider` is used to track features and exceptions in Progress Telerik Analytics. Our requirement is to track features in two projects (same data should be send to both of them) and exceptions in another project. Each of them should be in a separate child process, so failure in one of them will not reflect the others. So the provider starts all required processes. When broker sends information to the provider, it sends it to all required child processes, for example if a feature should be tracked, the provider will send information for it to the two processes used for feature tracking. Each eqatec child process is a new Node.js process runnign `eqatec-analytics-process`. Again - this is a detached process. Once it is initialized, it sends "EqatecAnalyticsReadyToReceive" message to the `EqatecAnalyitcsProvider` and it knows it can send information to the new child process. The initialization phase is creation of new instance of `EqatectAnalyticsService`, attaching to "message" and "disconnect" events. Once `EqatecAnalyitcsProvider` receives `EqatecAnalyticsReadyToReceive` message, it sends initialization data for the eqatec monitor. It includes the analytics project API key, user id, current sessions count, etc. The information is persisted in a file on user's machine, so this file should be updated from a single process. As we might have 3 eqatec analytics processes, the provider is the only place where the file should be read and updated. When `eqatec-analytics-process` receives initialization data, it creates a new instance of the `EqatectAnalyticsService` class with it. However a monitor is not started yet. It will be started when there's something to be tracked, i.e. once `eqatec-analytics-process` receives feature or exception to be tracked. When `eqatec-analytics-process` receives "finish" message, it awaits all pending requests and after that disconnets itself from the parent. In case this operation fails, the process just exits, which will allow the parent to exit gracefully. Remove the public `startEqatecMonitor` method from API - it has not been used. * Craete all eqatec analytics monitors in the broker process Eqatec Analytics monitors can be created in the broker process directly instead of starting multiple child processes. So delete some unneeded files and move tracking of `AcceptUsageReporting` out of CLI process. * Add tracking to Google Analytics Add new methods to track information in Google Analytics(GA). As the data that we want to track is different than the one we track in Eqatec Analytics, call the new methods whenever we want to send some information to GA. Change the broker process to send information to Eqatec Analytics when we want to track feature or exception and to GA only when the new method for tracking there is called. In Google Analytics (GA) we track two types of data - pages (called pageviews) and events. Each command is tracked as a page - we only track the name of the command as a page: * `tns build android` will be tracked as visited page - `build android` * `tns run` will be tracked as visited page - `run` When we want to track some additional actions that happen during execution of each command, we send events to GA. Each event has the following data: * category - currently we do not have a specific category for each action, so it is hardcoded to "CLI". * action - this is the real action that will be tracked, for example `Build`, `LiveSync`, `Deploy`, etc. * label - contains specific inforamation for the current action, for example what is the platform, device type, etc. In many cases we send labels with a lot of information, but as the value must be string, we use `_` as a separator. For example: * Action `Build` may have the following label: `Android_Debug_Incremental`. This is `__`. * Action `LiveSync` may have the following label: `Android_Emulator_5.1`. This is `__`. Custom dimensions For each additional data may be send to GA via Custom Dimensions. More about them can be found [here](https://support.google.com/analytics/answer/2709828). We are using several custom dimensions, most of them have hit scope, i.e. their values are saved for the current hit (event or page), but may be changed for the next time. The only exclusion is `session id` - it has session scope, so whenever it is sent, the new value overwrites the values of the "session id" property in all hits of the current session. One interesting custom dimension is `Client` - it will be set to the value of `--analyticsClient` in case it is passed. In case not, we'll set it to "CLI" when terminal is interactive and "Unknown" when terminal is not interactive. Sessions - what is session for GA In Eqatec Analytics we call `stopMonitor` at the end of each command. So the session there is one command. In GA we've decided to keep the default way of measuring a session - a session is stopped when there are 30 inactive minutes, i.e. no data is sent from 30 minutes. This allows us to investigate the behavior and the flow of commands a user is executing in a single session (during the day for example). Client ID and User ID: Currently we do not send User ID to GA, we use Client ID instead as sending User IDs requires different logic and agreements. For us all users are anonymous. User-Agent When sending requests to GA, we need to set correct User-Agent, so GA will be able to understand the Operating System. For each OS the string is painfully specific... Current implementation works fine for Windows, Linux and macOS. Implementation In order to send data to GA, we are using a `universal-analytics` npm package. We cannot use Google's default libraries (`analytics.js`, `gtag.js`) as they are written for browser. So the only way to track data is to use the Measurement Protocol (directly send http requests). However, `universal-analytics` package provides a very good abstraction over the Measurement Protocol, so we are using it instead of implementing each call. More information about Measurement protocol: https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters * Fix hanging commands In case the command is not related to LiveSync, once it finishes its work, CLI process should end. However, this works only on Windows, as the broker child process there is really detached from the parent. On macOS and Linux, the 'ipc' channel between the broker process and CLI, keeps both alive. So use the 'dispose' method, which is called at the end of command execution and disconnect the broker process. In case we are in LiveSync case, set shouldDispose to false and this way the broker will be kept alive. Once Ctrl + C is used to end the CLI process, send the finish message to broker and it will disconnect itself from CLI. --- .vscode/launch.json | 15 +- PublicAPI.md | 15 -- lib/bootstrap.ts | 4 +- lib/commands/run.ts | 1 - lib/common | 2 +- lib/config.ts | 2 +- lib/constants.ts | 18 ++ lib/nativescript-cli-lib-bootstrap.ts | 1 + lib/services/analytics-service.ts | 34 ---- lib/services/analytics-settings-service.ts | 37 ++-- .../analytics/analytics-broker-process.ts | 60 ++++++ lib/services/analytics/analytics-broker.ts | 45 +++++ lib/services/analytics/analytics-constants.ts | 14 ++ lib/services/analytics/analytics-service.ts | 189 ++++++++++++++++++ lib/services/analytics/analytics.d.ts | 97 +++++++++ .../analytics/eqatec-analytics-provider.ts | 60 ++++++ .../google-analytics-custom-dimensions.ts | 8 + .../analytics/google-analytics-provider.ts | 120 +++++++++++ lib/services/debug-service.ts | 11 +- .../livesync/livesync-command-helper.ts | 2 + lib/services/livesync/livesync-service.ts | 9 +- lib/services/platform-service.ts | 21 +- lib/services/project-templates-service.ts | 6 + lib/services/test-execution-service.ts | 2 + npm-shrinkwrap.json | 32 ++- package.json | 6 +- test/nativescript-cli-lib.ts | 1 - test/plugins-service.ts | 5 +- test/project-service.ts | 5 +- test/project-templates-service.ts | 5 +- test/services/debug-service.ts | 4 + test/services/livesync-service.ts | 3 + 32 files changed, 746 insertions(+), 88 deletions(-) delete mode 100644 lib/services/analytics-service.ts create mode 100644 lib/services/analytics/analytics-broker-process.ts create mode 100644 lib/services/analytics/analytics-broker.ts create mode 100644 lib/services/analytics/analytics-constants.ts create mode 100644 lib/services/analytics/analytics-service.ts create mode 100644 lib/services/analytics/analytics.d.ts create mode 100644 lib/services/analytics/eqatec-analytics-provider.ts create mode 100644 lib/services/analytics/google-analytics-custom-dimensions.ts create mode 100644 lib/services/analytics/google-analytics-provider.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index acf4559e49..88a6442f47 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -67,8 +67,19 @@ { "type": "node", "request": "attach", - "name": "Attach to Process", - "port": 5858, + "name": "Attach to Broker Process", + // In case you want to debug Analytics Broker process, add `--debug-brk=9897` (or --inspect-brk=9897) when spawning analytics-broker-process. + "port": 9897, + "sourceMaps": true + }, + + { + "type": "node", + "request": "attach", + "name": "Attach to Eqatec Process", + // In case you want to debug Eqatec Analytics process, add `--debug-brk=9855` (or --inspect-brk=9855) when spawning eqatec-analytics-process. + // NOTE: Ensure you set it only for one of the analytics processes. + "port": 9855, "sourceMaps": true } diff --git a/PublicAPI.md b/PublicAPI.md index a84043d074..206287aee4 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -394,21 +394,6 @@ tns.npm.view(["nativescript"], {}).then(result => { }); ``` -## analyticsService -Provides a way to configure analytics. - -### startEqatecMonitor -* Definition: -```TypeScript -/** - * Starts analytics monitor with provided key. - * @param {string} projectApiKey API key with which to start analytics monitor. - * @returns {Promise}. - */ -startEqatecMonitor(projectApiKey: string): Promise; -``` - - ## debugService Provides methods for debugging applications on devices. The service is also event emitter, that raises the following events: * `connectionError` event - this event is raised when the debug operation cannot start on iOS device. The causes can be: diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 96c6f24eda..f4ad743cdd 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -30,7 +30,9 @@ $injector.require("androidDebugService", "./services/android-debug-service"); $injector.require("userSettingsService", "./services/user-settings-service"); $injector.require("analyticsSettingsService", "./services/analytics-settings-service"); -$injector.requirePublic("analyticsService", "./services/analytics-service"); +$injector.require("analyticsService", "./services/analytics/analytics-service"); +$injector.require("eqatecAnalyticsProvider", "./services/analytics/eqatec-analytics-provider"); +$injector.require("googleAnalyticsProvider", "./services/analytics/google-analytics-provider"); $injector.require("emulatorSettingsService", "./services/emulator-settings-service"); diff --git a/lib/commands/run.ts b/lib/commands/run.ts index da4da5148b..505552f685 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -5,7 +5,6 @@ export class RunCommandBase implements ICommand { public platform: string; constructor(protected $platformService: IPlatformService, - protected $liveSyncService: ILiveSyncService, protected $projectData: IProjectData, protected $options: IOptions, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, diff --git a/lib/common b/lib/common index 5b4c6da25c..804e28c137 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 5b4c6da25ca015f52756431ddc63b5a7fbd8a995 +Subproject commit 804e28c137442922609caced942dee3d6512523b diff --git a/lib/config.ts b/lib/config.ts index 247c721ede..4b6b21c942 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -73,7 +73,7 @@ export class StaticConfig extends StaticConfigBase implements IStaticConfig { } public get PATH_TO_BOOTSTRAP(): string { - return path.join(__dirname, "bootstrap"); + return path.join(__dirname, "bootstrap.js"); } public async getAdbFilePath(): Promise { diff --git a/lib/constants.ts b/lib/constants.ts index 346d475dab..6405df9471 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -103,3 +103,21 @@ export const enum NativePlatformStatus { requiresPrepare = "2", alreadyPrepared = "3" } + +export const enum DebugTools { + Chrome = "Chrome", + Inspector = "Inspector" +} + +export const enum TrackActionNames { + Build = "Build", + CreateProject = "Create project", + Debug = "Debug", + Deploy = "Deploy", + LiveSync = "LiveSync" +} + +export const enum BuildStates { + Clean = "Clean", + Incremental = "Incremental" +} diff --git a/lib/nativescript-cli-lib-bootstrap.ts b/lib/nativescript-cli-lib-bootstrap.ts index 95dcf39e2f..1e545239e3 100644 --- a/lib/nativescript-cli-lib-bootstrap.ts +++ b/lib/nativescript-cli-lib-bootstrap.ts @@ -13,3 +13,4 @@ $injector.requirePublicClass("localBuildService", "./services/local-build-servic // We need this because some services check if (!$options.justlaunch) to start the device log after some operation. // We don't want this behaviour when the CLI is required as library. $injector.resolve("options").justlaunch = true; +$injector.resolve("staticConfig").disableAnalytics = true; diff --git a/lib/services/analytics-service.ts b/lib/services/analytics-service.ts deleted file mode 100644 index ba98c7f662..0000000000 --- a/lib/services/analytics-service.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { AnalyticsServiceBase } from "../common/services/analytics-service-base"; -import { exported } from "../common/decorators"; - -export class AnalyticsService extends AnalyticsServiceBase implements IAnalyticsService { - private static ANALYTICS_FEATURE_USAGE_TRACKING_API_KEY = "9912cff308334c6d9ad9c33f76a983e3"; - - constructor(protected $logger: ILogger, - protected $options: IOptions, - $staticConfig: Config.IStaticConfig, - $prompter: IPrompter, - $userSettingsService: UserSettings.IUserSettingsService, - $analyticsSettingsService: IAnalyticsSettingsService, - $progressIndicator: IProgressIndicator, - $osInfo: IOsInfo) { - super($logger, $options, $staticConfig, $prompter, $userSettingsService, $analyticsSettingsService, $progressIndicator, $osInfo); - } - - @exported("analyticsService") - public async startEqatecMonitor(projectApiKey: string): Promise { - if (await this.isEnabled(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME) || await this.isEnabled(this.$staticConfig.ERROR_REPORT_SETTING_NAME)) { - await this.restartEqatecMonitor(projectApiKey); - } - } - - protected async checkConsentCore(trackFeatureUsage: boolean): Promise { - await this.restartEqatecMonitor(AnalyticsService.ANALYTICS_FEATURE_USAGE_TRACKING_API_KEY); - await super.checkConsentCore(trackFeatureUsage); - - // Stop the monitor, so correct API_KEY will be used when features are tracked. - this.tryStopEqatecMonitor(); - } -} - -$injector.register("analyticsService", AnalyticsService); diff --git a/lib/services/analytics-settings-service.ts b/lib/services/analytics-settings-service.ts index 2147bbd273..6ca50db27e 100644 --- a/lib/services/analytics-settings-service.ts +++ b/lib/services/analytics-settings-service.ts @@ -1,7 +1,6 @@ import { createGUID } from "../common/helpers"; class AnalyticsSettingsService implements IAnalyticsSettingsService { - private static SESSIONS_STARTED_OBSOLETE_KEY = "SESSIONS_STARTED"; private static SESSIONS_STARTED_KEY_PREFIX = "SESSIONS_STARTED_"; constructor(private $userSettingsService: UserSettings.IUserSettingsService, @@ -12,16 +11,12 @@ class AnalyticsSettingsService implements IAnalyticsSettingsService { return true; } - public async getUserId(): Promise { - let currentUserId = await this.$userSettingsService.getSettingValue("USER_ID"); - if (!currentUserId) { - currentUserId = createGUID(false); - - this.$logger.trace(`Setting new USER_ID: ${currentUserId}.`); - await this.$userSettingsService.saveSetting("USER_ID", currentUserId); - } + public getUserId(): Promise { + return this.getSettingValueOrDefault("USER_ID"); + } - return currentUserId; + public getClientId(): Promise { + return this.getSettingValueOrDefault(this.$staticConfig.ANALYTICS_INSTALLATION_ID_SETTING_NAME); } public getClientName(): string { @@ -33,14 +28,8 @@ class AnalyticsSettingsService implements IAnalyticsSettingsService { } public async getUserSessionsCount(projectName: string): Promise { - const oldSessionCount = await this.$userSettingsService.getSettingValue(AnalyticsSettingsService.SESSIONS_STARTED_OBSOLETE_KEY); - - if (oldSessionCount) { - // remove the old property for sessions count - await this.$userSettingsService.removeSetting(AnalyticsSettingsService.SESSIONS_STARTED_OBSOLETE_KEY); - } - - return await this.$userSettingsService.getSettingValue(this.getSessionsProjectKey(projectName)) || oldSessionCount || 0; + const sessionsCountForProject = await this.$userSettingsService.getSettingValue(this.getSessionsProjectKey(projectName)); + return sessionsCountForProject || 0; } public async setUserSessionsCount(count: number, projectName: string): Promise { @@ -50,5 +39,17 @@ class AnalyticsSettingsService implements IAnalyticsSettingsService { private getSessionsProjectKey(projectName: string): string { return `${AnalyticsSettingsService.SESSIONS_STARTED_KEY_PREFIX}${projectName}`; } + + private async getSettingValueOrDefault(settingName: string): Promise { + let guid = await this.$userSettingsService.getSettingValue(settingName); + if (!guid) { + guid = createGUID(false); + + this.$logger.trace(`Setting new ${settingName}: ${guid}.`); + await this.$userSettingsService.saveSetting(settingName, guid); + } + + return guid; + } } $injector.register("analyticsSettingsService", AnalyticsSettingsService); diff --git a/lib/services/analytics/analytics-broker-process.ts b/lib/services/analytics/analytics-broker-process.ts new file mode 100644 index 0000000000..78769ae8ea --- /dev/null +++ b/lib/services/analytics/analytics-broker-process.ts @@ -0,0 +1,60 @@ +import * as fs from "fs"; +import { AnalyticsBroker } from "./analytics-broker"; + +const pathToBootstrap = process.argv[2]; +if (!pathToBootstrap || !fs.existsSync(pathToBootstrap)) { + throw new Error("Invalid path to bootstrap."); +} + +// After requiring the bootstrap we can use $injector +require(pathToBootstrap); + +const analyticsBroker = $injector.resolve(AnalyticsBroker, { pathToBootstrap }); +let trackingQueue: Promise = Promise.resolve(); + +let sentFinishMsg = false; +let receivedFinishMsg = false; + +const sendDataForTracking = async (data: ITrackingInformation) => { + trackingQueue = trackingQueue.then(() => analyticsBroker.sendDataForTracking(data)); + await trackingQueue; +}; + +const finishTracking = async (data?: ITrackingInformation) => { + if (!sentFinishMsg) { + sentFinishMsg = true; + + data = data || { type: TrackingTypes.Finish }; + const action = async () => { + await sendDataForTracking(data); + process.disconnect(); + }; + + if (receivedFinishMsg) { + await action(); + } else { + // In case we've got here without receiving "finish" message from parent (receivedFinishMsg is false) + // there might be various reasons, but most probably the parent is dead. + // However, there's no guarantee that we've received all messages. So wait some time before sending finish message to children. + setTimeout(async () => { + await action(); + }, 1000); + } + } +}; + +process.on("message", async (data: ITrackingInformation) => { + if (data.type === TrackingTypes.Finish) { + receivedFinishMsg = true; + await finishTracking(data); + return; + } + + await sendDataForTracking(data); +}); + +process.on("disconnect", async () => { + await finishTracking(); +}); + +process.send(AnalyticsMessages.BrokerReadyToReceive); diff --git a/lib/services/analytics/analytics-broker.ts b/lib/services/analytics/analytics-broker.ts new file mode 100644 index 0000000000..471840cd7b --- /dev/null +++ b/lib/services/analytics/analytics-broker.ts @@ -0,0 +1,45 @@ +import { cache } from "../../common/decorators"; + +export class AnalyticsBroker implements IAnalyticsBroker { + + @cache() + private async getEqatecAnalyticsProvider(): Promise { + return this.$injector.resolve("eqatecAnalyticsProvider"); + } + + @cache() + private async getGoogleAnalyticsProvider(): Promise { + const clientId = await this.$analyticsSettingsService.getClientId(); + return this.$injector.resolve("googleAnalyticsProvider", { clientId }); + } + + constructor(private $analyticsSettingsService: IAnalyticsSettingsService, + private $injector: IInjector) { } + + public async sendDataForTracking(trackInfo: ITrackingInformation): Promise { + const eqatecProvider = await this.getEqatecAnalyticsProvider(); + const googleProvider = await this.getGoogleAnalyticsProvider(); + + switch (trackInfo.type) { + case TrackingTypes.Exception: + await eqatecProvider.trackError(trackInfo); + break; + case TrackingTypes.Feature: + await eqatecProvider.trackInformation(trackInfo); + break; + case TrackingTypes.AcceptTrackFeatureUsage: + await eqatecProvider.acceptFeatureUsageTracking(trackInfo); + break; + case TrackingTypes.GoogleAnalyticsData: + await googleProvider.trackHit(trackInfo); + break; + case TrackingTypes.Finish: + await eqatecProvider.finishTracking(); + break; + default: + throw new Error(`Invalid tracking type: ${trackInfo.type}`); + } + + } + +} diff --git a/lib/services/analytics/analytics-constants.ts b/lib/services/analytics/analytics-constants.ts new file mode 100644 index 0000000000..5aff79435c --- /dev/null +++ b/lib/services/analytics/analytics-constants.ts @@ -0,0 +1,14 @@ +/** + * Defines messages used in communication between CLI's process and analytics subprocesses. + */ +const enum AnalyticsMessages { + /** + * Analytics Broker is initialized and is ready to receive information for tracking. + */ + BrokerReadyToReceive = "BrokerReadyToReceive", + + /** + * Eqatec Analytics process is initialized and is ready to receive information for tracking. + */ + EqatecAnalyticsReadyToReceive = "EqatecAnalyticsReadyToReceive" +} diff --git a/lib/services/analytics/analytics-service.ts b/lib/services/analytics/analytics-service.ts new file mode 100644 index 0000000000..a15f7f7583 --- /dev/null +++ b/lib/services/analytics/analytics-service.ts @@ -0,0 +1,189 @@ +import { AnalyticsServiceBase } from "../../common/services/analytics-service-base"; +import { ChildProcess } from "child_process"; +import * as path from "path"; +import { cache } from "../../common/decorators"; +import { isInteractive } from '../../common/helpers'; +import { DeviceTypes, AnalyticsClients } from "../../common/constants"; + +export class AnalyticsService extends AnalyticsServiceBase { + private static ANALYTICS_BROKER_START_TIMEOUT = 30 * 1000; + private brokerProcess: ChildProcess; + + constructor(protected $logger: ILogger, + protected $options: IOptions, + $staticConfig: Config.IStaticConfig, + $prompter: IPrompter, + $userSettingsService: UserSettings.IUserSettingsService, + $analyticsSettingsService: IAnalyticsSettingsService, + $osInfo: IOsInfo, + private $childProcess: IChildProcess, + private $processService: IProcessService, + private $projectDataService: IProjectDataService, + private $mobileHelper: Mobile.IMobileHelper) { + super($logger, $options, $staticConfig, $prompter, $userSettingsService, $analyticsSettingsService, $osInfo); + } + + public track(featureName: string, featureValue: string): Promise { + const data: IFeatureTrackingInformation = { + type: TrackingTypes.Feature, + featureName: featureName, + featureValue: featureValue + }; + + return this.sendInfoForTracking(data, this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME); + } + + public trackException(exception: any, message: string): Promise { + const data: IExceptionsTrackingInformation = { + type: TrackingTypes.Exception, + exception, + message + }; + + return this.sendInfoForTracking(data, this.$staticConfig.ERROR_REPORT_SETTING_NAME); + } + + public async trackAcceptFeatureUsage(settings: { acceptTrackFeatureUsage: boolean }): Promise { + this.sendMessageToBroker({ + type: TrackingTypes.AcceptTrackFeatureUsage, + acceptTrackFeatureUsage: settings.acceptTrackFeatureUsage + }); + } + + public async trackInGoogleAnalytics(gaSettings: IGoogleAnalyticsData): Promise { + await this.initAnalyticsStatuses(); + + if (!this.$staticConfig.disableAnalytics && this.analyticsStatuses[this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME] === AnalyticsStatus.enabled) { + gaSettings.customDimensions = gaSettings.customDimensions || {}; + gaSettings.customDimensions[GoogleAnalyticsCustomDimensions.client] = this.$options.analyticsClient || (isInteractive() ? AnalyticsClients.Cli : AnalyticsClients.Unknown); + + const googleAnalyticsData: IGoogleAnalyticsTrackingInformation = _.merge({ type: TrackingTypes.GoogleAnalyticsData, category: AnalyticsClients.Cli }, gaSettings); + return this.sendMessageToBroker(googleAnalyticsData); + } + } + + public async trackEventActionInGoogleAnalytics(data: IEventActionData): Promise { + const device = data.device; + const platform = device ? device.deviceInfo.platform : data.platform; + const isForDevice = device ? !device.isEmulator : data.isForDevice; + + let label: string = ""; + label = this.addDataToLabel(label, platform); + + // In some cases (like in case action is Build and platform is Android), we do not know if the deviceType is emulator or device. + // Just exclude the device_type in this case. + if (isForDevice !== null) { + const deviceType = isForDevice ? DeviceTypes.Device : (this.$mobileHelper.isAndroidPlatform(platform) ? DeviceTypes.Emulator : DeviceTypes.Simulator); + label = this.addDataToLabel(label, deviceType); + } + + if (device) { + label = this.addDataToLabel(label, device.deviceInfo.version); + } + + if (data.additionalData) { + label = this.addDataToLabel(label, data.additionalData); + } + + const customDimensions: IStringDictionary = {}; + if (data.projectDir) { + const projectData = this.$projectDataService.getProjectData(data.projectDir); + customDimensions[GoogleAnalyticsCustomDimensions.projectType] = projectData.projectType; + } + + const googleAnalyticsEventData: IGoogleAnalyticsEventData = { + googleAnalyticsDataType: GoogleAnalyticsDataType.Event, + action: data.action, + label, + customDimensions + }; + + this.$logger.trace("Will send the following information to Google Analytics:", googleAnalyticsEventData); + + await this.trackInGoogleAnalytics(googleAnalyticsEventData); + } + + public dispose(): void { + if (this.brokerProcess && this.shouldDisposeInstance) { + this.brokerProcess.disconnect(); + } + } + + private addDataToLabel(label: string, newData: string): string { + if (newData && label) { + return `${label}_${newData}`; + } + + return label || newData || ""; + } + + @cache() + private getAnalyticsBroker(): Promise { + return new Promise((resolve, reject) => { + const broker = this.$childProcess.spawn("node", + [ + path.join(__dirname, "analytics-broker-process.js"), + this.$staticConfig.PATH_TO_BOOTSTRAP + ], + { + stdio: ["ignore", "ignore", "ignore", "ipc"], + detached: true + } + ); + + broker.unref(); + + let isSettled = false; + + const timeoutId = setTimeout(() => { + if (!isSettled) { + reject(new Error("Unable to start Analytics Broker process.")); + } + }, AnalyticsService.ANALYTICS_BROKER_START_TIMEOUT); + + broker.on("error", (err: Error) => { + clearTimeout(timeoutId); + + if (!isSettled) { + isSettled = true; + reject(err); + } + }); + + broker.on("message", (data: any) => { + if (data === AnalyticsMessages.BrokerReadyToReceive) { + clearTimeout(timeoutId); + + if (!isSettled) { + isSettled = true; + + this.$processService.attachToProcessExitSignals(this, () => { + broker.send({ + type: TrackingTypes.Finish + }); + }); + + this.brokerProcess = broker; + + resolve(broker); + } + } + }); + }); + } + + private async sendInfoForTracking(trackingInfo: ITrackingInformation, settingName: string): Promise { + await this.initAnalyticsStatuses(); + + if (!this.$staticConfig.disableAnalytics && this.analyticsStatuses[settingName] === AnalyticsStatus.enabled) { + return this.sendMessageToBroker(trackingInfo); + } + } + + private async sendMessageToBroker(message: ITrackingInformation): Promise { + const broker = await this.getAnalyticsBroker(); + return new Promise((resolve, reject) => broker.send(message, resolve)); + } +} + +$injector.register("analyticsService", AnalyticsService); diff --git a/lib/services/analytics/analytics.d.ts b/lib/services/analytics/analytics.d.ts new file mode 100644 index 0000000000..ecb51c8b1f --- /dev/null +++ b/lib/services/analytics/analytics.d.ts @@ -0,0 +1,97 @@ +/** + * Describes if the user allows to be tracked. + */ +interface IAcceptUsageReportingInformation extends ITrackingInformation { + /** + * The answer of the question if user allows us to track them. + */ + acceptTrackFeatureUsage: boolean; +} + +/** + * Describes information used for tracking feature. + */ +interface IFeatureTrackingInformation extends ITrackingInformation { + /** + * The name of the feature that should be tracked. + */ + featureName: string; + + /** + * Value of the feature that should be tracked. + */ + featureValue: string; +} + +/** + * Describes information for exception that should be tracked. + */ +interface IExceptionsTrackingInformation extends ITrackingInformation { + /** + * The exception that should be tracked. + */ + exception: Error; + + /** + * The message of the error that should be tracked. + */ + message: string; +} + +/** + * Describes the broker used to pass information to all analytics providers. + */ +interface IAnalyticsBroker { + /** + * Sends the specified tracking information to all providers. + * @param {ITrackingInformation} trackInfo The information that should be passed to all providers. + * @returns {Promise} + */ + sendDataForTracking(trackInfo: ITrackingInformation): Promise; +} + +/** + * Describes analytics provider used for tracking in a specific Analytics Service. + */ +interface IAnalyticsProvider { + /** + * Sends exception for tracking in the analytics service provider. + * @param {IExceptionsTrackingInformation} trackInfo The information for exception that should be tracked. + * @returns {Promise} + */ + trackError(trackInfo: IExceptionsTrackingInformation): Promise; + + /** + * Sends feature for tracking in the analytics service provider. + * @param {IFeatureTrackingInformation} trackInfo The information for feature that should be tracked. + * @returns {Promise} + */ + trackInformation(trackInfo: IFeatureTrackingInformation): Promise; + + /** + * Sends information if user accepts to be tracked. + * @param {IAcceptUsageReportingInformation} trackInfo The information, containing user's answer if they allow to be tracked. + * @returns {Promise} + */ + acceptFeatureUsageTracking(data: IAcceptUsageReportingInformation): Promise; + + /** + * Waits for execution of all pending requests and finishes tracking operation + * @returns {Promise} + */ + finishTracking(): Promise; +} + +interface IGoogleAnalyticsTrackingInformation extends IGoogleAnalyticsData, ITrackingInformation { } + +/** + * Describes methods required to track in Google Analytics. + */ +interface IGoogleAnalyticsProvider { + /** + * Tracks hit types. + * @param {IGoogleAnalyticsData} data Data that has to be tracked. + * @returns {Promise} + */ + trackHit(data: IGoogleAnalyticsData): Promise; +} diff --git a/lib/services/analytics/eqatec-analytics-provider.ts b/lib/services/analytics/eqatec-analytics-provider.ts new file mode 100644 index 0000000000..e2b2f43163 --- /dev/null +++ b/lib/services/analytics/eqatec-analytics-provider.ts @@ -0,0 +1,60 @@ +import { AnalyticsServiceBase } from "../../common/services/analytics-service-base"; + +export class EqatecAnalyticsProvider extends AnalyticsServiceBase implements IAnalyticsProvider { + private static ANALYTICS_FEATURE_USAGE_TRACKING_API_KEY = "9912cff308334c6d9ad9c33f76a983e3"; + private static NEW_PROJECT_ANALYTICS_API_KEY = "b40f24fcb4f94bccaf64e4dc6337422e"; + + protected featureTrackingAPIKeys: string[] = [ + this.$staticConfig.ANALYTICS_API_KEY, + EqatecAnalyticsProvider.NEW_PROJECT_ANALYTICS_API_KEY + ]; + + protected acceptUsageReportingAPIKeys: string[] = [ + EqatecAnalyticsProvider.ANALYTICS_FEATURE_USAGE_TRACKING_API_KEY + ]; + + constructor(protected $logger: ILogger, + protected $options: IOptions, + $staticConfig: Config.IStaticConfig, + $prompter: IPrompter, + $userSettingsService: UserSettings.IUserSettingsService, + $analyticsSettingsService: IAnalyticsSettingsService, + $osInfo: IOsInfo) { + super($logger, $options, $staticConfig, $prompter, $userSettingsService, $analyticsSettingsService, $osInfo); + } + + public async trackInformation(data: IFeatureTrackingInformation): Promise { + try { + await this.trackFeatureCore(`${data.featureName}.${data.featureValue}`); + } catch (e) { + this.$logger.trace(`Analytics exception: ${e}`); + } + } + + public async trackError(data: IExceptionsTrackingInformation): Promise { + try { + await this.trackException(data.exception, data.message); + } catch (e) { + this.$logger.trace(`Analytics exception: ${e}`); + } + } + + public async acceptFeatureUsageTracking(data: IAcceptUsageReportingInformation): Promise { + try { + await this.trackAcceptFeatureUsage({ acceptTrackFeatureUsage: data.acceptTrackFeatureUsage }); + } catch (e) { + this.$logger.trace(`Analytics exception: ${e}`); + } + } + + public async finishTracking(): Promise { + this.tryStopEqatecMonitors(); + } + + public dispose(): void { + // Intentionally left blank. + } + +} + +$injector.register("eqatecAnalyticsProvider", EqatecAnalyticsProvider); diff --git a/lib/services/analytics/google-analytics-custom-dimensions.ts b/lib/services/analytics/google-analytics-custom-dimensions.ts new file mode 100644 index 0000000000..9e7c1d7007 --- /dev/null +++ b/lib/services/analytics/google-analytics-custom-dimensions.ts @@ -0,0 +1,8 @@ +const enum GoogleAnalyticsCustomDimensions { + cliVersion = "cd1", + projectType = "cd2", + clientID = "cd3", + sessionID = "cd4", + client = "cd5", + nodeVersion = "cd6" +} diff --git a/lib/services/analytics/google-analytics-provider.ts b/lib/services/analytics/google-analytics-provider.ts new file mode 100644 index 0000000000..1bb8d9fe13 --- /dev/null +++ b/lib/services/analytics/google-analytics-provider.ts @@ -0,0 +1,120 @@ +import * as uuid from "uuid"; +import * as ua from "universal-analytics"; +import { AnalyticsClients } from "../../common/constants"; + +export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider { + private static GA_TRACKING_ID = "UA-111455-44"; + private currentPage: string; + + constructor(private clientId: string, + private $staticConfig: IStaticConfig, + private $hostInfo: IHostInfo, + private $osInfo: IOsInfo) { + } + + public async trackHit(trackInfo: IGoogleAnalyticsData): Promise { + const visitor = ua({ + tid: GoogleAnalyticsProvider.GA_TRACKING_ID, + cid: this.clientId, + headers: { + ["User-Agent"]: this.getUserAgentString() + } + }); + + this.setCustomDimensions(visitor, trackInfo.customDimensions); + + switch (trackInfo.googleAnalyticsDataType) { + case GoogleAnalyticsDataType.Page: + await this.trackPageView(visitor, trackInfo); + break; + case GoogleAnalyticsDataType.Event: + await this.trackEvent(visitor, trackInfo); + break; + } + } + + private setCustomDimensions(visitor: ua.Visitor, customDimensions: IStringDictionary): void { + const defaultValues: IStringDictionary = { + [GoogleAnalyticsCustomDimensions.cliVersion]: this.$staticConfig.version, + [GoogleAnalyticsCustomDimensions.nodeVersion]: process.version, + [GoogleAnalyticsCustomDimensions.clientID]: this.clientId, + [GoogleAnalyticsCustomDimensions.projectType]: null, + [GoogleAnalyticsCustomDimensions.sessionID]: uuid.v4(), + [GoogleAnalyticsCustomDimensions.client]: AnalyticsClients.Unknown + }; + + customDimensions = _.merge(defaultValues, customDimensions); + + _.each(customDimensions, (value, key) => { + visitor.set(key, value); + }); + } + + private trackEvent(visitor: ua.Visitor, trackInfo: IGoogleAnalyticsEventData): Promise { + return new Promise((resolve, reject) => { + visitor.event(trackInfo.category, trackInfo.action, trackInfo.label, trackInfo.value, { p: this.currentPage }, (err: Error) => { + if (err) { + reject(err); + return; + } + + resolve(); + }); + }); + } + + private trackPageView(visitor: ua.Visitor, trackInfo: IGoogleAnalyticsPageviewData): Promise { + return new Promise((resolve, reject) => { + this.currentPage = trackInfo.path; + + const pageViewData: ua.PageviewParams = { + dp: trackInfo.path, + dt: trackInfo.title + }; + + visitor.pageview(pageViewData, (err) => { + if (err) { + reject(err); + return; + } + + resolve(); + }); + }); + } + + private getUserAgentString(): string { + let osString = ""; + const osRelease = this.$osInfo.release(); + + if (this.$hostInfo.isWindows) { + osString = `Windows NT ${osRelease}`; + } else if (this.$hostInfo.isDarwin) { + osString = `Macintosh`; + const macRelease = this.getMacOSReleaseVersion(osRelease); + if (macRelease) { + osString += `; Intel Mac OS X ${macRelease}`; + } + } else { + osString = `Linux x86`; + if (this.$osInfo.arch() === "x64") { + osString += "_64"; + } + } + + const userAgent = `tnsCli/${this.$staticConfig.version} (${osString}; ${this.$osInfo.arch()})`; + + return userAgent; + } + + private getMacOSReleaseVersion(osRelease: string): string { + // https://en.wikipedia.org/wiki/Darwin_(operating_system)#Release_history + // Each macOS version is labeled 10., where it looks like is taken from the major version returned by os.release() (16.x.x for example) and subtracting 4 from it. + // So the version becomes "10.12" in this case. + // Could be improved by spawning `system_profiler SPSoftwareDataType` and getting the System Version line from the result. + const majorVersion = osRelease && _.first(osRelease.split(".")); + return majorVersion && `10.${+majorVersion - 4}`; + } +} + +$injector.register("googleAnalyticsProvider", GoogleAnalyticsProvider); diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index a8c314019b..ab6e730835 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -3,6 +3,7 @@ import { parse } from "url"; import { EventEmitter } from "events"; import { CONNECTION_ERROR_EVENT_NAME, DebugCommandErrors } from "../constants"; import { CONNECTED_STATUS } from "../common/constants"; +import { DebugTools, TrackActionNames } from "../constants"; export class DebugService extends EventEmitter implements IDebugService { private _platformDebugServices: IDictionary; @@ -10,7 +11,8 @@ export class DebugService extends EventEmitter implements IDebugService { private $errors: IErrors, private $injector: IInjector, private $hostInfo: IHostInfo, - private $mobileHelper: Mobile.IMobileHelper) { + private $mobileHelper: Mobile.IMobileHelper, + private $analyticsService: IAnalyticsService) { super(); this._platformDebugServices = {}; } @@ -26,6 +28,13 @@ export class DebugService extends EventEmitter implements IDebugService { this.$errors.failWithoutHelp(`The device with identifier ${debugData.deviceIdentifier} is unreachable. Make sure it is Trusted and try again.`); } + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.Debug, + device, + additionalData: this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform) && (!options || !options.chrome) ? DebugTools.Inspector : DebugTools.Chrome, + projectDir: debugData.projectDir + }); + if (!(await device.applicationManager.isApplicationInstalled(debugData.applicationIdentifier))) { this.$errors.failWithoutHelp(`The application ${debugData.applicationIdentifier} is not installed on device with identifier ${debugData.deviceIdentifier}.`); } diff --git a/lib/services/livesync/livesync-command-helper.ts b/lib/services/livesync/livesync-command-helper.ts index fae89af3c9..54be3c7a19 100644 --- a/lib/services/livesync/livesync-command-helper.ts +++ b/lib/services/livesync/livesync-command-helper.ts @@ -7,7 +7,9 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { private $iosDeviceOperations: IIOSDeviceOperations, private $mobileHelper: Mobile.IMobileHelper, private $platformsData: IPlatformsData, + private $analyticsService: IAnalyticsService, private $errors: IErrors) { + this.$analyticsService.setShouldDispose(this.$options.justlaunch || !this.$options.watch); } public getPlatformsForOperation(platform: string): string[] { diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index e4bb9936c6..4d18546ea8 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -3,7 +3,7 @@ import * as choki from "chokidar"; import { EOL } from "os"; import { EventEmitter } from "events"; import { hook } from "../../common/helpers"; -import { APP_FOLDER_NAME, PACKAGE_JSON_FILE_NAME, LiveSyncTrackActionNames, USER_INTERACTION_NEEDED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME, DEBUGGER_DETACHED_EVENT_NAME } from "../../constants"; +import { APP_FOLDER_NAME, PACKAGE_JSON_FILE_NAME, LiveSyncTrackActionNames, USER_INTERACTION_NEEDED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME, DEBUGGER_DETACHED_EVENT_NAME, TrackActionNames } from "../../constants"; import { FileExtensions, DeviceTypes } from "../../common/constants"; const deviceDescriptorPrimaryKey = "identifier"; @@ -33,6 +33,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi private $debugService: IDebugService, private $errors: IErrors, private $debugDataService: IDebugDataService, + private $analyticsService: IAnalyticsService, private $injector: IInjector) { super(); } @@ -366,6 +367,12 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi action = LiveSyncTrackActionNames.LIVESYNC_OPERATION_BUILD; } + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.LiveSync, + device: options.device, + projectDir: options.projectData.projectDir + }); + await this.trackAction(action, platform, options); const shouldInstall = await this.$platformService.shouldInstall(options.device, options.projectData, options.deviceBuildInfoDescriptor.outputPath); diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 206fbcc2ec..268c2c2095 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -1,6 +1,7 @@ import * as path from "path"; import * as shell from "shelljs"; import * as constants from "../constants"; +import { Configurations } from "../common/constants"; import * as helpers from "../common/helpers"; import * as semver from "semver"; import { EventEmitter } from "events"; @@ -466,9 +467,18 @@ export class PlatformService extends EventEmitter implements IPlatformService { public async buildPlatform(platform: string, buildConfig: IBuildConfig, projectData: IProjectData): Promise { this.$logger.out("Building project..."); + const action = constants.TrackActionNames.Build; await this.trackProjectType(projectData); const isForDevice = this.$mobileHelper.isAndroidPlatform(platform) ? null : buildConfig && buildConfig.buildForDevice; - await this.trackActionForPlatform({ action: "Build", platform, isForDevice }); + await this.trackActionForPlatform({ action, platform, isForDevice }); + + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action, + isForDevice, + platform, + projectDir: projectData.projectDir, + additionalData: `${buildConfig.release ? Configurations.Release : Configurations.Debug}_${buildConfig.clean ? constants.BuildStates.Clean : constants.BuildStates.Incremental}` + }); const platformData = this.$platformsData.getPlatformData(platform, projectData); const handler = (data: any) => { @@ -510,6 +520,13 @@ export class PlatformService extends EventEmitter implements IPlatformService { public async installApplication(device: Mobile.IDevice, buildConfig: IBuildConfig, projectData: IProjectData, packageFile?: string, outputFilePath?: string): Promise { this.$logger.out("Installing..."); + + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: constants.TrackActionNames.Deploy, + device, + projectDir: projectData.projectDir + }); + const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); if (!packageFile) { if (this.$devicesService.isiOSSimulator(device)) { @@ -567,7 +584,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$logger.out("Skipping install."); } - await this.trackActionForPlatform({ action: "Deploy", platform: device.deviceInfo.platform, isForDevice: !device.isEmulator, deviceOsVersion: device.deviceInfo.version }); + await this.trackActionForPlatform({ action: constants.TrackActionNames.Deploy, platform: device.deviceInfo.platform, isForDevice: !device.isEmulator, deviceOsVersion: device.deviceInfo.version }); }; await this.$devicesService.execute(action, this.getCanExecuteAction(platform, deployOptions)); diff --git a/lib/services/project-templates-service.ts b/lib/services/project-templates-service.ts index d3cb0a0489..32ac27ec57 100644 --- a/lib/services/project-templates-service.ts +++ b/lib/services/project-templates-service.ts @@ -20,6 +20,12 @@ export class ProjectTemplatesService implements IProjectTemplatesService { await this.$analyticsService.track("Template used for project creation", templateName); + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: constants.TrackActionNames.CreateProject, + isForDevice: null, + additionalData: templateName + }); + const realTemplatePath = await this.prepareNativeScriptTemplate(templateName, version, projectDir); // this removes dependencies from templates so they are not copied to app folder diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index b3d4221d26..318032c65b 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -26,7 +26,9 @@ class TestExecutionService implements ITestExecutionService { private $errors: IErrors, private $debugService: IDebugService, private $devicesService: Mobile.IDevicesService, + private $analyticsService: IAnalyticsService, private $childProcess: IChildProcess) { + this.$analyticsService.setShouldDispose(this.$options.justlaunch || !this.$options.watch); } public platform: string; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 78883fb1e0..eb666497e4 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "nativescript", - "version": "3.2.0", + "version": "3.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -74,6 +74,12 @@ "integrity": "sha1-3TS72OMv5OdPLj2KwH+KpbRaR6w=", "dev": true }, + "@types/universal-analytics": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/universal-analytics/-/universal-analytics-0.4.1.tgz", + "integrity": "sha512-AZSPpDUEZ4mAgO9geHc62dp/xCLmBJ1yIpbgTq5W/cWcVQsxmU/FyKwYKHXk2hnT9TAmYVFFdAijMrCdYjuHsA==", + "dev": true + }, "abbrev": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", @@ -1838,8 +1844,7 @@ }, "jsbn": { "version": "0.1.1", - "bundled": true, - "optional": true + "bundled": true }, "json-schema": { "version": "0.2.3", @@ -2207,8 +2212,7 @@ }, "tweetnacl": { "version": "0.14.5", - "bundled": true, - "optional": true + "bundled": true }, "uid-number": { "version": "0.0.6", @@ -5164,6 +5168,24 @@ "integrity": "sha1-gGmSYzZl1eX8tNsfs6hi62jp5to=", "dev": true }, + "universal-analytics": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.15.tgz", + "integrity": "sha512-9Dt6WBWsHsmv74G+N/rmEgi6KFZxVvQXkVhr0disegeUryybQAUQwMD1l5EtqaOu+hSOGbhL/hPPQYisZIqPRw==", + "requires": { + "async": "1.2.1", + "request": "2.81.0", + "underscore": "1.5.2", + "uuid": "3.0.1" + }, + "dependencies": { + "async": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/async/-/async-1.2.1.tgz", + "integrity": "sha1-pIFqF81f9RbfosdpikUzabl5DeA=" + } + } + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/package.json b/package.json index 114d1bc60d..1d2593406f 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ "mocha": "node test-scripts/mocha.js", "tsc": "tsc", "tslint": "tslint -p tsconfig.json --type-check", - "test-watch": "node ./dev/tsc-to-mocha-watch.js", - "tslint-fix": "tslint -p tsconfig.json --type-check --fix" + "test-watch": "node ./dev/tsc-to-mocha-watch.js", + "tslint-fix": "tslint -p tsconfig.json --type-check --fix" }, "repository": { "type": "git", @@ -72,6 +72,7 @@ "source-map": "0.5.6", "tabtab": "https://github.com/Icenium/node-tabtab/tarball/master", "temp": "0.8.3", + "universal-analytics": "0.4.15", "uuid": "3.0.1", "winreg": "0.0.17", "ws": "2.2.0", @@ -91,6 +92,7 @@ "@types/request": "0.0.45", "@types/semver": "^5.3.31", "@types/source-map": "0.5.0", + "@types/universal-analytics": "0.4.1", "chai": "4.0.2", "chai-as-promised": "7.0.0", "grunt": "1.0.1", diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index 2168f38da0..10854e48f0 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -21,7 +21,6 @@ describe("nativescript-cli-lib", () => { npm: ["install", "uninstall", "view", "search"], extensibilityService: ["loadExtensions", "loadExtension", "getInstalledExtensions", "installExtension", "uninstallExtension"], liveSyncService: ["liveSync", "stopLiveSync", "enableDebugging", "disableDebugging", "attachDebugger"], - analyticsService: ["startEqatecMonitor"], debugService: ["debug"] }; diff --git a/test/plugins-service.ts b/test/plugins-service.ts index 57533401b3..3fdd2c24f2 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -76,7 +76,10 @@ function createTestInjector() { testInjector.register("analyticsService", { trackException: () => { return Promise.resolve(); }, checkConsent: () => { return Promise.resolve(); }, - trackFeature: () => { return Promise.resolve(); } + trackFeature: () => { return Promise.resolve(); }, + trackEventActionInGoogleAnalytics: (data: IEventActionData) => Promise.resolve(), + trackInGoogleAnalytics: (data: IGoogleAnalyticsData) => Promise.resolve(), + trackAcceptFeatureUsage: (settings: { acceptTrackFeatureUsage: boolean }) => Promise.resolve() }); testInjector.register("projectFilesManager", ProjectFilesManager); testInjector.register("pluginVariablesService", { diff --git a/test/project-service.ts b/test/project-service.ts index 4cb3367dce..d0d3090367 100644 --- a/test/project-service.ts +++ b/test/project-service.ts @@ -137,7 +137,10 @@ class ProjectIntegrationTest { this.testInjector.register("fs", FileSystem); this.testInjector.register("projectDataService", ProjectDataServiceLib.ProjectDataService); this.testInjector.register("staticConfig", StaticConfig); - this.testInjector.register("analyticsService", { track: async (): Promise => undefined }); + this.testInjector.register("analyticsService", { + track: async (): Promise => undefined, + trackEventActionInGoogleAnalytics: (data: IEventActionData) => Promise.resolve() + }); this.testInjector.register("npmInstallationManager", NpmInstallationManager); this.testInjector.register("npm", NpmLib.NodePackageManager); diff --git a/test/project-templates-service.ts b/test/project-templates-service.ts index 46e2d1c918..e227a232e3 100644 --- a/test/project-templates-service.ts +++ b/test/project-templates-service.ts @@ -50,7 +50,10 @@ function createTestInjector(configuration?: { shouldNpmInstallThrow: boolean, np injector.register("projectTemplatesService", ProjectTemplatesService); - injector.register("analyticsService", { track: async (): Promise => undefined }); + injector.register("analyticsService", { + track: async (): Promise => undefined, + trackEventActionInGoogleAnalytics: (data: IEventActionData) => Promise.resolve() + }); return injector; } diff --git a/test/services/debug-service.ts b/test/services/debug-service.ts index 8d4887c61f..e3ab301b44 100644 --- a/test/services/debug-service.ts +++ b/test/services/debug-service.ts @@ -93,6 +93,10 @@ describe("debugService", () => { testInjector.register("logger", stubs.LoggerStub); + testInjector.register("analyticsService", { + trackEventActionInGoogleAnalytics: (data: IEventActionData) => Promise.resolve() + }); + return testInjector; }; diff --git a/test/services/livesync-service.ts b/test/services/livesync-service.ts index 0dde25150b..3279bea316 100644 --- a/test/services/livesync-service.ts +++ b/test/services/livesync-service.ts @@ -25,6 +25,7 @@ const createTestInjector = (): IInjector => { }); testInjector.register("pluginsService", {}); + testInjector.register("analyticsService", {}); testInjector.register("injector", testInjector); return testInjector; @@ -44,6 +45,7 @@ class LiveSyncServiceInheritor extends LiveSyncService { $debugService: IDebugService, $errors: IErrors, $debugDataService: IDebugDataService, + $analyticsService: IAnalyticsService, $injector: IInjector) { super( @@ -60,6 +62,7 @@ class LiveSyncServiceInheritor extends LiveSyncService { $debugService, $errors, $debugDataService, + $analyticsService, $injector ); } From 3774db59620bedc7b2c006136806b5ed10e3e6fa Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Thu, 7 Sep 2017 11:10:13 +0300 Subject: [PATCH 210/212] Update ios-device-lib and plist-merge-patch (#3107) Update two of the dependencies to their latest versions. ios-device-lib has a fix that causes hanging in a very rare cases when trying to get installed applications on iOS device: https://github.com/telerik/ios-device-lib/releases/tag/v0.4.9 plist-merge-patch has a fix for merging `LSApplicationQueriesSchemes` values: https://github.com/NativeScript/plist-merge-patch/releases/tag/v0.1.1 --- npm-shrinkwrap.json | 27 ++++++++++++++++++--------- package.json | 4 ++-- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index eb666497e4..185087f936 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1844,7 +1844,8 @@ }, "jsbn": { "version": "0.1.1", - "bundled": true + "bundled": true, + "optional": true }, "json-schema": { "version": "0.2.3", @@ -2212,7 +2213,8 @@ }, "tweetnacl": { "version": "0.14.5", - "bundled": true + "bundled": true, + "optional": true }, "uid-number": { "version": "0.0.6", @@ -2867,12 +2869,19 @@ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" }, "ios-device-lib": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/ios-device-lib/-/ios-device-lib-0.4.8.tgz", - "integrity": "sha1-VJJWQk+3HzrCnJHuxTqVYrybxhY=", + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/ios-device-lib/-/ios-device-lib-0.4.9.tgz", + "integrity": "sha1-aSmddfsrTeNakIb/CbQMQFWv1fY=", "requires": { "bufferpack": "0.0.6", - "node-uuid": "1.3.3" + "node-uuid": "1.4.7" + }, + "dependencies": { + "node-uuid": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz", + "integrity": "sha1-baWhdmjEs91ZYjvaEc9/pMH2Cm8=" + } } }, "ios-mobileprovision-finder": { @@ -4075,9 +4084,9 @@ } }, "plist-merge-patch": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/plist-merge-patch/-/plist-merge-patch-0.1.0.tgz", - "integrity": "sha1-eOTAlL6dtLpHWdpktvGkz2rTIJE=", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/plist-merge-patch/-/plist-merge-patch-0.1.1.tgz", + "integrity": "sha1-1xceGwXAh+W6BpN1QK385QQot/w=", "requires": { "lodash": "4.17.4", "plist": "2.1.0" diff --git a/package.json b/package.json index 1d2593406f..35cc0262d7 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "glob": "^7.0.3", "iconv-lite": "0.4.11", "inquirer": "0.9.0", - "ios-device-lib": "0.4.8", + "ios-device-lib": "0.4.9", "ios-mobileprovision-finder": "1.0.10", "ios-sim-portable": "3.1.1", "lockfile": "1.0.1", @@ -60,7 +60,7 @@ "osenv": "0.1.3", "pbxproj-dom": "1.0.11", "plist": "1.1.0", - "plist-merge-patch": "0.1.0", + "plist-merge-patch": "0.1.1", "plistlib": "0.2.1", "progress-stream": "1.1.1", "properties-parser": "0.2.3", From 4a7371a63ebb98132dc35a1973acefabd7a93e09 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Thu, 7 Sep 2017 20:21:49 +0300 Subject: [PATCH 211/212] Minor LiveSync+Debug fixes (#3109) --- lib/services/livesync/livesync-service.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 4d18546ea8..941f396f07 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -166,6 +166,9 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi } catch (err) { this.$logger.trace("Could not stop application during debug livesync. Will try to restart app instead.", err); if ((err.message || err) === "Could not find developer disk image") { + // Set isFullSync here to true because we are refreshing with debugger + // We want to force a restart instead of accidentally performing LiveEdit or FastSync + liveSyncResultInfo.isFullSync = true; await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, outputPath, { shouldSkipEmitLiveSyncNotification: true }); this.emit(USER_INTERACTION_NEEDED_EVENT_NAME, attachDebuggerOptions); return; @@ -254,7 +257,12 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi } catch (err) { this.$logger.trace("Couldn't attach debugger, will modify options and try again.", err); attachDebuggerOptions.debugOptions.start = false; - debugInformation = await this.attachDebugger(attachDebuggerOptions); + try { + debugInformation = await this.attachDebugger(attachDebuggerOptions); + } catch (innerErr) { + this.$logger.trace("Couldn't attach debugger with modified options.", innerErr); + throw err; + } } return debugInformation; From 7889c55f3843798cb8dc23e21c767663a753a211 Mon Sep 17 00:00:00 2001 From: Rosen Vladimirov Date: Fri, 8 Sep 2017 10:35:49 +0300 Subject: [PATCH 212/212] Analytics fixes (#3110) Move lockfile service to common and lock operation for writing user settings file Add new lockfile service (use lockfile npm package) and lock all operations for reading/writing user-settings.json. These operations may be executed in different processes, so they must be synced between them. Clean obsolete code for lockfile in services that do not longer use it. Ensure failures in eqatec analytics will not prevent tracking in google analytics by try-catching execution in broker service. --- lib/bootstrap.ts | 1 - lib/common | 2 +- lib/declarations.d.ts | 6 --- lib/definitions/lockfile.d.ts | 24 ---------- lib/lockfile.ts | 31 ------------ .../analytics/analytics-broker-process.ts | 2 + lib/services/analytics/analytics-broker.ts | 47 ++++++++++--------- lib/services/analytics/analytics-service.ts | 13 ++++- lib/services/user-settings-service.ts | 9 +++- npm-shrinkwrap.json | 12 +++-- package.json | 3 +- test/npm-installation-manager.ts | 1 - test/npm-support.ts | 2 - test/platform-commands.ts | 1 - test/platform-service.ts | 1 - test/plugins-service.ts | 1 - test/project-service.ts | 1 - 17 files changed, 57 insertions(+), 100 deletions(-) delete mode 100644 lib/definitions/lockfile.d.ts delete mode 100644 lib/lockfile.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index f4ad743cdd..6d2c0cf6b1 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -71,7 +71,6 @@ $injector.require("itmsTransporterService", "./services/itmstransporter-service" $injector.requirePublic("npm", "./node-package-manager"); $injector.require("npmInstallationManager", "./npm-installation-manager"); -$injector.require("lockfile", "./lockfile"); $injector.require("dynamicHelpProvider", "./dynamic-help-provider"); $injector.require("mobilePlatformsCapabilities", "./mobile-platforms-capabilities"); $injector.require("commandsServiceProvider", "./providers/commands-service-provider"); diff --git a/lib/common b/lib/common index 804e28c137..6522b37bc2 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 804e28c137442922609caced942dee3d6512523b +Subproject commit 6522b37bc2beadfccb98f9f1ca9002c7046650b4 diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index d6d40af9f1..c6f18218d7 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -318,12 +318,6 @@ interface IApplicationPackage { time: Date; } -interface ILockFile { - lock(): void; - unlock(): void; - check(): boolean; -} - interface IOpener { open(target: string, appname: string): void; } diff --git a/lib/definitions/lockfile.d.ts b/lib/definitions/lockfile.d.ts deleted file mode 100644 index af62919fee..0000000000 --- a/lib/definitions/lockfile.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -declare module "lockfile" { - export function lock(lockFilename: string, lockParams: ILockParams, callback: (err: Error) => void): void; - export function lockSync(lockFilename: string, lockParams: ILockSyncParams): void; - export function unlock(lockFilename: string, callback: (err: Error) => void): void; - export function unlockSync(lockFilename: string): void; - export function check(lockFilename: string, lockParams: ILockParams, callback: (err: Error, isLocked: boolean) => void): boolean; - export function checkSync(path: string, opts: Options): boolean; - - export interface Options { - wait?: number; - stale?: number; - retries?: number; - retryWait?: number; - } - - interface ILockSyncParams { - retries: number; - stale: number; - } - - interface ILockParams extends ILockSyncParams { - retryWait: number; - } -} diff --git a/lib/lockfile.ts b/lib/lockfile.ts deleted file mode 100644 index d9e0082274..0000000000 --- a/lib/lockfile.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as lockfile from "lockfile"; -import * as path from "path"; - -export class LockFile implements ILockFile { - private lockFilePath: string; - - constructor(private $options: IOptions) { - this.lockFilePath = path.join(this.$options.profileDir, ".lock"); - } - - private static LOCK_EXPIRY_PERIOD_SEC = 180; - private static LOCK_PARAMS = { - retryWait: 100, - retries: LockFile.LOCK_EXPIRY_PERIOD_SEC * 10, - stale: LockFile.LOCK_EXPIRY_PERIOD_SEC * 1000 - }; - - public lock(): void { - lockfile.lockSync(this.lockFilePath, LockFile.LOCK_PARAMS); - } - - public unlock(): void { - lockfile.unlockSync(this.lockFilePath); - } - - public check(): boolean { - return lockfile.checkSync(this.lockFilePath, LockFile.LOCK_PARAMS); - } -} - -$injector.register("lockfile", LockFile); diff --git a/lib/services/analytics/analytics-broker-process.ts b/lib/services/analytics/analytics-broker-process.ts index 78769ae8ea..02b806aa75 100644 --- a/lib/services/analytics/analytics-broker-process.ts +++ b/lib/services/analytics/analytics-broker-process.ts @@ -1,3 +1,5 @@ +// NOTE: This file is used to track data in a separate process. +// The instances here are not shared with the ones in main CLI process. import * as fs from "fs"; import { AnalyticsBroker } from "./analytics-broker"; diff --git a/lib/services/analytics/analytics-broker.ts b/lib/services/analytics/analytics-broker.ts index 471840cd7b..25d4bf1c3f 100644 --- a/lib/services/analytics/analytics-broker.ts +++ b/lib/services/analytics/analytics-broker.ts @@ -17,29 +17,32 @@ export class AnalyticsBroker implements IAnalyticsBroker { private $injector: IInjector) { } public async sendDataForTracking(trackInfo: ITrackingInformation): Promise { - const eqatecProvider = await this.getEqatecAnalyticsProvider(); - const googleProvider = await this.getGoogleAnalyticsProvider(); - - switch (trackInfo.type) { - case TrackingTypes.Exception: - await eqatecProvider.trackError(trackInfo); - break; - case TrackingTypes.Feature: - await eqatecProvider.trackInformation(trackInfo); - break; - case TrackingTypes.AcceptTrackFeatureUsage: - await eqatecProvider.acceptFeatureUsageTracking(trackInfo); - break; - case TrackingTypes.GoogleAnalyticsData: - await googleProvider.trackHit(trackInfo); - break; - case TrackingTypes.Finish: - await eqatecProvider.finishTracking(); - break; - default: - throw new Error(`Invalid tracking type: ${trackInfo.type}`); + try { + const eqatecProvider = await this.getEqatecAnalyticsProvider(); + const googleProvider = await this.getGoogleAnalyticsProvider(); + + switch (trackInfo.type) { + case TrackingTypes.Exception: + await eqatecProvider.trackError(trackInfo); + break; + case TrackingTypes.Feature: + await eqatecProvider.trackInformation(trackInfo); + break; + case TrackingTypes.AcceptTrackFeatureUsage: + await eqatecProvider.acceptFeatureUsageTracking(trackInfo); + break; + case TrackingTypes.GoogleAnalyticsData: + await googleProvider.trackHit(trackInfo); + break; + case TrackingTypes.Finish: + await eqatecProvider.finishTracking(); + break; + default: + throw new Error(`Invalid tracking type: ${trackInfo.type}`); + } + } catch (err) { + // So, lets ignore the error for now until we find out what to do with it. } - } } diff --git a/lib/services/analytics/analytics-service.ts b/lib/services/analytics/analytics-service.ts index a15f7f7583..a999e1a96d 100644 --- a/lib/services/analytics/analytics-service.ts +++ b/lib/services/analytics/analytics-service.ts @@ -65,10 +65,11 @@ export class AnalyticsService extends AnalyticsServiceBase { public async trackEventActionInGoogleAnalytics(data: IEventActionData): Promise { const device = data.device; const platform = device ? device.deviceInfo.platform : data.platform; + const normalizedPlatform = platform ? this.$mobileHelper.normalizePlatformName(platform) : platform; const isForDevice = device ? !device.isEmulator : data.isForDevice; let label: string = ""; - label = this.addDataToLabel(label, platform); + label = this.addDataToLabel(label, normalizedPlatform); // In some cases (like in case action is Build and platform is Android), we do not know if the deviceType is emulator or device. // Just exclude the device_type in this case. @@ -182,7 +183,15 @@ export class AnalyticsService extends AnalyticsServiceBase { private async sendMessageToBroker(message: ITrackingInformation): Promise { const broker = await this.getAnalyticsBroker(); - return new Promise((resolve, reject) => broker.send(message, resolve)); + return new Promise((resolve, reject) => { + if (broker && broker.connected) { + try { + broker.send(message, resolve); + } catch (err) { + this.$logger.trace("Error while trying to send message to broker:", err); + } + } + }); } } diff --git a/lib/services/user-settings-service.ts b/lib/services/user-settings-service.ts index 07201c799f..a89c8495e1 100644 --- a/lib/services/user-settings-service.ts +++ b/lib/services/user-settings-service.ts @@ -3,9 +3,14 @@ import * as userSettingsServiceBaseLib from "../common/services/user-settings-se class UserSettingsService extends userSettingsServiceBaseLib.UserSettingsServiceBase { constructor($fs: IFileSystem, - $options: IOptions) { + $options: IOptions, + $lockfile: ILockFile) { const userSettingsFilePath = path.join($options.profileDir, "user-settings.json"); - super(userSettingsFilePath, $fs); + super(userSettingsFilePath, $fs, $lockfile); + } + + public async loadUserSettingsFile(): Promise { + await this.loadUserSettingsData(); } } $injector.register("userSettingsService", UserSettingsService); diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 185087f936..86732bf537 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -37,6 +37,12 @@ "@types/node": "6.0.61" } }, + "@types/lockfile": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/lockfile/-/lockfile-1.0.0.tgz", + "integrity": "sha512-pD6JuijPmrfi84qF3/TzGQ7zi0QIX+d7ZdetD6jUA6cp+IsCzAquXZfi5viesew+pfpOTIdAVKuh1SHA7KeKzg==", + "dev": true + }, "@types/node": { "version": "6.0.61", "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.61.tgz", @@ -3405,9 +3411,9 @@ } }, "lockfile": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.1.tgz", - "integrity": "sha1-nTU+z+P1TRULtX+J1RdGk1o5xPU=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.3.tgz", + "integrity": "sha1-Jjj8OaAzHpysGgS3F5mTHJxQ33k=" }, "lodash": { "version": "4.13.1", diff --git a/package.json b/package.json index 35cc0262d7..ec9320ba40 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "ios-device-lib": "0.4.9", "ios-mobileprovision-finder": "1.0.10", "ios-sim-portable": "3.1.1", - "lockfile": "1.0.1", + "lockfile": "1.0.3", "lodash": "4.13.1", "log4js": "1.0.1", "marked": "0.3.6", @@ -87,6 +87,7 @@ "@types/chai": "4.0.1", "@types/chai-as-promised": "0.0.31", "@types/chokidar": "1.6.0", + "@types/lockfile": "1.0.0", "@types/node": "6.0.61", "@types/qr-image": "3.2.0", "@types/request": "0.0.45", diff --git a/test/npm-installation-manager.ts b/test/npm-installation-manager.ts index f36ff54e90..fa06505ca0 100644 --- a/test/npm-installation-manager.ts +++ b/test/npm-installation-manager.ts @@ -15,7 +15,6 @@ function createTestInjector(): IInjector { testInjector.register("config", ConfigLib.Configuration); testInjector.register("logger", LoggerLib.Logger); - testInjector.register("lockfile", {}); testInjector.register("errors", ErrorsLib.Errors); testInjector.register("options", OptionsLib.Options); testInjector.register("fs", FsLib.FileSystem); diff --git a/test/npm-support.ts b/test/npm-support.ts index 1b9bb23c3c..b4757e924c 100644 --- a/test/npm-support.ts +++ b/test/npm-support.ts @@ -23,7 +23,6 @@ import { ProjectFilesProvider } from "../lib/providers/project-files-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 ProjectChangesLib = require("../lib/services/project-changes-service"); import { Messages } from "../lib/common/messages/messages"; import { NodeModulesDependenciesBuilder } from "../lib/tools/node-modules/node-modules-dependencies-builder"; @@ -48,7 +47,6 @@ function createTestInjector(): IInjector { testInjector.register("platformService", PlatformServiceLib.PlatformService); testInjector.register("logger", stubs.LoggerStub); testInjector.register("npmInstallationManager", {}); - testInjector.register("lockfile", LockFile); testInjector.register("prompter", {}); testInjector.register("sysInfo", {}); testInjector.register("androidProjectService", {}); diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 51dc5acb70..07d11e6430 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -112,7 +112,6 @@ function createTestInjector() { testInjector.registerCommand("platform|remove", PlatformRemoveCommandLib.RemovePlatformCommand); testInjector.registerCommand("platform|update", PlatformUpdateCommandLib.UpdatePlatformCommand); testInjector.registerCommand("platform|clean", PlatformCleanCommandLib.CleanCommand); - testInjector.register("lockfile", {}); testInjector.register("resources", {}); testInjector.register("commandsServiceProvider", { registerDynamicSubCommands: () => { /* intentionally left blank */ } diff --git a/test/platform-service.ts b/test/platform-service.ts index 8a4faa965a..5c12c376fa 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -39,7 +39,6 @@ function createTestInjector() { testInjector.register('projectDataService', stubs.ProjectDataService); testInjector.register('prompter', {}); testInjector.register('sysInfo', {}); - testInjector.register('lockfile', stubs.LockFile); testInjector.register("commandsService", { tryExecuteCommand: () => { /* intentionally left blank */ } }); diff --git a/test/plugins-service.ts b/test/plugins-service.ts index 3fdd2c24f2..6360e2ee0d 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -69,7 +69,6 @@ function createTestInjector() { registerDynamicSubCommands: () => { /* intentionally empty body */ } }); testInjector.register("hostInfo", HostInfo); - testInjector.register("lockfile", {}); testInjector.register("projectHelper", ProjectHelper); testInjector.register("pluginsService", PluginsService); diff --git a/test/project-service.ts b/test/project-service.ts index d0d3090367..2ed5961274 100644 --- a/test/project-service.ts +++ b/test/project-service.ts @@ -145,7 +145,6 @@ class ProjectIntegrationTest { this.testInjector.register("npmInstallationManager", NpmInstallationManager); this.testInjector.register("npm", NpmLib.NodePackageManager); this.testInjector.register("httpClient", HttpClientLib.HttpClient); - this.testInjector.register("lockfile", stubs.LockFile); this.testInjector.register("options", Options); this.testInjector.register("hostInfo", HostInfo);