From f2d1655b9d5f76a6573cf707ac45ff8136b01488 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 30 May 2019 16:52:23 +0300 Subject: [PATCH 1/2] feat: expose public API https://github.com/NativeScript/nativescript-cli/issues/4644 --- lib/bootstrap.ts | 14 +- lib/commands/debug.ts | 9 +- lib/commands/preview.ts | 5 +- lib/commands/run.ts | 6 +- lib/commands/test.ts | 10 +- lib/common/definitions/mobile.d.ts | 2 +- .../mobile/mobile-core/devices-service.ts | 2 +- lib/controllers/debug-controller.ts | 270 ++++++++++++------ lib/controllers/deploy-controller.ts | 4 +- lib/controllers/preview-app-controller.ts | 57 +++- lib/controllers/run-controller.ts | 176 ++++++++---- lib/data/run-data.ts | 2 +- lib/definitions/debug.d.ts | 68 ++--- lib/definitions/livesync.d.ts | 69 +++-- lib/definitions/preview-app-livesync.d.ts | 10 +- lib/definitions/project.d.ts | 2 +- lib/definitions/run.d.ts | 47 ++- lib/emitters/preview-app-emitter.ts | 14 - lib/emitters/run-emitter.ts | 79 ----- lib/helpers/deploy-command-helper.ts | 5 +- lib/helpers/livesync-command-helper.ts | 72 ++--- lib/services/debug-data-service.ts | 10 +- lib/services/debug-service.ts | 101 ------- lib/services/livesync-process-data-service.ts | 34 +++ .../android-device-livesync-service-base.ts | 2 +- .../livesync/device-livesync-service-base.ts | 2 +- lib/services/livesync/ios-livesync-service.ts | 2 +- .../platform-livesync-service-base.ts | 8 +- .../preview-app-livesync-service.ts | 55 ---- .../platform-environment-requirements.ts | 17 +- lib/services/test-execution-service.ts | 3 +- .../webpack/webpack-compiler-service.ts | 12 +- lib/services/webpack/webpack.d.ts | 4 - test/services/debug-service.ts | 267 ----------------- 34 files changed, 539 insertions(+), 901 deletions(-) delete mode 100644 lib/emitters/preview-app-emitter.ts delete mode 100644 lib/emitters/run-emitter.ts delete mode 100644 lib/services/debug-service.ts create mode 100644 lib/services/livesync-process-data-service.ts delete mode 100644 lib/services/livesync/playground/preview-app-livesync-service.ts delete mode 100644 test/services/debug-service.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index ddd9ce8278..167bb6f21d 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -43,24 +43,21 @@ $injector.require("buildArtefactsService", "./services/build-artefacts-service") $injector.require("deviceInstallAppService", "./services/device/device-install-app-service"); -$injector.require("runEmitter", "./emitters/run-emitter"); -$injector.require("previewAppEmitter", "./emitters/preview-app-emitter"); - $injector.require("platformController", "./controllers/platform-controller"); $injector.require("prepareController", "./controllers/prepare-controller"); -$injector.require("buildController", "./controllers/build-controller"); $injector.require("deployController", "./controllers/deploy-controller"); -$injector.require("runController", "./controllers/run-controller"); -$injector.require("debugController", "./controllers/debug-controller"); -$injector.require("previewAppController", "./controllers/preview-app-controller"); +$injector.requirePublicClass("buildController", "./controllers/build-controller"); +$injector.requirePublicClass("runController", "./controllers/run-controller"); +$injector.requirePublicClass("debugController", "./controllers/debug-controller"); +$injector.requirePublicClass("previewAppController", "./controllers/preview-app-controller"); $injector.require("prepareDataService", "./services/prepare-data-service"); $injector.require("buildDataService", "./services/build-data-service"); $injector.require("liveSyncServiceResolver", "./resolvers/livesync-service-resolver"); +$injector.require("liveSyncProcessDataService", "./services/livesync-process-data-service"); $injector.require("debugDataService", "./services/debug-data-service"); -$injector.requirePublicClass("debugService", "./services/debug-service"); $injector.require("iOSDeviceDebugService", "./services/ios-device-debug-service"); $injector.require("androidDeviceDebugService", "./services/android-device-debug-service"); @@ -162,7 +159,6 @@ $injector.require("androidLiveSyncService", "./services/livesync/android-livesyn $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("previewAppFilesService", "./services/livesync/playground/preview-app-files-service"); -$injector.require("previewAppLiveSyncService", "./services/livesync/playground/preview-app-livesync-service"); $injector.require("previewAppLogProvider", "./services/livesync/playground/preview-app-log-provider"); $injector.require("previewAppPluginsService", "./services/livesync/playground/preview-app-plugins-service"); $injector.require("previewSdkService", "./services/livesync/playground/preview-sdk-service"); diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index bb80ed752a..30a2cfe5ab 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -7,7 +7,6 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements constructor(private platform: string, private $bundleValidatorHelper: IBundleValidatorHelper, - private $debugService: IDebugService, protected $devicesService: Mobile.IDevicesService, $platformValidationService: IPlatformValidationService, $projectData: IProjectData, @@ -36,19 +35,17 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements deviceId: this.$options.device }); - const debugData = this.$debugDataService.createDebugData(this.$projectData, { device: selectedDeviceForDebug.deviceInfo.identifier }); - if (this.$options.start) { const debugOptions = _.cloneDeep(this.$options.argv); - await this.$debugController.printDebugInformation(await this.$debugService.debug(debugData, debugOptions)); + const debugData = this.$debugDataService.getDebugData(selectedDeviceForDebug.deviceInfo.identifier, this.$projectData, debugOptions); + await this.$debugController.printDebugInformation(await this.$debugController.startDebug(debugData)); return; } - await this.$liveSyncCommandHelper.executeLiveSyncOperationWithDebug([selectedDeviceForDebug], this.platform, { + await this.$liveSyncCommandHelper.executeLiveSyncOperation([selectedDeviceForDebug], this.platform, { deviceDebugMap: { [selectedDeviceForDebug.deviceInfo.identifier]: true }, - // This will default in the liveSyncCommandHelper buildPlatform: undefined, skipNativePrepare: false }); diff --git a/lib/commands/preview.ts b/lib/commands/preview.ts index 08dccb9994..a8bffb0bc4 100644 --- a/lib/commands/preview.ts +++ b/lib/commands/preview.ts @@ -1,5 +1,4 @@ import { DEVICE_LOG_EVENT_NAME } from "../common/constants"; -import { PreviewAppController } from "../controllers/preview-app-controller"; export class PreviewCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; @@ -9,7 +8,7 @@ export class PreviewCommand implements ICommand { private $bundleValidatorHelper: IBundleValidatorHelper, private $errors: IErrors, private $logger: ILogger, - private $previewAppController: PreviewAppController, + private $previewAppController: IPreviewAppController, private $networkConnectivityValidator: INetworkConnectivityValidator, private $projectData: IProjectData, private $options: IOptions, @@ -25,7 +24,7 @@ export class PreviewCommand implements ICommand { this.$logger.info(message); }); - await this.$previewAppController.preview({ + await this.$previewAppController.startPreview({ projectDir: this.$projectData.projectDir, useHotModuleReload: this.$options.hmr, env: this.$options.env diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 32985e65d5..e8fcda7cdc 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -36,12 +36,8 @@ export class RunCommandBase implements ICommand { this.platform = this.$devicePlatformsConstants.Android; } - const validatePlatformOutput = await this.$liveSyncCommandHelper.validatePlatform(this.platform); + await this.$liveSyncCommandHelper.validatePlatform(this.platform); - if (this.platform && validatePlatformOutput && validatePlatformOutput[this.platform.toLowerCase()]) { - const checkEnvironmentRequirementsOutput = validatePlatformOutput[this.platform.toLowerCase()].checkEnvironmentRequirementsOutput; - this.liveSyncCommandHelperAdditionalOptions.syncToPreviewApp = checkEnvironmentRequirementsOutput && checkEnvironmentRequirementsOutput.selectedOption === "Sync to Playground"; - } return true; } } diff --git a/lib/commands/test.ts b/lib/commands/test.ts index 7a47e2904b..abe1502d5f 100644 --- a/lib/commands/test.ts +++ b/lib/commands/test.ts @@ -1,5 +1,3 @@ -import { LiveSyncCommandHelper } from "../helpers/livesync-command-helper"; - abstract class TestCommandBase { public allowedParameters: ICommandParameter[] = []; protected abstract platform: string; @@ -10,7 +8,7 @@ abstract class TestCommandBase { protected abstract $platformEnvironmentRequirements: IPlatformEnvironmentRequirements; protected abstract $errors: IErrors; protected abstract $cleanupService: ICleanupService; - protected abstract $liveSyncCommandHelper: LiveSyncCommandHelper; + protected abstract $liveSyncCommandHelper: ILiveSyncCommandHelper; protected abstract $devicesService: Mobile.IDevicesService; async execute(args: string[]): Promise { @@ -31,7 +29,7 @@ abstract class TestCommandBase { if (!this.$options.env) { this.$options.env = { }; } this.$options.env.unitTesting = true; - const liveSyncInfo = this.$liveSyncCommandHelper.createLiveSyncInfo(); + const liveSyncInfo = this.$liveSyncCommandHelper.getLiveSyncData(this.$projectData.projectDir); const deviceDebugMap: IDictionary = {}; devices.forEach(device => deviceDebugMap[device.deviceInfo.identifier] = this.$options.debugBrk); @@ -79,7 +77,7 @@ class TestAndroidCommand extends TestCommandBase implements ICommand { protected $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, protected $errors: IErrors, protected $cleanupService: ICleanupService, - protected $liveSyncCommandHelper: LiveSyncCommandHelper, + protected $liveSyncCommandHelper: ILiveSyncCommandHelper, protected $devicesService: Mobile.IDevicesService) { super(); } @@ -95,7 +93,7 @@ class TestIosCommand extends TestCommandBase implements ICommand { protected $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, protected $errors: IErrors, protected $cleanupService: ICleanupService, - protected $liveSyncCommandHelper: LiveSyncCommandHelper, + protected $liveSyncCommandHelper: ILiveSyncCommandHelper, protected $devicesService: Mobile.IDevicesService) { super(); } diff --git a/lib/common/definitions/mobile.d.ts b/lib/common/definitions/mobile.d.ts index 6997689d80..328f5467ab 100644 --- a/lib/common/definitions/mobile.d.ts +++ b/lib/common/definitions/mobile.d.ts @@ -497,7 +497,7 @@ declare module Mobile { */ pickSingleDevice(options: IPickSingleDeviceOptions): Promise; - getPlatformsFromDeviceDescriptors(deviceDescriptors: ILiveSyncDeviceInfo[]): string[]; + getPlatformsFromDeviceDescriptors(deviceDescriptors: ILiveSyncDeviceDescriptor[]): string[]; } interface IPickSingleDeviceOptions { diff --git a/lib/common/mobile/mobile-core/devices-service.ts b/lib/common/mobile/mobile-core/devices-service.ts index 41190f2fae..0670b21d40 100644 --- a/lib/common/mobile/mobile-core/devices-service.ts +++ b/lib/common/mobile/mobile-core/devices-service.ts @@ -601,7 +601,7 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi } } - public getPlatformsFromDeviceDescriptors(deviceDescriptors: ILiveSyncDeviceInfo[]): string[] { + public getPlatformsFromDeviceDescriptors(deviceDescriptors: ILiveSyncDeviceDescriptor[]): string[] { const platforms = _(deviceDescriptors) .map(device => this.getDeviceByIdentifier(device.identifier)) .map(device => device.deviceInfo.platform.toLowerCase()) diff --git a/lib/controllers/debug-controller.ts b/lib/controllers/debug-controller.ts index 3e7c23c91a..b4ac68f1c3 100644 --- a/lib/controllers/debug-controller.ts +++ b/lib/controllers/debug-controller.ts @@ -1,139 +1,147 @@ import { performanceLog } from "../common/decorators"; import { EOL } from "os"; -import { RunController } from "./run-controller"; +import { parse } from "url"; +import { CONNECTED_STATUS } from "../common/constants"; +import { TrackActionNames, DebugCommandErrors, CONNECTION_ERROR_EVENT_NAME, DebugTools, DEBUGGER_DETACHED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME } from "../constants"; +import { EventEmitter } from "events"; -export class DebugController extends RunController implements IDebugController { +export class DebugController extends EventEmitter implements IDebugController { + private _platformDebugServices: IDictionary = {}; constructor( - $analyticsService: IAnalyticsService, - $buildDataService: IBuildDataService, - $buildController: IBuildController, + private $analyticsService: IAnalyticsService, private $debugDataService: IDebugDataService, - private $debugService: IDebugService, - $deviceInstallAppService: IDeviceInstallAppService, - $devicesService: Mobile.IDevicesService, - $errors: IErrors, - $hmrStatusService: IHmrStatusService, - $hooksService: IHooksService, - $liveSyncServiceResolver: ILiveSyncServiceResolver, - $logger: ILogger, - $platformsDataService: IPlatformsDataService, - $pluginsService: IPluginsService, - $prepareController: IPrepareController, - $prepareDataService: IPrepareDataService, - $prepareNativePlatformService: IPrepareNativePlatformService, - $projectDataService: IProjectDataService, - $runEmitter: IRunEmitter + private $devicesService: Mobile.IDevicesService, + private $errors: IErrors, + private $injector: IInjector, + private $liveSyncProcessDataService: ILiveSyncProcessDataService, + private $logger: ILogger, + private $mobileHelper: Mobile.IMobileHelper, + private $projectDataService: IProjectDataService ) { - super( - $analyticsService, - $buildDataService, - $buildController, - $deviceInstallAppService, - $devicesService, - $errors, - $hmrStatusService, - $hooksService, - $liveSyncServiceResolver, - $logger, - $platformsDataService, - $pluginsService, - $prepareController, - $prepareDataService, - $prepareNativePlatformService, - $projectDataService, - $runEmitter - ); + super(); } @performanceLog() - public async enableDebugging(projectData: IProjectData, deviceDescriptor: ILiveSyncDeviceInfo, refreshInfo: IRestartApplicationInfo): Promise { - const { debugOptions } = deviceDescriptor; - // we do not stop the application when debugBrk is false, so we need to attach, instead of launch - // if we try to send the launch request, the debugger port will not be printed and the command will timeout - debugOptions.start = !debugOptions.debugBrk; - - debugOptions.forceDebuggerAttachedEvent = refreshInfo.didRestart; - const deviceOption = { - deviceIdentifier: deviceDescriptor.identifier, - debugOptions: debugOptions, - }; + public async startDebug(debugData: IDebugData): Promise { + const { debugOptions: options } = debugData; + const device = this.$devicesService.getDeviceByIdentifier(debugData.deviceIdentifier); + + if (!device) { + this.$errors.failWithoutHelp(`Cannot find device with identifier ${debugData.deviceIdentifier}.`); + } + + if (device.deviceInfo.status !== CONNECTED_STATUS) { + this.$errors.failWithoutHelp(`The device with identifier ${debugData.deviceIdentifier} is unreachable. Make sure it is Trusted and try again.`); + } + + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.Debug, + device, + additionalData: this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform) && options && options.inspector ? DebugTools.Inspector : DebugTools.Chrome, + projectDir: debugData.projectDir + }); + + if (!(await device.applicationManager.isApplicationInstalled(debugData.applicationIdentifier))) { + this.$errors.failWithoutHelp(`The application ${debugData.applicationIdentifier} is not installed on device with identifier ${debugData.deviceIdentifier}.`); + } + + const debugService = this.getDeviceDebugService(device); + if (!debugService) { + this.$errors.failWithoutHelp(`Unsupported device OS: ${device.deviceInfo.platform}. You can debug your applications only on iOS or Android.`); + } - return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, deviceDescriptor, { projectDir: projectData.projectDir }); + const debugOptions: IDebugOptions = _.cloneDeep(options); + const debugResultInfo = await debugService.debug(debugData, debugOptions); + + return this.getDebugInformation(debugResultInfo, device.deviceInfo.identifier); } - public async attachDebugger(settings: IAttachDebuggerOptions): Promise { + public enableDebugging(enableDebuggingData: IEnableDebuggingData): Promise[] { + const { deviceIdentifiers } = enableDebuggingData; + + return _.map(deviceIdentifiers, deviceIdentifier => this.enableDebuggingCore(enableDebuggingData.projectDir, deviceIdentifier, enableDebuggingData.debugOptions)); + } + + public async disableDebugging(disableDebuggingData: IDisableDebuggingData): Promise { + const { deviceIdentifiers, projectDir } = disableDebuggingData; + + for (const deviceIdentifier of deviceIdentifiers) { + const liveSyncProcessInfo = this.$liveSyncProcessDataService.getPersistedData(projectDir); + if (liveSyncProcessInfo.currentSyncAction) { + await liveSyncProcessInfo.currentSyncAction; + } + + const currentDeviceDescriptor = this.getDeviceDescriptor(projectDir, deviceIdentifier); + + if (currentDeviceDescriptor) { + currentDeviceDescriptor.debuggingEnabled = false; + } else { + this.$errors.failWithoutHelp(`Couldn't disable debugging for ${deviceIdentifier}`); + } + + const currentDevice = this.$devicesService.getDeviceByIdentifier(currentDeviceDescriptor.identifier); + if (!currentDevice) { + this.$errors.failWithoutHelp(`Couldn't disable debugging for ${deviceIdentifier}. Could not find device.`); + } + + await this.stopDebug(currentDevice.deviceInfo.identifier); + + this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier }); + } + } + + public async attachDebugger(attachDebuggerData: IAttachDebuggerData): Promise { // Default values - if (settings.debugOptions) { - settings.debugOptions.chrome = settings.debugOptions.chrome === undefined ? true : settings.debugOptions.chrome; - settings.debugOptions.start = settings.debugOptions.start === undefined ? true : settings.debugOptions.start; + if (attachDebuggerData.debugOptions) { + attachDebuggerData.debugOptions.chrome = attachDebuggerData.debugOptions.chrome === undefined ? true : attachDebuggerData.debugOptions.chrome; + attachDebuggerData.debugOptions.start = attachDebuggerData.debugOptions.start === undefined ? true : attachDebuggerData.debugOptions.start; } else { - settings.debugOptions = { + attachDebuggerData.debugOptions = { chrome: true, start: true }; } - const projectData = this.$projectDataService.getProjectData(settings.projectDir); - const debugData = this.$debugDataService.createDebugData(projectData, { device: settings.deviceIdentifier }); + const projectData = this.$projectDataService.getProjectData(attachDebuggerData.projectDir); + const debugData = this.$debugDataService.getDebugData(attachDebuggerData.deviceIdentifier, projectData, attachDebuggerData.debugOptions); // const platformData = this.$platformsDataService.getPlatformData(settings.platform, projectData); // Of the properties below only `buildForDevice` and `release` are currently used. // Leaving the others with placeholder values so that they may not be forgotten in future implementations. - const debugInfo = await this.$debugService.debug(debugData, settings.debugOptions); - const result = this.printDebugInformation(debugInfo, settings.debugOptions.forceDebuggerAttachedEvent); + const debugInfo = await this.startDebug(debugData); + const result = this.printDebugInformation(debugInfo, attachDebuggerData.debugOptions.forceDebuggerAttachedEvent); return result; } - public printDebugInformation(debugInformation: IDebugInformation, fireDebuggerAttachedEvent: boolean = true): IDebugInformation { - if (!!debugInformation.url) { - if (fireDebuggerAttachedEvent) { - this.$runEmitter.emitDebuggerAttachedEvent(debugInformation); - } - - this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${debugInformation.url}${EOL}`.cyan); - } - - return debugInformation; - } - - // IMPORTANT: This method overrides the refresh logic of runController as enables debugging for provided deviceDescriptor - public async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo): Promise { - liveSyncResultInfo.waitForDebugger = deviceDescriptor.debugOptions && deviceDescriptor.debugOptions.debugBrk; - - const refreshInfo = await super.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); - - await this.enableDebugging(projectData, deviceDescriptor, refreshInfo); - - return refreshInfo; - } - @performanceLog() - private async enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption: IEnableDebuggingDeviceOptions, deviceDescriptor: ILiveSyncDeviceInfo, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { + public async enableDebuggingCoreWithoutWaitingCurrentAction(projectDir: string, deviceIdentifier: string, debugOptions: IDebugOptions): Promise { + const deviceDescriptor = this.getDeviceDescriptor(projectDir, deviceIdentifier); if (!deviceDescriptor) { - this.$errors.failWithoutHelp(`Couldn't enable debugging for ${deviceOption.deviceIdentifier}`); + this.$errors.failWithoutHelp(`Couldn't enable debugging for ${deviceIdentifier}`); } deviceDescriptor.debuggingEnabled = true; - deviceDescriptor.debugOptions = deviceOption.debugOptions; - const currentDeviceInstance = this.$devicesService.getDeviceByIdentifier(deviceOption.deviceIdentifier); - const attachDebuggerOptions: IAttachDebuggerOptions = { - deviceIdentifier: deviceOption.deviceIdentifier, + deviceDescriptor.debugOptions = debugOptions; + + const currentDeviceInstance = this.$devicesService.getDeviceByIdentifier(deviceIdentifier); + const attachDebuggerData: IAttachDebuggerData = { + deviceIdentifier, isEmulator: currentDeviceInstance.isEmulator, outputPath: deviceDescriptor.outputPath, platform: currentDeviceInstance.deviceInfo.platform, - projectDir: debuggingAdditionalOptions.projectDir, - debugOptions: deviceOption.debugOptions + projectDir, + debugOptions }; let debugInformation: IDebugInformation; try { - debugInformation = await this.attachDebugger(attachDebuggerOptions); + debugInformation = await this.attachDebugger(attachDebuggerData); } catch (err) { this.$logger.trace("Couldn't attach debugger, will modify options and try again.", err); - attachDebuggerOptions.debugOptions.start = false; + attachDebuggerData.debugOptions.start = false; try { - debugInformation = await this.attachDebugger(attachDebuggerOptions); + debugInformation = await this.attachDebugger(attachDebuggerData); } catch (innerErr) { this.$logger.trace("Couldn't attach debugger with modified options.", innerErr); throw err; @@ -142,5 +150,79 @@ export class DebugController extends RunController implements IDebugController { return debugInformation; } + + public printDebugInformation(debugInformation: IDebugInformation, fireDebuggerAttachedEvent: boolean = true): IDebugInformation { + if (!!debugInformation.url) { + if (fireDebuggerAttachedEvent) { + this.emit(DEBUGGER_ATTACHED_EVENT_NAME, debugInformation); + } + + this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${debugInformation.url}${EOL}`.cyan); + } + + return debugInformation; + } + + public async stopDebug(deviceIdentifier: string): Promise { + const device = this.$devicesService.getDeviceByIdentifier(deviceIdentifier); + const debugService = this.getDeviceDebugService(device); + await debugService.debugStop(); + } + + private getDeviceDescriptor(projectDir: string, deviceIdentifier: string): ILiveSyncDeviceDescriptor { + const deviceDescriptors = this.$liveSyncProcessDataService.getDeviceDescriptors(projectDir); + const currentDeviceDescriptor = _.find(deviceDescriptors, d => d.identifier === deviceIdentifier); + + return currentDeviceDescriptor; + } + + private getDeviceDebugService(device: Mobile.IDevice): IDeviceDebugService { + if (!this._platformDebugServices[device.deviceInfo.identifier]) { + const devicePlatform = device.deviceInfo.platform; + if (this.$mobileHelper.isiOSPlatform(devicePlatform)) { + this._platformDebugServices[device.deviceInfo.identifier] = this.$injector.resolve("iOSDeviceDebugService", { device }); + } else if (this.$mobileHelper.isAndroidPlatform(devicePlatform)) { + this._platformDebugServices[device.deviceInfo.identifier] = this.$injector.resolve("androidDeviceDebugService", { device }); + } else { + this.$errors.failWithoutHelp(DebugCommandErrors.UNSUPPORTED_DEVICE_OS_FOR_DEBUGGING); + } + + this.attachConnectionErrorHandlers(this._platformDebugServices[device.deviceInfo.identifier]); + } + + return this._platformDebugServices[device.deviceInfo.identifier]; + } + + private attachConnectionErrorHandlers(platformDebugService: IDeviceDebugService) { + let connectionErrorHandler = (e: Error) => this.emit(CONNECTION_ERROR_EVENT_NAME, e); + connectionErrorHandler = connectionErrorHandler.bind(this); + platformDebugService.on(CONNECTION_ERROR_EVENT_NAME, connectionErrorHandler); + } + + private getDebugInformation(debugResultInfo: IDebugResultInfo, deviceIdentifier: string): IDebugInformation { + const debugInfo: IDebugInformation = { + url: debugResultInfo.debugUrl, + port: 0, + deviceIdentifier + }; + + if (debugResultInfo.debugUrl) { + const parseQueryString = true; + const wsQueryParam = parse(debugResultInfo.debugUrl, parseQueryString).query.ws; + const hostPortSplit = wsQueryParam && wsQueryParam.split(":"); + debugInfo.port = hostPortSplit && +hostPortSplit[1]; + } + + return debugInfo; + } + + private async enableDebuggingCore(projectDir: string, deviceIdentifier: string, debugOptions: IDebugOptions): Promise { + const liveSyncProcessInfo = this.$liveSyncProcessDataService.getPersistedData(projectDir); + if (liveSyncProcessInfo && liveSyncProcessInfo.currentSyncAction) { + await liveSyncProcessInfo.currentSyncAction; + } + + return this.enableDebuggingCoreWithoutWaitingCurrentAction(projectDir, deviceIdentifier, debugOptions); + } } $injector.register("debugController", DebugController); diff --git a/lib/controllers/deploy-controller.ts b/lib/controllers/deploy-controller.ts index 5f43a8d08f..080d0ad142 100644 --- a/lib/controllers/deploy-controller.ts +++ b/lib/controllers/deploy-controller.ts @@ -8,10 +8,10 @@ export class DeployController { ) { } public async deploy(data: IRunData): Promise { - const { projectDir, liveSyncInfo, deviceDescriptors } = data; + const { liveSyncInfo, deviceDescriptors } = data; const executeAction = async (device: Mobile.IDevice) => { - const buildData = this.$buildDataService.getBuildData(projectDir, device.deviceInfo.platform, liveSyncInfo); + const buildData = this.$buildDataService.getBuildData(liveSyncInfo.projectDir, device.deviceInfo.platform, liveSyncInfo); await this.$buildController.prepareAndBuild(buildData); await this.$deviceInstallAppService.installOnDevice(device, buildData); }; diff --git a/lib/controllers/preview-app-controller.ts b/lib/controllers/preview-app-controller.ts index 9dd961d5e1..1531958def 100644 --- a/lib/controllers/preview-app-controller.ts +++ b/lib/controllers/preview-app-controller.ts @@ -5,10 +5,10 @@ import { performanceLog } from "../common/decorators"; import { stringify } from "../common/helpers"; import { HmrConstants } from "../common/constants"; import { EventEmitter } from "events"; -import { PreviewAppEmitter } from "../emitters/preview-app-emitter"; import { PrepareDataService } from "../services/prepare-data-service"; +import { PreviewAppLiveSyncEvents } from "../services/livesync/playground/preview-app-constants"; -export class PreviewAppController extends EventEmitter { +export class PreviewAppController extends EventEmitter implements IPreviewAppController { private deviceInitializationPromise: IDictionary> = {}; private promise = Promise.resolve(); @@ -18,16 +18,29 @@ export class PreviewAppController extends EventEmitter { private $hmrStatusService: IHmrStatusService, private $logger: ILogger, private $prepareController: PrepareController, - private $previewAppEmitter: PreviewAppEmitter, private $previewAppFilesService: IPreviewAppFilesService, - private $previewAppLiveSyncService: IPreviewAppLiveSyncService, private $previewAppPluginsService: IPreviewAppPluginsService, private $previewDevicesService: IPreviewDevicesService, + private $previewQrCodeService: IPreviewQrCodeService, private $previewSdkService: IPreviewSdkService, private $prepareDataService: PrepareDataService ) { super(); } - public async preview(data: IPreviewAppLiveSyncData): Promise { + public async startPreview(data: IPreviewAppLiveSyncData): Promise { + await this.previewCore(data); + + const url = this.$previewSdkService.getQrCodeUrl({ projectDir: data.projectDir, useHotModuleReload: data.useHotModuleReload }); + const result = await this.$previewQrCodeService.getLiveSyncQrCode(url); + + return result; + } + + public async stopPreview(): Promise { + this.$previewSdkService.stop(); + this.$previewDevicesService.updateConnectedDevices([]); + } + + private async previewCore(data: IPreviewAppLiveSyncData): Promise { await this.$previewSdkService.initialize(data.projectDir, async (device: Device) => { try { if (!device) { @@ -72,24 +85,24 @@ export class PreviewAppController extends EventEmitter { } } catch (error) { this.$logger.trace(`Error while sending files on device ${device && device.id}. Error is`, error); - this.$previewAppEmitter.emitPreviewAppLiveSyncError(data, device.id, error, device.platform); + this.emit(PreviewAppLiveSyncEvents.PREVIEW_APP_LIVE_SYNC_ERROR, { + error, + data, + platform: device.platform, + deviceId: device.id + }); } }); return null; } - public async stopPreview(): Promise { - this.$previewSdkService.stop(); - this.$previewDevicesService.updateConnectedDevices([]); - } - @performanceLog() private async handlePrepareReadyEvent(data: IPreviewAppLiveSyncData, hmrData: IPlatformHmrData, files: string[], platform: string) { await this.promise .then(async () => { const platformHmrData = _.cloneDeep(hmrData); - this.promise = this.$previewAppLiveSyncService.syncFilesForPlatformSafe(data, { filesToSync: files }, platform); + this.promise = this.syncFilesForPlatformSafe(data, { filesToSync: files }, platform); await this.promise; if (data.useHotModuleReload && platformHmrData.hash) { @@ -100,7 +113,7 @@ export class PreviewAppController extends EventEmitter { if (status === HmrConstants.HMR_ERROR_STATUS) { const originalUseHotModuleReload = data.useHotModuleReload; data.useHotModuleReload = false; - await this.$previewAppLiveSyncService.syncFilesForPlatformSafe(data, { filesToSync: platformHmrData.fallbackFiles }, platform, previewDevice.id ); + await this.syncFilesForPlatformSafe(data, { filesToSync: platformHmrData.fallbackFiles }, platform, previewDevice.id ); data.useHotModuleReload = originalUseHotModuleReload; } })); @@ -119,5 +132,23 @@ export class PreviewAppController extends EventEmitter { this.$logger.warn(`Unable to apply changes for platform ${platform}. Error is: ${err}, ${stringify(err)}`); } } + + private async syncFilesForPlatformSafe(data: IPreviewAppLiveSyncData, filesData: IPreviewAppFilesData, platform: string, deviceId?: string): Promise { + try { + const payloads = this.$previewAppFilesService.getFilesPayload(data, filesData, platform); + if (payloads && payloads.files && payloads.files.length) { + this.$logger.info(`Start syncing changes for platform ${platform}.`); + await this.$previewSdkService.applyChanges(payloads); + this.$logger.info(`Successfully synced ${payloads.files.map(filePayload => filePayload.file.yellow)} for platform ${platform}.`); + } + } catch (error) { + this.$logger.warn(`Unable to apply changes for platform ${platform}. Error is: ${error}, ${JSON.stringify(error, null, 2)}.`); + this.emit(PreviewAppLiveSyncEvents.PREVIEW_APP_LIVE_SYNC_ERROR, { + error, + data, + deviceId + }); + } + } } $injector.register("previewAppController", PreviewAppController); diff --git a/lib/controllers/run-controller.ts b/lib/controllers/run-controller.ts index ae88eea96d..055484a6e9 100644 --- a/lib/controllers/run-controller.ts +++ b/lib/controllers/run-controller.ts @@ -1,32 +1,38 @@ import { HmrConstants, DeviceDiscoveryEventNames } from "../common/constants"; -import { PREPARE_READY_EVENT_NAME, TrackActionNames } from "../constants"; +import { PREPARE_READY_EVENT_NAME, TrackActionNames, DEBUGGER_DETACHED_EVENT_NAME, RunOnDeviceEvents, USER_INTERACTION_NEEDED_EVENT_NAME } from "../constants"; import { cache, performanceLog } from "../common/decorators"; +import { EventEmitter } from "events"; -export class RunController implements IRunController { - private processesInfo: IDictionary = {}; +export class RunController extends EventEmitter implements IRunController { constructor( - private $analyticsService: IAnalyticsService, + protected $analyticsService: IAnalyticsService, private $buildDataService: IBuildDataService, private $buildController: IBuildController, + private $debugController: IDebugController, private $deviceInstallAppService: IDeviceInstallAppService, protected $devicesService: Mobile.IDevicesService, protected $errors: IErrors, + protected $injector: IInjector, private $hmrStatusService: IHmrStatusService, public $hooksService: IHooksService, private $liveSyncServiceResolver: ILiveSyncServiceResolver, + private $liveSyncProcessDataService: ILiveSyncProcessDataService, protected $logger: ILogger, + protected $mobileHelper: Mobile.IMobileHelper, private $platformsDataService: IPlatformsDataService, private $pluginsService: IPluginsService, private $prepareController: IPrepareController, private $prepareDataService: IPrepareDataService, private $prepareNativePlatformService: IPrepareNativePlatformService, - protected $projectDataService: IProjectDataService, - protected $runEmitter: IRunEmitter - ) { } + protected $projectDataService: IProjectDataService + ) { + super(); + } public async run(runData: IRunData): Promise { - const { projectDir, liveSyncInfo, deviceDescriptors } = runData; + const { liveSyncInfo, deviceDescriptors } = runData; + const { projectDir } = liveSyncInfo; const projectData = this.$projectDataService.getProjectData(projectDir); await this.initializeSetup(projectData); @@ -34,9 +40,9 @@ export class RunController implements IRunController { const platforms = this.$devicesService.getPlatformsFromDeviceDescriptors(deviceDescriptors); const deviceDescriptorsForInitialSync = this.getDeviceDescriptorsForInitialSync(projectDir, deviceDescriptors); - this.persistData(projectDir, deviceDescriptors, platforms); + this.$liveSyncProcessDataService.persistData(projectDir, deviceDescriptors, platforms); - const shouldStartWatcher = !liveSyncInfo.skipWatcher && !!this.processesInfo[projectDir].deviceDescriptors.length; + const shouldStartWatcher = !liveSyncInfo.skipWatcher && this.$liveSyncProcessDataService.hasDeviceDescriptors(projectDir); if (shouldStartWatcher && liveSyncInfo.useHotModuleReload) { this.$hmrStatusService.attachToHmrStatusEvent(); } @@ -50,8 +56,9 @@ export class RunController implements IRunController { this.attachDeviceLostHandler(); } - public async stop(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise { - const liveSyncProcessInfo = this.processesInfo[projectDir]; + public async stop(data: IStopRunData): Promise { + const { projectDir, deviceIdentifiers, stopOptions } = data; + const liveSyncProcessInfo = this.$liveSyncProcessDataService.getPersistedData(projectDir); if (liveSyncProcessInfo && !liveSyncProcessInfo.isStopped) { // In case we are coming from error during livesync, the current action is the one that erred (but we are still executing it), // so we cannot await it as this will cause infinite loop. @@ -100,32 +107,62 @@ export class RunController implements IRunController { // Emit RunOnDevice stopped when we've really stopped. _.each(removedDeviceIdentifiers, deviceIdentifier => { - this.$runEmitter.emitRunStoppedEvent(projectDir, deviceIdentifier); + this.emitCore(RunOnDeviceEvents.runOnDeviceStopped, { + projectDir, + deviceIdentifier + }); }); } } - public getDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { - const liveSyncProcessesInfo = this.processesInfo[projectDir] || {}; - const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors; - return currentDescriptors || []; + public getDeviceDescriptors(data: { projectDir: string }): ILiveSyncDeviceDescriptor[] { + return this.$liveSyncProcessDataService.getDeviceDescriptors(data.projectDir); + } + + protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, filesChangeEventData: IFilesChangeEventData, deviceDescriptor: ILiveSyncDeviceDescriptor, settings?: IRefreshApplicationSettings): Promise { + const result = deviceDescriptor.debuggingEnabled ? + await this.refreshApplicationWithDebug(projectData, liveSyncResultInfo, filesChangeEventData, deviceDescriptor, settings) : + await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, filesChangeEventData, deviceDescriptor, settings); + + return result; + } + + protected async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, filesChangeEventData: IFilesChangeEventData, deviceDescriptor: ILiveSyncDeviceDescriptor, settings?: IRefreshApplicationSettings): Promise { + const debugOptions = deviceDescriptor.debugOptions || {}; + + liveSyncResultInfo.waitForDebugger = !!debugOptions.debugBrk; + + const refreshInfo = await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, filesChangeEventData, deviceDescriptor, settings); + + // we do not stop the application when debugBrk is false, so we need to attach, instead of launch + // if we try to send the launch request, the debugger port will not be printed and the command will timeout + debugOptions.start = !debugOptions.debugBrk; + debugOptions.forceDebuggerAttachedEvent = refreshInfo.didRestart; + + await this.$debugController.enableDebuggingCoreWithoutWaitingCurrentAction(projectData.projectDir, deviceDescriptor.identifier, debugOptions); + + return refreshInfo; } @performanceLog() - protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo, settings?: IRefreshApplicationSettings): Promise { + protected async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, filesChangeEventData: IFilesChangeEventData, deviceDescriptor: ILiveSyncDeviceDescriptor, settings?: IRefreshApplicationSettings): Promise { const result = { didRestart: false }; const platform = liveSyncResultInfo.deviceAppData.platform; const applicationIdentifier = projectData.projectIdentifiers[platform.toLowerCase()]; const platformLiveSyncService = this.$liveSyncServiceResolver.resolveLiveSyncService(platform); try { - let shouldRestart = await platformLiveSyncService.shouldRestart(projectData, liveSyncResultInfo); + let shouldRestart = filesChangeEventData && filesChangeEventData.hasNativeChanges; + if (!shouldRestart) { + shouldRestart = await platformLiveSyncService.shouldRestart(projectData, liveSyncResultInfo); + } + if (!shouldRestart) { shouldRestart = !await platformLiveSyncService.tryRefreshApplication(projectData, liveSyncResultInfo); } if (shouldRestart) { - this.$runEmitter.emitDebuggerDetachedEvent(liveSyncResultInfo.deviceAppData.device); + this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier }); await platformLiveSyncService.restartApplication(projectData, liveSyncResultInfo); result.didRestart = true; } @@ -133,20 +170,37 @@ export class RunController implements IRunController { this.$logger.info(`Error while trying to start application ${applicationIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Error is: ${err.message || err}`); const msg = `Unable to start application ${applicationIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Try starting it manually.`; this.$logger.warn(msg); + + const device = liveSyncResultInfo.deviceAppData.device; + const deviceIdentifier = device.deviceInfo.identifier; + if (!settings || !settings.shouldSkipEmitLiveSyncNotification) { - this.$runEmitter.emitRunNotificationEvent(projectData, liveSyncResultInfo.deviceAppData.device, msg); + this.emitCore(RunOnDeviceEvents.runOnDeviceNotification, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], + notification: msg + }); } if (settings && settings.shouldCheckDeveloperDiscImage && (err.message || err) === "Could not find developer disk image") { - this.$runEmitter.emitUserInteractionNeededEvent(projectData, liveSyncResultInfo.deviceAppData.device, deviceDescriptor); + const attachDebuggerOptions: IAttachDebuggerData = { + platform: device.deviceInfo.platform, + isEmulator: device.isEmulator, + projectDir: projectData.projectDir, + deviceIdentifier, + debugOptions: deviceDescriptor.debugOptions, + outputPath: deviceDescriptor.outputPath + }; + this.emit(USER_INTERACTION_NEEDED_EVENT_NAME, attachDebuggerOptions); } } return result; } - private getDeviceDescriptorsForInitialSync(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[]) { - const currentRunData = this.processesInfo[projectDir]; + private getDeviceDescriptorsForInitialSync(projectDir: string, deviceDescriptors: ILiveSyncDeviceDescriptor[]) { + const currentRunData = this.$liveSyncProcessDataService.getPersistedData(projectDir); const isAlreadyLiveSyncing = currentRunData && !currentRunData.isStopped; // Prevent cases where liveSync is called consecutive times with the same device, for example [ A, B, C ] and then [ A, B, D ] - we want to execute initialSync only for D. const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentRunData.deviceDescriptors, "identifier") : deviceDescriptors; @@ -168,11 +222,11 @@ export class RunController implements IRunController { this.$devicesService.on(DeviceDiscoveryEventNames.DEVICE_LOST, async (device: Mobile.IDevice) => { this.$logger.trace(`Received ${DeviceDiscoveryEventNames.DEVICE_LOST} event in LiveSync service for ${device.deviceInfo.identifier}. Will stop LiveSync operation for this device.`); - for (const projectDir in this.processesInfo) { + for (const projectDir in this.$liveSyncProcessDataService.getAllPersistedData()) { try { - const deviceDescriptors = this.getDeviceDescriptors(projectDir); + const deviceDescriptors = this.getDeviceDescriptors({ projectDir }); if (_.find(deviceDescriptors, d => d.identifier === device.deviceInfo.identifier)) { - await this.stop(projectDir, [device.deviceInfo.identifier]); + await this.stop({ projectDir, deviceIdentifiers: [device.deviceInfo.identifier] }); } } catch (err) { this.$logger.warn(`Unable to stop LiveSync operation for ${device.deviceInfo.identifier}.`, err); @@ -181,7 +235,7 @@ export class RunController implements IRunController { }); } - private async syncInitialDataOnDevices(projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { + private async syncInitialDataOnDevices(projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceDescriptor[]): Promise { const deviceAction = async (device: Mobile.IDevice) => { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); const platformData = this.$platformsDataService.getPlatformData(device.deviceInfo.platform, projectData); @@ -206,29 +260,41 @@ export class RunController implements IRunController { const platformLiveSyncService = this.$liveSyncServiceResolver.resolveLiveSyncService(platformData.platformNameLowerCase); const { force, useHotModuleReload, skipWatcher } = liveSyncInfo; - const liveSyncResultInfo = await platformLiveSyncService.fullSync({ force, useHotModuleReload, projectData, device, watch: !skipWatcher, liveSyncDeviceInfo: deviceDescriptor }); + const liveSyncResultInfo = await platformLiveSyncService.fullSync({ force, useHotModuleReload, projectData, device, watch: !skipWatcher, liveSyncDeviceData: deviceDescriptor }); - await this.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); + await this.refreshApplication(projectData, liveSyncResultInfo, null, deviceDescriptor); - this.$runEmitter.emitRunExecutedEvent(projectData, device, { + this.emitCore(RunOnDeviceEvents.runOnDeviceExecuted, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), isFullSync: liveSyncResultInfo.isFullSync }); this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); - this.$runEmitter.emitRunStartedEvent(projectData, device); + this.emitCore(RunOnDeviceEvents.runOnDeviceStarted, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()] + }); } catch (err) { this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`); - this.$runEmitter.emitRunErrorEvent(projectData, device, err); + this.emitCore(RunOnDeviceEvents.runOnDeviceError, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], + error: err, + }); } }; await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier))); } - private async syncChangedDataOnDevices(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { + private async syncChangedDataOnDevices(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceDescriptor[]): Promise { const deviceAction = async (device: Mobile.IDevice) => { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); const platformData = this.$platformsDataService.getPlatformData(data.platform, projectData); @@ -238,7 +304,7 @@ export class RunController implements IRunController { try { if (data.hasNativeChanges) { await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData); - await this.$buildController.prepareAndBuild(buildData); + await this.$buildController.build(buildData); } const isInHMRMode = liveSyncInfo.useHotModuleReload && data.hmrData && data.hmrData.hash; @@ -248,7 +314,7 @@ export class RunController implements IRunController { const platformLiveSyncService = this.$liveSyncServiceResolver.resolveLiveSyncService(device.deviceInfo.platform); const watchInfo = { - liveSyncDeviceInfo: deviceDescriptor, + liveSyncDeviceData: deviceDescriptor, projectData, filesToRemove: [], filesToSync: data.files, @@ -260,9 +326,12 @@ export class RunController implements IRunController { }; let liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo); - await this.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); + await this.refreshApplication(projectData, liveSyncResultInfo, data, deviceDescriptor); - this.$runEmitter.emitRunExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, { + this.emitCore(RunOnDeviceEvents.runOnDeviceExecuted, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), isFullSync: liveSyncResultInfo.isFullSync }); @@ -274,9 +343,12 @@ export class RunController implements IRunController { liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo); // We want to force a restart of the application. liveSyncResultInfo.isFullSync = true; - await this.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); + await this.refreshApplication(projectData, liveSyncResultInfo, data, deviceDescriptor); - this.$runEmitter.emitRunExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, { + this.emitCore(RunOnDeviceEvents.runOnDeviceExecuted, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), isFullSync: liveSyncResultInfo.isFullSync }); @@ -290,20 +362,26 @@ export class RunController implements IRunController { if (allErrors && _.isArray(allErrors)) { for (const deviceError of allErrors) { this.$logger.warn(`Unable to apply changes for device: ${deviceError.deviceIdentifier}. Error is: ${deviceError.message}.`); - this.$runEmitter.emitRunErrorEvent(projectData, device, deviceError); + + this.emitCore(RunOnDeviceEvents.runOnDeviceError, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], + error: err, + }); } } } }; await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => { - const liveSyncProcessInfo = this.processesInfo[projectData.projectDir]; + const liveSyncProcessInfo = this.$liveSyncProcessDataService.getPersistedData(projectData.projectDir); return (data.platform.toLowerCase() === device.deviceInfo.platform.toLowerCase()) && liveSyncProcessInfo && _.some(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); })); } private async addActionToChain(projectDir: string, action: () => Promise): Promise { - const liveSyncInfo = this.processesInfo[projectDir]; + const liveSyncInfo = this.$liveSyncProcessDataService.getPersistedData(projectDir); if (liveSyncInfo) { liveSyncInfo.actionsChain = liveSyncInfo.actionsChain.then(async () => { if (!liveSyncInfo.isStopped) { @@ -318,15 +396,9 @@ export class RunController implements IRunController { } } - private persistData(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], platforms: string[]): void { - this.processesInfo[projectDir] = this.processesInfo[projectDir] || Object.create(null); - this.processesInfo[projectDir].actionsChain = this.processesInfo[projectDir].actionsChain || Promise.resolve(); - this.processesInfo[projectDir].currentSyncAction = this.processesInfo[projectDir].actionsChain; - this.processesInfo[projectDir].isStopped = false; - this.processesInfo[projectDir].platforms = platforms; - - const currentDeviceDescriptors = this.getDeviceDescriptors(projectDir); - this.processesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), "identifier"); + private emitCore(event: string, data: ILiveSyncEventData): void { + this.$logger.trace(`Will emit event ${event} with data`, data); + this.emit(event, data); } } $injector.register("runController", RunController); diff --git a/lib/data/run-data.ts b/lib/data/run-data.ts index 4d67d8043d..5018a1b9ae 100644 --- a/lib/data/run-data.ts +++ b/lib/data/run-data.ts @@ -1,5 +1,5 @@ export class RunData { constructor(public projectDir: string, public liveSyncInfo: ILiveSyncInfo, - public deviceDescriptors: ILiveSyncDeviceInfo[]) { } + public deviceDescriptors: ILiveSyncDeviceDescriptor[]) { } } diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 2cf4700bc8..b835ab261b 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -1,21 +1,5 @@ -/** - * Describes information for starting debug process. - */ -interface IDebugData extends IAppDebugData, Mobile.IDeviceIdentifier { -} - -/** - * Describes information for application that will be debugged. - */ -interface IAppDebugData extends IProjectDir { - /** - * Application identifier of the app that it will be debugged. - */ +interface IDebugData extends IProjectDir, Mobile.IDeviceIdentifier, IOptionalDebuggingOptions { applicationIdentifier: string; - - /** - * The name of the application, for example `MyProject`. - */ projectName?: string; } @@ -103,33 +87,12 @@ interface IDebugOptions { interface IDebugDataService { /** * Creates the debug data based on specified options. + * @param {string} deviceIdentifier The identifier of the device * @param {IProjectData} projectData The data describing project that will be debugged. - * @param {IOptions} options The options based on which debugData will be created + * @param {IDebugOptions} debugOptions The debug options * @returns {IDebugData} Data describing the required information for starting debug process. */ - createDebugData(projectData: IProjectData, options: IDeviceIdentifier): IDebugData; -} - -/** - * Describes methods for debug operation. - */ -interface IDebugServiceBase extends NodeJS.EventEmitter { - /** - * Starts debug operation based on the specified debug data. - * @param {IDebugData} debugData Describes information for device and application that will be debugged. - * @param {IDebugOptions} debugOptions Describe possible options to modify the behaivor of the debug operation, for example stop on the first line. - * @returns {Promise} Device Identifier, full url and port where the frontend client can be connected. - */ - debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; -} - -interface IDebugService extends IDebugServiceBase { - /** - * Stops debug operation for a specific device. - * @param {string} deviceIdentifier Identifier of the device fo which debugging will be stopped. - * @returns {Promise} - */ - debugStop(deviceIdentifier: string): Promise; + getDebugData(deviceIdentifier: string, projectData: IProjectData, debugOptions: IDebugOptions): IDebugData; } /** @@ -155,9 +118,24 @@ interface IDebugResultInfo { debugUrl: string; } -interface IDebugController extends IRunController { - // TODO: add disableDebugging method - enableDebugging(projectData: IProjectData, deviceDescriptor: ILiveSyncDeviceInfo, refreshInfo: IRestartApplicationInfo): Promise; - attachDebugger(settings: IAttachDebuggerOptions): Promise; +interface IAppDebugData extends IProjectDir { + /** + * Application identifier of the app that it will be debugged. + */ + applicationIdentifier: string; + + /** + * The name of the application, for example `MyProject`. + */ + projectName?: string; +} + +interface IDebugController { + startDebug(debugData: IDebugData): Promise; + stopDebug(deviceIdentifier: string): Promise; printDebugInformation(debugInformation: IDebugInformation, fireDebuggerAttachedEvent?: boolean): IDebugInformation; + enableDebuggingCoreWithoutWaitingCurrentAction(projectDir: string, deviceIdentifier: string, debugOptions: IDebugOptions): Promise; + enableDebugging(enableDebuggingData: IEnableDebuggingData): Promise[]; + disableDebugging(disableDebuggingData: IDisableDebuggingData): Promise; + attachDebugger(attachDebuggerData: IAttachDebuggerData): Promise; } diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index d55b1a88c7..dd72481d34 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -1,11 +1,11 @@ import { EventEmitter } from "events"; declare global { - interface IRunOnDeviceProcessInfo { + interface ILiveSyncProcessData { timer: NodeJS.Timer; actionsChain: Promise; isStopped: boolean; - deviceDescriptors: ILiveSyncDeviceInfo[]; + deviceDescriptors: ILiveSyncDeviceDescriptor[]; currentSyncAction: Promise; syncToPreviewApp: boolean; platforms: string[]; @@ -48,7 +48,7 @@ declare global { /** * Describes information for LiveSync on a device. */ - interface ILiveSyncDeviceInfo extends IOptionalOutputPath, IOptionalDebuggingOptions { + interface ILiveSyncDeviceDescriptor extends IOptionalOutputPath, IOptionalDebuggingOptions { /** * Device identifier. */ @@ -73,7 +73,7 @@ declare global { /** * Describes a LiveSync operation. */ - interface ILiveSyncInfo extends IProjectDir, IEnvOptions, IRelease, IHasUseHotModuleReloadOption, IHasSyncToPreviewAppOption { + interface ILiveSyncInfo extends IProjectDir, IEnvOptions, IRelease, IHasUseHotModuleReloadOption { emulator?: boolean; /** @@ -102,13 +102,6 @@ declare global { nativePrepare?: INativePrepare; } - interface IHasSyncToPreviewAppOption { - /** - * Defines if the livesync should be executed in preview app on device. - */ - syncToPreviewApp?: boolean; - } - interface IHasUseHotModuleReloadOption { /** * Defines if the hot module reload should be used. @@ -140,11 +133,11 @@ declare global { interface ILiveSyncService extends EventEmitter { /** * 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 {ILiveSyncDeviceDescriptor[]} 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; + liveSync(deviceDescriptors: ILiveSyncDeviceDescriptor[], liveSyncData: ILiveSyncInfo): Promise; /** * Starts LiveSync operation to Preview app. @@ -167,16 +160,11 @@ declare global { * In case LiveSync has been started on many devices, but stopped for some of them at a later point, * calling the method after that will return information only for devices for which LiveSync operation is in progress. * @param {string} projectDir The path to project for which the LiveSync operation is executed - * @returns {ILiveSyncDeviceInfo[]} Array of elements describing parameters used to start LiveSync on each device. + * @returns {ILiveSyncDeviceDescriptor[]} Array of elements describing parameters used to start LiveSync on each device. */ - getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[]; + getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceDescriptor[]; } - /** - * Describes additional debugging settings. - */ - interface IDebuggingAdditionalOptions extends IProjectDir { } - /** * Describes settings used when disabling debugging. */ @@ -189,10 +177,15 @@ declare global { debugOptions?: IDebugOptions; } - /** - * Describes settings used when enabling debugging. - */ - interface IEnableDebuggingDeviceOptions extends Mobile.IDeviceIdentifier, IOptionalDebuggingOptions { } + interface IEnableDebuggingData extends IProjectDir, IOptionalDebuggingOptions { + deviceIdentifiers: string[]; + } + + interface IDisableDebuggingData extends IProjectDir { + deviceIdentifiers: string[]; + } + + interface IAttachDebuggerData extends IProjectDir, Mobile.IDeviceIdentifier, IOptionalDebuggingOptions, IIsEmulator, IPlatform, IOptionalOutputPath { } /** * Describes settings passed to livesync service in order to control event emitting during refresh application. @@ -202,12 +195,6 @@ declare global { shouldCheckDeveloperDiscImage: boolean; } - /** - * Describes settings used for attaching a debugger. - */ - interface IAttachDebuggerOptions extends IDebuggingAdditionalOptions, IEnableDebuggingDeviceOptions, IIsEmulator, IPlatform, IOptionalOutputPath { - } - interface IConnectTimeoutOption { /** * Time to wait for successful connection. Defaults to 30000 miliseconds. @@ -219,7 +206,7 @@ declare global { filesToRemove: string[]; filesToSync: string[]; isReinstalled: boolean; - liveSyncDeviceInfo: ILiveSyncDeviceInfo; + liveSyncDeviceData: ILiveSyncDeviceDescriptor; hmrData: IPlatformHmrData; force?: boolean; } @@ -237,7 +224,7 @@ declare global { interface IFullSyncInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption, IConnectTimeoutOption { device: Mobile.IDevice; watch: boolean; - liveSyncDeviceInfo: ILiveSyncDeviceInfo; + liveSyncDeviceData: ILiveSyncDeviceDescriptor; force?: boolean; } @@ -305,7 +292,7 @@ declare global { * @param {boolean} isFullSync Indicates if the operation is part of a fullSync * @return {Promise} Returns the ILocalToDevicePathData of all transfered files */ - transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo, options: ITransferFilesOptions): Promise; + transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceData: ILiveSyncDeviceDescriptor, options: ITransferFilesOptions): Promise; } interface IAndroidNativeScriptDeviceLiveSyncService extends INativeScriptDeviceLiveSyncService { @@ -445,7 +432,7 @@ declare global { /** * Describes additional options, that can be passed to LiveSyncCommandHelper. */ - interface ILiveSyncCommandHelperAdditionalOptions extends IBuildPlatformAction, INativePrepare, IHasSyncToPreviewAppOption { + interface ILiveSyncCommandHelperAdditionalOptions extends IBuildPlatformAction, INativePrepare { /** * A map representing devices which have debugging enabled initially. */ @@ -468,7 +455,6 @@ declare global { * @returns {Promise} */ executeLiveSyncOperation(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise; - executeLiveSyncOperationWithDebug(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise; getPlatformsForOperation(platform: string): string[]; /** @@ -485,9 +471,20 @@ declare global { * @returns {Promise} */ executeCommandLiveSync(platform?: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise; + createDeviceDescriptors(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise; + getDeviceInstances(platform?: string): Promise; + getLiveSyncData(projectDir: string): ILiveSyncInfo; } interface ILiveSyncServiceResolver { resolveLiveSyncService(platform: string): IPlatformLiveSyncService; } -} \ No newline at end of file + + interface ILiveSyncProcessDataService { + getPersistedData(projectDir: string): ILiveSyncProcessData; + getDeviceDescriptors(projectDir: string): ILiveSyncDeviceDescriptor[]; + getAllPersistedData(): IDictionary; + persistData(projectDir: string, deviceDescriptors: ILiveSyncDeviceDescriptor[], platforms: string[]): void; + hasDeviceDescriptors(projectDir: string): boolean; + } +} diff --git a/lib/definitions/preview-app-livesync.d.ts b/lib/definitions/preview-app-livesync.d.ts index 1fb10a0972..414b4cffe7 100644 --- a/lib/definitions/preview-app-livesync.d.ts +++ b/lib/definitions/preview-app-livesync.d.ts @@ -2,11 +2,6 @@ import { FilePayload, Device, FilesPayload } from "nativescript-preview-sdk"; import { EventEmitter } from "events"; declare global { - interface IPreviewAppLiveSyncService extends EventEmitter { - syncFiles(data: IPreviewAppLiveSyncData, filesToSync: string[], filesToRemove: string[]): Promise; - syncFilesForPlatformSafe(data: IPreviewAppLiveSyncData, filesData: IPreviewAppFilesData, platform: string, deviceId?: string): Promise; - } - interface IPreviewAppFilesService { getInitialFilesPayload(liveSyncData: IPreviewAppLiveSyncData, platform: string, deviceId?: string): FilesPayload; getFilesPayload(liveSyncData: IPreviewAppLiveSyncData, filesData: IPreviewAppFilesData, platform: string, deviceId?: string): FilesPayload; @@ -79,4 +74,9 @@ declare global { subscribeKey: string; default?: boolean; } + + interface IPreviewAppController { + startPreview(data: IPreviewAppLiveSyncData): Promise; + stopPreview(): Promise; + } } \ No newline at end of file diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 4e6008991a..4cca0dee0f 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -358,7 +358,7 @@ interface IValidatePlatformOutput { } interface ITestExecutionService { - startKarmaServer(platform: string, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise; + startKarmaServer(platform: string, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceDescriptor[]): Promise; canStartKarmaServer(projectData: IProjectData): Promise; } diff --git a/lib/definitions/run.d.ts b/lib/definitions/run.d.ts index b2014cf661..2fb421c716 100644 --- a/lib/definitions/run.d.ts +++ b/lib/definitions/run.d.ts @@ -1,28 +1,27 @@ -interface IRunData { - projectDir: string; - liveSyncInfo: ILiveSyncInfo; - deviceDescriptors: ILiveSyncDeviceInfo[]; -} +import { EventEmitter } from "events"; -interface IRunController { - run(runData: IRunData): Promise; - stop(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise; - getDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[]; -} +declare global { -interface IRunEmitter { - emitRunStartedEvent(projectData: IProjectData, device: Mobile.IDevice): void; - emitRunNotificationEvent(projectData: IProjectData, device: Mobile.IDevice, notification: string): void; - emitRunErrorEvent(projectData: IProjectData, device: Mobile.IDevice, error: Error): void; - emitRunExecutedEvent(projectData: IProjectData, device: Mobile.IDevice, options: { syncedFiles: string[], isFullSync: boolean }): void; - emitRunStoppedEvent(projectDir: string, deviceIdentifier: string): void; - emitDebuggerAttachedEvent(debugInformation: IDebugInformation): void; - emitDebuggerDetachedEvent(device: Mobile.IDevice): void; - emitUserInteractionNeededEvent(projectData: IProjectData, device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo): void; -} + interface IRunData { + liveSyncInfo: ILiveSyncInfo; + deviceDescriptors: ILiveSyncDeviceDescriptor[]; + } + + interface IStopRunData { + projectDir: string; + deviceIdentifiers?: string[]; + stopOptions?: { shouldAwaitAllActions: boolean }; + } + + interface IRunController extends EventEmitter { + run(runData: IRunData): Promise; + stop(data: IStopRunData): Promise; + getDeviceDescriptors(data: { projectDir: string }): ILiveSyncDeviceDescriptor[]; + } -interface IDeviceInstallAppService { - installOnDevice(device: Mobile.IDevice, buildData: IBuildData, packageFile?: string): Promise; - installOnDeviceIfNeeded(device: Mobile.IDevice, buildData: IBuildData, packageFile?: string): Promise; - shouldInstall(device: Mobile.IDevice, buildData: IBuildData): Promise; + interface IDeviceInstallAppService { + installOnDevice(device: Mobile.IDevice, buildData: IBuildData, packageFile?: string): Promise; + installOnDeviceIfNeeded(device: Mobile.IDevice, buildData: IBuildData, packageFile?: string): Promise; + shouldInstall(device: Mobile.IDevice, buildData: IBuildData): Promise; + } } diff --git a/lib/emitters/preview-app-emitter.ts b/lib/emitters/preview-app-emitter.ts deleted file mode 100644 index 99923fbef2..0000000000 --- a/lib/emitters/preview-app-emitter.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { EventEmitter } from "events"; -import { PreviewAppLiveSyncEvents } from "../services/livesync/playground/preview-app-constants"; - -export class PreviewAppEmitter extends EventEmitter { - public emitPreviewAppLiveSyncError(data: IPreviewAppLiveSyncData, deviceId: string, error: Error, platform?: string) { - this.emit(PreviewAppLiveSyncEvents.PREVIEW_APP_LIVE_SYNC_ERROR, { - error, - data, - platform, - deviceId - }); - } -} -$injector.register("previewAppEmitter", PreviewAppEmitter); diff --git a/lib/emitters/run-emitter.ts b/lib/emitters/run-emitter.ts deleted file mode 100644 index 947222292a..0000000000 --- a/lib/emitters/run-emitter.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { EventEmitter } from "events"; -import { RunOnDeviceEvents, DEBUGGER_DETACHED_EVENT_NAME, USER_INTERACTION_NEEDED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME } from "../constants"; - -export class RunEmitter extends EventEmitter implements IRunEmitter { - constructor( - private $logger: ILogger - ) { super(); } - - public emitRunStartedEvent(projectData: IProjectData, device: Mobile.IDevice): void { - this.emitCore(RunOnDeviceEvents.runOnDeviceStarted, { - projectDir: projectData.projectDir, - deviceIdentifier: device.deviceInfo.identifier, - applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()] - }); - } - - public emitRunNotificationEvent(projectData: IProjectData, device: Mobile.IDevice, notification: string): void { - this.emitCore(RunOnDeviceEvents.runOnDeviceNotification, { - projectDir: projectData.projectDir, - deviceIdentifier: device.deviceInfo.identifier, - applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], - notification - }); - } - - public emitRunErrorEvent(projectData: IProjectData, device: Mobile.IDevice, error: Error): void { - this.emitCore(RunOnDeviceEvents.runOnDeviceError, { - projectDir: projectData.projectDir, - deviceIdentifier: device.deviceInfo.identifier, - applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], - error, - }); - } - - public emitRunExecutedEvent(projectData: IProjectData, device: Mobile.IDevice, options: { syncedFiles: string[], isFullSync: boolean }): void { - this.emitCore(RunOnDeviceEvents.runOnDeviceExecuted, { - projectDir: projectData.projectDir, - deviceIdentifier: device.deviceInfo.identifier, - applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], - syncedFiles: options.syncedFiles, - isFullSync: options.isFullSync - }); - } - - public emitRunStoppedEvent(projectDir: string, deviceIdentifier: string): void { - this.emitCore(RunOnDeviceEvents.runOnDeviceStopped, { - projectDir, - deviceIdentifier - }); - } - - public emitDebuggerAttachedEvent(debugInformation: IDebugInformation): void { - this.emit(DEBUGGER_ATTACHED_EVENT_NAME, debugInformation); - } - - public emitDebuggerDetachedEvent(device: Mobile.IDevice): void { - const deviceIdentifier = device.deviceInfo.identifier; - this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier }); - } - - public emitUserInteractionNeededEvent(projectData: IProjectData, device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo): void { - const deviceIdentifier = device.deviceInfo.identifier; - const attachDebuggerOptions: IAttachDebuggerOptions = { - platform: device.deviceInfo.platform, - isEmulator: device.isEmulator, - projectDir: projectData.projectDir, - deviceIdentifier, - debugOptions: deviceDescriptor.debugOptions, - outputPath: deviceDescriptor.outputPath - }; - this.emit(USER_INTERACTION_NEEDED_EVENT_NAME, attachDebuggerOptions); - } - - private emitCore(event: string, data: ILiveSyncEventData): void { - this.$logger.trace(`Will emit event ${event} with data`, data); - this.emit(event, data); - } -} -$injector.register("runEmitter", RunEmitter); diff --git a/lib/helpers/deploy-command-helper.ts b/lib/helpers/deploy-command-helper.ts index d037ad435d..40a78e01cd 100644 --- a/lib/helpers/deploy-command-helper.ts +++ b/lib/helpers/deploy-command-helper.ts @@ -23,7 +23,7 @@ export class DeployCommandHelper { const devices = this.$devicesService.getDeviceInstances() .filter(d => !platform || d.deviceInfo.platform.toLowerCase() === platform.toLowerCase()); - const deviceDescriptors: ILiveSyncDeviceInfo[] = devices + const deviceDescriptors: ILiveSyncDeviceDescriptor[] = devices .map(d => { const buildConfig: IBuildConfig = { buildForDevice: !d.isEmulator, @@ -50,7 +50,7 @@ export class DeployCommandHelper { projectDir: this.$projectData.projectDir }); - const info: ILiveSyncDeviceInfo = { + const info: ILiveSyncDeviceDescriptor = { identifier: d.deviceInfo.identifier, buildAction, debuggingEnabled: additionalOptions && additionalOptions.deviceDebugMap && additionalOptions.deviceDebugMap[d.deviceInfo.identifier], @@ -75,7 +75,6 @@ export class DeployCommandHelper { }; await this.$deployController.deploy({ - projectDir: this.$projectData.projectDir, liveSyncInfo, deviceDescriptors }); diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 2413dd749c..419694233e 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -1,5 +1,4 @@ import { RunOnDeviceEvents } from "../constants"; -import { RunEmitter } from "../emitters/run-emitter"; import { DeployController } from "../controllers/deploy-controller"; export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { @@ -9,7 +8,6 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { private $buildDataService: IBuildDataService, private $projectData: IProjectData, private $options: IOptions, - private $runEmitter: RunEmitter, private $deployController: DeployController, private $iosDeviceOperations: IIOSDeviceOperations, private $mobileHelper: Mobile.IMobileHelper, @@ -21,7 +19,6 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { private $errors: IErrors, private $iOSSimulatorLogProvider: Mobile.IiOSSimulatorLogProvider, private $cleanupService: ICleanupService, - private $debugController: IDebugController, private $runController: IRunController ) { } @@ -29,6 +26,23 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { return this.$injector.resolve("platformsDataService"); } + // TODO: Remove this and replace it with buildData + public getLiveSyncData(projectDir: string): ILiveSyncInfo { + const liveSyncInfo: ILiveSyncInfo = { + projectDir, + skipWatcher: !this.$options.watch || this.$options.justlaunch, + clean: this.$options.clean, + release: this.$options.release, + env: this.$options.env, + timeout: this.$options.timeout, + useHotModuleReload: this.$options.hmr, + force: this.$options.force, + emulator: this.$options.emulator + }; + + return liveSyncInfo; + } + public async getDeviceInstances(platform?: string): Promise { await this.$devicesService.initialize({ platform, @@ -44,25 +58,9 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { return devices; } - public createLiveSyncInfo(): ILiveSyncInfo { - const liveSyncInfo: ILiveSyncInfo = { - projectDir: this.$projectData.projectDir, - skipWatcher: !this.$options.watch || this.$options.justlaunch, - clean: this.$options.clean, - release: this.$options.release, - env: this.$options.env, - timeout: this.$options.timeout, - useHotModuleReload: this.$options.hmr, - force: this.$options.force, - emulator: this.$options.emulator - }; - - return liveSyncInfo; - } - - public async createDeviceDescriptors(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise { + public async createDeviceDescriptors(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise { // Now let's take data for each device: - const deviceDescriptors: ILiveSyncDeviceInfo[] = devices + const deviceDescriptors: ILiveSyncDeviceDescriptor[] = devices .map(d => { const buildConfig: IBuildConfig = { buildForDevice: !d.isEmulator, @@ -91,7 +89,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { projectDir: this.$projectData.projectDir }); - const info: ILiveSyncDeviceInfo = { + const info: ILiveSyncDeviceDescriptor = { identifier: d.deviceInfo.identifier, buildAction, debuggingEnabled: additionalOptions && additionalOptions.deviceDebugMap && additionalOptions.deviceDebugMap[d.deviceInfo.identifier], @@ -120,13 +118,12 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { const { liveSyncInfo, deviceDescriptors } = await this.executeLiveSyncOperationCore(devices, platform, additionalOptions); await this.$runController.run({ - projectDir: this.$projectData.projectDir, liveSyncInfo, deviceDescriptors }); const remainingDevicesToSync = devices.map(d => d.deviceInfo.identifier); - this.$runEmitter.on(RunOnDeviceEvents.runOnDeviceStopped, (data: { projectDir: string, deviceIdentifier: string }) => { + this.$runController.on(RunOnDeviceEvents.runOnDeviceStopped, (data: { projectDir: string, deviceIdentifier: string }) => { _.remove(remainingDevicesToSync, d => d === data.deviceIdentifier); if (remainingDevicesToSync.length === 0) { @@ -152,26 +149,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { return result; } - public async executeLiveSyncOperationWithDebug(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise { - const { liveSyncInfo, deviceDescriptors } = await this.executeLiveSyncOperationCore(devices, platform, additionalOptions); - - await this.$debugController.run({ - projectDir: this.$projectData.projectDir, - liveSyncInfo, - deviceDescriptors - }); - - const remainingDevicesToSync = devices.map(d => d.deviceInfo.identifier); - this.$runEmitter.on(RunOnDeviceEvents.runOnDeviceStopped, (data: { projectDir: string, deviceIdentifier: string }) => { - _.remove(remainingDevicesToSync, d => d === data.deviceIdentifier); - - if (remainingDevicesToSync.length === 0) { - process.exit(ErrorCodes.ALL_DEVICES_DISCONNECTED); - } - }); - } - - private async executeLiveSyncOperationCore(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise<{liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]}> { + private async executeLiveSyncOperationCore(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise<{liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceDescriptor[]}> { if (!devices || !devices.length) { if (platform) { this.$errors.failWithoutHelp("Unable to find applicable devices to execute operation. Ensure connected devices are trusted and try again."); @@ -192,9 +170,8 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { } } - // Extract this to 2 separate services -> deviceDescriptorsService, liveSyncDataService -> getLiveSyncData() const deviceDescriptors = await this.createDeviceDescriptors(devices, platform, additionalOptions); - const liveSyncInfo = this.createLiveSyncInfo(); + const liveSyncInfo = this.getLiveSyncData(this.$projectData.projectDir); if (this.$options.release) { await this.runInRelease(platform, deviceDescriptors, liveSyncInfo); @@ -204,7 +181,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { return { liveSyncInfo, deviceDescriptors }; } - private async runInRelease(platform: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise { + private async runInRelease(platform: string, deviceDescriptors: ILiveSyncDeviceDescriptor[], liveSyncInfo: ILiveSyncInfo): Promise { await this.$devicesService.initialize({ platform, deviceId: this.$options.device, @@ -214,7 +191,6 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { }); await this.$deployController.deploy({ - projectDir: this.$projectData.projectDir, liveSyncInfo: { ...liveSyncInfo, clean: true, skipWatcher: true }, deviceDescriptors }); diff --git a/lib/services/debug-data-service.ts b/lib/services/debug-data-service.ts index 77ae6705f8..910799d986 100644 --- a/lib/services/debug-data-service.ts +++ b/lib/services/debug-data-service.ts @@ -2,14 +2,16 @@ export class DebugDataService implements IDebugDataService { constructor( private $devicesService: Mobile.IDevicesService ) { } - public createDebugData(projectData: IProjectData, options: IDeviceIdentifier): IDebugData { - const device = this.$devicesService.getDeviceByIdentifier(options.device); + + public getDebugData(deviceIdentifier: string, projectData: IProjectData, debugOptions: IDebugOptions): IDebugData { + const device = this.$devicesService.getDeviceByIdentifier(deviceIdentifier); return { applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], projectDir: projectData.projectDir, - deviceIdentifier: options.device, - projectName: projectData.projectName + deviceIdentifier, + projectName: projectData.projectName, + debugOptions }; } } diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts deleted file mode 100644 index eca19315b1..0000000000 --- a/lib/services/debug-service.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { parse } from "url"; -import { EventEmitter } from "events"; -import { CONNECTION_ERROR_EVENT_NAME, DebugCommandErrors } from "../constants"; -import { CONNECTED_STATUS } from "../common/constants"; -import { DebugTools, TrackActionNames } from "../constants"; -import { performanceLog } from "../common/decorators"; - -export class DebugService extends EventEmitter implements IDebugService { - private _platformDebugServices: IDictionary; - constructor(private $devicesService: Mobile.IDevicesService, - private $errors: IErrors, - private $injector: IInjector, - private $mobileHelper: Mobile.IMobileHelper, - private $analyticsService: IAnalyticsService) { - super(); - this._platformDebugServices = {}; - } - - // TODO: Move this logic to debugController - @performanceLog() - public async debug(debugData: IDebugData, options: IDebugOptions): Promise { - const device = this.$devicesService.getDeviceByIdentifier(debugData.deviceIdentifier); - - if (!device) { - this.$errors.failWithoutHelp(`Cannot find device with identifier ${debugData.deviceIdentifier}.`); - } - - if (device.deviceInfo.status !== CONNECTED_STATUS) { - this.$errors.failWithoutHelp(`The device with identifier ${debugData.deviceIdentifier} is unreachable. Make sure it is Trusted and try again.`); - } - - await this.$analyticsService.trackEventActionInGoogleAnalytics({ - action: TrackActionNames.Debug, - device, - additionalData: this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform) && options && options.inspector ? DebugTools.Inspector : DebugTools.Chrome, - projectDir: debugData.projectDir - }); - - if (!(await device.applicationManager.isApplicationInstalled(debugData.applicationIdentifier))) { - this.$errors.failWithoutHelp(`The application ${debugData.applicationIdentifier} is not installed on device with identifier ${debugData.deviceIdentifier}.`); - } - - const debugOptions: IDebugOptions = _.cloneDeep(options); - const debugService = this.getDeviceDebugService(device); - if (!debugService) { - this.$errors.failWithoutHelp(`Unsupported device OS: ${device.deviceInfo.platform}. You can debug your applications only on iOS or Android.`); - } - - const debugResultInfo = await debugService.debug(debugData, debugOptions); - - return this.getDebugInformation(debugResultInfo, device.deviceInfo.identifier); - } - - public debugStop(deviceIdentifier: string): Promise { - const device = this.$devicesService.getDeviceByIdentifier(deviceIdentifier); - const debugService = this.getDeviceDebugService(device); - return debugService.debugStop(); - } - - protected getDeviceDebugService(device: Mobile.IDevice): IDeviceDebugService { - if (!this._platformDebugServices[device.deviceInfo.identifier]) { - const devicePlatform = device.deviceInfo.platform; - if (this.$mobileHelper.isiOSPlatform(devicePlatform)) { - this._platformDebugServices[device.deviceInfo.identifier] = this.$injector.resolve("iOSDeviceDebugService", { device }); - } else if (this.$mobileHelper.isAndroidPlatform(devicePlatform)) { - this._platformDebugServices[device.deviceInfo.identifier] = this.$injector.resolve("androidDeviceDebugService", { device }); - } else { - this.$errors.failWithoutHelp(DebugCommandErrors.UNSUPPORTED_DEVICE_OS_FOR_DEBUGGING); - } - - this.attachConnectionErrorHandlers(this._platformDebugServices[device.deviceInfo.identifier]); - } - - return this._platformDebugServices[device.deviceInfo.identifier]; - } - - private attachConnectionErrorHandlers(platformDebugService: IDeviceDebugService) { - let connectionErrorHandler = (e: Error) => this.emit(CONNECTION_ERROR_EVENT_NAME, e); - connectionErrorHandler = connectionErrorHandler.bind(this); - platformDebugService.on(CONNECTION_ERROR_EVENT_NAME, connectionErrorHandler); - } - - private getDebugInformation(debugResultInfo: IDebugResultInfo, deviceIdentifier: string): IDebugInformation { - const debugInfo: IDebugInformation = { - url: debugResultInfo.debugUrl, - port: 0, - deviceIdentifier - }; - - if (debugResultInfo.debugUrl) { - const parseQueryString = true; - const wsQueryParam = parse(debugResultInfo.debugUrl, parseQueryString).query.ws; - const hostPortSplit = wsQueryParam && wsQueryParam.split(":"); - debugInfo.port = hostPortSplit && +hostPortSplit[1]; - } - - return debugInfo; - } -} - -$injector.register("debugService", DebugService); diff --git a/lib/services/livesync-process-data-service.ts b/lib/services/livesync-process-data-service.ts new file mode 100644 index 0000000000..6edd76ef88 --- /dev/null +++ b/lib/services/livesync-process-data-service.ts @@ -0,0 +1,34 @@ +export class LiveSyncProcessDataService implements ILiveSyncProcessDataService { + protected processes: IDictionary = {}; + + public persistData(projectDir: string, deviceDescriptors: ILiveSyncDeviceDescriptor[], platforms: string[]): void { + this.processes[projectDir] = this.processes[projectDir] || Object.create(null); + this.processes[projectDir].actionsChain = this.processes[projectDir].actionsChain || Promise.resolve(); + this.processes[projectDir].currentSyncAction = this.processes[projectDir].actionsChain; + this.processes[projectDir].isStopped = false; + this.processes[projectDir].platforms = platforms; + + const currentDeviceDescriptors = this.getDeviceDescriptors(projectDir); + this.processes[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), "identifier"); + } + + public getPersistedData(projectDir: string): ILiveSyncProcessData { + return this.processes[projectDir]; + } + + public getDeviceDescriptors(projectDir: string): ILiveSyncDeviceDescriptor[] { + const liveSyncProcessesInfo = this.processes[projectDir] || {}; + const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors; + return currentDescriptors || []; + } + + public hasDeviceDescriptors(projectDir: string): boolean { + const deviceDescriptors = this.getDeviceDescriptors(projectDir); + return !!deviceDescriptors.length; + } + + public getAllPersistedData() { + return this.processes; + } +} +$injector.register("liveSyncProcessDataService", LiveSyncProcessDataService); diff --git a/lib/services/livesync/android-device-livesync-service-base.ts b/lib/services/livesync/android-device-livesync-service-base.ts index 2459d625dd..1f3049232d 100644 --- a/lib/services/livesync/android-device-livesync-service-base.ts +++ b/lib/services/livesync/android-device-livesync-service-base.ts @@ -12,7 +12,7 @@ export abstract class AndroidDeviceLiveSyncServiceBase extends DeviceLiveSyncSer public abstract async transferFilesOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise; public abstract async transferDirectoryOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise; - public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo, options: ITransferFilesOptions): Promise { + public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceDescriptor: ILiveSyncDeviceDescriptor, options: ITransferFilesOptions): Promise { const deviceHashService = this.device.fileSystem.getDeviceHashService(deviceAppData.appIdentifier); const currentHashes = await deviceHashService.generateHashesFromLocalToDevicePaths(localToDevicePaths); const transferredFiles = await this.transferFilesCore(deviceAppData, localToDevicePaths, projectFilesPath, currentHashes, options); diff --git a/lib/services/livesync/device-livesync-service-base.ts b/lib/services/livesync/device-livesync-service-base.ts index c10c23fd7d..bd250bbbe4 100644 --- a/lib/services/livesync/device-livesync-service-base.ts +++ b/lib/services/livesync/device-livesync-service-base.ts @@ -29,7 +29,7 @@ export abstract class DeviceLiveSyncServiceBase { } @performanceLog() - public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo, options: ITransferFilesOptions): Promise { + public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceDescriptor: ILiveSyncDeviceDescriptor, options: ITransferFilesOptions): Promise { let transferredFiles: Mobile.ILocalToDevicePathData[] = []; if (options.isFullSync) { diff --git a/lib/services/livesync/ios-livesync-service.ts b/lib/services/livesync/ios-livesync-service.ts index 89b367616e..70928ea873 100644 --- a/lib/services/livesync/ios-livesync-service.ts +++ b/lib/services/livesync/ios-livesync-service.ts @@ -62,7 +62,7 @@ export class IOSLiveSyncService extends PlatformLiveSyncServiceBase implements I return this.fullSync({ projectData: liveSyncInfo.projectData, device, - liveSyncDeviceInfo: liveSyncInfo.liveSyncDeviceInfo, + liveSyncDeviceData: liveSyncInfo.liveSyncDeviceData, watch: true, useHotModuleReload: liveSyncInfo.useHotModuleReload }); diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index e3e3b6e615..577a2f9048 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -62,7 +62,7 @@ export abstract class PlatformLiveSyncServiceBase { const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, null, []); - const modifiedFilesData = await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, projectData, syncInfo.liveSyncDeviceInfo, { isFullSync: true, force: syncInfo.force }); + const modifiedFilesData = await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, projectData, syncInfo.liveSyncDeviceData, { isFullSync: true, force: syncInfo.force }); return { modifiedFilesData, @@ -101,7 +101,7 @@ export abstract class PlatformLiveSyncServiceBase { const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, existingFiles, []); modifiedLocalToDevicePaths.push(...localToDevicePaths); - modifiedLocalToDevicePaths = await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, projectData, liveSyncInfo.liveSyncDeviceInfo, { isFullSync: false, force: liveSyncInfo.force }); + modifiedLocalToDevicePaths = await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, projectData, liveSyncInfo.liveSyncDeviceData, { isFullSync: false, force: liveSyncInfo.force }); } } @@ -128,11 +128,11 @@ export abstract class PlatformLiveSyncServiceBase { }; } - protected async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo, options: ITransferFilesOptions): Promise { + protected async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceData: ILiveSyncDeviceDescriptor, options: ITransferFilesOptions): Promise { let transferredFiles: Mobile.ILocalToDevicePathData[] = []; const deviceLiveSyncService = this.getDeviceLiveSyncService(deviceAppData.device, projectData); - transferredFiles = await deviceLiveSyncService.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, projectData, liveSyncDeviceInfo, options); + transferredFiles = await deviceLiveSyncService.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, projectData, liveSyncDeviceData, options); this.logFilesSyncInformation(transferredFiles, "Successfully transferred %s on device %s.", this.$logger.info, deviceAppData.device.deviceInfo.identifier); diff --git a/lib/services/livesync/playground/preview-app-livesync-service.ts b/lib/services/livesync/playground/preview-app-livesync-service.ts deleted file mode 100644 index 66e0e1ac84..0000000000 --- a/lib/services/livesync/playground/preview-app-livesync-service.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { APP_RESOURCES_FOLDER_NAME } from "../../../constants"; -import { EventEmitter } from "events"; -import { performanceLog } from "../../../common/decorators"; -import { PreviewAppEmitter } from "../../../emitters/preview-app-emitter"; - -export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewAppLiveSyncService { - - constructor( - private $logger: ILogger, - private $previewAppEmitter: PreviewAppEmitter, - private $previewAppFilesService: IPreviewAppFilesService, - private $previewAppPluginsService: IPreviewAppPluginsService, - private $previewDevicesService: IPreviewDevicesService, - private $previewSdkService: IPreviewSdkService, - ) { super(); } - - @performanceLog() - public async syncFiles(data: IPreviewAppLiveSyncData, filesToSync: string[], filesToRemove: string[]): Promise { - this.showWarningsForNativeFiles(filesToSync); - - const connectedDevices = this.$previewDevicesService.getConnectedDevices(); - for (const device of connectedDevices) { - await this.$previewAppPluginsService.comparePluginsOnDevice(data, device); - } - - const platforms = _(connectedDevices) - .map(device => device.platform) - .uniq() - .value(); - - for (const platform of platforms) { - await this.syncFilesForPlatformSafe(data, { filesToSync, filesToRemove }, platform); - } - } - - public async syncFilesForPlatformSafe(data: IPreviewAppLiveSyncData, filesData: IPreviewAppFilesData, platform: string, deviceId?: string): Promise { - try { - const payloads = this.$previewAppFilesService.getFilesPayload(data, filesData, platform); - if (payloads && payloads.files && payloads.files.length) { - this.$logger.info(`Start syncing changes for platform ${platform}.`); - await this.$previewSdkService.applyChanges(payloads); - this.$logger.info(`Successfully synced ${payloads.files.map(filePayload => filePayload.file.yellow)} for platform ${platform}.`); - } - } catch (error) { - this.$logger.warn(`Unable to apply changes for platform ${platform}. Error is: ${error}, ${JSON.stringify(error, null, 2)}.`); - this.$previewAppEmitter.emitPreviewAppLiveSyncError(data, deviceId, error); - } - } - - private showWarningsForNativeFiles(files: string[]): void { - _.filter(files, file => file.indexOf(APP_RESOURCES_FOLDER_NAME) > -1) - .forEach(file => this.$logger.warn(`Unable to apply changes from ${APP_RESOURCES_FOLDER_NAME} folder. You need to build your application in order to make changes in ${APP_RESOURCES_FOLDER_NAME} folder.`)); - } -} -$injector.register("previewAppLiveSyncService", PreviewAppLiveSyncService); diff --git a/lib/services/platform-environment-requirements.ts b/lib/services/platform-environment-requirements.ts index c7cf8f52c6..d7bdadd84f 100644 --- a/lib/services/platform-environment-requirements.ts +++ b/lib/services/platform-environment-requirements.ts @@ -11,9 +11,13 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ private $prompter: IPrompter, private $staticConfig: IStaticConfig, private $analyticsService: IAnalyticsService, - // private $previewAppLiveSyncService: IPreviewAppLiveSyncService, + private $injector: IInjector, private $previewQrCodeService: IPreviewQrCodeService) { } + public get $previewAppController(): IPreviewAppController { + return this.$injector.resolve("previewAppController"); + } + public static CLOUD_SETUP_OPTION_NAME = "Configure for Cloud Builds"; public static LOCAL_SETUP_OPTION_NAME = "Configure for Local Builds"; public static TRY_CLOUD_OPERATION_OPTION_NAME = "Try Cloud Operation"; @@ -175,12 +179,11 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ this.$errors.failWithoutHelp(`No project found. In order to sync to playground you need to go to project directory or specify --path option.`); } - // await this.$previewAppLiveSyncService.initialize({ - // projectDir, - // env: options.env, - // useHotModuleReload: options.hmr, - // bundle: true - // }); + await this.$previewAppController.startPreview({ + projectDir, + env: options.env, + useHotModuleReload: options.hmr, + }); await this.$previewQrCodeService.printLiveSyncQrCode({ projectDir, useHotModuleReload: options.hmr, link: options.link }); } diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index 1a050404a1..9f9ebb4728 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -25,7 +25,7 @@ export class TestExecutionService implements ITestExecutionService { public platform: string; - public async startKarmaServer(platform: string, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { + public async startKarmaServer(platform: string, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceDescriptor[]): Promise { platform = platform.toLowerCase(); this.platform = platform; @@ -57,7 +57,6 @@ export class TestExecutionService implements ITestExecutionService { // so it will be sent to device. await this.$runController.run({ - projectDir: liveSyncInfo.projectDir, liveSyncInfo, deviceDescriptors }); diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index c657583381..6a3ad3c342 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -11,8 +11,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp constructor( private $childProcess: IChildProcess, public $hooksService: IHooksService, - private $logger: ILogger, - private $projectData: IProjectData, + private $logger: ILogger ) { super(); } public async compileWithWatch(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { @@ -101,7 +100,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp @performanceLog() @hook('prepareJSApp') private async startWebpackProcess(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { - const envData = this.buildEnvData(platformData.platformNameLowerCase, config.env); + const envData = this.buildEnvData(platformData.platformNameLowerCase, config.env, projectData); const envParams = this.buildEnvCommandLineParams(envData, platformData); const args = [ @@ -123,14 +122,15 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp return childProcess; } - private buildEnvData(platform: string, env: any) { + private buildEnvData(platform: string, env: any, projectData: IProjectData) { const envData = Object.assign({}, env, { [platform.toLowerCase()]: true } ); - const appPath = this.$projectData.getAppDirectoryRelativePath(); - const appResourcesPath = this.$projectData.getAppResourcesRelativeDirectoryPath(); + const appPath = projectData.getAppDirectoryRelativePath(); + const appResourcesPath = projectData.getAppResourcesRelativeDirectoryPath(); + Object.assign(envData, appPath && { appPath }, appResourcesPath && { appResourcesPath } diff --git a/lib/services/webpack/webpack.d.ts b/lib/services/webpack/webpack.d.ts index d58c7978ac..34b34c5399 100644 --- a/lib/services/webpack/webpack.d.ts +++ b/lib/services/webpack/webpack.d.ts @@ -35,10 +35,6 @@ declare global { hasNativeChanges: boolean; } - interface IDeviceRestartApplicationService { - restartOnDevice(deviceDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, platformLiveSyncService: IPlatformLiveSyncService): Promise; - } - interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectServiceBase { getPlatformData(projectData: IProjectData): IPlatformData; validate(projectData: IProjectData, options: IOptions, notConfiguredEnvOptions?: INotConfiguredEnvOptions): Promise; diff --git a/test/services/debug-service.ts b/test/services/debug-service.ts deleted file mode 100644 index 0fe938a557..0000000000 --- a/test/services/debug-service.ts +++ /dev/null @@ -1,267 +0,0 @@ -import { DebugService } from "../../lib/services/debug-service"; -import { Yok } from "../../lib/common/yok"; -import * as stubs from "../stubs"; -import { assert } from "chai"; -import { EventEmitter } from "events"; -import * as constants from "../../lib/common/constants"; -import { CONNECTION_ERROR_EVENT_NAME, DebugCommandErrors, TrackActionNames, DebugTools } from "../../lib/constants"; - -const fakeChromeDebugPort = 123; -const fakeChromeDebugUrl = `fakeChromeDebugUrl?experiments=true&ws=localhost:${fakeChromeDebugPort}`; -const defaultDeviceIdentifier = "Nexus5"; - -class PlatformDebugService extends EventEmitter /* implements IPlatformDebugService */ { - public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { - return { debugUrl: fakeChromeDebugUrl }; - } -} - -interface IDebugTestDeviceInfo { - deviceInfo: { - status: string; - platform: string; - identifier: string; - }; - - isEmulator: boolean; -} - -interface IDebugTestData { - isDeviceFound: boolean; - deviceInformation: IDebugTestDeviceInfo; - isApplicationInstalledOnDevice: boolean; - hostInfo: { - isWindows: boolean; - isDarwin: boolean; - }; -} - -const getDefaultDeviceInformation = (platform?: string): IDebugTestDeviceInfo => ({ - deviceInfo: { - status: constants.CONNECTED_STATUS, - platform: platform || "Android", - identifier: defaultDeviceIdentifier - }, - - isEmulator: false -}); - -const getDefaultTestData = (platform?: string): IDebugTestData => ({ - isDeviceFound: true, - deviceInformation: getDefaultDeviceInformation(platform), - isApplicationInstalledOnDevice: true, - hostInfo: { - isWindows: false, - isDarwin: true - } -}); - -describe("debugService", () => { - const getTestInjectorForTestConfiguration = (testData: IDebugTestData): IInjector => { - const testInjector = new Yok(); - testInjector.register("devicesService", { - getDeviceByIdentifier: (identifier: string): Mobile.IDevice => { - return testData.isDeviceFound ? - { - deviceInfo: testData.deviceInformation.deviceInfo, - - applicationManager: { - isApplicationInstalled: async (appIdentifier: string): Promise => testData.isApplicationInstalledOnDevice - }, - - isEmulator: testData.deviceInformation.isEmulator - } : null; - } - }); - - testInjector.register("androidDeviceDebugService", PlatformDebugService); - - testInjector.register("iOSDeviceDebugService", PlatformDebugService); - - testInjector.register("mobileHelper", { - isAndroidPlatform: (platform: string) => { - return platform.toLowerCase() === "android"; - }, - isiOSPlatform: (platform: string) => { - return platform.toLowerCase() === "ios"; - } - }); - - testInjector.register("errors", stubs.ErrorsStub); - - testInjector.register("hostInfo", testData.hostInfo); - - testInjector.register("logger", stubs.LoggerStub); - - testInjector.register("analyticsService", { - trackEventActionInGoogleAnalytics: (data: IEventActionData) => Promise.resolve() - }); - - return testInjector; - }; - - describe("debug", () => { - const getDebugData = (deviceIdentifier?: string): IDebugData => ({ - deviceIdentifier: deviceIdentifier || defaultDeviceIdentifier, - applicationIdentifier: "org.nativescript.app1", - projectDir: "/Users/user/app1", - projectName: "app1" - }); - - describe("rejects the result promise when", () => { - const assertIsRejected = async (testData: IDebugTestData, expectedError: string, userSpecifiedOptions?: IDebugOptions): Promise => { - const testInjector = getTestInjectorForTestConfiguration(testData); - const debugService = testInjector.resolve(DebugService); - - const debugData = getDebugData(); - await assert.isRejected(debugService.debug(debugData, userSpecifiedOptions), expectedError); - }; - - it("there's no attached device as the specified identifier", async () => { - const testData = getDefaultTestData(); - testData.isDeviceFound = false; - - await assertIsRejected(testData, "Cannot find device with identifier"); - }); - - it("the device is not trusted", async () => { - const testData = getDefaultTestData(); - testData.deviceInformation.deviceInfo.status = constants.UNREACHABLE_STATUS; - - await assertIsRejected(testData, "is unreachable. Make sure it is Trusted "); - }); - - it("the application is not installed on device", async () => { - const testData = getDefaultTestData(); - testData.isApplicationInstalledOnDevice = false; - - await assertIsRejected(testData, "is not installed on device with identifier"); - }); - - it("device is neither iOS or Android", async () => { - const testData = getDefaultTestData(); - testData.deviceInformation.deviceInfo.platform = "WP8"; - - await assertIsRejected(testData, DebugCommandErrors.UNSUPPORTED_DEVICE_OS_FOR_DEBUGGING); - }); - - const assertIsRejectedWhenPlatformDebugServiceFails = async (platform: string): Promise => { - const testData = getDefaultTestData(); - testData.deviceInformation.deviceInfo.platform = platform; - - const testInjector = getTestInjectorForTestConfiguration(testData); - const expectedErrorMessage = "Platform specific error"; - const platformDebugService = testInjector.resolve(`${platform}DeviceDebugService`); - platformDebugService.debug = async (data: IDebugData, debugOptions: IDebugOptions): Promise => { - throw new Error(expectedErrorMessage); - }; - - const debugService = testInjector.resolve(DebugService); - - const debugData = getDebugData(); - await assert.isRejected(debugService.debug(debugData, null), expectedErrorMessage); - }; - - it("androidDeviceDebugService's debug method fails", async () => { - await assertIsRejectedWhenPlatformDebugServiceFails("android"); - }); - - it("iOSDeviceDebugService's debug method fails", async () => { - await assertIsRejectedWhenPlatformDebugServiceFails("iOS"); - }); - }); - - describe(`raises ${CONNECTION_ERROR_EVENT_NAME} event`, () => { - _.each(["android", "iOS"], platform => { - it(`when ${platform}DebugService raises ${CONNECTION_ERROR_EVENT_NAME} event`, async () => { - const testData = getDefaultTestData(); - testData.deviceInformation.deviceInfo.platform = platform; - - const testInjector = getTestInjectorForTestConfiguration(testData); - const debugService = testInjector.resolve(DebugService); - let dataRaisedForConnectionError: any = null; - debugService.on(CONNECTION_ERROR_EVENT_NAME, (data: any) => { - dataRaisedForConnectionError = data; - }); - - const debugData = getDebugData(); - await assert.isFulfilled(debugService.debug(debugData, null)); - - const expectedErrorData = { deviceIdentifier: "deviceId", message: "my message", code: 2048 }; - const platformDebugService = testInjector.resolve(`${platform}DeviceDebugService`); - platformDebugService.emit(CONNECTION_ERROR_EVENT_NAME, expectedErrorData); - assert.deepEqual(dataRaisedForConnectionError, expectedErrorData); - }); - }); - }); - - describe("returns chrome url along with port returned by platform specific debug service", () => { - _.each(["android", "iOS"], platform => { - it(`for ${platform} device`, async () => { - const testData = getDefaultTestData(); - testData.deviceInformation.deviceInfo.platform = platform; - - const testInjector = getTestInjectorForTestConfiguration(testData); - const debugService = testInjector.resolve(DebugService); - - const debugData = getDebugData(); - const debugInfo = await debugService.debug(debugData, null); - - assert.deepEqual(debugInfo, { - url: fakeChromeDebugUrl, - port: fakeChromeDebugPort, - deviceIdentifier: debugData.deviceIdentifier - }); - }); - }); - }); - - describe("tracks to google analytics", () => { - _.each([ - { - testName: "Inspector when --inspector is passed", - debugOptions: { inspector: true }, - additionalData: DebugTools.Inspector - }, - { - testName: "Chrome when no options are passed", - debugOptions: null, - additionalData: DebugTools.Chrome - }, - { - testName: "Chrome when --chrome is passed", - debugOptions: { chrome: true }, - additionalData: DebugTools.Chrome - }], testCase => { - - it(testCase.testName, async () => { - const testData = getDefaultTestData(); - testData.deviceInformation.deviceInfo.platform = "iOS"; - - const testInjector = getTestInjectorForTestConfiguration(testData); - const analyticsService = testInjector.resolve("analyticsService"); - let dataTrackedToGA: IEventActionData = null; - analyticsService.trackEventActionInGoogleAnalytics = async (data: IEventActionData): Promise => { - dataTrackedToGA = data; - }; - - const debugService = testInjector.resolve(DebugService); - const debugData = getDebugData(); - await debugService.debug(debugData, testCase.debugOptions); - const devicesService = testInjector.resolve("devicesService"); - const device = devicesService.getDeviceByIdentifier(testData.deviceInformation.deviceInfo.identifier); - - const expectedData = JSON.stringify({ - action: TrackActionNames.Debug, - device, - additionalData: testCase.additionalData, - projectDir: debugData.projectDir - }, null, 2); - - // Use JSON.stringify as the compared objects link to new instances of different classes. - assert.deepEqual(JSON.stringify(dataTrackedToGA, null, 2), expectedData); - }); - }); - }); - }); -}); From 5cec974ca79089c4ea3ce649a6b0ee433338d112 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 30 May 2019 16:52:41 +0300 Subject: [PATCH 2/2] test: fix unit tests --- test/controllers/debug-controller.ts | 296 ++++++++++++++++++ test/controllers/run-controller.ts | 23 +- test/nativescript-cli-lib.ts | 7 +- .../preview-app-livesync-service.ts | 13 +- test/stubs.ts | 4 +- 5 files changed, 317 insertions(+), 26 deletions(-) create mode 100644 test/controllers/debug-controller.ts diff --git a/test/controllers/debug-controller.ts b/test/controllers/debug-controller.ts new file mode 100644 index 0000000000..a0b6a64b3f --- /dev/null +++ b/test/controllers/debug-controller.ts @@ -0,0 +1,296 @@ +import { Yok } from "../../lib/common/yok"; +import * as stubs from "../stubs"; +import { assert } from "chai"; +import { EventEmitter } from "events"; +import * as constants from "../../lib/common/constants"; +import { CONNECTION_ERROR_EVENT_NAME, DebugCommandErrors, TrackActionNames, DebugTools } from "../../lib/constants"; +import { DebugController } from "../../lib/controllers/debug-controller"; +import { BuildDataService } from "../../lib/services/build-data-service"; +import { DebugDataService } from "../../lib/services/debug-data-service"; +import { LiveSyncServiceResolver } from "../../lib/resolvers/livesync-service-resolver"; +import { PrepareDataService } from "../../lib/services/prepare-data-service"; +import { ProjectDataService } from "../../lib/services/project-data-service"; +import { StaticConfig } from "../../lib/config"; +import { DevicePlatformsConstants } from "../../lib/common/mobile/device-platforms-constants"; +import { LiveSyncProcessDataService } from "../../lib/services/livesync-process-data-service"; + +const fakeChromeDebugPort = 123; +const fakeChromeDebugUrl = `fakeChromeDebugUrl?experiments=true&ws=localhost:${fakeChromeDebugPort}`; +const defaultDeviceIdentifier = "Nexus5"; + +class PlatformDebugService extends EventEmitter /* implements IPlatformDebugService */ { + public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + return { debugUrl: fakeChromeDebugUrl }; + } +} + +interface IDebugTestDeviceInfo { + deviceInfo: { + status: string; + platform: string; + identifier: string; + }; + + isEmulator: boolean; +} + +interface IDebugTestData { + isDeviceFound: boolean; + deviceInformation: IDebugTestDeviceInfo; + isApplicationInstalledOnDevice: boolean; + hostInfo: { + isWindows: boolean; + isDarwin: boolean; + }; +} + +const getDefaultDeviceInformation = (platform?: string): IDebugTestDeviceInfo => ({ + deviceInfo: { + status: constants.CONNECTED_STATUS, + platform: platform || "Android", + identifier: defaultDeviceIdentifier + }, + + isEmulator: false +}); + +const getDefaultTestData = (platform?: string): IDebugTestData => ({ + isDeviceFound: true, + deviceInformation: getDefaultDeviceInformation(platform), + isApplicationInstalledOnDevice: true, + hostInfo: { + isWindows: false, + isDarwin: true + } +}); + +describe("debugController", () => { + const getTestInjectorForTestConfiguration = (testData: IDebugTestData): IInjector => { + const testInjector = new Yok(); + testInjector.register("devicesService", { + getDeviceByIdentifier: (identifier: string): Mobile.IDevice => { + return testData.isDeviceFound ? + { + deviceInfo: testData.deviceInformation.deviceInfo, + + applicationManager: { + isApplicationInstalled: async (appIdentifier: string): Promise => testData.isApplicationInstalledOnDevice + }, + + isEmulator: testData.deviceInformation.isEmulator + } : null; + } + }); + + testInjector.register("androidDeviceDebugService", PlatformDebugService); + + testInjector.register("iOSDeviceDebugService", PlatformDebugService); + + testInjector.register("mobileHelper", { + isAndroidPlatform: (platform: string) => { + return platform.toLowerCase() === "android"; + }, + isiOSPlatform: (platform: string) => { + return platform.toLowerCase() === "ios"; + } + }); + + testInjector.register("errors", stubs.ErrorsStub); + + testInjector.register("hostInfo", testData.hostInfo); + + testInjector.register("logger", stubs.LoggerStub); + + testInjector.register("analyticsService", { + trackEventActionInGoogleAnalytics: (data: IEventActionData) => Promise.resolve() + }); + + testInjector.register("buildDataService", BuildDataService); + testInjector.register("buildController", {}); + + testInjector.register("debugDataService", DebugDataService); + testInjector.register("deviceInstallAppService", {}); + testInjector.register("hmrStatusService", {}); + testInjector.register("hooksService", {}); + testInjector.register("liveSyncServiceResolver", LiveSyncServiceResolver); + testInjector.register("platformsDataService", {}); + testInjector.register("pluginsService", {}); + testInjector.register("prepareController", {}); + testInjector.register("prepareDataService", PrepareDataService); + testInjector.register("prepareNativePlatformService", {}); + testInjector.register("projectDataService", ProjectDataService); + testInjector.register("fs", {}); + testInjector.register("staticConfig", StaticConfig); + testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); + testInjector.register("androidResourcesMigrationService", {}); + testInjector.register("liveSyncProcessDataService", LiveSyncProcessDataService); + + return testInjector; + }; + + describe("debug", () => { + const getDebugData = (debugOptions?: IDebugOptions): IDebugData => ({ + deviceIdentifier: defaultDeviceIdentifier, + applicationIdentifier: "org.nativescript.app1", + projectDir: "/Users/user/app1", + projectName: "app1", + debugOptions: debugOptions || {} + }); + + describe("rejects the result promise when", () => { + const assertIsRejected = async (testData: IDebugTestData, expectedError: string, userSpecifiedOptions?: IDebugOptions): Promise => { + const testInjector = getTestInjectorForTestConfiguration(testData); + const debugController = testInjector.resolve(DebugController); + + const debugData = getDebugData(); + await assert.isRejected(debugController.startDebug(debugData, userSpecifiedOptions), expectedError); + }; + + it("there's no attached device as the specified identifier", async () => { + const testData = getDefaultTestData(); + testData.isDeviceFound = false; + + await assertIsRejected(testData, "Cannot find device with identifier"); + }); + + it("the device is not trusted", async () => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.status = constants.UNREACHABLE_STATUS; + + await assertIsRejected(testData, "is unreachable. Make sure it is Trusted "); + }); + + it("the application is not installed on device", async () => { + const testData = getDefaultTestData(); + testData.isApplicationInstalledOnDevice = false; + + await assertIsRejected(testData, "is not installed on device with identifier"); + }); + + it("device is neither iOS or Android", async () => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.platform = "WP8"; + + await assertIsRejected(testData, DebugCommandErrors.UNSUPPORTED_DEVICE_OS_FOR_DEBUGGING); + }); + + const assertIsRejectedWhenPlatformDebugServiceFails = async (platform: string): Promise => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.platform = platform; + + const testInjector = getTestInjectorForTestConfiguration(testData); + const expectedErrorMessage = "Platform specific error"; + const platformDebugService = testInjector.resolve(`${platform}DeviceDebugService`); + platformDebugService.debug = async (data: IDebugData, debugOptions: IDebugOptions): Promise => { + throw new Error(expectedErrorMessage); + }; + + const debugController = testInjector.resolve(DebugController); + + const debugData = getDebugData(); + await assert.isRejected(debugController.startDebug(debugData, null), expectedErrorMessage); + }; + + it("androidDeviceDebugService's debug method fails", async () => { + await assertIsRejectedWhenPlatformDebugServiceFails("android"); + }); + + it("iOSDeviceDebugService's debug method fails", async () => { + await assertIsRejectedWhenPlatformDebugServiceFails("iOS"); + }); + }); + + describe(`raises ${CONNECTION_ERROR_EVENT_NAME} event`, () => { + _.each(["android", "iOS"], platform => { + it(`when ${platform}DebugService raises ${CONNECTION_ERROR_EVENT_NAME} event`, async () => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.platform = platform; + + const testInjector = getTestInjectorForTestConfiguration(testData); + const debugController = testInjector.resolve(DebugController); + let dataRaisedForConnectionError: any = null; + debugController.on(CONNECTION_ERROR_EVENT_NAME, (data: any) => { + dataRaisedForConnectionError = data; + }); + + const debugData = getDebugData(); + await assert.isFulfilled(debugController.startDebug(debugData, null)); + + const expectedErrorData = { deviceIdentifier: "deviceId", message: "my message", code: 2048 }; + const platformDebugService = testInjector.resolve(`${platform}DeviceDebugService`); + platformDebugService.emit(CONNECTION_ERROR_EVENT_NAME, expectedErrorData); + assert.deepEqual(dataRaisedForConnectionError, expectedErrorData); + }); + }); + }); + + describe("returns chrome url along with port returned by platform specific debug service", () => { + _.each(["android", "iOS"], platform => { + it(`for ${platform} device`, async () => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.platform = platform; + + const testInjector = getTestInjectorForTestConfiguration(testData); + const debugController = testInjector.resolve(DebugController); + + const debugData = getDebugData(); + const debugInfo = await debugController.startDebug(debugData, null); + + assert.deepEqual(debugInfo, { + url: fakeChromeDebugUrl, + port: fakeChromeDebugPort, + deviceIdentifier: debugData.deviceIdentifier + }); + }); + }); + }); + + describe("tracks to google analytics", () => { + _.each([ + { + testName: "Inspector when --inspector is passed", + debugOptions: { inspector: true }, + additionalData: DebugTools.Inspector + }, + { + testName: "Chrome when no options are passed", + debugOptions: null, + additionalData: DebugTools.Chrome + }, + { + testName: "Chrome when --chrome is passed", + debugOptions: { chrome: true }, + additionalData: DebugTools.Chrome + }], testCase => { + + it(testCase.testName, async () => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.platform = "iOS"; + + const testInjector = getTestInjectorForTestConfiguration(testData); + const analyticsService = testInjector.resolve("analyticsService"); + let dataTrackedToGA: IEventActionData = null; + analyticsService.trackEventActionInGoogleAnalytics = async (data: IEventActionData): Promise => { + dataTrackedToGA = data; + }; + + const debugController = testInjector.resolve(DebugController); + const debugData = getDebugData(testCase.debugOptions); + await debugController.startDebug(debugData); + const devicesService = testInjector.resolve("devicesService"); + const device = devicesService.getDeviceByIdentifier(testData.deviceInformation.deviceInfo.identifier); + + const expectedData = JSON.stringify({ + action: TrackActionNames.Debug, + device, + additionalData: testCase.additionalData, + projectDir: debugData.projectDir + }, null, 2); + + // Use JSON.stringify as the compared objects link to new instances of different classes. + assert.deepEqual(JSON.stringify(dataTrackedToGA, null, 2), expectedData); + }); + }); + }); + }); +}); diff --git a/test/controllers/run-controller.ts b/test/controllers/run-controller.ts index bfdc76caff..84ff0a9774 100644 --- a/test/controllers/run-controller.ts +++ b/test/controllers/run-controller.ts @@ -3,12 +3,12 @@ import { InjectorStub } from "../stubs"; import { LiveSyncServiceResolver } from "../../lib/resolvers/livesync-service-resolver"; import { MobileHelper } from "../../lib/common/mobile/mobile-helper"; import { assert } from "chai"; -import { RunEmitter } from "../../lib/emitters/run-emitter"; import { RunOnDeviceEvents } from "../../lib/constants"; import { PrepareData } from "../../lib/data/prepare-data"; import { PrepareDataService } from "../../lib/services/prepare-data-service"; import { BuildDataService } from "../../lib/services/build-data-service"; import { PrepareController } from "../../lib/controllers/prepare-controller"; +import { LiveSyncProcessDataService } from "../../lib/services/livesync-process-data-service"; let isAttachToHmrStatusCalled = false; let prepareData: IPrepareData = null; @@ -22,7 +22,7 @@ const iOSDeviceDescriptor = { identifier: "myiOSDevice", buildAction: async () = const androidDevice = { deviceInfo: { identifier: "myAndroidDevice", platform: "android" } }; const androidDeviceDescriptor = { identifier: "myAndroidDevice", buildAction: async () => buildOutputPath }; -const map: IDictionary<{ device: Mobile.IDevice, descriptor: ILiveSyncDeviceInfo }> = { +const map: IDictionary<{ device: Mobile.IDevice, descriptor: ILiveSyncDeviceDescriptor }> = { myiOSDevice: { device: iOSDevice, descriptor: iOSDeviceDescriptor @@ -101,14 +101,15 @@ function createTestInjector() { injector.register("prepareNativePlatformService", {}); injector.register("projectChangesService", {}); injector.register("runController", RunController); - injector.register("runEmitter", RunEmitter); injector.register("prepareDataService", PrepareDataService); injector.register("buildDataService", BuildDataService); injector.register("analyticsService", ({})); + injector.register("debugController", {}); + injector.register("liveSyncProcessDataService", LiveSyncProcessDataService); const devicesService = injector.resolve("devicesService"); devicesService.getDevicesForPlatform = () => [{ identifier: "myTestDeviceId1" }]; - devicesService.getPlatformsFromDeviceDescriptors = (devices: ILiveSyncDeviceInfo[]) => devices.map(d => map[d.identifier].device.deviceInfo.platform); + devicesService.getPlatformsFromDeviceDescriptors = (devices: ILiveSyncDeviceDescriptor[]) => devices.map(d => map[d.identifier].device.deviceInfo.platform); devicesService.on = () => ({}); return injector; @@ -117,7 +118,6 @@ function createTestInjector() { describe("RunController", () => { let injector: IInjector = null; let runController: RunController = null; - let runEmitter: RunEmitter = null; beforeEach(() => { isAttachToHmrStatusCalled = false; @@ -125,7 +125,6 @@ describe("RunController", () => { injector = createTestInjector(); runController = injector.resolve("runController"); - runEmitter = injector.resolve("runEmitter"); }); describe("runOnDevices", () => { @@ -134,7 +133,6 @@ describe("RunController", () => { mockDevicesService(injector, [iOSDevice]); await runController.run({ - projectDir, liveSyncInfo: { ...liveSyncInfo, skipWatcher: true }, deviceDescriptors: [iOSDeviceDescriptor] }); @@ -145,7 +143,6 @@ describe("RunController", () => { mockDevicesService(injector, [iOSDevice]); await runController.run({ - projectDir, liveSyncInfo: { ...liveSyncInfo, skipWatcher: true, useHotModuleReload: true }, deviceDescriptors: [iOSDeviceDescriptor] }); @@ -156,7 +153,6 @@ describe("RunController", () => { mockDevicesService(injector, [iOSDevice]); await runController.run({ - projectDir, liveSyncInfo, deviceDescriptors: [iOSDeviceDescriptor] }); @@ -167,7 +163,6 @@ describe("RunController", () => { mockDevicesService(injector, [iOSDevice]); await runController.run({ - projectDir, liveSyncInfo, deviceDescriptors: [] }); @@ -206,7 +201,6 @@ describe("RunController", () => { }; await runController.run({ - projectDir, liveSyncInfo, deviceDescriptors: testCase.connectedDevices }); @@ -251,16 +245,17 @@ describe("RunController", () => { for (const testCase of testCases) { it(testCase.name, async () => { - (runController).persistData(projectDir, testCase.currentDeviceIdentifiers.map(identifier => ({ identifier })), ["ios"]); + const liveSyncProcessDataService = injector.resolve("liveSyncProcessDataService"); + (liveSyncProcessDataService).persistData(projectDir, testCase.currentDeviceIdentifiers.map(identifier => ({ identifier })), ["ios"]); const emittedDeviceIdentifiersForLiveSyncStoppedEvent: string[] = []; - runEmitter.on(RunOnDeviceEvents.runOnDeviceStopped, (data: any) => { + runController.on(RunOnDeviceEvents.runOnDeviceStopped, (data: any) => { assert.equal(data.projectDir, projectDir); emittedDeviceIdentifiersForLiveSyncStoppedEvent.push(data.deviceIdentifier); }); - await runController.stop(projectDir, testCase.deviceIdentifiersToBeStopped); + await runController.stop({ projectDir, deviceIdentifiers: testCase.deviceIdentifiersToBeStopped }); assert.deepEqual(emittedDeviceIdentifiersForLiveSyncStoppedEvent, testCase.expectedDeviceIdentifiers); }); diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index 1b6cb50e0e..d51dc9cfa7 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -23,13 +23,14 @@ describe("nativescript-cli-lib", () => { "getIOSAssetsStructure", "getAndroidAssetsStructure" ], - // localBuildService: ["build"], + buildController: ["build"], constants: ["CONFIG_NS_APP_RESOURCES_ENTRY", "CONFIG_NS_APP_ENTRY", "CONFIG_NS_FILE_NAME", "LoggerLevel", "LoggerAppenders"], deviceLogProvider: null, packageManager: ["install", "uninstall", "view", "search"], extensibilityService: ["loadExtensions", "loadExtension", "getInstalledExtensions", "installExtension", "uninstallExtension"], - // liveSyncService: ["liveSync", "stopLiveSync", "enableDebugging", "disableDebugging", "attachDebugger"], - debugService: ["debug"], + runController: ["run", "stop"], + debugController: ["enableDebugging", "disableDebugging", "attachDebugger"], + previewAppController: ["startPreview", "stopPreview"], analyticsSettingsService: ["getClientId"], devicesService: [ "addDeviceDiscovery", diff --git a/test/services/playground/preview-app-livesync-service.ts b/test/services/playground/preview-app-livesync-service.ts index fea024d29d..eb63848ea5 100644 --- a/test/services/playground/preview-app-livesync-service.ts +++ b/test/services/playground/preview-app-livesync-service.ts @@ -10,7 +10,6 @@ import { PreviewAppFilesService } from "../../../lib/services/livesync/playgroun import { PREPARE_READY_EVENT_NAME } from "../../../lib/constants"; import { PrepareData } from "../../../lib/data/prepare-data"; import { PreviewAppController } from "../../../lib/controllers/preview-app-controller"; -import { PreviewAppEmitter } from "../../../lib/emitters/preview-app-emitter"; import { PrepareDataService } from "../../../lib/services/prepare-data-service"; import { MobileHelper } from "../../../lib/common/mobile/mobile-helper"; import { DevicePlatformsConstants } from "../../../lib/common/mobile/device-platforms-constants"; @@ -142,10 +141,6 @@ function createTestInjector(options?: { getExternalPlugins: () => [] }); injector.register("projectFilesManager", ProjectFilesManager); - injector.register("previewAppLiveSyncService", { - syncFilesForPlatformSafe: () => ({}) - }); - injector.register("previewAppEmitter", PreviewAppEmitter); injector.register("previewAppController", PreviewAppController); injector.register("prepareController", PrepareControllerMock); injector.register("prepareDataService", PrepareDataService); @@ -178,6 +173,10 @@ function createTestInjector(options?: { getConnectedDevices: () => [deviceMockData] }); injector.register("previewAppFilesService", PreviewAppFilesService); + injector.register("previewQrCodeService", { + getQrCodeUrl: () => ({}), + getLiveSyncQrCode: () => ({}) + }); injector.register("analyticsService", { trackEventActionInGoogleAnalytics: () => ({}) }); @@ -206,7 +205,7 @@ async function initialSync(input?: IActInput) { const { previewAppController, previewSdkService, actOptions } = input; const syncFilesData = _.cloneDeep(syncFilesMockData); syncFilesData.useHotModuleReload = actOptions.hmr; - await previewAppController.preview(syncFilesData); + await previewAppController.startPreview(syncFilesData); if (actOptions.callGetInitialFiles) { await previewSdkService.getInitialFiles(deviceMockData); } @@ -219,7 +218,7 @@ async function syncFiles(input?: IActInput) { const syncFilesData = _.cloneDeep(syncFilesMockData); syncFilesData.useHotModuleReload = actOptions.hmr; - await previewAppController.preview(syncFilesData); + await previewAppController.startPreview(syncFilesData); if (actOptions.callGetInitialFiles) { await previewSdkService.getInitialFiles(deviceMockData); } diff --git a/test/stubs.ts b/test/stubs.ts index a1ba362197..29bdf55dc5 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -667,7 +667,7 @@ export class LiveSyncServiceStub extends EventEmitter implements ILiveSyncServic return; } - public async liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise { + public async liveSync(deviceDescriptors: ILiveSyncDeviceDescriptor[], liveSyncData: ILiveSyncInfo): Promise { return; } @@ -675,7 +675,7 @@ export class LiveSyncServiceStub extends EventEmitter implements ILiveSyncServic return; } - public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { + public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceDescriptor[] { return []; } }