From 9b83cda6fd84f4f06475f034c40286c5e394f7ed Mon Sep 17 00:00:00 2001 From: Nadya Atanasova Date: Wed, 10 May 2017 17:55:20 +0300 Subject: [PATCH 01/43] 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 02/43] 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 03/43] 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 04/43] 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 05/43] 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 06/43] 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 07/43] 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 08/43] 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 09/43] 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 10/43] 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 11/43] 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 12/43] 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 13/43] 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 14/43] 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 15/43] 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 16/43] 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 17/43] 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 18/43] 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 19/43] 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 20/43] 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 21/43] 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 22/43] 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 23/43] 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 24/43] 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 25/43] 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 26/43] 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 27/43] 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 28/43] 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 29/43] 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 30/43] 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 31/43] 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 32/43] 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 33/43] 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 34/43] 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 35/43] 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 36/43] 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 37/43] 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 38/43] 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 39/43] 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 40/43] 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 41/43] 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 42/43] 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 43/43] 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",